ne-2.5/version.pl0000755000076600007660000000502411641067312013017 0ustar vignavigna#!/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-2.5/makefile0000644000076600007660000000703012066150663012475 0ustar vignavigna# Makefile for ne's distribution archive. VERSION = 2.5 # 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 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 ) make install PREFIX=/usr CMDSUFFIX=.exe tar zcvf ne-cygwin-ansi-$(VERSION).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 ) make install PREFIX=/usr CMDSUFFIX=.exe tar zcvf ne-cygwin-$(VERSION).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: ( cd src; make clean; make NE_GLOBAL_DIR=$(PREFIX)/share/ne ) mkdir -p $(DESTDIR)$(PREFIX)/bin mkdir -p $(DESTDIR)$(PREFIX)/share/ne/syntax 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 cp -p doc/ne.1 $(DESTDIR)$(PREFIX)/share/man/man1 cp -pr doc/ne.pdf doc/html doc/ne.txt doc/default.* README 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 with # # export MACOSX_DEPLOYMENT_TARGET=10.3 # make CC=/Developer/usr/bin/gcc OPTS="-mmacosx-version-min=10.3" # # Then do 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) 10MB disk image containing the package. -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-2.5/COPYING0000644000076600007660000010451311535375417012042 0ustar vignavigna 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-2.5/INSTALL0000644000076600007660000001000612101771421012012 0ustar vignavigna1. 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-2.5/README0000644000076600007660000000167512101771421011655 0ustar vignavignaWelcome 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. seba (vigna@di.unimi.it) Todd (Todd_Lewis@unc.edu) ne-2.5/NEWS0000644000076600007660000000726012075302262011473 0ustar vignavigna(This file, NEWS, lists new features and enhancements. See CHANGES for fixes.) 2.5 * 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 * 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 * Takes 'SEQ "sequence" KEYCODE' in ~/.ne/.keys to bind character sequences to key codes. 2.2 * 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 * 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-2.5/CHANGES0000644000076600007660000000621012075302300011752 0ustar vignavigna(This file, CHANGES, lists fixes. See NEWS for new features and enhancements.) 2.5 * 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 returining a generic error code instead of success, thus stopping macros. 2.4 * 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 * 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 * 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 * 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-2.5/src/actions.c0000644000076600007660000013627512100714620013372 0ustar vignavigna/* Main command processing loop. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 "netmp.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, int c, unsigned char *p) { static char msg[MAX_MESSAGE_SIZE]; line_desc *next_ld; HIGHLIGHT_STATE next_line_state; int i, error = OK, recording, 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 PUSHPREFS_A: NORMALIZE(c); for (i = 0; i < c && !(error = push_prefs(b)) && !stop; i++); return stop ? STOPPED : error ; case POPPREFS_A: NORMALIZE(c); for (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(i = 0; i < c && !(error = line_up(b)) && !stop; i++); return stop ? STOPPED : error; case LINEDOWN_A: NORMALIZE(c); for(i = 0; i < c && !(error = line_down(b)) && !stop; i++); return stop ? STOPPED : error; case PREVPAGE_A: NORMALIZE(c); for(i = 0; i < c && !(error = prev_page(b)) && !stop; i++); return stop ? STOPPED : error; case NEXTPAGE_A: NORMALIZE(c); for(i = 0; i < c && !(error = next_page(b)) && !stop; i++); return stop ? STOPPED : error; case MOVELEFT_A: NORMALIZE(c); for(i = 0; i < c && !(error = char_left(b)) && !stop; i++); return stop ? STOPPED : error; case MOVERIGHT_A: NORMALIZE(c); for(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(i = 0; i < c && !(error = page_up(b)) && !stop; i++); return stop ? STOPPED : error; case PAGEDOWN_A: NORMALIZE(c); for(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(i = 0; i < c && !(error = search_word(b, 1)) && !stop; i++); return stop ? STOPPED : error; case PREVWORD_A: NORMALIZE(c); for(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(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: { int 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; 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'+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'+c : '-'); print_message(msg); break; case GOTOBOOKMARK_A: if (! (b->bookmark_mask & (1 << c))) return BOOKMARK_NOT_SET; else { const int prev_line = b->cur_line; const int 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, FALSE, 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(i = 0; p[i] && error == OK; i = next_pos(p, i, encoding)) error = do_action(b, INSERTCHAR_A, get_char(&p[i], 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, error = ERROR; 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); end_undo_chain(b); 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. */ if (b->opt.word_wrap && b->win_x + b->cur_x >= (b->opt.right_margin ? b->opt.right_margin : ne_columns - 1)) word_wrap(b); if (b->syn) update_line(b, b->cur_y, TRUE, FALSE); assert_buffer_content(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(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 int 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 stop ? STOPPED : 0; case INSERTLINE_A: if (b->opt.read_only) return FILE_IS_READ_ONLY; NORMALIZE(c); for(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) { int 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 { int 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(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(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); error = save_buffer_to_file(b, p); if (!print_error(error)) { const int 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(); i = CHAR_CLASS(c); col = (c < 0) ? -c-1 : c; snprintf(msg, MAX_MESSAGE_SIZE, "Key Code: 0x%02x, Input Class: %s, Assigned Command: %s", col, input_class_names[i], (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 int 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))) { b->syn = NULL; /* So that autoprefs will load the right syntax. */ if (b->opt.auto_prefs && extension(p)) load_auto_prefs(b, extension(p)); 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); 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, FALSE, 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, FALSE, 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, FALSE, b->encoding == ENC_UTF8 || b->encoding == ENC_ASCII && b->opt.utf8auto))) { const encoding_type replace_encoding = detect_encoding(p, strlen(p)); int first_search = TRUE, 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, "%d 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(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) { i = b->cur_pos; move_to_sol(b); b->opt.tab_size = c; goto_pos(b,i); 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: %d", 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(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: if ((i = request_document()) < 0 || !(b = get_nth_buffer(i))) 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, TRUE, 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, TRUE, 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, TRUE, 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(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(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(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(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(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 ? b->syn->name : NULL, TRUE, FALSE, 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)); if (error == OK) 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(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(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; i = b->cur_pos; if ( !p ) { /* no prefix given; find one left of the cursor. */ if ( context_prefix(b, &p, &i, b->encoding) ) return OUT_OF_MEMORY; } snprintf(msg, MAX_MESSAGE_SIZE, "AutoComplete: prefix \"%s\"", p); if (p = autocomplete(p, msg, TRUE, &col)) { b->recording = 0; start_undo_chain(b); if (i >= 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[col]); } else if (stop) error = STOPPED; else if (col == AUTOCOMPLETE_NO_MATCH) print_message(info_msg[AUTOCOMPLETE_NO_MATCH]); return print_error(error) ? ERROR : 0; default: if (p) free(p); return OK; } } ne-2.5/src/ansi.c0000644000076600007660000000444412076214662012671 0ustar vignavigna/* Hardwired ANSI terminal control sequences. Copyright (C) 2001-2013 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-2.5/src/ansi.h0000644000076600007660000000146412076214662012675 0ustar vignavigna/* Hardwired ANSI terminal control sequences (prototypes). Copyright (C) 2001-2013 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-2.5/src/autocomp.c0000644000076600007660000001721612076214662013567 0ustar vignavigna/* AutoComplete Copyright (C) 2010-2013 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 INITIAL_HASH_TABLE_SIZE (1024) #define INITIAL_BUFFER_SIZE (16*1024) #define EXTERNAL_FLAG_CHAR '*' /* The string buffer. Contains all strings found so far, concatenated. Each string has an additional NUL at its end to make room for an EXTERNAL_FLAG_CHAR. */ static unsigned char *strings; /* The size of the string buffer, and the first free location. */ static int strings_size, strings_first_free; /* The hash table. Entries are offsets into strings. */ static int *hash_table; /* The allocated size of the table, the mask to compute the modulo (i.e., size - 1) and the number of entries in the table. */ static int size, mask, n; static int hash2(unsigned char *s, int len) { unsigned long h = 42; while(len-- != 0) h ^= ( h << 5 ) + s[len] + ( h >> 2 ); return h & mask; } static void init_hash_table(void) { hash_table = calloc(size = INITIAL_HASH_TABLE_SIZE, sizeof *hash_table); mask = size - 1; n = 0; strings = malloc(strings_size = INITIAL_BUFFER_SIZE); strings_first_free = 1; /* We want to use 0 to denote empty slots. */ } static void delete_hash_table(void) { free(strings); free(hash_table); } /* Add a given string, with specified length, to the string buffer, returning a pointer to the copy. */ static unsigned char *add_to_strings(const unsigned char * const s, int len) { if (strings_size - strings_first_free < len + 2) { if ( (strings_size *= 2) - strings_first_free < len + 2 ) strings_size += len + 2; strings = realloc(strings, strings_size); } strncpy(strings + strings_first_free, s, len); strings[strings_first_free + len] = 0; strings[strings_first_free + len + 1] = 0; strings_first_free += len + 2; return strings + strings_first_free - len - 2; } static int code(const int hash, const int ext) { return ext ? -hash: hash; } static int decode(const int hash) { return hash < 0 ? -hash : hash; } static void add_string(unsigned char * const s, const int len, const int ext) { int hash = hash2(s, len); while(hash_table[hash] && strncmp(&strings[decode(hash_table[hash])], s, len)) hash = ( hash + 1 ) & mask; if (hash_table[hash]) return; n++; hash_table[hash] = code(add_to_strings(s, len) - strings, ext); if ( size < ( ( n * 4 ) / 3 ) ) { int i, l; unsigned char *p; int *new_hash_table = calloc(size *= 2, sizeof *hash_table); mask = size - 1; for(i = 0; i < size / 2; i++) { if (hash_table[i]) { p = &strings[decode(hash_table[i])]; l = strlen(p); hash = hash2(p, l); while(new_hash_table[hash] && strncmp(&strings[decode(new_hash_table[hash])], p, l)) hash = ( hash + 1 ) & mask; new_hash_table[hash] = code(p - strings, hash_table[i] < 0); p += l + 2; } } free(hash_table); hash_table = new_hash_table; } } static void search_buff(const buffer *b, const unsigned char *p, const int encoding, const int case_search, const int ext) { line_desc *ld = (line_desc *)b->line_desc_list.head, *next; int p_len = strlen(p); int l, r; assert(p); while (next = (line_desc *)ld->ld_node.next) { l = 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; } assert(l <= ld->line_len); if (stop) return; } while (l < ld->line_len - p_len); ld = next; } } /* 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_. */ unsigned char *autocomplete(unsigned char *p, char *req_msg, const int ext, int * const error) { int i, j, l, m, max_len = 0, min_len = INT_MAX, prefix_len = strlen(p); char **entries; assert(p); init_hash_table(); search_buff(cur_buffer, p, cur_buffer->encoding, cur_buffer->opt.case_search, FALSE); if (stop) { delete_hash_table(); 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) { delete_hash_table(); free(p); return NULL; } } b = (buffer *)b->b_node.next; } } /* We compact the table into a vector of char pointers. */ entries = malloc(n * sizeof *entries); for(i = j = 0; i < size; i++) if (hash_table[i]) { entries[j] = &strings[decode(hash_table[i])]; l = strlen(entries[j]); if (max_len < l) max_len = l; if (min_len > l) min_len = l; if (hash_table[i] < 0) entries[j][strlen(entries[j])] = EXTERNAL_FLAG_CHAR; j++; } assert(j == n); free(p); p = NULL; #ifdef NE_TEST /* During tests, we always output the middle entry. */ if (n) { if (entries[n/2][strlen(entries[n/2]) - 1] == EXTERNAL_FLAG_CHAR) entries[n/2][strlen(entries[n/2]) - 1] = 0; p = str_dup(entries[n/2]); } *error = AUTOCOMPLETE_COMPLETED; free(entries); delete_hash_table(); return p; #endif if (n != 0) { qsort(entries, n, sizeof *entries, strdictcmp); /* Find maximum common prefix. */ m = strlen(entries[0]); if (entries[0][m-1] == EXTERNAL_FLAG_CHAR) m--; for(i = 1; i < n; i++) { for(j = 0; j < m; j++) if (entries[i][j] != entries[0][j]) break; m = j; } /* If we can output more character than the prefix len, we do so without starting the requester. */ if (m > prefix_len) { p = malloc(m + 1); strncpy(p, entries[0], m); p[m] = 0; *error = min_len == m ? AUTOCOMPLETE_COMPLETED : AUTOCOMPLETE_PARTIAL; } else { print_message(req_msg); if ((i = request_strings((const char * const *)entries, n, 0, max_len + 1, EXTERNAL_FLAG_CHAR)) != ERROR) { i = i >= 0 ? i : -i - 2; /* Delete EXTERNAL_FLAG_CHAR at the end of the strings if necessary. */ if (entries[i][strlen(entries[i]) - 1] == EXTERNAL_FLAG_CHAR) entries[i][strlen(entries[i]) - 1] = 0; p = str_dup(entries[i]); *error = AUTOCOMPLETE_COMPLETED; } else *error = AUTOCOMPLETE_CANCELLED; reset_window(); } } else *error = AUTOCOMPLETE_NO_MATCH; free(entries); delete_hash_table(); D(fprintf(stderr,"autocomp returning '%s', entries: %d\n", p, n);) return p; } ne-2.5/src/buffer.c0000644000076600007660000011517712076214662013216 0ustar vignavigna/* Buffer handling functions, including allocation, deallocation, and I/O. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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(int size) { char_pool *cp; if (size < STD_POOL_SIZE) size = STD_POOL_SIZE; if (cp = calloc(1, sizeof(char_pool))) { if (cp->pool = calloc(sizeof(char), size)) { cp->size = size; return cp; } free(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, unsigned char * const p) { char_pool *cp; cp = (char_pool *)b->char_pool_list.head; while(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(int pool_size) { line_desc_pool *ldp; if (pool_size < STD_LINE_DESC_POOL_SIZE) pool_size = STD_LINE_DESC_POOL_SIZE; if (ldp = calloc(1, sizeof(line_desc_pool))) { if (ldp->pool = calloc(pool_size, do_syntax ? sizeof(line_desc) : sizeof(no_syntax_line_desc))) { int i; ldp->size = pool_size; new_list(&ldp->free_list); for(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; 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) { line_desc *ld; if (!b) return; block_signals(); free_buffer_contents(b); 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(). */ int calc_lost_chars(const buffer * const b) { int n = 0; char_pool *cp = (char_pool *)b->char_pool_list.head; while(cp->cp_node.next) { n += cp->size - (cp->last_used - cp->first_used + 1); cp = (char_pool *)cp->cp_node.next; } 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) { buffer *b = (buffer *)buffers.head; while(b->b_node.next) { if (!n--) return b; b = (buffer *)b->b_node.next; } 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) { buffer *b = (buffer *)buffers.head; if (!p) return NULL; p = file_part((char *)p); while(b->b_node.next) { if (b->filename && !strcmp(file_part(b->filename), p)) return b; b = (buffer *)b->b_node.next; } return NULL; } /* Returns TRUE if any of the buffers has been modified since the last save. */ int modified_buffers(void) { buffer *b = (buffer *)buffers.head; while(b->b_node.next) { if (b->is_modified) return TRUE; b = (buffer *)b->b_node.next; } return FALSE; } /* Saves all buffers which have been modified since the last save. Returns an error if a save is unsuccessful, or if a buffer has no name. */ int save_all_modified_buffers(void) { buffer *b = (buffer *)buffers.head; while(b->b_node.next) { if (b->is_modified) if (save_buffer_to_file(b, NULL)) return ERROR; b = (buffer *)b->b_node.next; } return 0; } /* 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) { line_desc_pool *ldp; line_desc *ld; ldp = (line_desc_pool *)b->line_desc_pool_list.head; block_signals(); while(ldp->ldp_node.next) { assert_line_desc_pool(ldp); if (ldp->free_list.head->next) { 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; } ldp = (line_desc_pool *)ldp->ldp_node.next; } /* 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); rem(&(ld = (line_desc *)ldp->free_list.head)->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) { line_desc_pool *ldp; ldp = (line_desc_pool *)b->line_desc_pool_list.head; assert(ldp != NULL); /* We scan the pool list in order to find where the given line descriptor lives. */ while(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; ldp = (line_desc_pool *)ldp->ldp_node.next; } 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. */ unsigned char *alloc_chars(buffer * const b, const int len) { char_pool *cp; if (!len || !b) return NULL; assert_buffer(b); cp = (char_pool *)b->char_pool_list.head; assert(cp != NULL); block_signals(); while(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; } cp = (char_pool *)cp->cp_node.next; } /* 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. */ int alloc_chars_around(buffer * const b, line_desc * const ld, const int n, const int check_first_before) { unsigned char *before, *after; char_pool *cp; assert(ld->line != NULL); cp = get_char_pool(b, ld->line); assert_char_pool(cp); block_signals(); before = ld->line - 1; 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, unsigned char *const p, const int len) { char_pool *cp; if (!b || !p || !len) return; 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 int line, const int 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 int line) { int error; 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. */ 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 *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 int line, const int 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, int line, int pos, const unsigned char * const stream, const int stream_len) { int i, len, mask; unsigned char *p; const unsigned char *s = stream; 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)) { int error = add_undo_step(b, line, pos, -stream_len); if (error) { release_signals(); return error; } } while(s - stream < stream_len) { if (len = strnlen_ne(s, stream_len - (s - stream))) { /* 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 if ((i = alloc_chars_around(b, ld, len, pos < ld->line_len / 2))<0) { if (p = alloc_chars(b, ld->line_len + len)) { 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; } } /* Third case. There are enough free characters around ld->line. */ else { if (len - i) memmove(ld->line - (len - i), ld->line, pos); if (i) memmove(ld->line + pos + i, ld->line + pos, ld->line_len - pos); memcpy(ld->line - (len - i) + pos, s, len); ld->line -= (len - i); 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 (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 (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 int line, const int pos, const int c) { static unsigned 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 int line, const int pos, int n) { static unsigned 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 int line, const int pos, int len) { int n, m, mask; 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) { unsigned char *p; 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 (n=0, mask=b->bookmark_mask; mask; n++, mask >>= 1) { if (mask & 1) { if (b->bookmark[n].line == line+1) { b->bookmark[n].line--; b->bookmark[n].pos += ld->line_len; } else if (b->bookmark[n].line > line) b->bookmark[n].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 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. */ if (p = alloc_chars(b, ld->line_len + next_ld->line_len)) { 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 { 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 (m=0, mask=b->bookmark_mask; mask; m++, mask>>=1) { if (mask & 1) { if (b->bookmark[m].line == line) if (b->bookmark[m].pos >= pos) if (b->bookmark[m].pos < pos + n) b->bookmark[m].pos = pos; else b->bookmark[m].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 int line, const int pos) { return delete_stream(b, ld, line, pos, b->encoding == ENC_UTF8 && pos < ld->line_len ? utf8len(ld->line[pos]) : 1); } /* 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) { int fh, result; 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; if ((fh = open(name, READ_FLAGS)) >= 0) { result = load_fh_in_buffer(b, fh); close(fh); 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 struture of ne allows to load the file with a single read in a big pool. */ int load_fh_in_buffer(buffer *b, int fh) { off_t len; int i, num_lines; encoding_type encoding; unsigned char *p, *q; char_pool *cp; line_desc_pool *ldp; unsigned char terminators[] = { 0x0d, 0x0a }; if (b->opt.preserve_cr) terminators[0] = 0; len = lseek(fh, 0, SEEK_END); if (len < 0 || lseek(fh, 0, SEEK_SET) < 0) return IO_ERROR; if (len > INT_MAX) return FILE_IS_TOO_LARGE; free_buffer_contents(b); if (!len) { clear_buffer(b); b->encoding = ENC_ASCII; if (b->opt.do_undo) b->undo.last_save_step = 0; return OK; } block_signals(); if (cp = alloc_char_pool(len + STANDARD_INCREMENT)) { if (read(fh, cp->pool, len) < len) { free_char_pool(cp); release_signals(); clear_buffer(b); return IO_ERROR; } b->allocated_chars = cp->size; b->free_chars = cp->size - len; p = cp->pool; /* This is the first pass on the data we just read. We count the number of lines. If we meet a CR/LF sequence and we did not ask for binary files, we decide the file is of CR/LF type. Note that this cannot happen if preserve_cr is set. */ for(num_lines = i=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. */ 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; } if (ldp = alloc_line_desc_pool(num_lines + STANDARD_LINE_INCREMENT)) { 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(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); /* ultima riga */ 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 { 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; reset_syntax_states(b); 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) { line_desc *ld; HIGHLIGHT_STATE next_line_state = { 0, 0, "" }; ld = (line_desc *)b->line_desc_list.head; while(ld->ld_node.next) { ld->highlight_state = next_line_state; next_line_state = parse(b->syn, ld, next_line_state, b->encoding == ENC_UTF8); ld = (line_desc *)ld->ld_node.next; } } } /* Ensures that the attribute buffer of this buffer is large enough. */ void ensure_attr_buf(buffer * const b, const int capacity) { /* 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. */ int save_buffer_to_file(buffer *b, const char *name) { int fh, error = OK; char *p; 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(); if ((fh = open(name, WRITE_FLAGS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) >= 0) { /* If we can allocate SAVE_BLOCK_LEN bytes, we will use them as a buffer for our saves. */ if (p = malloc(SAVE_BLOCK_LEN + 1)) { /* 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. */ int 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; } 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) { char *p; if (b->is_modified) { 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-2.5/src/clips.c0000644000076600007660000004262112100714620013033 0ustar vignavigna/* Clip handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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, int size) { clip_desc *cd; assert(n >= 0); assert(size >= 0); if (cd = calloc(1, sizeof(clip_desc))) { 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, int size) { char_stream *cs; assert(n >= 0); assert(size >= 0); if (!cd) return alloc_clip_desc(n, size); assert_clip_desc(cd); if (cd->n != n) return NULL; if (cs = realloc_char_stream(cd->cs, size)) { 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) { clip_desc *cd = (clip_desc *)clips.head; while (cd->cd_node.next) { assert_clip_desc(cd); if (cd->n == n) return cd; cd = (clip_desc *)cd->cd_node.next; } 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, int cut) { int i, chaining, bsp, pass, start_pos, end_pos, len, clip_len, y = b->cur_line; unsigned char *p = NULL; clip_desc *cd, *new_cd; line_desc *ld = b->cur_line_desc; 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. */ cd = get_nth_clip(n); 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)) { if (!(new_cd = realloc_clip_desc(cd, n, 0))) 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. */ if (y > b->block_start_line || y == b->block_start_line && b->cur_pos > b->block_start_pos) { /* mark before/above cursor */ for(chaining = pass = clip_len = 0; pass < 2; pass++) { ld = b->cur_line_desc; for(i = y; i >= b->block_start_line; i--) { start_pos = 0; if (i == b->block_start_line) { if (!pass && cut && ld->line_len < b->block_start_pos) { if (!chaining) { chaining = 1; start_undo_chain(b); } 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); } if (i == y) end_pos = min(ld->line_len,b->cur_pos); else end_pos = ld->line_len; 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; } if (!(new_cd = realloc_clip_desc(cd, n, clip_len))) { 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 */ for(chaining = pass = clip_len = 0; pass < 2; pass++) { ld = b->cur_line_desc; for(i = y; i <= b->block_start_line; i++) { start_pos = 0; if (i == y) { if (!pass && cut && b->cur_pos > ld->line_len) { if (!chaining) { chaining = 1; 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; } if (i == b->block_start_line) end_pos = min(b->block_start_pos, ld->line_len); else end_pos = ld->line_len; 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; } if (!(new_cd = realloc_clip_desc(cd, n, clip_len))) { 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) { int i, bsp, start_pos, end_pos, len, erase_len = 0, chaining = 0, y = b->cur_line; line_desc *ld = b->cur_line_desc; if (!b->marking) return MARK_BLOCK_FIRST; if (b->block_start_line >= b->num_lines) return MARK_OUT_OF_BUFFER; 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; if (y > b->block_start_line || y == b->block_start_line && b->cur_pos > b->block_start_pos) { for(i = y; i >= b->block_start_line; i--) { start_pos = 0; if (i == b->block_start_line) { if (ld->line_len < b->block_start_pos) { if (!chaining) { chaining = 1; start_undo_chain(b); } 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); } if (i == y) end_pos = min(ld->line_len,b->cur_pos); else end_pos = ld->line_len; 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(i = y; i <= b->block_start_line; i++) { start_pos = 0; if (i == y) { if (b->cur_pos > ld->line_len) { if (!chaining) { chaining = 1; 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; } if (i == b->block_start_line) end_pos = min(b->block_start_pos, ld->line_len); else end_pos = ld->line_len; 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 *cd; if (!(cd = get_nth_clip(n))) 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, int cut) { int i, pass, start_pos, len, clip_len, y = b->cur_line, start_x, end_x; char *p = NULL; clip_desc *cd, *new_cd; line_desc *ld = b->cur_line_desc; if (!b->marking) return MARK_BLOCK_FIRST; if (b->block_start_line >= b->num_lines) return MARK_OUT_OF_BUFFER; 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) { if (!(new_cd = realloc_clip_desc(cd, n, 0))) return OUT_OF_MEMORY; set_stream_encoding(new_cd->cs, ENC_ASCII); if (!cd) add_head(&clips, &new_cd->cd_node); return OK; } start_x = calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding); end_x = b->win_x + b->cur_x; if (end_x < start_x) { i = start_x; start_x = end_x; end_x = i; } if (cut) start_undo_chain(b); if (y > b->block_start_line) { for(pass = clip_len = 0; pass < 2; pass++) { ld = b->cur_line_desc; for(i = y; i >= b->block_start_line; i--) { start_pos = calc_pos(ld, start_x, b->opt.tab_size, b->encoding); 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; } if (!(new_cd = realloc_clip_desc(cd, n, clip_len))) return OUT_OF_MEMORY; if (!cd) add_head(&clips, &new_cd->cd_node); cd = new_cd; p = cd->cs->stream + clip_len; } } else { for(pass = clip_len = 0; pass < 2; pass++) { ld = b->cur_line_desc; for(i = y; i <= b->block_start_line; i++) { start_pos = calc_pos(ld, start_x, b->opt.tab_size, b->encoding); 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; } if (!(new_cd = realloc_clip_desc(cd, n, clip_len))) 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) { int i, start_pos, len, y = b->cur_line, start_x, end_x; line_desc *ld = b->cur_line_desc; if (!b->marking) return MARK_BLOCK_FIRST; if (b->block_start_line >= b->num_lines) return MARK_OUT_OF_BUFFER; 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; start_x = calc_width(b->cur_line_desc, b->block_start_pos, b->opt.tab_size, b->encoding); end_x = b->win_x + b->cur_x; if (end_x < start_x) { i = start_x; start_x = end_x; end_x = i; } start_undo_chain(b); if (y > b->block_start_line) { for(i = y; i >= b->block_start_line; i--) { start_pos = calc_pos(ld, start_x, b->opt.tab_size, b->encoding); 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(i = y; i <= b->block_start_line; i++) { start_pos = calc_pos(ld, start_x, b->opt.tab_size, b->encoding); 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) { int len, stream_len, i, x, line; unsigned char *p; clip_desc *cd; line_desc * ld = b->cur_line_desc; unsigned char *stream; if (!(cd = get_nth_clip(n))) 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; p = stream = cd->cs->stream; stream_len = cd->cs->len; line = b->cur_line; x = b->cur_x + b->win_x; 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; } if (len = strnlen_ne(p, stream_len - (p - stream))) { for(n = i=0; i < ld->line_len && n < x; i = next_pos(ld->line, i, b->encoding)) { if (ld->line[i] == '\t') n += b->opt.tab_size - n % b->opt.tab_size; else n += get_char_width(&ld->line[i], b->encoding); } if (i == 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, i, 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 int preserve_cr, const int binary) { int error; 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); } 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 int CRLF, const int binary) { clip_desc *cd = get_nth_clip(n); if (!cd) return CLIP_DOESNT_EXIST; return save_stream(cd->cs, name, CRLF, binary); } ne-2.5/src/cm.c0000644000076600007660000002215512076214662012335 0ustar vignavigna/* 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-2013 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; 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-2.5/src/cm.h0000644000076600007660000001031612076214662012336 0ustar vignavigna/* 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-2013 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-2.5/src/command.c0000644000076600007660000007514412076214662013362 0ustar vignavigna/* Command table manipulation functions and vectors. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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(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 unsigned char *c, const unsigned char *m) { assert(c != NULL); assert(m != NULL); while (*c && ascii_up_case[*c] == ascii_up_case[*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 unsigned char * const s, int len) { int h = -1; while(len-- != 0) h = (h * 31 + ascii_up_case[s[len]]) % HASH_TABLE_SIZE; return (h + HASH_TABLE_SIZE) % HASH_TABLE_SIZE; } static int hash_macro(const unsigned char * const s, int len) { int h = -1; while(len-- != 0) h = (h * 31 + ascii_up_case[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. */ action parse_command_line(const unsigned char * command_line, int * const num_arg, unsigned char ** const string_arg, const int exec_only_options) { int h; action a; const unsigned char *p = command_line; if (num_arg) *num_arg = -1; if (string_arg) *string_arg = NULL; if (!command_line) return NOP_A; if (!*p) return NOP_A; while(isasciispace(*p)) p++; command_line = p; if (!isalpha(*p)) { /* Comment, treated as NOP. */ 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++; h = hash_cmd(command_line, p - command_line); 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(*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 = strtol(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 unsigned char *command_line) { int n, a, len = strlen(command_line); encoding_type encoding = detect_encoding(command_line, len); unsigned char *p; if (b->encoding != ENC_ASCII && encoding != ENC_ASCII && b->encoding != encoding) return INCOMPATIBLE_COMMAND_ENCODING; 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, int c, unsigned char *p, int verbose) { char t[MAX_INT_LEN + 2]; if (commands[a].flags & DO_NOT_RECORD) return; /* 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, " %d", 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 unsigned char *p) { int h; const unsigned char *cmd; action a; if ( !p || !*p) return 0; while(isasciispace(*p)) p++; cmd = p; if (!isalpha(*p)) return 0; while(*p && !isasciispace(*p)) p++; h = hash_cmd(cmd, p - cmd); 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; } /* 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, int verbose) { char *cmd; int pos; int chr; int building = 0; if (!cs || !cs->len) return; for (pos = 0; pos < cs->len; pos += strlen(&cs->stream[pos]) + 1) { cmd = &cs->stream[pos]; chr = insertchar_val(cmd); if (chr < 0x80 && isprint(chr)) { char two[2] = " "; two[0] = chr; delete_from_stream(cs, pos, strlen(cmd) + 1); if (building) { building++; insert_in_stream(cs, two, building, 1); } else { const char *insert; int len; insert = verbose ? INSERTSTRING_NAME : INSERTSTRING_ABBREV; 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) { int error = OK, len; unsigned char *p, *stream; if (!cs) return ERROR; /* If len is 0 or 1, the character stream does not contain anything. */ if ((len = cs->len) < 2) return OK; if (!(p = stream = malloc(len))) return OUT_OF_MEMORY; memcpy(stream, cs->stream, len); stop = FALSE; b->executing_macro = 1; 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) { int h; char *macro_dir, *prefs_dir; char_stream *cs; macro_desc *md, **m; assert(name != NULL); if (!(md = alloc_macro_desc())) return NULL; cs = load_stream(md->cs, name, FALSE, FALSE); 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)))) { strcat(strcpy(macro_dir, prefs_dir), 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)); h = hash_macro(md->name, strlen(md->name)); 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 call_depth = 0; const char *p; macro_desc *md; int h; if (++call_depth > 32) { --call_depth; return MAX_MACRO_DEPTH_EXCEEDED; } p = file_part(name); h = hash_macro(p, strlen(p)); md = macro_hash_table[h]; while(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) { int i; macro_desc *m, *n; for(i = 0; i < MACRO_HASH_TABLE_SIZE; i++) { m = macro_hash_table[i]; macro_hash_table[i] = NULL; while(m) { 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) { int i; char *str=NULL, *p; for (i=0; i= 0) { D(fprintf(stderr,"Help check #2: p=%p, i=%d\n",p,i);) if (p) { for(i = 0; i < strlen(p); i++) if (isasciispace((unsigned char)p[i])) break; i = hash_cmd(p, i); if ((a = hash_table[i]) && !cmdcmp(commands[--a].name, p) || (a = short_hash_table[i]) && !cmdcmp(commands[--a].short_name, p)) i = a; else i = -1; p = NULL; } else { D(fprintf(stderr,"Gonna parse_command_line(\"%s\",NULL,NULL,FALSE);\n",command_names[i]);) i = parse_command_line(command_names[i], NULL, NULL, FALSE); D(fprintf(stderr,"...and got i=%d\n",i);) } if (i < 0) { i = 0; continue; } assert(i >= 0 && i < ACTION_COUNT); print_message("Help: press Enter, or F1 or Escape or Escape-Escape"); if ((key_strokes = find_key_strokes(i)) && (tmphelp = calloc(commands[i].help_len+1, sizeof(char *)))) { tmphelp[0] = (char *)commands[i].help[0]; tmphelp[1] = (char *)commands[i].help[1]; tmphelp[2] = key_strokes; memcpy(&tmphelp[3], &commands[i].help[2], sizeof(char *) * (commands[i].help_len-2)); if ((j = request_strings((const char * const* const)tmphelp, commands[i].help_len+1, 0, ne_columns, FALSE)) < 0 ) i = j; free(tmphelp); } else { if ((j = request_strings(commands[i].help, commands[i].help_len, 0, ne_columns, FALSE)) < 0 ) i = j; } if (key_strokes) free(key_strokes); } } while(i >= 0); draw_status_bar(); } ne-2.5/src/debug.h0000644000076600007660000000164212076214662013027 0ustar vignavigna/* Definition for debug statements and assertions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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-2.5/src/display.c0000644000076600007660000007566212076214662013416 0ustar vignavigna/* Display handling functions with optional update delay Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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. */ int 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 int window_needs_refresh, first_line, last_line, updated_lines; /* A fixed reference for a pointer to the value -1. */ static const int no_attr = -1; /* 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(x->saved_s, 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) { int got_end_ld = end_ld == NULL; int 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 int from_col, const int num_cols, const int tab_size, const int cleared_at_end, const int utf8, const int * const attr, const int * const diff, const int diff_size) { int curr_col, output_col, pos, attr_pos, c, c_len; const unsigned char *s = ld->line; 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. */ curr_col = pos = attr_pos = 0; while(curr_col - from_col < num_cols && pos < ld->line_len) { output_col = col + curr_col - from_col; c = utf8 ? get_char(s, ENC_UTF8) : *s; 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 int from_col, const int cleared_at_end, const int differential) { int i; line_desc *ld = b->top_line_desc; assert(row < ne_lines - 1); assert_line_desc(ld, b->encoding); 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; } for(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 int cleared_at_end, const int 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 int doit) { int i; line_desc *ld = b->top_line_desc; assert_line_desc(ld, b->encoding); 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; 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, int pos, int attr_pos, const int line, const int x) { int i, j, c_width, tab_width, tab_found = FALSE, curr_attr_pos; 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); if (c == '\t') c_width = b->opt.tab_size - x % b->opt.tab_size; else c_width = 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. */ for(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. */ 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 int pos, const int attr_pos, const int line, const int x) { int i, j, c_width, c_len; const int * const attr = b->syn ? &attr_buf[attr_pos] : NULL; assert(pos < ld->line_len); 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); c_len = b->encoding == ENC_UTF8 ? utf8seqlen(c) : 1; if (c == '\t') c_width = b->opt.tab_size - x % b->opt.tab_size; else c_width = 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(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, int pos, const int attr_pos, const int line, const int x) { int i, j, new_width, old_width, curr_attr_pos; const int * const attr = b->syn ? &attr_buf[attr_pos] : &no_attr; assert(ld != NULL); assert(pos < ld->line_len); 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; } if (old_char == '\t') old_width = b->opt.tab_size - x % b->opt.tab_size; else old_width = output_width(old_char); if (new_char == '\t') new_width = b->opt.tab_size - x % b->opt.tab_size; else new_width = 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, 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(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, 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, 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, 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(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, 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, 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, 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 int show) { static int c, orig_attr; int match_pos, tmp_attr; line_desc *matching_ld; if (show) { 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, &b->automatch.y, &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 -= 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-2.5/src/edit.c0000644000076600007660000006622212101732573012661 0ustar vignavigna/* Various editing functions such as word wrap, to upper, etc. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 4 /* Applies a given to_first() function to the first letter of the starting at the cursor, and to_rest() to the following alphabetical letter (see the functions below). */ static int to_something(buffer *b, int (to_first)(int), int (to_rest)(int)) { int c, new_c, x, pos = b->cur_pos, len, new_len, changed = FALSE; unsigned char *word; 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; /* 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; x = b->cur_x; /* The original x position (to perform the line update). */ pos = b->cur_pos; new_len = 0; /* 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)) { 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); } if (!(len = pos - b->cur_pos)) { char_right(b); return OK; } if (changed) { /* We actually perform changes only if some character was case folded. */ if (!(word = malloc(new_len * sizeof *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) { int match_line, match_pos, rc; 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 int min_line, int max_line, int *match_line, int *match_pos, int *c, line_desc ** match_ld) { static unsigned char bracket_table[NUM_BRACKETS][2] = { { '(',')' }, { '[',']' }, { '{','}' }, { '<','>' } }; int i, j, n, y, dir, pos; line_desc *ld = b->cur_line_desc; const unsigned char *line; if (b->cur_pos >= ld->line_len) return NOT_ON_A_BRACKET; 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; n = 0; 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) { 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; } /* 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_wrap(buffer * const b) { static char avcmd[16]; int non_blank_added, pos; unsigned char * line = b->cur_line_desc->line; int avshift; if (!b->cur_pos || b->cur_pos > b->cur_line_desc->line_len) return ERROR; /* If the char to our left is a space, we need to insert something else 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 (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)); /* Get rid of any spaces starting at cur_pos */ pos = b->cur_pos; while (pos < b->cur_line_desc->line_len && ne_isspace(get_char(&line[pos ], b->encoding), b->encoding)) pos = next_pos(line, pos, b->encoding); if (pos > b->cur_pos) delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, pos - b->cur_pos); } b->bookmark[WORDWRAP_BOOKMARK].pos = b->cur_pos; b->bookmark[WORDWRAP_BOOKMARK].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; if (avshift = b->cur_y - b->bookmark[WORDWRAP_BOOKMARK].cur_y-1) { 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 b->cur_line_desc->line_len; } /* 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) { int rc = 0; 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 = NULL; /* Where we keep space for paragraph left offsets */ static int pa_space_len; /* How long pa_space is when tabs are expanded */ static int 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) { int pos; 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. */ 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 int line, const encoding_type encoding) { int pos; if (!ld->line) return; 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, int * const first_non_blank, const encoding_type encoding) { int pos = 0; *first_non_blank = -1; if (!ld->line) return 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) { int pos, done, skip, line = b->cur_line, right_margin = b->opt.right_margin ? b->opt.right_margin : ne_columns; 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. */ insert_one_char(b, ld, line, 0, ' '); delete_stream(b, ld, line, 0, 1); done = FALSE; 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) { int spaces; int split_pos; int 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 = 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 = 1; } /** 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 != 0. **/ 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; int 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 int line, line_desc * const ld, const int up_to_col) { line_desc * const prev_ld = (line_desc *)ld->ld_node.prev; int pos = 0, col = 0, c; if (!prev_ld->ld_node.prev) return 0; assert_line_desc(prev_ld, b->encoding); if (prev_ld->line_len == 0) return 0; while(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) { int use_tabs = b->opt.tabs && b->opt.shift_tabs; line_desc *ld, *start_line_desc; long int shift_size = 1, shift_mag = b->opt.tab_size; char dir = '>'; int init_line = b->cur_line, init_pos = b->cur_pos, init_y = b->cur_y; int first_line = b->cur_line, last_line = b->cur_line, left_col = 0; int line; int avshift; int 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(*p))) { errno = 0; shift_size = strtol(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; 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 (line=first_line; !rc && line<=last_line; line++) { int pos, col; goto_line(b,line); pos = calc_pos(b->cur_line_desc, left_col, b->opt.tab_size, b->encoding); col = left_col; 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 (line=first_line; line<=last_line; line++) { int 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(); if (avshift = b->cur_y - init_y) { snprintf(msg, msg_size, "%c%d", avshift > 0 ? 'T' :'B', avshift > 0 ? avshift : -avshift); adjust_view(b,msg); } return rc; } ne-2.5/src/enums.h0000644000076600007660000000540612102010575013055 0ustar vignavigna/* 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-2013 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, 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-2.5/src/errors.c0000644000076600007660000001072112076214662013246 0ustar vignavigna/* Error message vector. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 */ "You are not positioned over {}, (), [] or <>.", /* 5 */ "Can't find matching bracket.", /* 6 */ "Bookmark not set.", /* 7 */ "Invalid Bookmark designation (use 0 through 9, -1, +1, or '-').", /* 8 */ "No unset Bookmarks to set.", /* 9 */ "No set Bookmarks to goto.", /* 10 */ "No set Bookmarks to unset.", /* 11 */ "Invalid level (use 0, '+', '-', or none).", /* 12 */ "You cannot insert a character whose ASCII code is 0.", /* 13 */ "No search string.", /* 14 */ "No replace string.", /* 15 */ "TAB size out of range.", /* 16 */ "Invalid match mode.", /* 17 */ "Mark a block first.", /* 18 */ "Out of memory. DANGER!", /* 19 */ "Nothing to undo.", /* 20 */ "Nothing to redo", /* 21 */ "Undo is not enabled", /* 22 */ "No such command.", /* 23 */ "Can execute only preference commands.", /* 24 */ "Command needs a numeric argument.", /* 25 */ "Command has no arguments.", /* 26 */ "Command requires an argument.", /* 27 */ "Wrong character after backslash.", /* 28 */ "Can't open file.", /* 29 */ "Can't open temporary files.", /* 30 */ "Error while writing.", /* 31 */ "Document name has no extension.", /* 32 */ "Can't find or create $HOME/.ne directory.", /* 33 */ "Clip does not exist.", /* 34 */ "Mark is out of document.", /* 35 */ "Can't open macro.", /* 36 */ "Maximum macro depth exceeded.", /* 37 */ "This file is read-only.", /* 38 */ "Can't open file (file is migrated).", /* 39 */ "Can't open file (file is a directory).", /* 40 */ "Can't open file (file is too large).", /* 41 */ "Stopped.", /* 42 */ "I/O error.", /* 43 */ "The argument string is empty.", /* 44 */ "External command error.", /* 45 */ "Escape time out of range.", /* 46 */ "Prefs stack is full.", /* 47 */ "Prefs stack is empty.", /* 48 */ "The argument is not a number.", /* 49 */ "This character is not supported in this configuration.", /* 50 */ "This string is not supported in this configuration.", /* 51 */ "This buffer is not UTF-8 encoded.", /* 52 */ "This clip cannot be pasted in this buffer (incompatible encoding).", /* 53 */ "This command line cannot be executed in this buffer (incompatible encoding).", /* 54 */ "This string cannot be searched for in this buffer (incompatible encoding).", /* 55 */ "This replacement string cannot be used in this buffer (incompatible encoding).", /* 56 */ "UTF-8 character classes in regular expressions are not supported.", /* 57 */ "Character classes cannot be complemented when matching against UTF-8 text.", /* 58 */ "The specified regex replacement group is not available in UTF-8 mode", /* 59 */ "Syntax highlighting is not enabled", /* 60 */ "There is no syntax for that extension", /* 61 */ "Invalid Shift specified (use [<|>][#][s|t]; default is \">1t\").", /* 62 */ "Insufficient white space for requested left shift.", }; char *info_msg[INFO_COUNT] = { "Saving...", "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.", }; ne-2.5/src/errors.h0000644000076600007660000000744312076214662013262 0ustar vignavigna/* Error index enum, and extern declaration of the error vector. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 */ NOT_ON_A_BRACKET, /* 5 */ CANT_FIND_BRACKET, /* 6 */ BOOKMARK_NOT_SET, /* 7 */ INVALID_BOOKMARK_DESIGNATION, /* 8 */ NO_UNSET_BOOKMARKS_TO_SET, /* 9 */ NO_SET_BOOKMARKS_TO_GOTO, /* 10 */ NO_SET_BOOKMARKS_TO_UNSET, /* 11 */ INVALID_LEVEL, /* 12 */ CANT_INSERT_0, /* 13 */ NO_SEARCH_STRING, /* 14 */ NO_REPLACE_STRING, /* 15 */ TAB_SIZE_OUT_OF_RANGE, /* 16 */ INVALID_MATCH_MODE, /* 17 */ MARK_BLOCK_FIRST, /* 18 */ OUT_OF_MEMORY, /* 19 */ NOTHING_TO_UNDO, /* 20 */ NOTHING_TO_REDO, /* 21 */ UNDO_NOT_ENABLED, /* 22 */ NO_SUCH_COMMAND, /* 23 */ CAN_EXECUTE_ONLY_OPTIONS, /* 24 */ HAS_NUMERIC_ARGUMENT, /* 25 */ HAS_NO_ARGUMENT, /* 26 */ REQUIRES_ARGUMENT, /* 27 */ WRONG_CHAR_AFTER_BACKSLASH, /* 28 */ CANT_OPEN_FILE, /* 29 */ CANT_OPEN_TEMPORARY_FILE, /* 30 */ ERROR_WHILE_WRITING, /* 31 */ HAS_NO_EXTENSION, /* 32 */ CANT_FIND_PREFS_DIR, /* 33 */ CLIP_DOESNT_EXIST, /* 34 */ MARK_OUT_OF_BUFFER, /* 35 */ CANT_OPEN_MACRO, /* 36 */ MAX_MACRO_DEPTH_EXCEEDED, /* 37 */ FILE_IS_READ_ONLY, /* 38 */ FILE_IS_MIGRATED, /* 39 */ FILE_IS_DIRECTORY, /* 40 */ FILE_IS_TOO_LARGE, /* 41 */ STOPPED, /* 42 */ IO_ERROR, /* 43 */ STRING_IS_EMPTY, /* 44 */ EXTERNAL_COMMAND_ERROR, /* 45 */ ESCAPE_TIME_OUT_OF_RANGE, /* 46 */ PREFS_STACK_FULL, /* 47 */ PREFS_STACK_EMPTY, /* 48 */ NOT_A_NUMBER, /* 49 */ INVALID_CHARACTER, /* 50 */ INVALID_STRING, /* 51 */ BUFFER_IS_NOT_UTF8, /* 52 */ INCOMPATIBLE_CLIP_ENCODING, /* 53 */ INCOMPATIBLE_COMMAND_ENCODING, /* 54 */ INCOMPATIBLE_SEARCH_STRING_ENCODING, /* 55 */ INCOMPATIBLE_REPLACE_STRING_ENCODING, /* 56 */ UTF8_REGEXP_CHARACTER_CLASS_NOT_SUPPORTED, /* 57 */ UTF8_REGEXP_COMP_CHARACTER_CLASS_NOT_SUPPORTED, /* 58 */ GROUP_NOT_AVAILABLE, /* 59 */ SYNTAX_NOT_ENABLED, /* 60 */ NO_SYNTAX_FOR_EXT, /* 61 */ INVALID_SHIFT_SPECIFIED, /* 62 */ INSUFFICIENT_WHITESPACE, ERROR_COUNT }; enum info { SAVING, 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, INFO_COUNT }; extern char *error_msg[ERROR_COUNT]; extern char *info_msg[INFO_COUNT]; ne-2.5/src/exec.c0000644000076600007660000000511312076214662012655 0ustar vignavigna/* List handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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-2.5/src/ext.c0000644000076600007660000000504512102010575012520 0ustar vignavigna/* Extern's for names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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" }, { "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-2.5/src/hash.c0000644000076600007660000007071712102010575012653 0ustar vignavigna/* Precompiled hash tables for internal commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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, 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, 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, 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, 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, 110, 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, 106, 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, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 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, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 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, 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, 122, 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, 132, 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, 69, 0, 0, 0, 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 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, 124, 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, 127, 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, 109, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 80, 121, 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, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 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, 118, 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, 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, 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, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 115, 0, 0, 111, 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, 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, 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, 134, 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, 123, 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, 122, 129, 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, 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, 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, 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, 105, 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, 111, 0, 0, 136, 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, 107, 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, 112, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 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, 0, 125, 128, 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, 109, 0, 130, 135, 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, 110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 121, 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, 116, 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, 117, 126, 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, 137, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 120, 0, 53, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 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, 113, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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-2.5/src/hash.h0000644000076600007660000000317412102010575012651 0ustar vignavigna/* Header for precompiled hash tables for internal commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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-2.5/src/help.c0000644000076600007660000023366312102010575012661 0ustar vignavigna/* Help strings. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 your", "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[16] = { "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[13] = { "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[6] = { "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.", }; 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[15] = { "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[7] = { "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", "line.", }; 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 line 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[32] = { "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 `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[8] = { "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[15] = { "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[26] = { "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[46] = { "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[13] = { "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.", }; const char * const SAVEAS_HELP[14] = { "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.", }; const char * const SAVEAUTOPREFS_HELP[7] = { "Syntax: SaveAutoPrefs", "Abbreviation: SAP", "", "saves the current preferences on 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 on 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 on 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 on 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[14] = { "Syntax: SelectDoc", "Abbreviation: SD", "", "displays a requester containing the names of all the documents in", "memory. You select whichever document you want to become the current", "document.", "", " If you escape from the requester the requester goes away and you are", "returned to your original current document.", "", " `SelectDoc' is especially useful if you have a large number of", "documents open (say, more than 10). Otherwise, `NextDoc' and `PrevDoc'", "should be enough. 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 line. 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[23] = { "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[43] = { "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. 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", " 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-2.5/src/help.h0000644000076600007660000001607312102010575012660 0ustar vignavigna/* Help string externs. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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[ 16 ]; 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[ 13 ]; extern const char * const EXIT_HELP[ 6 ]; 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[ 15 ]; extern const char * const HEXCODE_HELP[ 7 ]; 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[ 32 ]; extern const char * const MARK_HELP[ 12 ]; extern const char * const MARKVERT_HELP[ 21 ]; extern const char * const MATCHBRACKET_HELP[ 8 ]; extern const char * const MODIFIED_HELP[ 15 ]; 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[ 26 ]; extern const char * const REPLACE_HELP[ 46 ]; 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[ 13 ]; extern const char * const SAVEAS_HELP[ 14 ]; 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[ 14 ]; 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[ 23 ]; extern const char * const SUSPEND_HELP[ 6 ]; extern const char * const SYNTAX_HELP[ 43 ]; 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-2.5/src/info2cap.c0000644000076600007660000002027312076214662013436 0ustar vignavigna/* terminfo emulation through GNU termcap code. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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; char *p; int i, arg[4]; va_start(ap, cap_string); for(i = 0; i < 4; i++) arg[i] = va_arg(ap, int); va_end(ap); 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) { int l,c; char *term_name = getenv("TERM"), *s; struct termios termios; if (!term_name) return ERR; 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); }*/ 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; 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-2.5/src/info2cap.h0000644000076600007660000000634712076214662013451 0ustar vignavigna/* terminfo emulation through GNU termcap definitions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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() /* These are defined in curses.h. The code relies on them. */ #define TRUE 1 #define FALSE 0 ne-2.5/src/input.c0000644000076600007660000005070612076214662013100 0ustar vignavigna/* Input line handling. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 unsigned char * const prompt) { assert(prompt != NULL); move_cursor(ne_lines - 1, 0); clear_to_eol(); standout_on(); output_string(prompt, FALSE); output_string(":", FALSE); standout_off(); output_string(" ", FALSE); reset_status_bar(); return strlen(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. */ int request_response(const buffer * const b, const char * const prompt, const int default_value) { const char response = request_char(b, prompt, default_value ? 'y' : 'n'); if (response == 'Y') return TRUE; return 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) { int c; input_class ic; 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) { 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. */ int request_number(const char * const prompt, const int default_value) { static char t[MAX_INT_LEN], *result, *endptr; int n; if (default_value >= 0) sprintf(t, "%d", default_value); if (!(result = request(prompt, default_value >= 0 ? t : NULL, FALSE, 0, io_utf8))) return ABORT; if (!*result) return ERROR; n = strtol(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_allowed and prefer_utf8 work as in request(). */ char *request_string(const char * const prompt, const char * const default_string, const int accept_null_string, const int completion_allowed, const int prefer_utf8) { const char * const result = request(prompt, default_string, TRUE, completion_allowed, 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 unsigned 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_allowed has three possible values: 0 means no completion, 1 complete as a filename, 2 complete as a command followed by a filename. 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 unsigned 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 x position for editing; len is the current raw length of the input buffer (input_buffer[len] is always a NULL); x is the 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) { int i, j; move_cursor(ne_lines - 1, start_x); for(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_move_left(const int 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 int 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); int width, prev, i; if (x + width_to_end < ne_columns) { x += width_to_end; pos = len; return; } x = start_x; i = ne_columns - 1 - start_x; pos = offset = len; for(;;) { prev = prev_pos(input_buffer, offset, encoding); 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) { int 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) { int word_skipped = FALSE, c; while(pos > 0) { input_move_left(FALSE); 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); int paste_len; if (cd) { if (cd->cs->encoding != ENC_ASCII && encoding != ENC_ASCII && cd->cs->encoding != encoding) { alert(); return; } 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 int alpha_allowed, const int completion_allowed, const int prefer_utf8) { action a; input_class ic; int c, c_len, c_width, first_char_typed = TRUE, last_char_completion = FALSE, selection = FALSE, quoted; unsigned char *completion, *prefix, *p; 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(); } while(TRUE) { assert(input_buffer[len] == 0); move_cursor(ne_lines - 1, x); 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_allowed) 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; c_len = encoding == ENC_UTF8 ? utf8seqlen(c) : 1; 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_allowed) { quoted = FALSE; 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 = p = request_files(prefix, TRUE); reset_window(); if (completion) { if (*completion) selection = TRUE; else completion++; } } else { completion = p = complete(prefix); 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; if ((a = parse_command_line(key_binding[c], NULL, NULL, FALSE))>=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) { c_len = encoding == ENC_UTF8 ? utf8len(input_buffer[pos]) : 1; 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 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-2.5/src/inputclass.c0000644000076600007660000004723612076214662014132 0ustar vignavigna/* Input class, key bindings and upper casing vector definitions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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, MOVEINCUP_ABBREV, MOVEINCDOWN_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-2.5/src/keycodes.h0000644000076600007660000000455112076214662013551 0ustar vignavigna/* Extended codes for special keys. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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-2.5/src/keys.c0000644000076600007660000004145112076214662012711 0ustar vignavigna/* Terminfo database scanning and keyboard escape sequence matching functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 { unsigned char *string; int code; } term_key; static term_key key[MAX_TERM_KEY]; static int num_keys = 0; /* Function to pass to qsort for sorting the key capabilities array. */ static int keycmp(const void *t1, const void *t2) { return -strcmp((char *)((term_key *)t1)->string, (char *)((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) { int l,r,m = 0; if (num_keys) { l = 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; } } else return 0; return l; } #ifdef DEBUGPRINTF void dump_keys(void) { int i; for (i = 0; i < num_keys; i++) { unsigned 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 = (unsigned char *)cap_string; key[num_keys].code = code; num_keys++; } /* key_may_set() sets a key capability string IFF it isn't already assigned. It assumes the array is already sorted, and it keeps it that way. 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 is already defined. */ int key_may_set(const char * const cap_string, const int code) { int pos=0; if (num_keys >= MAX_TERM_KEY - 1) return 0; if (!cap_string || (pos = binsearch(cap_string)) < 0) return pos; memmove(key + pos + 1, key + pos, (num_keys - pos) * sizeof *key); key[pos].string = (unsigned char *)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) { int i; 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(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 unsigned char kbd_buffer[KBD_BUF_SIZE]; int c, e, last_match = 0, cur_key = 0, 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 && 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-2.5/src/menu.c0000644000076600007660000007461112076214662012706 0ustar vignavigna/* Menu handling function. Includes also key and menu configuration parsing. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 }, { "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 }, { "Incr Up Home", MOVEINCUP_ABBREV }, { "Incr Down End", 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 = 0, 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) { int i; 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); 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) { int i; line_desc *ld = cur_buffer->top_line_desc; set_attr(0); standout_on(); move_cursor(0, menus[n].xpos); output_string(menus[n].text, TRUE); standout_off(); for(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) { int i; for(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) { int i,j; c = toupper(c); for(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) { int i = 0, n = 0; move_cursor(0,0); set_attr(0); standout_on(); if (!fast_gui && standout_ok) cursor_off(); while(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 int showing_msg; static int 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]; int i = 0, j; int ch = b->cur_pos < b->cur_line_desc->line_len ? (b->encoding == ENC_UTF8 ? utf8char(&b->cur_line_desc->line[b->cur_pos]) : b->cur_line_desc->line[b->cur_pos]) : -1; 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(j = 0; j < 4; j++) string[i++] = ' '; if (ch > 0xFF) { string[i++] = "0123456789abcdef"[(ch >> 12) & 0x0f]; string[i++] = "0123456789abcdef"[(ch >> 8) & 0x0f]; } else for(j = 0; j < 2; j++) string[i++] = ' '; if (ch > -1) { string[i++] = "0123456789abcdef"[(ch >> 4) & 0x0f]; string[i++] = "0123456789abcdef"[ch & 0x0f]; } else for(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 int x = -1, y = -1, percent = -1; char *p; int len; if (showing_msg) { showing_msg = FALSE; bar_gone = TRUE; return; } set_attr(0); 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 int update_x = x != cur_buffer->win_x + cur_buffer->cur_x; const int update_y = y != cur_buffer->cur_line; const int update_percent = percent != new_percent; const int update_flags = strcmp(flag_string, p = gen_flag_string(cur_buffer)); const int update_filename = strlen(flag_string) != strlen(p); const int 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, "%9d", y + 1); output_chars(bar_buffer, NULL, len, TRUE); } if (update_x) { move_cursor(ne_lines - 1, offset + 12); len = sprintf(bar_buffer, "%9d", x + 1); output_chars(bar_buffer, NULL, len, TRUE); } if (update_percent) { move_cursor(ne_lines - 1, offset + 22); 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 + 27); 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:%9d C:%9d %3d%% %s " : " L:%9d C:%9d %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) { input_class ic; action a; int c, n; unsigned char *p; draw_first_menu(); while(TRUE) { 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; if ((a = parse_command_line(key_binding[c], &n, &p, FALSE))>=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)()) { char *prefs_dir, *menu_conf; char_stream *cs; unsigned char *p; int pass, cur_menu, cur_item, num_items_in_menu, line; menu *new_menus; menu_item *new_items; if (!menu_conf_name) menu_conf_name = MENU_CONF_NAME; if (prefs_dir = exists_prefs_func()) { if (menu_conf = malloc(strlen(prefs_dir) + strlen(menu_conf_name) + 1)) { strcat(strcpy(menu_conf, prefs_dir), menu_conf_name); if ((cs = load_stream(NULL, menu_conf_name, FALSE, FALSE)) || (cs = load_stream(NULL, menu_conf, FALSE, FALSE))) { for(pass = 0; pass < 2; pass++) { p = cs->stream; line = 1; cur_menu = -1; cur_item = 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)()) { char *prefs_dir, *key_bindings; char_stream *cs; unsigned char *p; int c, line; if (!key_bindings_name) key_bindings_name = KEY_BINDINGS_NAME; if (prefs_dir = exists_prefs_func()) { if (key_bindings = malloc(strlen(prefs_dir) + strlen(key_bindings_name) + 1)) { strcat(strcpy(key_bindings, prefs_dir), key_bindings_name); if (cs = load_stream(NULL, key_bindings, FALSE, FALSE)) { p = cs->stream; line = 1; while(p - cs->stream < cs->len) { if (*p && !cmdcmp(KEY_KEYWORD, p)) { while(*p && !isasciispace(*p)) p++; 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(&p, buf, strlen(p)) > 0) { /* parse_string() expects double-quoted string. */ while(*p && isasciispace(*p)) p++; /* skip to key code */ if (*p && sscanf(p, "%x %*s", &c) == 1) { /* convert key code */ if (c >= 0 && c < NUM_KEYS) { if (c != 27 && c != 13) { if ((c=key_may_set(buf,c)) < 0) error_in_key_bindings(line, "sequence already assigned." ); else if (c==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-2.5/src/names.c0000644000076600007660000003502612102010575013025 0ustar vignavigna/* Names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 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, 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-2.5/src/names.h0000644000076600007660000002571112102010575013032 0ustar vignavigna/* Extern's for names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 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-2.5/src/navigation.c0000644000076600007660000006514012076214662014076 0ustar vignavigna/* Navigation functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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) { line_desc *ld = b->cur_line_desc; int pos, i, width, last_char_width, 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; } 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) { int shift_right; b->opt.tab_size = min(b->opt.tab_size, max(ne_columns / 2 - 1,1)); if (shift_right = b->win_x % b->opt.tab_size) { 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) { int 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) { int i; line_desc *ld_top, *ld_cur; 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; ld_top = b->top_line_desc; ld_cur = b->cur_line_desc; for(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) { int i, disp; line_desc *ld_top, *ld_cur; b->y_wanted = 0; if (b->cur_y < ne_lines - 2) { update_syntax_states(b, b->cur_y, b->cur_line_desc, NULL); if (b->win_y >= b->num_lines - (ne_lines - 1)) { ld_cur = b->top_line_desc; 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(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; 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; ld_top = b->top_line_desc; ld_cur = b->cur_line_desc; for(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) { int i, disp; disp = ne_lines - 2; /* 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 (i = 0; i < disp; 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) { int i, disp, shift_view; disp = ne_lines - 2; /* 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; } shift_view = (b->win_y + disp < b->num_lines); /* can't already see the last line */ for (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) { int i; line_desc *ld_cur; 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; if (b->win_y >= b->num_lines - (ne_lines - 1)) { ld_cur = b->top_line_desc; 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(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 unsigned char *p) { int i, disp, mag, rc = OK; char *q; b->y_wanted = 0; if (!p) p = "t"; while(*p) { disp = 0; 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(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(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 int n) { int i; line_desc *ld; b->y_wanted = 0; if (n >= b->num_lines || n == b->cur_line) return; 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(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; b->cur_line = n; if (n < b->num_lines / 2) { ld = (line_desc *)b->line_desc_list.head; for(i = 0; i < b->win_y; i++) ld = (line_desc *)ld->ld_node.next; } else { ld = (line_desc *)b->line_desc_list.tail_pred; for(i = b->num_lines - 1; i > b->win_y; i--) ld = (line_desc *)ld->ld_node.prev; } b->top_line_desc = ld; for(i = 0; i < b->cur_y; i++) ld = (line_desc *)ld->ld_node.next; b->cur_line_desc = ld; if (b == cur_buffer) update_window(b); resync_pos(b); } void goto_column(buffer * const b, const int 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 int pos) { goto_column(b, calc_width(b->cur_line_desc, pos, b->opt.tab_size, b->encoding)); } void move_to_sol(buffer * const b) { int t; b->x_wanted = 0; b->y_wanted = 0; t = b->win_x; b->win_x = b->cur_x = b->cur_pos = b->cur_char = 0; if (t && b == cur_buffer) update_window(b); } void move_to_eol(buffer * const b) { int i, pos, width, total_width; 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; } 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(i = pos = 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) { int 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) { int 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) { int i; int old_win_x = b->win_x, old_win_y = b->win_y; line_desc *ld = (line_desc *)b->line_desc_list.tail_pred; b->x_wanted = 0; b->y_wanted = 0; for(i = 0; i < ne_lines - 2 && ld->ld_node.prev->prev; i++) ld = (line_desc *)ld->ld_node.prev; 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) { line_desc *ld; int c, y, pos = b->cur_pos, word_started = FALSE, space_skipped = FALSE; assert(dir == -1 || dir == 1); ld = b->cur_line_desc; if (pos >= ld->line_len) pos = ld->line_len; else { c = get_char(&ld->line[pos], b->encoding); if (!ne_isword(c, b->encoding)) space_skipped = TRUE; } if (dir < 0 || pos < ld->line_len) pos = (dir > 0 ? next_pos : prev_pos)(ld->line, pos, b->encoding); y = b->cur_line; while(y < b->num_lines && y >= 0) { while(pos < ld->line_len && pos >= 0) { 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; int pos = b->cur_pos, c; if (pos >= ld->line_len || !ne_isword(c = get_char(&ld->line[pos], b->encoding), b->encoding)) return; while(pos < ld->line_len) { c = get_char(&ld->line[pos], b->encoding); if (!ne_isword(c, b->encoding)) break; pos += b->encoding == ENC_UTF8 ? utf8len(ld->line[pos]) : 1; } 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-2.5/src/ne.c0000644000076600007660000003276712101771421012340 0ustar vignavigna/* main(), global initialization and global buffer functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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-2013 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" "--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(). */ int req_order; int fast_gui; int status_bar = TRUE; int verbose_macros = TRUE; /* end of global prefs */ buffer *cur_buffer; int turbo; int 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(int show) { int i; if (show) { clear_entire_screen(); 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); reset_window(); } } /* 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) { input_class ic; int i, c, no_config = FALSE, displaying_info = FALSE, first_line = 0; char *macro_name = NULL, *key_bindings_name = NULL, *menu_conf_name = NULL; clip_desc *cd; char *skiplist, *locale; if (!(skiplist = calloc(argc, 1))) /* We need this many flags. */ exit(1); /* This would be bad. */ locale = setlocale(LC_ALL, ""); for(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 = (char *)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); } for(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], "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. */ 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. */ if (!(cd = alloc_clip_desc(INT_MAX, 0))) 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_CHAR_CLASSES | I would *love* character classes, but they do not seem to work... 8^( */ 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 ); /* 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(); load_auto_prefs(cur_buffer, DEF_PREFS_NAME); 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). */ int first_file = TRUE; int first_line=0, first_col=0, binary=0, skip_plus=0; char *d; stop = FALSE; for(i = 1; i < argc && !stop; i++) { if (argv[i] && !skiplist[i]) { if (argv[i][0] == '+' && !skip_plus) { /* looking for "+", or "+N" or "+N,M" */ int tmp_l=INT_MAX, tmp_c=0; char *d; errno = 0; if (argv[i][1]) { if (isdigit(argv[i][1])) { tmp_l = strtol(argv[i]+1, &d, 10); if (!errno) { if (*d) { /* separator between N and M */ if (isdigit(d[1])) { tmp_c = strtol(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 = 1; i--; } } else if (!strcmp(argv[i],"--binary")) { binary = 1; } 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 (iopt.automatch) automatch_bracket(cur_buffer, TRUE); } draw_status_bar(); move_cursor(cur_buffer->cur_y, cur_buffer->cur_x); 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. */ ic = CHAR_CLASS(c); if (displaying_info) { about(0); 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-2.5/src/ne.h0000644000076600007660000004653612076214662012356 0ustar vignavigna/* Main typedefs and defines. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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" #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" /* 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 - 1) == 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 }; /* 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 { int size, len; encoding_type encoding; unsigned char *stream; } 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 zero, 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; unsigned char *line; int 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; unsigned char *line; int 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; int size; int 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; int size, first_used, last_used; unsigned 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 { int line; int pos; int 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; int steps_size; int streams_size; int cur_step; int cur_stream; int last_step; int last_stream; int 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; int win_x, win_y; /* line and pos of upper left-most visible character. */ int cur_x, cur_y; /* position of cursor within the window */ int wanted_x; /* desired x position modulo short lines, tabs, etc. Valid only if x_wanted is TRUE. */ int wanted_y; /* desired y position modulo top or bottom of buffer. Valid if y_wanted is TRUE. */ int wanted_cur_y; /* desired cur_y, valid if y_wanted is TRUE */ int cur_line; /* the current line */ int cur_pos; /* position of cursor within the document buffer (counts bytes) */ int cur_char; /* position of cursor within the attribute buffer (counts characters) */ int num_lines; int 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; int allocated_chars; int free_chars; encoding_type encoding; undo_buffer undo; struct { int pos; int 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. */ int *attr_buf; /* If attr_len >= 0, a pointer to the list of *current* attributes of the *current* line. */ int attr_size; /* attr_buf size. */ int 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. */ unsigned int link_undos, /* Link the undo steps. Multilevel. */ 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 int need_attr_update; /* If true, we want the hardwired ANSI terminal, not a real one. */ extern int ansi; /* If true, we want requests by column, otherwise by row. */ extern int req_order; /* The status bar is displayed */ extern int status_bar; /* If true, we want abbreviated screen updates. */ extern int fast_gui; /* Recorded macros use long command names */ extern int verbose_macros; /* If true, we want syntax highlighting. */ extern int 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 unsigned int stop; /* This is set by the signal handler whenever a SIGWINCH happens. */ extern unsigned int 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 int 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-2.5/src/prefs.c0000644000076600007660000002524212076214662013055 0ustar vignavigna/* Preferences functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 int 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) { int i; assert(filename != NULL); for(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; char *home_dir; struct stat s; /* 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. */ 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); 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; struct stat s; const char *global_dir; /* 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; } if ((global_dir = get_global_dir()) && (gprefs_dir = malloc(strlen(global_dir) + 3 ))) { strcpy(gprefs_dir, global_dir); 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) { int error; char_stream *cs; if (!b || !name) return ERROR; assert_buffer(b); if (cs = alloc_char_stream(PREF_FILE_SIZE_GUESS)) { /* We create a macro by recording an action for each kind of flag. */ if (!saving_global && b->syn) record_action(cs, SYNTAX_A, -1, 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. */ if (req_order) record_action(cs, REQUESTORDER_A, req_order, NULL, verbose_macros); 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 = 0; } 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) { int error = OK; char_stream *cs; if (!b || !name) return ERROR; assert_buffer(b); b->exec_only_options = 1; if (cs = load_stream(NULL, name, FALSE, FALSE)) { 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) { struct high_syntax *syn; assert_buffer(b); assert(name != NULL); syn = load_syntax((char *)name); if (!syn) syn = load_syntax((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 *)) { int error = OK; char *auto_name, *prefs_dir; 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. */ 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 ? 1 : 0; return do_auto_prefs(b, name, save_prefs); } /************************************** This bit has to do with pushing and poping 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-2.5/src/protos.h0000644000076600007660000003246312076214662013274 0ustar vignavigna/* Function prototypes Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 . */ /* actions.c */ int do_action(buffer *b, action a, int c, unsigned char *p); /* autocomp.c */ unsigned char *autocomplete(unsigned 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(int size); void free_char_pool(char_pool *cp); char_pool *get_char_pool(buffer *b, unsigned char *p); line_desc_pool *alloc_line_desc_pool(int 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); int 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); unsigned char *alloc_chars(buffer *b, int len); int alloc_chars_around(buffer *b, line_desc *ld, int n, int check_first_before); void free_chars(buffer *b, unsigned char *p, int len); int insert_one_line(buffer *b, line_desc *ld, int line, int pos); int delete_one_line(buffer *b, line_desc *ld, int line); int undelete_line(buffer *b); void delete_to_eol(buffer *b, line_desc *ld, int line, int pos); int insert_stream(buffer *b, line_desc *ld, int line, int pos, const unsigned char *stream, int stream_len); int insert_one_char(buffer *b, line_desc *ld, int line, int pos, int c); int insert_spaces(buffer *b, line_desc *ld, int line, int pos, int n); int delete_stream(buffer *b, line_desc *ld, int line, int pos, int len); int delete_one_char(buffer *b, line_desc *ld, int line, int pos); void change_filename(buffer *b, char *name); void ensure_attr_buf(buffer * const b, const int 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, int size); clip_desc *realloc_clip_desc(clip_desc *cd, int n, int 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, int cut); int erase_block(buffer *b); int paste_to_buffer(buffer *b, int n); int copy_vert_to_clip(buffer *b, int n, int cut); int erase_vert_block(buffer *b); int paste_vert_to_buffer(buffer *b, int n); int load_clip(int n, const char *name, int preserve_cr, int binary); int save_clip(int n, const char *name, int CRLF, int binary); /* command.c */ void build_hash_table(void); void build_command_name_table(void); action parse_command_line(const unsigned char *command_line, int *num_arg, unsigned char **string_arg, int exec_only_options); int execute_command_line(buffer *b, const unsigned char *command_line); macro_desc *alloc_macro_desc(void); void free_macro_desc(macro_desc *md); void record_action(char_stream *cs, action a, int c, unsigned char *p, int 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 unsigned char *c, const unsigned char *m); void unload_macros(void); void optimize_macro(char_stream *cs, int 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, int start, int len, int tab_size, int cleared_at_end, int utf8, const int * const attr, const int * const diff, const int diff_size); line_desc *update_partial_line(buffer *b, int n, int start_x, int cleared_at_end, const int differential); void update_line(buffer *b, int n, const int cleared_at_end, const int differential); void update_window_lines(buffer *b, int start_line, int end_line, int 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, int pos, int attr_pos, int line, int x); void update_inserted_char(buffer *b, int c, const line_desc *ld, int pos, int attr_pos, int line, int x); void update_overwritten_char(buffer *b, int old_char, int new_char, const line_desc *ld, int pos, int 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 int 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 int min_line, const int max_line, int *match_line, int *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 int line, line_desc * const ld, const int 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, const 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 unsigned 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, int n); void goto_line(buffer *b, int n); void goto_pos(buffer *b, int 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(int show); void automatch_bracket(buffer *b, int show); int main(int argc, char **argv); /* 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); int request_response(const buffer *b, const char *prompt, int default_value); char request_char(const buffer *b, const char *prompt, const char default_value); int request_number(const char *prompt, int default_value); char *request_string(const char *prompt, const char *default_string, int accept_null_string, int completion_allowed, int prefer_utf8); char *request(const char *prompt, const char *default_string, int alpha_allowed, int completion_allowed, int prefer_utf8); /* request.c */ int request_strings(const char * const * const entries, int num_entries, int default_entry, int max_name_len, int mark_char); char *request_files(const char *filename, int use_prefix); char *request_file(const buffer *b, const char *prompt, const char *default_name); int request_document(void); /* search.c */ int find(buffer *b, const unsigned char *pattern, const int skip_first); int replace(buffer *b, int n, const char *string); int find_regexp(buffer *b, const unsigned char *regex, const int 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(int size); void free_char_stream(char_stream *cs); char_stream *realloc_char_stream(char_stream *cs, int size); int add_to_stream(char_stream *cs, const unsigned char *s, int 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, int preserve_cr, int binary); char_stream *load_stream_from_fh(char_stream *cs, int fh, int preserve_cr, int binary); int save_stream(const char_stream *cs, const char *name, int CRLF, int binary); int save_stream_to_fh(const char_stream *cs, int fh, int CRLF, int binary); int delete_from_stream(char_stream *cs, int p, int len); int insert_in_stream(char_stream *cs, const char *s, int p, int len); /* support.c */ int 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); char *str_dup(const char *s); int strnlen_ne(const char *s, int n); int strcmpp(const void *a, const void *b); int strdictcmp(const void *a, const void *b); int filenamecmpp(const void *a, const void *b); void set_interactive_mode(void); void unset_interactive_mode(void); int calc_width(const line_desc *ld, int n, int tab_size, encoding_type encoding); int calc_char_len(const line_desc *ld, encoding_type encoding); int calc_pos(const line_desc *ld, int n, int tab_size, encoding_type encoding); int get_string_width(const unsigned char * const s, const int len, const encoding_type encoding); int max_prefix(const char *s, const char *t); int is_prefix(const char *p, const char *s); char *complete(const char *start_prefix); int is_migrated(const char *name); int is_directory(const char *name); int is_ascii(const unsigned char *s, int len); int isparaspot(int c); int isasciispace(int c); int isasciipunct(int c); int isasciialpha(int c); int asciitoupper(int c); int asciitolower(int c); encoding_type detect_encoding(const unsigned char *s, int len); int next_pos(const unsigned char *s, int pos, encoding_type encoding); int prev_pos(const unsigned char *s, int pos, encoding_type encoding); int get_char(const unsigned char *s, encoding_type encoding); int get_char_width(const unsigned char * const s, const encoding_type encoding); int ne_ispunct(const int c, const int encoding); int ne_isspace(const int c, const int encoding); int ne_isword(const int c, const int encoding); int context_prefix(const buffer *b, unsigned char **p, int *prefix_pos, encoding_type encoding); /* term.c */ 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 unsigned int); void output_chars(const unsigned char *string, const unsigned int *attr, int raw_len, int utf8); void output_string(const char *s, int utf8); void output_spaces(int n, const unsigned int *attr); void output_char(int c, unsigned int attr, int utf8); void insert_chars(const unsigned char *start, const unsigned int *attr, int raw_len, int utf8); void insert_char(int c, const unsigned int attr, int utf8); void delete_chars(int n); int ins_del_lines(int vpos, int n); int ttysize(void); void term_init(void); /* undo.c */ void start_undo_chain(buffer *b); void end_undo_chain(buffer *b); int add_undo_step(buffer *b, int line, int pos, int len); void fix_last_undo_step(buffer *b, int delta); int add_to_undo_stream(undo_buffer *ub, const char *p, int len); void reset_undo_buffer(undo_buffer *ub); int undo(buffer *b); int redo(buffer *b); ne-2.5/src/regcomp.c0000644000076600007660000033027511535371565013404 0ustar vignavigna/* Extended regular expression matching and search library. Copyright (C) 2002,2003,2004,2005,2006,2007,2009 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ 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 int duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint); static int search_duplicated_node (const re_dfa_t *dfa, int 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, int node, int root); static reg_errcode_t calc_inveclosure (re_dfa_t *dfa); static int 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, int 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, int 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, int 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, int 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, int 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, int *equiv_class_alloc, const unsigned char *name); static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, re_charset_t *mbcset, int *char_class_alloc, const unsigned 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 unsigned 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 unsigned char *class_name, const unsigned char *extra, int 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? */ const char __re_error_msgid[] attribute_hidden = { #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 */ }; const size_t __re_error_msgid_idx[] attribute_hidden = { REG_NOERROR_IDX, REG_NOMATCH_IDX, REG_BADPAT_IDX, REG_ECOLLATE_IDX, REG_ECTYPE_IDX, REG_EESCAPE_IDX, REG_ESUBREG_IDX, REG_EBRACK_IDX, REG_EPAREN_IDX, REG_EBRACE_IDX, REG_BADBR_IDX, REG_ERANGE_IDX, REG_ESPACE_IDX, REG_BADRPT_IDX, REG_EEND_IDX, REG_ESIZE_IDX, REG_ERPAREN_IDX }; /* Entry points for GNU code. */ /* re_compile_pattern is the GNU regular expression compiler: it compiles PATTERN (of length LENGTH) and puts the result in BUFP. Returns 0 if the pattern was valid, otherwise an error string. Assumes the `allocated' (and perhaps `buffer') and `translate' fields are set in BUFP on entry. */ const char * re_compile_pattern (pattern, length, bufp) const char *pattern; size_t length; struct re_pattern_buffer *bufp; { reg_errcode_t ret; /* And GNU code determines whether or not to get register information by passing null for the REGS argument to re_match, etc., not by setting no_sub, unless RE_NO_SUB is set. */ bufp->no_sub = !!(re_syntax_options & RE_NO_SUB); /* Match anchors at newline. */ bufp->newline_anchor = 1; ret = re_compile_internal (bufp, pattern, length, re_syntax_options); if (!ret) return NULL; return gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); } #ifdef _LIBC weak_alias (__re_compile_pattern, re_compile_pattern) #endif /* Set by `re_set_syntax' to the current regexp syntax to recognize. Can also be assigned to arbitrarily: each pattern buffer stores its own syntax, so it can be changed between regex compilations. */ /* This has no initializer because initialized variables in Emacs become read-only after dumping. */ reg_syntax_t re_syntax_options; /* Specify the precise syntax of regexps for compilation. This provides for compatibility for various utilities which historically have different, incompatible syntaxes. The argument SYNTAX is a bit mask comprised of the various bits defined in regex.h. We return the old syntax. */ reg_syntax_t re_set_syntax (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 = (re_dfa_t *) 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, int 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 = (re_dfa_t *) bufp->buffer; int node_cnt; int icase = (dfa->mb_cur_max == 1 && (bufp->syntax & RE_ICASE)); for (node_cnt = 0; node_cnt < init_state->nodes.nelem; ++node_cnt) { int 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 = alloca (dfa->mb_cur_max), *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, 0, 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; int 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. */ size_t regerror (errcode, preg, errbuf, errbuf_size) int errcode; const regex_t *__restrict preg; char *__restrict errbuf; size_t errbuf_size; { const char *msg; size_t msg_size; if (BE (errcode < 0 || errcode >= (int) (sizeof (__re_error_msgid_idx) / sizeof (__re_error_msgid_idx[0])), 0)) /* Only error codes returned by the rest of the code should be passed to this routine. If we are given anything else, or if other regex code generates an invalid error code, then the program has a bug. Dump core so we can fix it. */ abort (); msg = gettext (__re_error_msgid + __re_error_msgid_idx[errcode]); msg_size = strlen (msg) + 1; /* Includes the null. */ if (BE (errbuf_size != 0, 1)) { if (BE (msg_size > errbuf_size, 0)) { #if defined HAVE_MEMPCPY || defined _LIBC *((char *) __mempcpy (errbuf, msg, errbuf_size - 1)) = '\0'; #else memcpy (errbuf, msg, errbuf_size - 1); errbuf[errbuf_size - 1] = 0; #endif } else memcpy (errbuf, msg, msg_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. */ [0 ... 0x80 / BITSET_WORD_BITS - 1] = BITSET_WORD_MAX }; #endif static void free_dfa_content (re_dfa_t *dfa) { int 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 = (re_dfa_t *) preg->buffer; if (BE (dfa != NULL, 1)) 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 = (re_dfa_t *) 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 = (unsigned char *) dfa; } preg->used = sizeof (re_dfa_t); err = init_dfa (dfa, length); 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 __libc_lock_init (dfa->lock); err = re_string_construct (®exp, pattern, length, preg->translate, syntax & RE_ICASE, dfa); if (BE (err != REG_NOERROR, 0)) { re_compile_internal_free_return: free_workarea_compile (preg); re_string_destruct (®exp); 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)) { 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) { unsigned int table_size; #ifndef _LIBC char *codeset_name; #endif 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. */ if (pat_len == SIZE_MAX) 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 # ifdef HAVE_LANGINFO_CODESET codeset_name = nl_langinfo (CODESET); # else codeset_name = getenv ("LC_ALL"); if (codeset_name == NULL || codeset_name[0] == '\0') codeset_name = getenv ("LC_CTYPE"); if (codeset_name == NULL || codeset_name[0] == '\0') codeset_name = getenv ("LANG"); if (codeset_name == NULL) codeset_name = ""; else if (strchr (codeset_name, '.') != NULL) codeset_name = strchr (codeset_name, '.') + 1; # endif if (strcasecmp (codeset_name, "UTF-8") == 0 || strcasecmp (codeset_name, "UTF8") == 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, j, ch; dfa->word_ops_used = 1; for (i = 0, ch = 0; 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 = (re_dfa_t *) 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) { int 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) { int node_idx = init_nodes.elems[i]; re_token_type_t type = dfa->nodes[node_idx].type; int 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) { int dest_idx = dfa->edests[node_idx].elems[0]; if (!re_node_set_contains (&init_nodes, dest_idx)) { re_node_set_merge (&init_nodes, dfa->eclosures + dest_idx); 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) { int node, i, mb_chars = 0, has_period = 0; for (node = 0; node < dfa->nodes_len; ++node) switch (dfa->nodes[node].type) { case CHARACTER: if (dfa->nodes[node].opr.c >= 0x80) mb_chars = 1; 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 = 1; 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. The non-ASCII range starts at 0x80. */ assert (0x80 % BITSET_WORD_BITS == 0); for (i = 0x80 / BITSET_WORD_BITS; i < BITSET_WORDS; ++i) if (dfa->nodes[node].opr.sbcset[i]) return; 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 >= 0x80) 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 = (re_dfa_t *) preg->buffer; reg_errcode_t ret; /* Allocate arrays. */ dfa->nexts = re_malloc (int, dfa->nodes_alloc); dfa->org_indices = re_malloc (int, 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 (int, preg->re_nsub); if (dfa->subexp_map != NULL) { int 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) { int 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 = (re_dfa_t *) preg->buffer; bin_tree_t *body = node->left; bin_tree_t *op, *cls, *tree1, *tree; if (preg->no_sub /* We do not optimize empty subexpressions, because otherwise we may have bad CONCAT nodes with NULL children. This is obviously not very common, so we do not lose much. An example that triggers this case is the sed "script" /\(\)/x. */ && node->left != NULL && (node->token.opr.idx >= BITSET_WORD_BITS || !(dfa->used_bkref_map & ((bitset_word_t) 1 << node->token.opr.idx)))) return node->left; /* Convert the SUBEXP node to the concatenation of an OP_OPEN_SUBEXP, the contents, and an OP_CLOSE_SUBEXP. */ op = create_tree (dfa, NULL, NULL, OP_OPEN_SUBEXP); cls = create_tree (dfa, NULL, NULL, OP_CLOSE_SUBEXP); tree1 = body ? create_tree (dfa, body, cls, CONCAT) : cls; tree = create_tree (dfa, op, tree1, CONCAT); if (BE (tree == NULL || tree1 == NULL || op == NULL || cls == NULL, 0)) { *err = REG_ESPACE; return NULL; } op->token.opr.idx = cls->token.opr.idx = node->token.opr.idx; op->token.opt_subexp = cls->token.opt_subexp = node->token.opt_subexp; return tree; } /* Pass 1 in building the NFA: compute FIRST and create unlinked automaton nodes. Requires a postorder visit. */ static reg_errcode_t calc_first (void *extra, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) extra; if (node->token.type == CONCAT) { node->first = node->left->first; node->node_idx = node->left->node_idx; } else { node->first = node; node->node_idx = re_dfa_add_node (dfa, node->token); if (BE (node->node_idx == -1, 0)) return REG_ESPACE; if (node->token.type == ANCHOR) dfa->nodes[node->node_idx].constraint = node->token.opr.ctx_type; } return REG_NOERROR; } /* Pass 2: compute NEXT on the tree. Preorder visit. */ static reg_errcode_t calc_next (void *extra, bin_tree_t *node) { switch (node->token.type) { case OP_DUP_ASTERISK: node->left->next = node; break; case CONCAT: node->left->next = node->right->first; node->right->next = node->next; break; default: if (node->left) node->left->next = node->next; if (node->right) node->right->next = node->next; break; } return REG_NOERROR; } /* Pass 3: link all DFA nodes to their NEXT node (any order will do). */ static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node) { re_dfa_t *dfa = (re_dfa_t *) extra; int 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: { int left, right; dfa->has_plural_match = 1; if (node->left != NULL) left = node->left->first->node_idx; else left = node->next->node_idx; if (node->right != NULL) right = node->right->first->node_idx; else right = node->next->node_idx; assert (left > -1); assert (right > -1); err = re_node_set_init_2 (dfa->edests + idx, left, right); } break; case ANCHOR: case OP_OPEN_SUBEXP: case OP_CLOSE_SUBEXP: err = re_node_set_init_1 (dfa->edests + idx, node->next->node_idx); break; case OP_BACK_REF: dfa->nexts[idx] = node->next->node_idx; if (node->token.type == OP_BACK_REF) 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, int top_org_node, int top_clone_node, int root_node, unsigned int init_constraint) { int org_node, clone_node, ret; unsigned int constraint = init_constraint; for (org_node = top_org_node, clone_node = top_clone_node;;) { int org_dest, clone_dest; if (dfa->nodes[org_node].type == OP_BACK_REF) { /* If the back reference epsilon-transit, its destination must also have the constraint. Then duplicate the epsilon closure of the destination of the back reference, and store it in edests of the back reference. */ org_dest = dfa->nexts[org_node]; re_node_set_empty (dfa->edests + clone_node); clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; dfa->nexts[clone_node] = dfa->nexts[org_node]; ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 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 clsoure has a loop. Then tie it to the destination of the root_node. */ if (org_node == root_node && clone_node != org_node) { ret = re_node_set_insert (dfa->edests + clone_node, org_dest); if (BE (ret < 0, 0)) return REG_ESPACE; break; } /* In case of the node has another constraint, add it. */ constraint |= dfa->nodes[org_node].constraint; clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 0)) return REG_ESPACE; } else /* dfa->edests[org_node].nelem == 2 */ { /* In case of the node can epsilon-transit, and it has two destinations. In the bin_tree_t and DFA, that's '|' and '*'. */ org_dest = dfa->edests[org_node].elems[0]; re_node_set_empty (dfa->edests + clone_node); /* Search for a duplicated node which satisfies the constraint. */ clone_dest = search_duplicated_node (dfa, org_dest, constraint); if (clone_dest == -1) { /* There is no such duplicated node, create a new one. */ reg_errcode_t err; clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 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. */ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 0)) return REG_ESPACE; } org_dest = dfa->edests[org_node].elems[1]; clone_dest = duplicate_node (dfa, org_dest, constraint); if (BE (clone_dest == -1, 0)) return REG_ESPACE; ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); if (BE (ret < 0, 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 int search_duplicated_node (const re_dfa_t *dfa, int org_node, unsigned int constraint) { int idx; for (idx = dfa->nodes_len - 1; dfa->nodes[idx].duplicated && idx > 0; --idx) { if (org_node == dfa->org_indices[idx] && constraint == dfa->nodes[idx].constraint) return idx; /* Found. */ } return -1; /* Not found. */ } /* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT. Return the index of the new node, or -1 if insufficient storage is available. */ static int duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint) { int dup_idx = re_dfa_add_node (dfa, dfa->nodes[org_idx]); if (BE (dup_idx != -1, 1)) { dfa->nodes[dup_idx].constraint = constraint; dfa->nodes[dup_idx].constraint |= dfa->nodes[org_idx].constraint; dfa->nodes[dup_idx].duplicated = 1; /* Store the index of the original node. */ dfa->org_indices[dup_idx] = org_idx; } return dup_idx; } static reg_errcode_t calc_inveclosure (re_dfa_t *dfa) { int src, idx, ret; for (idx = 0; idx < dfa->nodes_len; ++idx) re_node_set_init_empty (dfa->inveclosures + idx); for (src = 0; src < dfa->nodes_len; ++src) { int *elems = dfa->eclosures[src].elems; for (idx = 0; idx < dfa->eclosures[src].nelem; ++idx) { ret = re_node_set_insert_last (dfa->inveclosures + elems[idx], src); if (BE (ret == -1, 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) { int node_idx, incomplete; #ifdef DEBUG assert (dfa->nodes_len > 0); #endif incomplete = 0; /* 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 = 0; node_idx = 0; } #ifdef DEBUG assert (dfa->eclosures[node_idx].nelem != -1); #endif /* If we have already calculated, skip it. */ if (dfa->eclosures[node_idx].nelem != 0) continue; /* Calculate epsilon closure of `node_idx'. */ err = calc_eclosure_iter (&eclosure_elem, dfa, node_idx, 1); if (BE (err != REG_NOERROR, 0)) return err; if (dfa->eclosures[node_idx].nelem == 0) { incomplete = 1; 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, int node, int root) { reg_errcode_t err; int i, incomplete; re_node_set eclosure; incomplete = 0; err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1); if (BE (err != REG_NOERROR, 0)) return err; /* This indicates that we are calculating this node now. We reference this value to avoid infinite loop. */ dfa->eclosures[node].nelem = -1; /* If the current node has constraints, duplicate all nodes since they must inherit the constraints. */ if (dfa->nodes[node].constraint && dfa->edests[node].nelem && !dfa->nodes[dfa->edests[node].elems[0]].duplicated) { err = duplicate_node_closure (dfa, node, node, node, dfa->nodes[node].constraint); if (BE (err != REG_NOERROR, 0)) return err; } /* Expand each epsilon destination nodes. */ if (IS_EPSILON_NODE(dfa->nodes[node].type)) for (i = 0; i < dfa->edests[node].nelem; ++i) { re_node_set eclosure_elem; int edest = dfa->edests[node].elems[i]; /* If calculating the epsilon closure of `edest' is in progress, return intermediate result. */ if (dfa->eclosures[edest].nelem == -1) { incomplete = 1; 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, 0); if (BE (err != REG_NOERROR, 0)) return err; } else eclosure_elem = dfa->eclosures[edest]; /* Merge the epsilon closure of `edest'. */ re_node_set_merge (&eclosure, &eclosure_elem); /* If the epsilon closure of `edest' is incomplete, the epsilon closure of this node is also incomplete. */ if (dfa->eclosures[edest].nelem == 0) { incomplete = 1; re_node_set_free (&eclosure_elem); } } /* Epsilon closures include itself. */ re_node_set_insert (&eclosure, node); 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 is occured, 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 = (re_dfa_t *) 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, int nest, reg_errcode_t *err) { re_dfa_t *dfa = (re_dfa_t *) 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)) 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, int nest, reg_errcode_t *err) { bin_tree_t *tree, *exp; re_dfa_t *dfa = (re_dfa_t *) 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)) { exp = parse_expression (regexp, preg, token, syntax, nest, err); if (BE (*err != REG_NOERROR && exp == NULL, 0)) { return NULL; } if (tree != NULL && exp != NULL) { tree = create_tree (dfa, tree, exp, CONCAT); if (tree == NULL) { *err = REG_ESPACE; return NULL; } } else if (tree == NULL) tree = exp; /* Otherwise exp == 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, int nest, reg_errcode_t *err) { re_dfa_t *dfa = (re_dfa_t *) 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, (const unsigned char *) "alnum", (const unsigned char *) "_", 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, (const unsigned char *) "space", (const unsigned char *) "", 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) { tree = parse_dup_op (tree, regexp, dfa, token, syntax, err); if (BE (*err != REG_NOERROR && tree == NULL, 0)) return NULL; /* In BRE consecutive duplications are not allowed. */ if ((syntax & RE_CONTEXT_INVALID_DUP) && (token->type == OP_DUP_ASTERISK || token->type == OP_OPEN_DUP_NUM)) { *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, int nest, reg_errcode_t *err) { re_dfa_t *dfa = (re_dfa_t *) 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)) *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; int i, start, end, start_idx = re_string_cur_idx (regexp); re_token_t start_token = *token; if (token->type == OP_OPEN_DUP_NUM) { end = 0; start = fetch_number (regexp, token, syntax); if (start == -1) { if (token->type == CHARACTER && token->opr.c == ',') start = 0; /* We treat "{,m}" as "{0,m}". */ else { *err = REG_BADBR; /* {} is invalid. */ return NULL; } } if (BE (start != -2, 1)) { /* We treat "{n}" as "{n,n}". */ end = ((token->type == OP_CLOSE_DUP_NUM) ? start : ((token->type == CHARACTER && token->opr.c == ',') ? fetch_number (regexp, token, syntax) : -2)); } if (BE (start == -2 || end == -2, 0)) { /* Invalid sequence. */ if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0)) { if (token->type == END_OF_RE) *err = REG_EBRACE; else *err = REG_BADBR; return NULL; } /* If the syntax bit is set, rollback. */ re_string_set_index (regexp, start_idx); *token = start_token; token->type = CHARACTER; /* mb_partial and word_char bits should be already initialized by peek_token. */ return elem; } if (BE ((end != -1 && start > end) || token->type != OP_CLOSE_DUP_NUM, 0)) { /* First number greater than second. */ *err = REG_BADBR; return NULL; } } else { start = (token->type == OP_DUP_PLUS) ? 1 : 0; end = (token->type == OP_DUP_QUESTION) ? 1 : -1; } fetch_token (token, regexp, syntax); if (BE (elem == NULL, 0)) return NULL; if (BE (start == 0 && end == 0, 0)) { postorder (elem, free_tree, NULL); return NULL; } /* Extract "{n,m}" to "...{0,}". */ if (BE (start > 0, 0)) { tree = elem; for (i = 2; i <= start; ++i) { elem = duplicate_tree (elem, dfa); tree = create_tree (dfa, tree, elem, CONCAT); if (BE (elem == NULL || tree == NULL, 0)) goto parse_dup_op_espace; } if (start == end) return tree; /* Duplicate ELEM before it is marked optional. */ elem = duplicate_tree (elem, dfa); old_tree = tree; } else old_tree = NULL; if (elem->token.type == SUBEXP) postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx); tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT)); if (BE (tree == NULL, 0)) goto parse_dup_op_espace; /* This loop is actually executed only when end != -1, to rewrite {0,n} as ((...?)?)?... We have already created the start+1-th copy. */ 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 sinse we may update it. */ static reg_errcode_t internal_function # ifdef RE_ENABLE_I18N build_range_exp (bitset_t sbcset, re_charset_t *mbcset, int *range_alloc, bracket_elem_t *start_elem, bracket_elem_t *end_elem) # else /* not RE_ENABLE_I18N */ build_range_exp (bitset_t sbcset, bracket_elem_t *start_elem, 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; wchar_t cmp_buf[6] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'}; 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; cmp_buf[0] = start_wc; cmp_buf[4] = end_wc; if (wcscoll (cmp_buf, cmp_buf + 4) > 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; int 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) { cmp_buf[2] = wc; if (wcscoll (cmp_buf, cmp_buf + 2) <= 0 && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0) 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, int *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 environement. Seek the collating symbol entry correspondings to NAME. Return the index of the symbol in the SYMB_TABLE. */ auto inline int32_t __attribute ((always_inline)) seek_collating_symbol_entry (name, name_len) const unsigned char *name; size_t name_len; { int32_t hash = elem_hash ((const char *) name, name_len); int32_t elem = hash % table_size; if (symb_table[2 * elem] != 0) { int32_t second = hash % (table_size - 2) + 1; do { /* First compare the hashing value. */ if (symb_table[2 * elem] == hash /* Compare the length of the name. */ && name_len == extra[symb_table[2 * elem + 1]] /* Compare the name. */ && memcmp (name, &extra[symb_table[2 * elem + 1] + 1], name_len) == 0) { /* Yep, this is the entry. */ break; } /* Next entry. */ elem += second; } while (symb_table[2 * elem] != 0); } return elem; } /* 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 (br_elem) 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 (symb_table[2 * elem] != 0) { /* 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 (symb_table[2 * elem] == 0 && 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 environement. 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 sinse we may update it. */ auto inline reg_errcode_t __attribute ((always_inline)) build_range_exp (sbcset, mbcset, range_alloc, start_elem, end_elem) re_charset_t *mbcset; int *range_alloc; bitset_t sbcset; bracket_elem_t *start_elem, *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; 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; int 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 environement. 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 sinse we may update it. */ auto inline reg_errcode_t __attribute ((always_inline)) build_collating_symbol (sbcset, mbcset, coll_sym_alloc, name) re_charset_t *mbcset; int *coll_sym_alloc; bitset_t sbcset; 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 (symb_table[2 * elem] != 0) { /* We found the entry. */ idx = symb_table[2 * elem + 1]; /* Skip the name of collating element name. */ idx += 1 + extra[idx]; } else if (symb_table[2 * elem] == 0 && 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. */ int 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; int coll_sym_alloc = 0, range_alloc = 0, mbchar_alloc = 0; int equiv_class_alloc = 0, char_class_alloc = 0; #endif /* not RE_ENABLE_I18N */ int non_match = 0; bin_tree_t *work_tree; int token_len; int first_round = 1; #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 */ { *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 = 1; 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, is_range_exp = 0; 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 = 0; /* 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 = 1; } } if (is_range_exp == 1) { end_elem.opr.name = end_name_buf; ret = parse_bracket_element (&end_elem, regexp, &token2, token_len2, dfa, syntax, 1); 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 (sbcset, dfa->mb_cur_max > 1 ? mbcset : NULL, &range_alloc, &start_elem, &end_elem); # else *err = build_range_exp (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 */ 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, int 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 sinse we may update it. */ static reg_errcode_t #ifdef RE_ENABLE_I18N build_equiv_class (bitset_t sbcset, re_charset_t *mbcset, int *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); if (BE (idx1 == 0 || cp < name + strlen ((const char *) name), 0)) /* This isn't a valid character. */ return REG_ECOLLATE; /* Build single byte matcing table for this equivalence class. */ char_buf[1] = (unsigned char) '\0'; len = weights[idx1 & 0xffffff]; for (ch = 0; ch < SBC_MAX; ++ch) { char_buf[0] = ch; cp = char_buf; idx2 = findidx (&cp); /* 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. */ int 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 sinse 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, int *char_class_alloc, const unsigned char *class_name, reg_syntax_t syntax) #else /* not RE_ENABLE_I18N */ build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, const unsigned char *class_name, reg_syntax_t syntax) #endif /* not RE_ENABLE_I18N */ { int i; const char *name = (const char *) 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. */ int 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 unsigned char *class_name, const unsigned char *extra, int non_match, reg_errcode_t *err) { re_bitset_ptr_t sbcset; #ifdef RE_ENABLE_I18N re_charset_t *mbcset; int 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 -1, if the number field is empty like "{,1}". Return -2, If an error is occured. */ static int fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax) { int num = -1; unsigned char c; while (1) { fetch_token (token, input, syntax); c = token->opr.c; if (BE (token->type == END_OF_RE, 0)) return -2; if (token->type == OP_CLOSE_DUP_NUM || c == ',') break; num = ((token->type != CHARACTER || c < '0' || '9' < c || num == -2) ? -2 : ((num == -1) ? c - '0' : num * 10 + c - '0')); num = (num > RE_DUP_MAX) ? -2 : num; } 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 = -1; if (left != NULL) left->parent = tree; if (right != NULL) right->parent = tree; return tree; } /* Mark the tree SRC as an optional subexpression. To be called from preorder or postorder. */ static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node) { int idx = (int) (long) 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-2.5/src/regex.c0000644000076600007660000000602011535371565013046 0ustar vignavigna/* Extended regular expression matching and search library. Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* Make sure noone compiles this code with a C++ compiler. */ #ifdef __cplusplus # 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-2.5/src/regex.h0000644000076600007660000005255711535371565013073 0ustar vignavigna/* Definitions for data structures and routines for the regular expression library. Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef _REGEX_H #define _REGEX_H 1 #include /* Allow the use in C++ code. */ #ifdef __cplusplus extern "C" { #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 an bre or immediately after an alternation or begin-group 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_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) #define RE_SYNTAX_GNU_AWK \ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DEBUG) \ & ~(RE_DOT_NOT_NULL | RE_INTERVALS | 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) #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. Some systems (erroneously) define this in other header files, but we want our value, so remove any previous define. */ # ifdef RE_DUP_MAX # undef RE_DUP_MAX # endif /* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ # 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 (REG_EXTENDED << 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 (REG_ICASE << 1) /* 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 (REG_NEWLINE << 1) /* 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_msg' table in regex.c. */ typedef enum { #if defined _XOPEN_SOURCE || defined __USE_XOPEN2K REG_ENOSYS = -1, /* This will never happen for this implementation. */ #endif 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, /* Inalid 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, /* Compiled pattern bigger than 2^16 bytes. */ REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ } reg_errcode_t; /* This data structure represents a compiled pattern. Before calling the pattern compiler, the fields `buffer', `allocated', `fastmap', `translate', and `no_sub' can be set. After the pattern has been compiled, the `re_nsub' field is 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. It is declared as `unsigned char *' because its elements are sometimes used as array indexes. */ unsigned char *__REPB_PREFIX(buffer); /* Number of bytes to which `buffer' points. */ unsigned long int __REPB_PREFIX(allocated); /* Number of bytes actually used in `buffer'. */ unsigned long int __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 `regex_compile' 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. */ typedef int regoff_t; #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 { unsigned 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. */ 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 int re_search (struct re_pattern_buffer *__buffer, const char *__string, int __length, int __start, int __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 int re_search_2 (struct re_pattern_buffer *__buffer, const char *__string1, int __length1, const char *__string2, int __length2, int __start, int __range, struct re_registers *__regs, int __stop); /* Like `re_search', but return how many characters in STRING the regexp in BUFFER matched, starting at position START. */ extern int re_match (struct re_pattern_buffer *__buffer, const char *__string, int __length, int __start, struct re_registers *__regs); /* Relates to `re_match' as `re_search_2' relates to `re_search'. */ extern int re_match_2 (struct re_pattern_buffer *__buffer, const char *__string1, int __length1, const char *__string2, int __length2, int __start, struct re_registers *__regs, int __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 PATTERN_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, unsigned int __num_regs, regoff_t *__starts, regoff_t *__ends); #endif /* Use GNU */ #if defined _REGEX_RE_COMP || (defined _LIBC && defined __USE_BSD) # 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". */ #ifndef __restrict # if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__)) # if defined restrict || 199901L <= __STDC_VERSION__ # define __restrict restrict # else # define __restrict # endif # endif #endif /* gcc 3.1 and up support the [restrict] syntax. */ #ifndef __restrict_arr # if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) \ && !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-2.5/src/regex_internal.c0000644000076600007660000013405211535371565014751 0ustar vignavigna/* Extended regular expression matching and search library. Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ static void re_string_construct_common (const char *str, int len, re_string_t *pstr, RE_TRANSLATE_TYPE trans, int 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, unsigned int hash) internal_function; static re_dfastate_t *create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context, unsigned int 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 re_string_allocate (re_string_t *pstr, const char *str, int len, int init_len, RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa) { reg_errcode_t ret; int 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 re_string_construct (re_string_t *pstr, const char *str, int len, RE_TRANSLATE_TYPE trans, int 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 re_string_realloc_buffers (re_string_t *pstr, int new_buf_len) { #ifdef RE_ENABLE_I18N if (pstr->mb_cur_max > 1) { wint_t *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) { int *new_offsets = re_realloc (pstr->offsets, int, 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, int len, re_string_t *pstr, RE_TRANSLATE_TYPE trans, int 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 ? 1 : 0; 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; int 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) -2, 0)) { /* The buffer doesn't have enough space, finish to build. */ pstr->cur_state = prev_st; break; } else if (BE (mbclen == (size_t) -1 || mbclen == 0, 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; } /* 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 build_wcs_upper_buffer (re_string_t *pstr) { mbstate_t prev_st; int 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 + 2 > 2, 1)) { wchar_t wcu = wc; if (iswlower (wc)) { size_t mbcdlen; wcu = towupper (wc); 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) { /* It is an invalid character 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 + 2 > 2, 1)) { wchar_t wcu = wc; if (iswlower (wc)) { size_t mbcdlen; wcu = towupper (wc); 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 (int, 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) { /* 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 int internal_function re_string_skip_chars (re_string_t *pstr, int new_raw_idx, wint_t *last_wc) { mbstate_t prev_st; int rawbuf_idx; size_t mbclen; wchar_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;) { int remain_len; remain_len = pstr->len - rawbuf_idx; prev_st = pstr->cur_state; mbclen = __mbrtowc (&wc, (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; } /* Then proceed the next character. */ rawbuf_idx += mbclen; } *last_wc = (wint_t) 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) { int 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]; if (islower (ch)) pstr->mbs[char_idx] = toupper (ch); else pstr->mbs[char_idx] = 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) { int 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 re_string_reconstruct (re_string_t *pstr, int idx, int eflags) { int offset = idx - pstr->raw_mbs_idx; if (BE (offset < 0, 0)) { /* 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)) { int 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 DEBUG assert (pstr->valid_len > 0); #endif } } else { /* No, skip all characters until IDX. */ int prev_valid_len = pstr->valid_len; #ifdef RE_ENABLE_I18N 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) { int wcs_idx; wint_t wc = WEOF; if (pstr->is_utf8) { const unsigned char *raw, *p, *q, *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; int mlen = raw + pstr->len - p; unsigned char buf[6]; size_t mbclen; q = p; if (BE (pstr->trans != NULL, 0)) { int i = mlen < 6 ? mlen : 6; while (--i >= 0) buf[i] = pstr->trans[p[i]]; q = 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 *) p, 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, int idx) { int ch, 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 __attribute ((pure)) 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) { int off, 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, int idx, int eflags) { int c; if (BE (idx < 0, 0)) /* In this case, we use the value stored in input->tip_context, since we can't know the character in input->mbs[-1] here. */ return input->tip_context; if (BE (idx == input->len, 0)) return ((eflags & REG_NOTEOL) ? CONTEXT_ENDBUF : CONTEXT_NEWLINE | CONTEXT_ENDBUF); #ifdef RE_ENABLE_I18N if (input->mb_cur_max > 1) { wint_t wc; int wc_idx = idx; while(input->wcs[wc_idx] == WEOF) { #ifdef DEBUG /* It must not happen. */ assert (wc_idx >= 0); #endif --wc_idx; if (wc_idx < 0) return input->tip_context; } wc = input->wcs[wc_idx]; if (BE (input->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc)) return CONTEXT_WORD; return (IS_WIDE_NEWLINE (wc) && input->newline_anchor ? CONTEXT_NEWLINE : 0); } else #endif { c = re_string_byte_at (input, idx); if (bitset_contain (input->word_char, c)) return CONTEXT_WORD; return IS_NEWLINE (c) && input->newline_anchor ? CONTEXT_NEWLINE : 0; } } /* Functions for set operation. */ static reg_errcode_t internal_function re_node_set_alloc (re_node_set *set, int size) { set->alloc = size; set->nelem = 0; set->elems = re_malloc (int, size); if (BE (set->elems == NULL, 0)) return REG_ESPACE; return REG_NOERROR; } static reg_errcode_t internal_function re_node_set_init_1 (re_node_set *set, int elem) { set->alloc = 1; set->nelem = 1; set->elems = re_malloc (int, 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 re_node_set_init_2 (re_node_set *set, int elem1, int elem2) { set->alloc = 2; set->elems = re_malloc (int, 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 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 (int, dest->alloc); if (BE (dest->elems == NULL, 0)) { dest->alloc = dest->nelem = 0; return REG_ESPACE; } memcpy (dest->elems, src->elems, src->nelem * sizeof (int)); } 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 re_node_set_add_intersect (re_node_set *dest, const re_node_set *src1, const re_node_set *src2) { int 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) { int new_alloc = src1->nelem + src2->nelem + dest->alloc; int *new_elems = re_realloc (dest->elems, int, new_alloc); if (BE (new_elems == NULL, 0)) return REG_ESPACE; dest->elems = new_elems; dest->alloc = new_alloc; } /* Find the items in the intersection of SRC1 and SRC2, and copy into the top of DEST those that are not already in DEST itself. */ sbase = dest->nelem + src1->nelem + src2->nelem; i1 = src1->nelem - 1; i2 = src2->nelem - 1; id = dest->nelem - 1; for (;;) { if (src1->elems[i1] == src2->elems[i2]) { /* Try to find the item in DEST. Maybe we could binary search? */ while (id >= 0 && dest->elems[id] > src1->elems[i1]) --id; if (id < 0 || dest->elems[id] != src1->elems[i1]) dest->elems[--sbase] = src1->elems[i1]; if (--i1 < 0 || --i2 < 0) break; } /* Lower the highest of the two items. */ else if (src1->elems[i1] < src2->elems[i2]) { if (--i2 < 0) break; } else { if (--i1 < 0) break; } } id = dest->nelem - 1; is = dest->nelem + src1->nelem + src2->nelem - 1; delta = is - sbase + 1; /* Now copy. When DELTA becomes zero, the remaining DEST elements are already in place; this is more or less the same loop that is in re_node_set_merge. */ dest->nelem += delta; if (delta > 0 && id >= 0) for (;;) { if (dest->elems[is] > dest->elems[id]) { /* Copy from the top. */ dest->elems[id + delta--] = dest->elems[is--]; if (delta == 0) break; } else { /* Slide from the bottom. */ dest->elems[id + delta] = dest->elems[id]; if (--id < 0) break; } } /* Copy remaining SRC elements. */ memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int)); 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 re_node_set_init_union (re_node_set *dest, const re_node_set *src1, const re_node_set *src2) { int i1, i2, id; if (src1 != NULL && src1->nelem > 0 && src2 != NULL && src2->nelem > 0) { dest->alloc = src1->nelem + src2->nelem; dest->elems = re_malloc (int, 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 (int)); id += src1->nelem - i1; } else if (i2 < src2->nelem) { memcpy (dest->elems + id, src2->elems + i2, (src2->nelem - i2) * sizeof (int)); 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 re_node_set_merge (re_node_set *dest, const re_node_set *src) { int is, id, sbase, delta; if (src == NULL || src->nelem == 0) return REG_NOERROR; if (dest->alloc < 2 * src->nelem + dest->nelem) { int new_alloc = 2 * (src->nelem + dest->alloc); int *new_buffer = re_realloc (dest->elems, int, 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 (int)); return REG_NOERROR; } /* Copy into the top of DEST the items of SRC that are not found in DEST. Maybe we could binary search in DEST? */ for (sbase = dest->nelem + 2 * src->nelem, is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; ) { if (dest->elems[id] == src->elems[is]) is--, id--; else if (dest->elems[id] < src->elems[is]) dest->elems[--sbase] = src->elems[is--]; else /* if (dest->elems[id] > src->elems[is]) */ --id; } if (is >= 0) { /* If DEST is exhausted, the remaining items of SRC must be unique. */ sbase -= is + 1; memcpy (dest->elems + sbase, src->elems, (is + 1) * sizeof (int)); } id = dest->nelem - 1; is = dest->nelem + 2 * src->nelem - 1; delta = is - sbase + 1; if (delta == 0) return REG_NOERROR; /* Now copy. When DELTA becomes zero, the remaining DEST elements are already in place. */ dest->nelem += delta; for (;;) { if (dest->elems[is] > dest->elems[id]) { /* Copy from the top. */ dest->elems[id + delta--] = dest->elems[is--]; if (delta == 0) break; } else { /* Slide from the bottom. */ dest->elems[id + delta] = dest->elems[id]; if (--id < 0) { /* Copy remaining SRC elements. */ memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int)); break; } } } return REG_NOERROR; } /* Insert the new element ELEM to the re_node_set* SET. SET should not already have ELEM. return -1 if an error is occured, return 1 otherwise. */ static int internal_function re_node_set_insert (re_node_set *set, int elem) { int idx; /* In case the set is empty. */ if (set->alloc == 0) { if (BE (re_node_set_init_1 (set, elem) == REG_NOERROR, 1)) return 1; else return -1; } if (BE (set->nelem, 0) == 0) { /* We already guaranteed above that set->alloc != 0. */ set->elems[0] = elem; ++set->nelem; return 1; } /* Realloc if we need. */ if (set->alloc == set->nelem) { int *new_elems; set->alloc = set->alloc * 2; new_elems = re_realloc (set->elems, int, set->alloc); if (BE (new_elems == NULL, 0)) return -1; 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 1; } /* 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 -1 if an error is occured, return 1 otherwise. */ static int internal_function re_node_set_insert_last (re_node_set *set, int elem) { /* Realloc if we need. */ if (set->alloc == set->nelem) { int *new_elems; set->alloc = (set->alloc + 1) * 2; new_elems = re_realloc (set->elems, int, set->alloc); if (BE (new_elems == NULL, 0)) return -1; set->elems = new_elems; } /* Insert the new element. */ set->elems[set->nelem++] = elem; return 1; } /* Compare two node sets SET1 and SET2. return 1 if SET1 and SET2 are equivalent, return 0 otherwise. */ static int internal_function __attribute ((pure)) re_node_set_compare (const re_node_set *set1, const re_node_set *set2) { int i; if (set1 == NULL || set2 == NULL || set1->nelem != set2->nelem) return 0; for (i = set1->nelem ; --i >= 0 ; ) if (set1->elems[i] != set2->elems[i]) return 0; return 1; } /* Return (idx + 1) if SET contains the element ELEM, return 0 otherwise. */ static int internal_function __attribute ((pure)) re_node_set_contains (const re_node_set *set, int elem) { unsigned int idx, right, mid; if (set->nelem <= 0) return 0; /* Binary search the element. */ idx = 0; right = set->nelem - 1; while (idx < right) { mid = (idx + right) / 2; if (set->elems[mid] < elem) idx = mid + 1; else right = mid; } return set->elems[idx] == elem ? idx + 1 : 0; } static void internal_function re_node_set_remove_at (re_node_set *set, int idx) { if (idx < 0 || idx >= set->nelem) return; --set->nelem; for (; idx < set->nelem; idx++) set->elems[idx] = set->elems[idx + 1]; } /* Add the token TOKEN to dfa->nodes, and return the index of the token. Or return -1, if an error will be occured. */ static int internal_function re_dfa_add_node (re_dfa_t *dfa, re_token_t token) { int type = token.type; if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0)) { size_t new_nodes_alloc = dfa->nodes_alloc * 2; int *new_nexts, *new_indices; re_node_set *new_edests, *new_eclosures; re_token_t *new_nodes; /* Avoid overflows. */ if (BE (new_nodes_alloc < dfa->nodes_alloc, 0)) return -1; new_nodes = re_realloc (dfa->nodes, re_token_t, new_nodes_alloc); if (BE (new_nodes == NULL, 0)) return -1; dfa->nodes = new_nodes; new_nexts = re_realloc (dfa->nexts, int, new_nodes_alloc); new_indices = re_realloc (dfa->org_indices, int, 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 -1; dfa->nexts = new_nexts; dfa->org_indices = new_indices; dfa->edests = new_edests; dfa->eclosures = new_eclosures; dfa->nodes_alloc = new_nodes_alloc; } dfa->nodes[dfa->nodes_len] = token; dfa->nodes[dfa->nodes_len].constraint = 0; #ifdef RE_ENABLE_I18N dfa->nodes[dfa->nodes_len].accept_mb = (type == OP_PERIOD && dfa->mb_cur_max > 1) || type == COMPLEX_BRACKET; #endif dfa->nexts[dfa->nodes_len] = -1; re_node_set_init_empty (dfa->edests + dfa->nodes_len); re_node_set_init_empty (dfa->eclosures + dfa->nodes_len); return dfa->nodes_len++; } static inline unsigned int internal_function calc_state_hash (const re_node_set *nodes, unsigned int context) { unsigned int hash = nodes->nelem + context; int 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 re_acquire_state (reg_errcode_t *err, const re_dfa_t *dfa, const re_node_set *nodes) { unsigned int hash; re_dfastate_t *new_state; struct re_state_table_entry *spot; int i; 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 re_acquire_state_context (reg_errcode_t *err, const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context) { unsigned int hash; re_dfastate_t *new_state; struct re_state_table_entry *spot; int i; 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 register_state (const re_dfa_t *dfa, re_dfastate_t *newstate, unsigned int hash) { struct re_state_table_entry *spot; reg_errcode_t err; int 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++) { int elem = newstate->nodes.elems[i]; if (!IS_EPSILON_NODE (dfa->nodes[elem].type)) re_node_set_insert_last (&newstate->non_eps_nodes, elem); } spot = dfa->state_table + (hash & dfa->state_hash_mask); if (BE (spot->alloc <= spot->num, 0)) { int 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 independ of contexts. Return the new state if succeeded, otherwise return NULL. */ static re_dfastate_t * internal_function create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int hash) { int 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 create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes, unsigned int context, unsigned int hash) { int 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; } re_node_set_init_copy (newstate->entrance_nodes, nodes); 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-2.5/src/regex_internal.h0000644000076600007660000005316611535371565014764 0ustar vignavigna/* Extended regular expression matching and search library. Copyright (C) 2002-2005, 2007, 2008 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef _REGEX_INTERNAL_H #define _REGEX_INTERNAL_H 1 #include #include #include #include #include #if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC # include #endif #if defined HAVE_LOCALE_H || defined _LIBC # include #endif #if defined HAVE_WCHAR_H || defined _LIBC # include #endif /* HAVE_WCHAR_H || _LIBC */ #if defined HAVE_WCTYPE_H || defined _LIBC # include #endif /* HAVE_WCTYPE_H || _LIBC */ #if defined HAVE_STDBOOL_H || defined _LIBC # include #endif /* HAVE_STDBOOL_H || _LIBC */ #if defined HAVE_STDINT_H || defined _LIBC # include #endif /* HAVE_STDINT_H || _LIBC */ #if defined _LIBC # include #else # define __libc_lock_define(CLASS,NAME) # define __libc_lock_init(NAME) do { } while (0) # define __libc_lock_lock(NAME) do { } while (0) # define __libc_lock_unlock(NAME) do { } while (0) #endif /* In case that the system doesn't have isblank(). */ #if !defined _LIBC && !defined HAVE_ISBLANK && !defined 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) \ INTUSE(__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 /* For loser systems without the definition. */ #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif #if (defined MB_CUR_MAX && HAVE_LOCALE_H && HAVE_WCTYPE_H && HAVE_WCHAR_H && HAVE_WCRTOMB && HAVE_MBRTOWC && HAVE_WCSCOLL) || _LIBC # define RE_ENABLE_I18N #endif #if __GNUC__ >= 3 # define BE(expr, val) __builtin_expect (expr, val) #else # define BE(expr, val) (expr) # define inline #endif /* Number of single byte character. */ #define SBC_MAX 256 #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 # define __wctype wctype # define __iswctype iswctype # define __btowc btowc # define __mbrtowc mbrtowc # define __mempcpy mempcpy # define __wcrtomb wcrtomb # define __regfree regfree # define attribute_hidden #endif /* not _LIBC */ #ifdef __GNUC__ # define __attribute(arg) __attribute__ (arg) #else # define __attribute(arg) #endif extern const char __re_error_msgid[] attribute_hidden; extern const size_t __re_error_msgid_idx[] attribute_hidden; /* 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. */ #define BITSET_WORD_BITS (sizeof (bitset_word_t) * CHAR_BIT) /* Number of bitset_word_t in a bit_set. */ #define BITSET_WORDS (SBC_MAX / 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 bitset_set(set,i) \ (set[i / BITSET_WORD_BITS] |= (bitset_word_t) 1 << i % BITSET_WORD_BITS) #define bitset_clear(set,i) \ (set[i / BITSET_WORD_BITS] &= ~((bitset_word_t) 1 << i % BITSET_WORD_BITS)) #define bitset_contain(set,i) \ (set[i / BITSET_WORD_BITS] & ((bitset_word_t) 1 << i % BITSET_WORD_BITS)) #define bitset_empty(set) memset (set, '\0', sizeof (bitset_t)) #define bitset_set_all(set) memset (set, '\xff', sizeof (bitset_t)) #define bitset_copy(dest,src) memcpy (dest, src, sizeof (bitset_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 { int alloc; int nelem; int *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. */ int nmbchars; /* # of collating symbols. */ int ncoll_syms; /* # of equivalence classes. */ int nequiv_classes; /* # of range expressions. */ int nranges; /* # of character classes. */ int 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 */ int idx; /* for BACK_REF */ re_context_type ctx_type; /* for ANCHOR */ } opr; #if __GNUC__ >= 2 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; int *offsets; mbstate_t cur_state; #endif /* Index in RAW_MBS. Each character mbs[i] corresponds to raw_mbs[raw_mbs_idx + i]. */ int raw_mbs_idx; /* The length of the valid characters in the buffers. */ int valid_len; /* The corresponding number of bytes in raw_mbs array. */ int valid_raw_len; /* The length of the buffers MBS and WCS. */ int bufs_len; /* The index in MBS, which is updated by re_string_fetch_byte. */ int cur_idx; /* length of RAW_MBS array. */ int raw_len; /* This is RAW_LEN - RAW_MBS_IDX + VALID_LEN - VALID_RAW_LEN. */ int 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. */ int raw_stop; /* This is RAW_STOP - RAW_MBS_IDX adjusted through OFFSETS. */ int 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; /* 1 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 # ifdef __i386__ # define internal_function __attribute ((regparm (3), stdcall)) # else # define internal_function # endif #endif #ifndef NOT_IN_libc static reg_errcode_t re_string_realloc_buffers (re_string_t *pstr, int 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, int 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)) #include #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 # endif #endif /* We redefine re_malloc() and re_realloc() to work on systems which return NULL on malloc(0) and realloc(0) -- AIX in particular. regex_internal.c is full of code which expects non-NULL free()-able pointers to be returned. Oddly enough, either is a valid return from malloc(0). We quietly turn malloc(0) into malloc(1). #define re_malloc(t,n) ((t *) malloc ((n) * sizeof (t))) #define re_realloc(p,t,n) ((t *) realloc (p, (n) * sizeof (t))) */ #define re_malloc(t,n) ((t *) malloc (((n)?(n):1) * sizeof (t))) #define re_realloc(p,t,n) ((t *) realloc (p, (!(p)&&!(n)?1:(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. */ int 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 { unsigned int 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 { int num; int alloc; re_dfastate_t **array; }; /* Array type used in re_sub_match_last_t and re_sub_match_top_t. */ typedef struct { int next_idx; int alloc; re_dfastate_t **array; } state_array_t; /* Store information about the node NODE whose type is OP_CLOSE_SUBEXP. */ typedef struct { int node; int 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 { int str_idx; int node; state_array_t *path; int alasts; /* Allocation size of LASTS. */ int nlasts; /* The number of LASTS. */ re_sub_match_last_t **lasts; } re_sub_match_top_t; struct re_backref_cache_entry { int node; int str_idx; int subexp_from; int 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. */ int match_last; int last_node; /* The state log used by the matcher. */ re_dfastate_t **state_log; int state_log_top; /* Back reference cache. */ int nbkref_ents; int abkref_ents; struct re_backref_cache_entry *bkref_ents; int max_mb_elem_len; int nsub_tops; int 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; int last_node; int last_str_idx; re_node_set limits; } re_sift_context_t; struct re_fail_stack_ent_t { int idx; int node; regmatch_t *regs; re_node_set eps_via_nodes; }; struct re_fail_stack_t { int num; int alloc; struct re_fail_stack_ent_t *stack; }; struct re_dfa_t { re_token_t *nodes; size_t nodes_alloc; size_t nodes_len; int *nexts; int *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. */ unsigned int state_hash_mask; int init_node; int 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; int *subexp_map; #ifdef DEBUG char* re_str; #endif __libc_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; /* Inline functions for bitset operation. */ static inline void bitset_not (bitset_t set) { int bitset_i; for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) set[bitset_i] = ~set[bitset_i]; } static inline void 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 inline void 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 /* Inline functions for re_string. */ static inline int internal_function __attribute ((pure)) re_string_char_size_at (const re_string_t *pstr, int 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 inline wint_t internal_function __attribute ((pure)) re_string_wchar_at (const re_string_t *pstr, int 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)) re_string_elem_size_at (const re_string_t *pstr, int idx) { # ifdef _LIBC const unsigned char *p, *extra; const int32_t *table, *indirect; int32_t tmp; # 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; tmp = findidx (&p); return p - pstr->mbs - idx; } else # endif /* _LIBC */ return 1; } # endif #endif /* RE_ENABLE_I18N */ #endif /* _REGEX_INTERNAL_H */ ne-2.5/src/regexec.c0000644000076600007660000037273411535371565013400 0ustar vignavigna/* Extended regular expression matching and search library. Copyright (C) 2002, 2003, 2004, 2005, 2007, 2009 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa . The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags, int 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, int node, int str_idx, int from, int to) internal_function; static int search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx) internal_function; static reg_errcode_t match_ctx_add_subtop (re_match_context_t *mctx, int node, int str_idx) internal_function; static re_sub_match_last_t * match_ctx_add_sublast (re_sub_match_top_t *subtop, int node, int str_idx) internal_function; static void sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts, re_dfastate_t **limited_sts, int last_node, int last_str_idx) internal_function; static reg_errcode_t re_search_internal (const regex_t *preg, const char *string, int length, int start, int range, int stop, size_t nmatch, regmatch_t pmatch[], int eflags) internal_function; static int re_search_2_stub (struct re_pattern_buffer *bufp, const char *string1, int length1, const char *string2, int length2, int start, int range, struct re_registers *regs, int stop, int ret_len) internal_function; static int re_search_stub (struct re_pattern_buffer *bufp, const char *string, int length, int start, int range, int stop, struct re_registers *regs, int ret_len) internal_function; static unsigned re_copy_regs (struct re_registers *regs, regmatch_t *pmatch, int nregs, int regs_allocated) internal_function; static reg_errcode_t prune_impossible_nodes (re_match_context_t *mctx) internal_function; static int check_matching (re_match_context_t *mctx, int fl_longest_match, int *p_match_first) internal_function; static int check_halt_state_context (const re_match_context_t *mctx, const re_dfastate_t *state, int idx) internal_function; static void update_regs (const re_dfa_t *dfa, regmatch_t *pmatch, regmatch_t *prev_idx_match, int cur_node, int cur_idx, int nmatch) internal_function; static reg_errcode_t push_fail_stack (struct re_fail_stack_t *fs, int str_idx, int dest_node, int 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, int 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, int node_idx, int str_idx, int 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, int 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, int 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 int check_dst_limits (const re_match_context_t *mctx, re_node_set *limits, int dst_node, int dst_idx, int src_node, int src_idx) internal_function; static int check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries, int subexp_idx, int from_node, int bkref_idx) internal_function; static int check_dst_limits_calc_pos (const re_match_context_t *mctx, int limit, int subexp_idx, int node, int str_idx, int 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, int str_idx) internal_function; static reg_errcode_t sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, int 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, int 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, int 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, int bkref_node, int 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, int bkref_node, int bkref_str) internal_function; static int find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, int subexp_idx, int type) internal_function; static reg_errcode_t check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node, int top_str, int last_node, int last_str, int type) internal_function; static reg_errcode_t check_arrival_add_next_nodes (re_match_context_t *mctx, int 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, int 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, int target, int ex_subexp, int type) internal_function; static reg_errcode_t expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes, int cur_str, int subexp_num, int type) internal_function; static int 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, int node_idx, const re_string_t *input, int 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 int 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 int check_node_accept (const re_match_context_t *mctx, const re_token_t *node, int idx) internal_function; static reg_errcode_t extend_buffers (re_match_context_t *mctx) 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[]; int eflags; { reg_errcode_t err; int start, length; re_dfa_t *dfa = (re_dfa_t *) 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); } __libc_lock_lock (dfa->lock); if (preg->no_sub) err = re_search_internal (preg, string, length, start, length - start, length, 0, NULL, eflags); else err = re_search_internal (preg, string, length, start, length - start, length, nmatch, pmatch, eflags); __libc_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 stroed 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. */ int re_match (bufp, string, length, start, regs) struct re_pattern_buffer *bufp; const char *string; int length, start; struct re_registers *regs; { return re_search_stub (bufp, string, length, start, 0, length, regs, 1); } #ifdef _LIBC weak_alias (__re_match, re_match) #endif int re_search (bufp, string, length, start, range, regs) struct re_pattern_buffer *bufp; const char *string; int length, start, range; struct re_registers *regs; { return re_search_stub (bufp, string, length, start, range, length, regs, 0); } #ifdef _LIBC weak_alias (__re_search, re_search) #endif int re_match_2 (bufp, string1, length1, string2, length2, start, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; int length1, length2, start, stop; struct re_registers *regs; { return re_search_2_stub (bufp, string1, length1, string2, length2, start, 0, regs, stop, 1); } #ifdef _LIBC weak_alias (__re_match_2, re_match_2) #endif int re_search_2 (bufp, string1, length1, string2, length2, start, range, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; int length1, length2, start, range, stop; struct re_registers *regs; { return re_search_2_stub (bufp, string1, length1, string2, length2, start, range, regs, stop, 0); } #ifdef _LIBC weak_alias (__re_search_2, re_search_2) #endif static int re_search_2_stub (bufp, string1, length1, string2, length2, start, range, regs, stop, ret_len) struct re_pattern_buffer *bufp; const char *string1, *string2; int length1, length2, start, range, stop, ret_len; struct re_registers *regs; { const char *str; int rval; int len = length1 + length2; int free_str = 0; if (BE (length1 < 0 || length2 < 0 || stop < 0, 0)) return -2; /* Concatenate the strings. */ if (length2 > 0) if (length1 > 0) { char *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; free_str = 1; } else str = string2; else str = string1; rval = re_search_stub (bufp, str, len, start, range, stop, regs, ret_len); if (free_str) re_free ((char *) str); return rval; } /* The parameters have the same meaning as those of re_search. Additional parameters: If RET_LEN is nonzero the length of the match is returned (re_match style); otherwise the position of the match is returned. */ static int re_search_stub (bufp, string, length, start, range, stop, regs, ret_len) struct re_pattern_buffer *bufp; const char *string; int length, start, range, stop, ret_len; struct re_registers *regs; { reg_errcode_t result; regmatch_t *pmatch; int nregs, rval; int eflags = 0; re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; /* Check for out-of-range. */ if (BE (start < 0 || start > length, 0)) return -1; if (BE (start + range > length, 0)) range = length - start; else if (BE (start + range < 0, 0)) range = -start; __libc_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 (range > 0 && 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 + 1, 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, range, stop, nregs, pmatch, eflags); rval = 0; /* I hope we needn't fill ther regs with -1's when no match was found. */ if (result != REG_NOERROR) rval = -1; 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: __libc_lock_unlock (dfa->lock); return rval; } static unsigned re_copy_regs (regs, pmatch, nregs, regs_allocated) struct re_registers *regs; regmatch_t *pmatch; int nregs, regs_allocated; { int rval = REGS_REALLOCATE; int i; int 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); regs->end = re_malloc (regoff_t, need_regs); if (BE (regs->start == NULL, 0) || BE (regs->end == NULL, 0)) 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 = re_realloc (regs->end, regoff_t, need_regs); if (BE (new_start == NULL, 0) || BE (new_end == NULL, 0)) 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; unsigned 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 = (regoff_t *) 0; } } #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 mingings with regexec. START, and RANGE have the same meanings 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. (START + RANGE >= 0 && START + RANGE <= LENGTH) */ static reg_errcode_t re_search_internal (preg, string, length, start, range, stop, nmatch, pmatch, eflags) const regex_t *preg; const char *string; int length, start, range, stop, eflags; size_t nmatch; regmatch_t pmatch[]; { reg_errcode_t err; const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer; int left_lim, right_lim, incr; int fl_longest_match, match_first, match_kind, match_last = -1; int extra_nmatch; int sb, 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 && range && !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 (start + range >= 0 && start + range <= 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 && start + range != 0) return REG_NOMATCH; start = range = 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, 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) { 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 of not the input string match. */ incr = (range < 0) ? -1 : 1; left_lim = (range < 0) ? start + range : start; right_lim = (range < 0) ? start : start + range; sb = dfa->mb_cur_max == 1; match_kind = (fastmap ? ((sb || !(preg->syntax & RE_ICASE || t) ? 4 : 0) | (range >= 0 ? 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. */ unsigned int offset = match_first - mctx.input.raw_mbs_idx; if (BE (offset >= (unsigned int) 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, range >= 0 ? &match_first : NULL); if (match_last != -1) { if (BE (match_last == -2, 0)) { err = REG_ESPACE; goto free_return; } else { mctx.match_last = match_last; if ((!preg->no_sub && nmatch > 1) || dfa->nbackref) { re_dfastate_t *pstate = mctx.state_log[match_last]; mctx.last_node = check_halt_state_context (&mctx, pstate, match_last); } if ((!preg->no_sub && nmatch > 1 && dfa->has_plural_match) || dfa->nbackref) { err = prune_impossible_nodes (&mctx); if (err == REG_NOERROR) break; if (BE (err != REG_NOMATCH, 0)) goto free_return; match_last = -1; } else break; /* We found a match. */ } } match_ctx_clean (&mctx); } #ifdef DEBUG assert (match_last != -1); assert (err == REG_NOERROR); #endif /* Set pmatch[] if we need. */ if (nmatch > 0) { int 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; 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 the each registers, since we slided 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 prune_impossible_nodes (mctx) re_match_context_t *mctx; { const re_dfa_t *const dfa = mctx->dfa; int 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; sifted_states = re_malloc (re_dfastate_t *, match_last + 1); if (BE (sifted_states == NULL, 0)) { ret = REG_ESPACE; goto free_return; } if (dfa->nbackref) { lim_states = re_malloc (re_dfastate_t *, match_last + 1); if (BE (lim_states == NULL, 0)) { ret = REG_ESPACE; goto free_return; } while (1) { memset (lim_states, '\0', sizeof (re_dfastate_t *) * (match_last + 1)); sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last); ret = sift_states_backward (mctx, &sctx); re_node_set_free (&sctx.limits); if (BE (ret != REG_NOERROR, 0)) goto free_return; if (sifted_states[0] != NULL || lim_states[0] != NULL) break; do { --match_last; if (match_last < 0) { ret = REG_NOMATCH; goto free_return; } } while (mctx->state_log[match_last] == NULL || !mctx->state_log[match_last]->halt); halt_node = check_halt_state_context (mctx, mctx->state_log[match_last], match_last); } ret = merge_state_array (dfa, sifted_states, lim_states, match_last + 1); re_free (lim_states); lim_states = NULL; if (BE (ret != REG_NOERROR, 0)) goto free_return; } else { sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last); ret = sift_states_backward (mctx, &sctx); re_node_set_free (&sctx.limits); if (BE (ret != REG_NOERROR, 0)) goto free_return; if (sifted_states[0] == NULL) { ret = REG_NOMATCH; goto free_return; } } re_free (mctx->state_log); mctx->state_log = sifted_states; sifted_states = NULL; mctx->last_node = halt_node; mctx->match_last = match_last; ret = REG_NOERROR; free_return: re_free (sifted_states); re_free (lim_states); return ret; } /* Acquire an initial state and return it. We must select appropriate initial state depending on the context, since initial states may have constraints like "\<", "^", etc.. */ static inline re_dfastate_t * __attribute ((always_inline)) internal_function acquire_init_state_context (reg_errcode_t *err, const re_match_context_t *mctx, int idx) { const re_dfa_t *const dfa = mctx->dfa; if (dfa->init_state->has_constraint) { unsigned int context; context = re_string_context_at (&mctx->input, idx - 1, mctx->eflags); if (IS_WORD_CONTEXT (context)) return dfa->init_state_word; else if (IS_ORDINARY_CONTEXT (context)) return dfa->init_state; else if (IS_BEGBUF_CONTEXT (context) && IS_NEWLINE_CONTEXT (context)) return dfa->init_state_begbuf; else if (IS_NEWLINE_CONTEXT (context)) return dfa->init_state_nl; else if (IS_BEGBUF_CONTEXT (context)) { /* It is relatively rare case, then calculate on demand. */ return re_acquire_state_context (err, dfa, dfa->init_state->entrance_nodes, context); } else /* Must not happen? */ return dfa->init_state; } else return dfa->init_state; } /* Check whether the regular expression match input string INPUT or not, and return the index where the matching end, return -1 if not match, or return -2 in case of an error. FL_LONGEST_MATCH means we want the POSIX longest matching. If P_MATCH_FIRST is not NULL, and the match fails, it is set to the next place where we may want to try matching. Note that the matcher assume that the maching starts from the current index of the buffer. */ static int internal_function check_matching (re_match_context_t *mctx, int fl_longest_match, int *p_match_first) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int match = 0; int match_last = -1; int cur_str_idx = re_string_cur_idx (&mctx->input); re_dfastate_t *cur_state; int at_init_state = p_match_first != NULL; int next_start_idx = cur_str_idx; err = REG_NOERROR; cur_state = acquire_init_state_context (&err, mctx, cur_str_idx); /* An initial state must not be NULL (invalid). */ if (BE (cur_state == NULL, 0)) { assert (err == REG_ESPACE); return -2; } if (mctx->state_log != NULL) { mctx->state_log[cur_str_idx] = cur_state; /* Check OP_OPEN_SUBEXP in the initial state in case that we use them later. E.g. Processing back references. */ if (BE (dfa->nbackref, 0)) { at_init_state = 0; 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; int next_char_idx = re_string_cur_idx (&mctx->input) + 1; if (BE (next_char_idx >= mctx->input.bufs_len, 0) || (BE (next_char_idx >= mctx->input.valid_len, 0) && mctx->input.valid_len < mctx->input.len)) { err = extend_buffers (mctx); if (BE (err != REG_NOERROR, 0)) { assert (err == REG_ESPACE); return -2; } } cur_state = transit_state (&err, mctx, cur_state); if (mctx->state_log != NULL) cur_state = merge_state_with_log (&err, mctx, cur_state); if (cur_state == NULL) { /* Reached the invalid state or an error. Try to recover a valid state using the state log, if available and if we have not already found a valid (even if not the longest) match. */ if (BE (err != REG_NOERROR, 0)) return -2; if (mctx->state_log == NULL || (match && !fl_longest_match) || (cur_state = find_recover_state (&err, mctx)) == NULL) break; } if (BE (at_init_state, 0)) { if (old_state == cur_state) next_start_idx = next_char_idx; else at_init_state = 0; } 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 int internal_function check_halt_node_context (const re_dfa_t *dfa, int 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 0; if (!constraint) return 1; if (NOT_SATISFY_NEXT_CONSTRAINT (constraint, context)) return 0; return 1; } /* 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 int internal_function check_halt_state_context (const re_match_context_t *mctx, const re_dfastate_t *state, int idx) { int i; unsigned int context; #ifdef DEBUG assert (state->halt); #endif context = re_string_context_at (&mctx->input, idx, mctx->eflags); for (i = 0; i < state->nodes.nelem; ++i) if (check_halt_node_context (mctx->dfa, state->nodes.elems[i], context)) return state->nodes.elems[i]; return 0; } /* Compute the next node to which "NFA" transit from NODE("NFA" is a NFA corresponding to the DFA). Return the destination node, and update EPS_VIA_NODES, return -1 in case of errors. */ static int internal_function proceed_next_node (const re_match_context_t *mctx, int nregs, regmatch_t *regs, int *pidx, int node, re_node_set *eps_via_nodes, struct re_fail_stack_t *fs) { const re_dfa_t *const dfa = mctx->dfa; int i, err; 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]; int dest_node; err = re_node_set_insert (eps_via_nodes, node); if (BE (err < 0, 0)) return -2; /* Pick up a valid destination, or return -1 if none is found. */ for (dest_node = -1, i = 0; i < edests->nelem; ++i) { int candidate = edests->elems[i]; if (!re_node_set_contains (cur_nodes, candidate)) continue; if (dest_node == -1) dest_node = candidate; else { /* In order to avoid infinite loop like "(a*)*", return the second epsilon-transition if the first was already considered. */ if (re_node_set_contains (eps_via_nodes, dest_node)) return candidate; /* Otherwise, push the second epsilon-transition on the fail stack. */ else if (fs != NULL && push_fail_stack (fs, *pidx, candidate, nregs, regs, eps_via_nodes)) return -2; /* We know we are going to exit. */ break; } } return dest_node; } else { int 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) { int subexp_idx = dfa->nodes[node].opr.idx + 1; naccepted = regs[subexp_idx].rm_eo - regs[subexp_idx].rm_so; if (fs != NULL) { if (regs[subexp_idx].rm_so == -1 || regs[subexp_idx].rm_eo == -1) return -1; else if (naccepted) { char *buf = (char *) re_string_get_buffer (&mctx->input); if (memcmp (buf + regs[subexp_idx].rm_so, buf + *pidx, naccepted) != 0) return -1; } } if (naccepted == 0) { int dest_node; err = re_node_set_insert (eps_via_nodes, node); if (BE (err < 0, 0)) return -2; dest_node = dfa->edests[node].elems[0]; if (re_node_set_contains (&mctx->state_log[*pidx]->nodes, dest_node)) return dest_node; } } if (naccepted != 0 || check_node_accept (mctx, dfa->nodes + node, *pidx)) { int dest_node = dfa->nexts[node]; *pidx = (naccepted == 0) ? *pidx + 1 : *pidx + naccepted; if (fs && (*pidx > mctx->match_last || mctx->state_log[*pidx] == NULL || !re_node_set_contains (&mctx->state_log[*pidx]->nodes, dest_node))) return -1; re_node_set_empty (eps_via_nodes); return dest_node; } } return -1; } static reg_errcode_t internal_function push_fail_stack (struct re_fail_stack_t *fs, int str_idx, int dest_node, int nregs, regmatch_t *regs, re_node_set *eps_via_nodes) { reg_errcode_t err; int 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 int internal_function pop_fail_stack (struct re_fail_stack_t *fs, int *pidx, int nregs, regmatch_t *regs, re_node_set *eps_via_nodes) { int num = --fs->num; assert (num >= 0); *pidx = fs->stack[num].idx; memcpy (regs, fs->stack[num].regs, sizeof (regmatch_t) * nregs); re_node_set_free (eps_via_nodes); re_free (fs->stack[num].regs); *eps_via_nodes = fs->stack[num].eps_via_nodes; return fs->stack[num].node; } /* Set the positions where the subexpressions are starts/ends to registers PMATCH. Note: We assume that pmatch[0] is already set, and pmatch[i].rm_so == pmatch[i].rm_eo == -1 for 0 < i < nmatch. */ static reg_errcode_t internal_function set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch, regmatch_t *pmatch, int fl_backtrack) { const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer; int 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; int prev_idx_match_malloced = 0; #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 = 1; } 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) { int reg_idx; if (fs) { for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) if (pmatch[reg_idx].rm_so > -1 && pmatch[reg_idx].rm_eo == -1) break; if (reg_idx == nmatch) { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return free_fail_stack_return (fs); } cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, &eps_via_nodes); } else { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return REG_NOERROR; } } /* Proceed to next node. */ cur_node = proceed_next_node (mctx, nmatch, pmatch, &idx, cur_node, &eps_via_nodes, fs); if (BE (cur_node < 0, 0)) { if (BE (cur_node == -2, 0)) { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); free_fail_stack_return (fs); return REG_ESPACE; } if (fs) cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, &eps_via_nodes); else { re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return REG_NOMATCH; } } } re_node_set_free (&eps_via_nodes); if (prev_idx_match_malloced) re_free (prev_idx_match); return free_fail_stack_return (fs); } static reg_errcode_t internal_function free_fail_stack_return (struct re_fail_stack_t *fs) { if (fs) { int 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, int cur_node, int cur_idx, int nmatch) { int type = dfa->nodes[cur_node].type; if (type == OP_OPEN_SUBEXP) { int 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) { int 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; int 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 build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx, int 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; int 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++) { int prev_node = cur_src->elems[i]; int naccepted = 0; int ret; #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) { int to_idx = str_idx + naccepted; if (check_dst_limits (mctx, &sctx->limits, dfa->nexts[prev_node], to_idx, prev_node, str_idx)) continue; } ret = re_node_set_insert (cur_dest, prev_node); if (BE (ret == -1, 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, int next_state_log_idx) { int top = mctx->state_log_top; if (next_state_log_idx >= mctx->input.bufs_len || (next_state_log_idx >= mctx->input.valid_len && mctx->input.valid_len < mctx->input.len)) { reg_errcode_t err; err = extend_buffers (mctx); 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, int num) { int 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, int 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 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; int 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++) re_node_set_merge (&state->inveclosure, dfa->inveclosures + dest_nodes->elems[i]); } 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, int node, re_node_set *dest_nodes, const re_node_set *candidates) { int 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) { int cur_node = inv_eclosure->elems[ecl_idx]; if (cur_node == node) continue; if (IS_EPSILON_NODE (dfa->nodes[cur_node].type)) { int edst1 = dfa->edests[cur_node].elems[0]; int edst2 = ((dfa->edests[cur_node].nelem > 1) ? dfa->edests[cur_node].elems[1] : -1); if ((!re_node_set_contains (inv_eclosure, edst1) && re_node_set_contains (dest_nodes, edst1)) || (edst2 > 0 && !re_node_set_contains (inv_eclosure, edst2) && re_node_set_contains (dest_nodes, edst2))) { err = re_node_set_add_intersect (&except_nodes, candidates, dfa->inveclosures + cur_node); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&except_nodes); return err; } } } } for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) { int cur_node = inv_eclosure->elems[ecl_idx]; if (!re_node_set_contains (&except_nodes, cur_node)) { int 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 int internal_function check_dst_limits (const re_match_context_t *mctx, re_node_set *limits, int dst_node, int dst_idx, int src_node, int src_idx) { const re_dfa_t *const dfa = mctx->dfa; int lim_idx, src_pos, dst_pos; int dst_bkref_idx = search_cur_bkref_entry (mctx, dst_idx); int src_bkref_idx = search_cur_bkref_entry (mctx, src_idx); for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) { int 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 1; } return 0; } static int internal_function check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries, int subexp_idx, int from_node, int bkref_idx) { const re_dfa_t *const dfa = mctx->dfa; const re_node_set *eclosures = dfa->eclosures + from_node; int 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) { int node = eclosures->elems[node_idx]; switch (dfa->nodes[node].type) { case OP_BACK_REF: if (bkref_idx != -1) { struct re_backref_cache_entry *ent = mctx->bkref_ents + bkref_idx; do { int dst, 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, int limit, int subexp_idx, int from_node, int str_idx, int 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, int str_idx) { reg_errcode_t err; int node_idx, lim_idx; for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) { int 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) { int ops_node = -1; int cls_node = -1; for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) { int node = dest_nodes->elems[node_idx]; re_token_type_t type = dfa->nodes[node].type; if (type == OP_OPEN_SUBEXP && subexp_idx == dfa->nodes[node].opr.idx) ops_node = node; else if (type == OP_CLOSE_SUBEXP && subexp_idx == dfa->nodes[node].opr.idx) cls_node = node; } /* Check the limitation of the open subexpression. */ /* Note that (ent->subexp_to = str_idx != ent->subexp_from). */ if (ops_node >= 0) { err = sub_epsilon_src_nodes (dfa, ops_node, dest_nodes, candidates); if (BE (err != REG_NOERROR, 0)) return err; } /* Check the limitation of the close subexpression. */ if (cls_node >= 0) for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) { int 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) { int 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 sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, int str_idx, const re_node_set *candidates) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int node_idx, node; re_sift_context_t local_sctx; int first_idx = search_cur_bkref_entry (mctx, str_idx); if (first_idx == -1) return REG_NOERROR; local_sctx.sifted_states = NULL; /* Mark that it hasn't been initialized. */ for (node_idx = 0; node_idx < candidates->nelem; ++node_idx) { int 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 { int subexp_len; int to_idx; int dst_node; int ret; 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; ret = re_node_set_insert (&local_sctx.limits, enabled_idx); if (BE (ret < 0, 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, int node_idx, int str_idx, int 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 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 */ 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; int 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. */ re_dfastate_t * internal_function find_recover_state (reg_errcode_t *err, re_match_context_t *mctx) { re_dfastate_t *cur_state; do { int max = mctx->state_log_top; int 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 correspoding back references. */ static reg_errcode_t internal_function check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes, int str_idx) { const re_dfa_t *const dfa = mctx->dfa; int 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) { int 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; int 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) { int 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; int i; for (i = 0; i < pstate->nodes.nelem; ++i) { re_node_set dest_nodes, *new_nodes; int cur_node_idx = pstate->nodes.elems[i]; int naccepted, dest_idx; unsigned int context; re_dfastate_t *dest_state; if (!dfa->nodes[cur_node_idx].accept_mb) continue; if (dfa->nodes[cur_node_idx].constraint) { context = re_string_context_at (&mctx->input, re_string_cur_idx (&mctx->input), mctx->eflags); if (NOT_SATISFY_NEXT_CONSTRAINT (dfa->nodes[cur_node_idx].constraint, context)) continue; } /* How many bytes the node can accept? */ naccepted = check_node_accept_bytes (dfa, cur_node_idx, &mctx->input, re_string_cur_idx (&mctx->input)); if (naccepted == 0) continue; /* The node can accepts `naccepted' bytes. */ dest_idx = re_string_cur_idx (&mctx->input) + naccepted; mctx->max_mb_elem_len = ((mctx->max_mb_elem_len < naccepted) ? naccepted : mctx->max_mb_elem_len); err = clean_state_log_if_needed (mctx, dest_idx); if (BE (err != REG_NOERROR, 0)) return err; #ifdef DEBUG assert (dfa->nexts[cur_node_idx] != -1); #endif new_nodes = dfa->eclosures + dfa->nexts[cur_node_idx]; dest_state = mctx->state_log[dest_idx]; if (dest_state == NULL) dest_nodes = *new_nodes; else { err = re_node_set_init_union (&dest_nodes, dest_state->entrance_nodes, new_nodes); if (BE (err != REG_NOERROR, 0)) return err; } context = re_string_context_at (&mctx->input, dest_idx - 1, mctx->eflags); mctx->state_log[dest_idx] = re_acquire_state_context (&err, dfa, &dest_nodes, context); if (dest_state != NULL) re_node_set_free (&dest_nodes); if (BE (mctx->state_log[dest_idx] == NULL && err != REG_NOERROR, 0)) return err; } return REG_NOERROR; } #endif /* RE_ENABLE_I18N */ static reg_errcode_t internal_function transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int i; int cur_str_idx = re_string_cur_idx (&mctx->input); for (i = 0; i < nodes->nelem; ++i) { int dest_str_idx, prev_nelem, bkc_idx; int node_idx = nodes->elems[i]; unsigned int context; const re_token_t *node = dfa->nodes + node_idx; re_node_set *new_dest_nodes; /* Check whether `node' is a backreference or not. */ if (node->type != OP_BACK_REF) continue; if (node->constraint) { context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags); if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) continue; } /* `node' is a backreference. Check the substring which the substring matched. */ bkc_idx = mctx->nbkref_ents; err = get_subexp (mctx, node_idx, cur_str_idx); if (BE (err != REG_NOERROR, 0)) goto free_return; /* And add the epsilon closures (which is `new_dest_nodes') of the backreference to appropriate state_log. */ #ifdef DEBUG assert (dfa->nexts[node_idx] != -1); #endif for (; bkc_idx < mctx->nbkref_ents; ++bkc_idx) { int 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 get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx) { const re_dfa_t *const dfa = mctx->dfa; int 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. */ int cache_idx = search_cur_bkref_entry (mctx, bkref_str_idx); if (cache_idx != -1) { const struct re_backref_cache_entry *entry = mctx->bkref_ents + cache_idx; do if (entry->node == bkref_node) return REG_NOERROR; /* We already checked it. */ while (entry++->more); } subexp_num = dfa->nodes[bkref_node].opr.idx; /* For each sub expression */ for (sub_top_idx = 0; sub_top_idx < mctx->nsub_tops; ++sub_top_idx) { reg_errcode_t err; re_sub_match_top_t *sub_top = mctx->sub_tops[sub_top_idx]; re_sub_match_last_t *sub_last; int 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) { int 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) { int cls_node, 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); if (BE (err != REG_NOERROR, 0)) return err; buf = (const char *) re_string_get_buffer (&mctx->input); } if (buf [bkref_str_off++] != buf[sl_str - 1]) break; /* We don't need to search this sub expression any more. */ } if (mctx->state_log[sl_str] == NULL) continue; /* Does this state have a ')' of the sub expression? */ nodes = &mctx->state_log[sl_str]->nodes; cls_node = find_subexp_node (dfa, nodes, subexp_num, OP_CLOSE_SUBEXP); if (cls_node == -1) continue; /* No. */ if (sub_top->path == NULL) { sub_top->path = calloc (sizeof (state_array_t), sl_str - sub_top->str_idx + 1); if (sub_top->path == NULL) return REG_ESPACE; } /* Can the OP_OPEN_SUBEXP node arrive the OP_CLOSE_SUBEXP node in the current context? */ err = check_arrival (mctx, sub_top->path, sub_top->node, sub_top->str_idx, cls_node, sl_str, OP_CLOSE_SUBEXP); if (err == REG_NOMATCH) continue; if (BE (err != REG_NOERROR, 0)) return err; sub_last = match_ctx_add_sublast (sub_top, cls_node, sl_str); if (BE (sub_last == NULL, 0)) return REG_ESPACE; err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, bkref_str_idx); if (err == REG_NOMATCH) continue; } } return REG_NOERROR; } /* Helper functions for get_subexp(). */ /* Check SUB_LAST can arrive to the back reference BKREF_NODE at BKREF_STR. If it can arrive, register the sub expression expressed with SUB_TOP and SUB_LAST. */ static reg_errcode_t internal_function get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top, re_sub_match_last_t *sub_last, int bkref_node, int bkref_str) { reg_errcode_t err; int 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 int internal_function find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, int subexp_idx, int type) { int cls_idx; for (cls_idx = 0; cls_idx < nodes->nelem; ++cls_idx) { int cls_node = nodes->elems[cls_idx]; const re_token_t *node = dfa->nodes + cls_node; if (node->type == type && node->opr.idx == subexp_idx) return cls_node; } return -1; } /* Check whether the node TOP_NODE at TOP_STR can arrive to the node LAST_NODE at LAST_STR. We record the path onto PATH since it will be heavily reused. Return REG_NOERROR if it can arrive, or REG_NOMATCH otherwise. */ static reg_errcode_t internal_function check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node, int top_str, int last_node, int last_str, int type) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err = REG_NOERROR; int 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; int old_alloc = path->alloc; path->alloc += last_str + mctx->max_mb_elem_len + 1; new_array = re_realloc (path->array, re_dfastate_t *, path->alloc); if (BE (new_array == NULL, 0)) { path->alloc = old_alloc; return REG_ESPACE; } path->array = new_array; 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 check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx, re_node_set *cur_nodes, re_node_set *next_nodes) { const re_dfa_t *const dfa = mctx->dfa; int result; int cur_idx; reg_errcode_t err = REG_NOERROR; 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; int 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; int next_node = dfa->nexts[cur_node]; int 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; } } result = re_node_set_insert (&union_set, next_node); if (BE (result < 0, 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)) { result = re_node_set_insert (next_nodes, dfa->nexts[cur_node]); if (BE (result < 0, 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, int ex_subexp, int type) { reg_errcode_t err; int 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) { int cur_node = cur_nodes->elems[idx]; const re_node_set *eclosure = dfa->eclosures + cur_node; outside_node = find_subexp_node (dfa, eclosure, ex_subexp, type); if (outside_node == -1) { /* There are no problematic nodes, just merge them. */ err = re_node_set_merge (&new_nodes, eclosure); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&new_nodes); return err; } } else { /* There are problematic nodes, re-calculate incrementally. */ err = check_arrival_expand_ecl_sub (dfa, &new_nodes, cur_node, ex_subexp, type); if (BE (err != REG_NOERROR, 0)) { re_node_set_free (&new_nodes); return err; } } } re_node_set_free (cur_nodes); *cur_nodes = new_nodes; return REG_NOERROR; } /* Helper function for check_arrival_expand_ecl. Check incrementally the epsilon closure of TARGET, and if it isn't problematic append it to DST_NODES. */ static reg_errcode_t internal_function check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes, int target, int ex_subexp, int type) { int cur_node; for (cur_node = target; !re_node_set_contains (dst_nodes, cur_node);) { int err; if (dfa->nodes[cur_node].type == type && dfa->nodes[cur_node].opr.idx == ex_subexp) { if (type == OP_CLOSE_SUBEXP) { err = re_node_set_insert (dst_nodes, cur_node); if (BE (err == -1, 0)) return REG_ESPACE; } break; } err = re_node_set_insert (dst_nodes, cur_node); if (BE (err == -1, 0)) return REG_ESPACE; if (dfa->edests[cur_node].nelem == 0) break; if (dfa->edests[cur_node].nelem == 2) { 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 expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes, int cur_str, int subexp_num, int type) { const re_dfa_t *const dfa = mctx->dfa; reg_errcode_t err; int cache_idx_start = search_cur_bkref_entry (mctx, cur_str); struct re_backref_cache_entry *ent; if (cache_idx_start == -1) return REG_NOERROR; restart: ent = mctx->bkref_ents + cache_idx_start; do { int 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]) { int ret; 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); ret = re_node_set_insert (&union_set, next_node); if (BE (err != REG_NOERROR || ret < 0, 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 1 if succeeded, otherwise return NULL. */ static int internal_function build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) { reg_errcode_t err; int i, j, ch, need_word_trtable = 0; bitset_word_t elem, mask; bool dests_node_malloced = false; bool dest_states_malloced = false; int 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 0; dests_node_malloced = true; } dests_node = dests_alloc->dests_node; dests_ch = dests_alloc->dests_ch; /* Initialize transiton table. */ state->word_trtable = state->trtable = NULL; /* At first, group all nodes belonging to `state' into several destinations. */ ndests = group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch); if (BE (ndests <= 0, 0)) { if (dests_node_malloced) free (dests_alloc); /* Return 0 in case of an error, 1 otherwise. */ if (ndests == 0) { state->trtable = (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); return 1; } return 0; } err = re_node_set_alloc (&follows, ndests + 1); if (BE (err != REG_NOERROR, 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 0; } 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) { int next_node; re_node_set_empty (&follows); /* Merge the follows of this destination states. */ for (j = 0; j < dests_node[i].nelem; ++j) { next_node = dfa->nexts[dests_node[i].elems[j]]; if (next_node != -1) { err = re_node_set_merge (&follows, dfa->eclosures + next_node); if (BE (err != REG_NOERROR, 0)) goto out_free; } } dest_states[i] = re_acquire_state_context (&err, dfa, &follows, 0); if (BE (dest_states[i] == NULL && err != REG_NOERROR, 0)) goto out_free; /* If the new state has context constraint, build appropriate states for these contexts. */ if (dest_states[i]->has_constraint) { dest_states_word[i] = re_acquire_state_context (&err, dfa, &follows, CONTEXT_WORD); if (BE (dest_states_word[i] == NULL && err != REG_NOERROR, 0)) goto out_free; if (dest_states[i] != dest_states_word[i] && dfa->mb_cur_max > 1) need_word_trtable = 1; 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 1; } /* 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 int 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; int result; int i, j, k; int 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) { memset (accepts, '\xff', sizeof (bitset_t) / 2); 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. */ result = re_node_set_insert (&dests_node[j], cur_nodes->elems[i]); if (BE (result < 0, 0)) goto error_return; /* If all characters are consumed, go to next node. */ if (!not_consumed) break; } /* Some characters remain, create a new group. */ if (j == ndests) { bitset_copy (dests_ch[ndests], accepts); err = re_node_set_init_1 (dests_node + ndests, cur_nodes->elems[i]); if (BE (err != REG_NOERROR, 0)) goto error_return; ++ndests; bitset_empty (accepts); } } return ndests; error_return: for (j = 0; j < ndests; ++j) re_node_set_free (dests_node + j); return -1; } #ifdef RE_ENABLE_I18N /* Check how many bytes the node `dfa->nodes[node_idx]' accepts. Return the number of the bytes the node accepts. STR_IDX is the current index of the input string. This function handles the nodes which can accept one character, or one collating element like '.', '[a-z]', opposite to the other nodes can only accept one byte. */ static int internal_function check_node_accept_bytes (const re_dfa_t *dfa, int node_idx, const re_string_t *input, int str_idx) { const re_token_t *node = dfa->nodes + node_idx; int char_len, elem_len; int 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); int 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? */ 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); 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)) { int 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? */ #if __GNUC__ >= 2 wchar_t cmp_buf[] = {L'\0', L'\0', wc, L'\0', L'\0', L'\0'}; #else wchar_t cmp_buf[] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'}; cmp_buf[2] = wc; #endif for (i = 0; i < cset->nranges; ++i) { cmp_buf[0] = cset->range_starts[i]; cmp_buf[4] = cset->range_ends[i]; if (wcscoll (cmp_buf, cmp_buf + 2) <= 0 && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0) { 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, found = 0; 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 = 1; } /* 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) * (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 int internal_function check_node_accept (const re_match_context_t *mctx, const re_token_t *node, int idx) { unsigned char ch; ch = re_string_byte_at (&mctx->input, idx); switch (node->type) { case CHARACTER: if (node->opr.c != ch) return 0; break; case SIMPLE_BRACKET: if (!bitset_contain (node->opr.sbcset, ch)) return 0; break; #ifdef RE_ENABLE_I18N case OP_UTF8_PERIOD: if (ch >= 0x80) return 0; /* FALLTHROUGH */ #endif case OP_PERIOD: if ((ch == '\n' && !(mctx->dfa->syntax & RE_DOT_NEWLINE)) || (ch == '\0' && (mctx->dfa->syntax & RE_DOT_NOT_NULL))) return 0; break; default: return 0; } 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 0; } return 1; } /* Extend the buffers, if the buffers have run out. */ static reg_errcode_t internal_function extend_buffers (re_match_context_t *mctx) { reg_errcode_t ret; re_string_t *pstr = &mctx->input; /* Double the lengthes of the buffers. */ ret = re_string_realloc_buffers (pstr, 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 match_ctx_init (re_match_context_t *mctx, int eflags, int n) { mctx->eflags = eflags; mctx->match_last = -1; if (n > 0) { 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) { int st_idx; for (st_idx = 0; st_idx < mctx->nsub_tops; ++st_idx) { int 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 match_ctx_add_entry (re_match_context_t *mctx, int node, int str_idx, int from, int 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 ? ~0 : 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; } /* Search for the first entry which has the same str_idx, or -1 if none is found. Note that MCTX->BKREF_ENTS is already sorted by MCTX->STR_IDX. */ static int internal_function search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx) { int left, right, mid, last; last = right = mctx->nbkref_ents; for (left = 0; left < right;) { mid = (left + right) / 2; if (mctx->bkref_ents[mid].str_idx < str_idx) left = mid + 1; else right = mid; } if (left < last && mctx->bkref_ents[left].str_idx == str_idx) return left; else return -1; } /* Register the node NODE, whose type is OP_OPEN_SUBEXP, and which matches at STR_IDX. */ static reg_errcode_t internal_function match_ctx_add_subtop (re_match_context_t *mctx, int node, int str_idx) { #ifdef DEBUG assert (mctx->sub_tops != NULL); assert (mctx->asub_tops > 0); #endif if (BE (mctx->nsub_tops == mctx->asub_tops, 0)) { int 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, int node, int str_idx) { re_sub_match_last_t *new_entry; if (BE (subtop->nlasts == subtop->alasts, 0)) { int 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, int last_node, int 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-2.5/src/request.c0000644000076600007660000004272512076214662013433 0ustar vignavigna/* Requester handling. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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) /* 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_NAMES_ALLOC_SIZE (4*1024) /* Prompts the user to choose one between several (num_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. */ static int x, y, page, max_names_per_line, max_names_per_col, names_per_page, num_entries, max_name_len, mark_char; static const char * const *entries; /* 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 true, the DISPLAYBYROW define should exibit the behavior of the older versions of ne. If false, the second set of macros are used, and these implement the by-column request display. For now, this may be considered experimental. */ #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 (num_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) ((num_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 - (num_entries % names_per_page))-1)*((names_per_page - (num_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) ? (((num_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 ) /* 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 max_name_len maximum width. */ static void print_strings() { int row,col; const char *p; set_attr(0); for(row = 0; row < max_names_per_col; row++) { move_cursor(row, 0); clear_to_eol(); if (row < NAMES_PER_COL(page)) { for(col = 0; col < NAMES_PER_LINE(page); col++) { if (PXY2N(page,col,row) < num_entries) { move_cursor(row, col * max_name_len); p = entries[PXY2N(page,col,row)]; if (mark_char) set_attr(p[strlen(p) - 1] == mark_char ? BOLD : 0); output_string(p, io_utf8); } } } } } static void normalize(int n) { int p = page; if (n < 0 ) n = 0; if (n >= num_entries ) n = num_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) < num_entries) { x++; } } static void request_move_to_sof(void) { normalize(0); } static void request_move_to_eof() { normalize(num_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_right(void) { if (y < NAMES_PER_COL(page) - 1 && PXY2N(page,0,y+1) < num_entries && (x == NAMES_PER_LINE(page) - 1 || PXY2N(page,x+1,y) > num_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) < num_entries) { normalize(PXY2N(page+1,0,0)); } else if (PXY2N(page,x,y) + DX(page) < num_entries ) { normalize(PXY2N(page,x,y) + DX(page)); } } /* If mark_char is not '\0', we bold names ending with it. */ int request_strings(const char * const * const _entries, const int _num_entries, int n, const int _max_name_len, int _mark_char) { action a; input_class ic; int c, i, ne_lines0, ne_columns0; assert(_num_entries > 0); ne_lines0 = ne_columns0 = max_names_per_line = max_names_per_col = x = y = page = 0; entries = _entries; num_entries = _num_entries; max_name_len = _max_name_len + 1; mark_char = _mark_char; 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 / (max_name_len))) 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); } move_cursor(y, x * max_name_len); do c = get_key_code(); while((ic = CHAR_CLASS(c)) == IGNORE || ic == INVALID); n = PXY2N(page,x,y); switch(ic) { case ALPHA: if (n >= num_entries) n = num_entries - 1; c = localised_up_case[(unsigned char)c]; for(i = 1; i < num_entries; i++) if (localised_up_case[(unsigned char)entries[(n + i) % num_entries][0]] == c) { normalize((n + i) % num_entries); break; } break; case TAB: if (n >= num_entries) return ERROR; else return -n - 2; case RETURN: if (n >= num_entries) return ERROR; else return n; case COMMAND: if (c < 0) c = -c - 1; if ((a = parse_command_line(key_binding[c], NULL, NULL, FALSE))>=0) { switch(a) { 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 CLOSEDOC_A: case QUIT_A: case ESCAPE_A: 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(const char *start_prefix) { int is_dir, unique = TRUE; char *p, *dir_name, *cur_dir_name, *cur_prefix = NULL, *result = NULL; DIR *d; struct dirent *de; /* 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. */ cur_dir_name = ne_getcwd(CUR_DIR_MAX_SIZE); if (dir_name = str_dup(start_prefix)) { *(p = (char *)file_part(dir_name)) = 0; if (p != dir_name && chdir(tilde_expand(dir_name)) == -1) { free(dir_name); return NULL; } } start_prefix = file_part(start_prefix); if (d = opendir(CURDIR)) { while(!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); } 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; } /* 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, int use_prefix) { int i, num_entries, name_len, max_name_len, total_len, next_dir, is_dir, entries_alloc_size = DEF_ENTRIES_ALLOC_SIZE, names_alloc_size = DEF_NAMES_ALLOC_SIZE; char *dir_name, **entries = NULL, *names = NULL, *cur_dir_name, *result = NULL, *p; DIR *d; struct dirent *de; if (!(cur_dir_name = ne_getcwd(CUR_DIR_MAX_SIZE))) return NULL; if (dir_name = str_dup(filename)) { i = 0; if ((p = (char *)file_part(dir_name)) != dir_name) { *p = 0; i = chdir(tilde_expand(dir_name)); } free(dir_name); if (i == -1) return NULL; } if (entries = malloc(sizeof(char *) * entries_alloc_size)) { if (names = malloc(sizeof(char) * names_alloc_size)) { do { next_dir = FALSE; if (d = opendir(CURDIR)) { num_entries = max_name_len = total_len = 0; stop = FALSE; while(!stop && (de = readdir(d))) { is_dir = is_directory(de->d_name); if (use_prefix && !is_prefix(file_part(filename), de->d_name)) continue; name_len = strlen(de->d_name) + is_dir + 1; if (name_len > max_name_len) max_name_len = name_len; if (total_len + name_len > names_alloc_size) { char *t; t = realloc(names, sizeof(char) * (names_alloc_size = names_alloc_size * 2 + name_len)); if (!t) break; names = t; /* Now adjust the entries to point to the newly reallocated strings */ entries[0] = names; for (i = 1; i < num_entries; i++) entries[i] = entries[i - 1] + strlen(entries[i - 1]) + 1; } if (num_entries >= entries_alloc_size) { char **t; t = realloc(entries, sizeof(char *) * (entries_alloc_size *= 2)); if (!t) break; entries = t; } strcpy(entries[num_entries] = names + total_len, de->d_name); if (is_dir) strcpy(names + total_len + name_len - 2, "/"); total_len += name_len; num_entries++; } if (num_entries) { qsort(entries, num_entries, sizeof(char *), filenamecmpp); if ((i = request_strings((const char * const *)entries, num_entries, 0, max_name_len, '/')) != ERROR) { p = entries[i >= 0 ? i : -i - 2]; if (p[strlen(p) - 1] == '/' && i >= 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 (i < 0) { memmove(result + 1, result, strlen(result) + 1); result[0] = 0; } } } } closedir(d); } else alert(); } while(next_dir); free(names); } free(entries); } 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, TRUE, 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, num_entries, max_name_len, total_len; char **entries, *names, *p, unnamed_name[] = UNNAMED_NAME; buffer *b = (buffer *)buffers.head; num_entries = max_name_len = total_len = 0; while(b->b_node.next) { p = b->filename ? b->filename : unnamed_name; if (strlen(p)>max_name_len) max_name_len = strlen(p); total_len += strlen(p) + 1; num_entries++; b = (buffer *)b->b_node.next; } max_name_len += 8; if (num_entries) { if (entries = malloc(sizeof(char *) * num_entries)) { if (names = malloc(sizeof(char) * total_len)) { p = names; b = (buffer *)buffers.head; for(i = 0; i < num_entries; i++) { entries[i] = p; strcpy(p, b->filename ? b->filename : unnamed_name); p += strlen(p) + 1; b = (buffer *)b->b_node.next; } i = request_strings((const char * const *)entries, num_entries, 0, max_name_len, '\0'); reset_window(); free(names); } free(entries); } } return i; } ne-2.5/src/search.c0000644000076600007660000003713012076214662013202 0ustar vignavigna/* Search/replace functions (with and without regular expressions). Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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). */ int 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 unsigned char *pattern, const int skip_first) { const unsigned char * const up_case = b->encoding == ENC_UTF8 ? ascii_up_case : localised_up_case; const int sense_case = (b->opt.case_search != 0); unsigned char c, first_char; unsigned char *p; long i, m; int recompile_string; long y; line_desc *ld; if (!pattern) { pattern = b->find_string; recompile_string = b->find_string_changed || b->last_was_regexp; } else recompile_string = TRUE; if (!pattern || !(m = strlen(pattern))) return ERROR; if (recompile_string) for(i = 0; i < sizeof d / sizeof *d; i++) d[i] = m; ld = b->cur_line_desc; y = b->cur_line; if (! b->opt.search_back) { if (recompile_string) { for(i = 0; i < m - 1; i++) d[CONV(pattern[i])] = m - i-1; b->find_string_changed = 0; } p = ld->line + b->cur_pos + m - 1 + (skip_first ? 1 : 0); first_char = CONV(pattern[m - 1]); while(y < b->num_lines) { assert(ld->ld_node.next != NULL); if (ld->line_len >= m) { while((p - ld->line) < ld->line_len) { if ((c = CONV(*p)) != first_char) p+=d[c]; else { for (i = 1; i < m; i++) if (CONV(*(p - i)) != CONV(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(i = m - 1; i > 0; i--) d[(unsigned char)CONV(pattern[i])] = i; b->find_string_changed = 0; } p = ld->line + (b->cur_pos > ld->line_len - m ? ld->line_len - m : b->cur_pos + (skip_first ? -1 : 0)); first_char = CONV(pattern[0]); while(y >= 0) { assert(ld->ld_node.prev != NULL); if (ld->line_len >= m) { while((p - ld->line) >= 0) { if ((c = CONV(*p)) != first_char) p-=d[c]; else { for (i = 1; i < m; i++) if (CONV(*(p + i)) != CONV(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 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) { int 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 unsigned char *regex, const int skip_first) { const unsigned char * const up_case = b->encoding == ENC_UTF8 ? ascii_up_case : localised_up_case; const unsigned char *p; int recompile_string, i, y, start_pos; line_desc *ld; 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 unsigned 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 unsigned char *s; unsigned char *q; int virtual_group = 0, real_group = 0, escape = FALSE, 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); } 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; ld = b->cur_line_desc; y = b->cur_line; if (! b->opt.search_back) { start_pos = b->cur_pos + (skip_first ? 1 : 0); while(y < b->num_lines) { assert(ld->ld_node.next != NULL); if (start_pos <= ld->line_len && (i = re_search(&re_pb, ld->line ? ld->line : (unsigned char *)"", ld->line_len, start_pos, ld->line_len - start_pos, &re_reg))>=0) { goto_line(b, y); goto_pos(b, i); return OK; } ld = (line_desc *)ld->ld_node.next; start_pos = 0; y++; } } else { start_pos = b->cur_pos + (skip_first ? -1 : 0); while(y >= 0) { assert(ld->ld_node.prev != NULL); if (start_pos >= 0 && (i =re_search(&re_pb, ld->line ? ld->line : (unsigned char *)"", ld->line_len, start_pos, -start_pos - 1, &re_reg))>=0) { goto_line(b, y); goto_pos(b, i); return OK; } ld = (line_desc *)ld->ld_node.prev; if (ld->ld_node.prev) start_pos = ld->line_len; y--; } } return 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) { int i, pos, len, c, reg_used = FALSE; unsigned char *p, *q, *t = NULL; assert(string != NULL); if (q = p = str_dup(string)) { len = strlen(p); while(TRUE) { while(*q && *q != '\\') q++; if (!*q) break; 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(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; 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[*q] - re_reg.start[*q]) { c = t[re_reg.end[*q]]; t[re_reg.end[*q]] = 0; insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos + pos, t + re_reg.start[*q], re_reg.end[*q] - re_reg.start[*q]); t[re_reg.end[*q]] = c; pos += re_reg.end[*q] - re_reg.start[*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-2.5/src/signals.c0000644000076600007660000001020412076214662013366 0ustar vignavigna/* Signal handling setup and code. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 int 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, 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 unsigned int window_changed_size; unsigned int stop; ne-2.5/src/streams.c0000644000076600007660000001656212076214662013421 0ustar vignavigna/* Stream handling functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 int 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 int 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 unsigned char * const s, const int 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 int pos, const int len ) { int 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 int pos, int 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 int preserve_cr, const int binary) { int fh; assert_char_stream(cs); assert(name != NULL); name = tilde_expand(name); if (is_directory(name) || is_migrated(name)) return NULL; cs = load_stream_from_fh(cs, fh = open(name, READ_FLAGS), preserve_cr, binary); if (fh >= 0) close(fh); return cs; } char_stream *load_stream_from_fh(char_stream *cs, const int fh, const int preserve_cr, const int binary) { int i, j, len; char terminators[] = { 0x0d, 0x0a }; if (preserve_cr) terminators[0] = 0; if (fh < 0) return NULL; assert_char_stream(cs); 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(fh, cs->stream, len) < len) { free_char_stream(cs); return NULL; } if (binary) { cs->len = len; assert_char_stream(cs); return cs; } for(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 int CRLF, const int binary) { int fh, error; 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 ; if ((fh = open(name, WRITE_FLAGS, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH))>=0) { 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 int CRLF, const int binary) { int pos = 0, len; 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; } while(pos < cs->len) { len = strnlen_ne(cs->stream + pos, cs->len - pos); 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-2.5/src/support.c0000644000076600007660000004216012100714620013433 0ustar vignavigna/* Miscellaneous support functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 int is_migrated(const char * const name) { struct cvxstat st; if (cvxstat(name, &st, sizeof(struct cvxstat)) == 0) if (st.st_dmonflags & IMIGRATED) return 1; return 0; } #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. */ int 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 1; else return 0; } #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. */ int is_migrated(const char * const name) { return 0; } #endif int is_directory(const char * const name) { struct stat statbuf; return stat(tilde_expand(name), &statbuf) == 0 && S_ISDIR(statbuf.st_mode); } /* 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; char *home_dir, *p; struct passwd *passwd = NULL; if (!filename) return NULL; if (filename[0] != '~') return filename; 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++; 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; } if (p = realloc(expanded_filename, strlen(filename) + strlen(home_dir) + 1)) { 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) { const char *p; if (!pathname) return NULL; p = pathname + strlen(pathname); while(p > pathname && *(p - 1) != '/') p--; return p; } /* Duplicates a string. */ char *str_dup(const char * const s) { char *dup; if (!s) return NULL; dup = malloc(strlen(s) + 1); if (dup) strcpy(dup, s); return dup; } /* Tries to compute the length as a string of the given pointer, but stops after n characters (returning n). */ int strnlen_ne(const char *s, int 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. */ int 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. */ int 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]; } /* 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 strdictcmp(const void *a, const void *b) { int ci; if ( ci = strcasecmp(*(char * const *)a, *(char * const *)b) ) return ci; return strcmp(*(char * const *) a, * (char * const *) 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) { const char * const s = *(const char **)a, * const t = *(const char **)b; if (strcmp(s, "../")==0) return strcmp(t, "../") == 0 ? 0 : -1; if (strcmp(t, "../")==0) return 1; if (strcmp(s, "./")==0) return strcmp(t, "./") == 0 ? 0 : -1; if (strcmp(t, "./")==0) return 1; /* return strcmp(s, t); */ return strdictcmp(a, b); } /* 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. */ int calc_width(const line_desc * const ld, const int n, const int tab_size, const encoding_type encoding) { int pos, len; for(pos = len = 0; pos < n; pos = pos < ld->line_len ? next_pos(ld->line, pos, encoding) : pos + 1) { if (pos >= ld->line_len) len++; else if (ld->line[pos] != '\t') len += get_char_width(&ld->line[pos], encoding); else len += tab_size - len % tab_size; } return len; } /* Computes character length of a line descriptor. */ int calc_char_len(const line_desc * const ld, const encoding_type encoding) { int pos, len; for(pos = len = 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. */ int calc_pos(const line_desc * const ld, const int col, const int tab_size, const encoding_type encoding) { int pos, width, c_width; for(pos = width = 0; pos < ld->line_len && width + (c_width = get_char_width(&ld->line[pos], encoding)) <= col; pos = next_pos(ld->line, pos, encoding)) { if (ld->line[pos] != '\t') width += c_width; else width += tab_size - width % tab_size; } return pos; } /* Returns true if the specified character is invariant on the left edge of re-wrapped paragraphs */ int 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. */ int isasciispace(const int c) { return c < 0x80 && isspace(c); } /* Returns true if the specified character is an US-ASCII alphabetic character. */ int isasciialpha(const int c) { return c < 0x80 && isalpha(c); } /* Returns true if the specified block of text is US-ASCII. */ int is_ascii(const unsigned char * const s, int len) { 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 unsigned char *s, const int len) { int i, is_ascii = TRUE; const unsigned char * const t = s + len; if (len == 0) return ENC_ASCII; 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. */ i = utf8len(*s); if (i == -1) return ENC_8_BIT; else if (i > 1) { if (s + i > t) return ENC_8_BIT; else { /* We check for redundant representations. */ if (i == 2) { if (!(*s & 0x1E)) return ENC_8_BIT; } else if (!(*s & (1 << 7 - i) - 1) && !(*(s + 1) & ((1 << i - 2) - 1) << 8 - i)) return ENC_8_BIT; while(--i != 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. */ int next_pos(const unsigned char * const s, const int pos, const encoding_type encoding) { 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 uses utf8len() to move backward. If pos is 0, this function returns -1. */ int prev_pos(const unsigned char * const s, int 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 unsigned char * const s, const encoding_type encoding) { if (encoding == ENC_UTF8) return utf8char(s); else return *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 unsigned char * const s, const encoding_type encoding) { assert(s != NULL); return encoding == ENC_UTF8 ? output_width(utf8char(s)) : output_width(*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 unsigned char * const s, const int len, const encoding_type encoding) { int pos, width = 0; if (s == NULL) return len; for(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. */ int ne_ispunct(const int c, const int encoding) { #ifdef NOWCHAR return encoding != ENC_UTF8 ? ispunct(c) : c < 0x80 ? ispunct(c) : 0; #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. */ int ne_isspace(const int c, const int encoding) { #ifdef NOWCHAR return encoding != ENC_UTF8 ? isspace(c) : c < 0x80 ? isspace(c) : 0; #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 '_'. */ int 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, unsigned char **p, int *prefix_pos, encoding_type encoding) { *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-2.5/src/syn_hash.c0000644000076600007660000000644212076214662013553 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Simple hash tables. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2013 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-2.5/src/syn_hash.h0000644000076600007660000000306212076214662013553 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Simple hash tables. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2013 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-2.5/src/syn_regex.c0000644000076600007660000002150112076214662013733 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Regular expression subroutines. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2013 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-2.5/src/syn_regex.h0000644000076600007660000000204512076214662013742 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Regular expression subroutines. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2009-2013 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-2.5/src/syn_types.h0000644000076600007660000001256312076214662014002 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Types Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2013 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-2.5/src/syn_utf8.c0000644000076600007660000001022312076214662013506 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: UTF-8 utilities. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2013 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-2.5/src/syn_utf8.h0000644000076600007660000000377612076214662013532 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: UTF-8 utilities. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2013 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-2.5/src/syn_utils.c0000644000076600007660000002001412076214662013757 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Various utilities. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2001 Marek 'Marx' Grac Copyright (C) 2009-2013 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-2.5/src/syn_utils.h0000644000076600007660000001112112076214662013763 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Various utilities. Copyright (C) 1992 Joseph H. Allen Copyright (C) 2001 Marek 'Marx' Grac Copyright (C) 2009-2013 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-2.5/src/syntax.c0000644000076600007660000006321012076214662013261 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Syntax highlighting DFA interpreter. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2013 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 */ int 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; } int meta_color(unsigned char *s) { int 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). */ int *attr_buf = 0; int attr_size = 0; int attr_len = 0; int stack_count = 0; HIGHLIGHT_STATE parse(struct high_syntax *syntax, line_desc *ld, HIGHLIGHT_STATE h_state, int 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 */ int *attr = attr_buf; int *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; unsigned char *p = ld->line; unsigned char *q = 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(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 = exists_prefs_dir()) && strlen(p) + 2 + strlen(SYNTAX_DIR) + strlen(SYNTAX_EXT) + strlen(syntax->name) < sizeof name) { strcat(strcat(strcat(strcat(strcpy(name, p), SYNTAX_DIR), "/"), syntax->name), SYNTAX_EXT); f = fopen((char *)name,"r"); } if (!f && (p = exists_gprefs_dir()) && strlen(p) + 2 + strlen(SYNTAX_DIR) + strlen(SYNTAX_EXT) + strlen(syntax->name) < sizeof name) { strcat(strcat(strcat(strcat(strcpy(name, p), SYNTAX_DIR), "/"), 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-2.5/src/syntax.h0000644000076600007660000001017512076214662013270 0ustar vignavigna/* Syntax highlighting from Joe's Own Editor: Syntax highlighting DFA interpreter. Copyright (C) 2004 Joseph H. Allen Copyright (C) 2009-2013 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 */ int color; /* Color value */ }; /* State */ struct high_state { int no; /* State number */ unsigned char *name; /* Highlight state name */ int color; /* Color for this state */ 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 int *attr_buf; extern int attr_len; HIGHLIGHT_STATE parse PARAMS((struct high_syntax *syntax, line_desc *ld, HIGHLIGHT_STATE h_state, int 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-2.5/src/term.c0000644000076600007660000007563212076214662012715 0ustar vignavigna/* 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-2013 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 "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 int ansi = TRUE; #else int 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. */ int 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; int 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; int 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; int ne_tilde_glitch; int ne_memory_below; int 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; int 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 */ int line_ins_del_ok; /* Terminal can insert and delete lines */ int char_ins_del_ok; /* Terminal can insert and delete chars */ int scroll_region_ok; /* Terminal supports setting the scroll window */ int standout_ok; /* Terminal supports standout without magic cookies */ int cursor_on_off_ok; /* Terminal can make the cursor visible or invisible */ int ansi_color_ok; /* Terminal supports ANSI color */ int color_ok; /* Terminal supports color */ static int RPov; /* Least number of chars to start a TS_repeat. Less wouldn't be worth. */ static int delete_in_insert_mode; /* delete mode == insert mode */ static int se_is_so; /* 1 if same string both enters and leaves standout mode */ static int esm_is_eam; /* 1 if exiting standout mode turns off all attributes */ static int insert_mode; /* Nonzero when in insert mode. */ static int standout_mode; /* Nonzero when in standout mode. */ static int standout_wanted; /* Nonzero if we should be writing in standout mode. */ static int 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. */ int 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 unsigned char *s, int *len, int maxWidth, int 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 unsigned int attr) { char *buf; 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) { buf = tparm(ne_set_foreground , joe2color(attr >> FG_SHIFT)); OUTPUT1(buf); } if (attr & BG_NOT_DEFAULT) { buf = tparm(ne_set_background , joe2color(attr >> BG_SHIFT)); OUTPUT1(buf); } } } #else void set_attr(const unsigned int attr) { int attr_reset = FALSE; char *buf; /* 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) { 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) { 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 int attr) { int 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) { char *buf; assert(scroll_region_ok); /* Both control string have line range 0 to lines-1 */ 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) { int i; 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 (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 { int i; for (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 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 unsigned char *string, const unsigned int *attr, const int raw_len, const int utf8) { int i, c, len; const unsigned char *first_check; if (raw_len == 0) return; turn_off_insert(); standout_if_wanted(); /* If the string is UTF-8 encoded, compute its real length. */ if (utf8 && string != NULL) len = utf8strlen(string, raw_len); else 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(i = 0; i < len; i++) { /* When outputting spaces, it's only the first attribute that's used. */ if (attr) set_attr(*attr); putchar(' '); } return; } first_check = string; if (!ne_transparent_underline && !ne_tilde_glitch) { for(i = 0; i < len; i++) { if (utf8) { c = utf8char(string); string += utf8len(*string); out(c, attr ? attr[i] : -1); } else { c = *string++; out(c, attr ? attr[i] : -1); } } } else for(i = 0; i < len; i++) { if (attr) set_attr(attr[i]); c = utf8 ? *string : utf8char(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 int 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 unsigned int attr, const int utf8) { static unsigned 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 unsigned int *attr) { output_chars(NULL, attr, n, FALSE); } /* Same as output_chars(), but inserts instead. */ void insert_chars(const unsigned char * start, const unsigned int * const attr, const int raw_len, const int utf8) { const unsigned char *buf; int i, c, len; if (raw_len == 0) return; standout_if_wanted(); /* If the string is non-NULL and UTF-8 encoded, compute its real length. */ if (utf8 && start != NULL) len = utf8strlen(start, raw_len); else len = raw_len; if (ne_parm_ich) { int i, width = 0; if (start != NULL) { if (utf8) for(i = 0; i < raw_len; i += utf8len(start[i])) width += output_width(utf8char(start + i)); else for(i = 0; i < raw_len; i++) width += output_width(start[i]); } else width = len; 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(i = 0; i < len; i++) { if (attr) set_attr(attr[i]); if (utf8) { c = utf8char(start); start += utf8len(*start); } else c = *start++; out(c, attr ? attr[i] : -1); } } else for(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]); if (utf8) { c = utf8char(start); start += utf8len(*start); } else c = *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 unsigned int attr, const int utf8) { static unsigned 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) { char *buf; 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) { 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) { 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) { int l, c; #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; l = size.ts_lines; 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; l = size.ws_row; 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; l = strtol(getenv("LINES"), NULL, 10); 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-2.5/src/termcap.c0000644000076600007660000004236711535373356013405 0ustar vignavigna/* 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. */ /* Emacs config.h may rename various library functions such as malloc. */ #ifdef HAVE_CONFIG_H #include #endif #ifdef emacs #include /* xmalloc is here */ /* Get the O_* definitions for open et al. */ #include #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #else /* not emacs */ #ifdef STDC_HEADERS #include #include #else char *getenv (); char *malloc (); char *realloc (); #endif /* Do this after the include, in case string.h prototypes bcopy. */ #if (defined(HAVE_STRING_H) || defined(STDC_HEADERS)) && !defined(bcopy) #define bcopy(s, d, n) memcpy ((d), (s), (n)) #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef _POSIX_VERSION #include #endif #endif /* not emacs */ #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-2.5/src/termcap.h0000644000076600007660000000315011535373356013375 0ustar vignavigna/* 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-2.5/src/termchar.h0000644000076600007660000001305612076214662013550 0ustar vignavigna/* extern's of flags describing terminal's characteristics. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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. */ #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< #endif #ifdef emacs #include "lisp.h" /* for xmalloc */ #else #ifdef STDC_HEADERS #include #include #else char *malloc (); char *realloc (); #endif /* Do this after the include, in case string.h prototypes bcopy. */ #if (defined(HAVE_STRING_H) || defined(STDC_HEADERS)) && !defined(bcopy) #define bcopy(s, d, n) memcpy ((d), (s), (n)) #endif #endif /* not emacs */ #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-2.5/src/undo.c0000644000076600007660000002061612076214662012703 0ustar vignavigna/* Undo/redo system management functions. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 int line, const int pos, const int len) { undo_step *ud; if (!ub) return FALSE; assert_undo_buffer(ub); if (ub->cur_step >= ub->steps_size) { if (ud = realloc(ub->steps, (ub->steps_size + STD_UNDO_STEP_SIZE) * sizeof(undo_step))) { 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 int line, const int pos, const int 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 int 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 int 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) { line_desc *cur_line_desc; 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)); cur_line_desc = b->cur_line_desc; 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) { line_desc *cur_line_desc; 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)); cur_line_desc = b->cur_line_desc; 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-2.5/src/utf8.c0000644000076600007660000000665412076214662012632 0ustar vignavigna/* UTF-8 support. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 "utf8.h" /* Computes the character length of an UTF-8 encoded sequence of bytes. */ int utf8strlen(const unsigned char * const s, const int len) { int 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 unsigned char * const s) { 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, unsigned char * const s) { 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-2.5/src/utf8.h0000644000076600007660000000272412076214662012631 0ustar vignavigna/* UTF-8 support prototypes. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 . */ int utf8char(const unsigned char *s); int utf8strlen(const unsigned char *s, int len); int utf8seqlen(int c); int utf8str(int c, unsigned 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-2.5/src/version.h0000644000076600007660000000221112102010565013401 0ustar vignavigna/* This file was automatically generated by ./version.pl. */ /* String definitions for version and 'About...' messages. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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 "(2013-01-29)" #define VERSION "2.5" #define PROGRAM_NAME "ne, the nice editor" #define ABOUT_MSG PROGRAM_NAME " " VERSION ". " DATE #define VERSION_STRING "@(#)"ABOUT_MSG ne-2.5/src/ext.c.in0000644000076600007660000000300112101732573013123 0ustar vignavigna/* Extern's for names and abbreviations of all the commands. Copyright (C) 1993-1998 Sebastiano Vigna Copyright (C) 1999-2013 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-2.5/src/copyright++.pl0000755000076600007660000000427212101732573014263 0ustar vignavigna#!/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-2013 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-2.5/src/info2src.pl0000755000076600007660000004231412076214662013656 0ustar vignavigna#!/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-2013 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-2.5/macros/DeleteSOL0000644000076600007660000000162411663511177013772 0ustar vignavigna# 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-2.5/syntax/4gl.jsf0000644000076600007660000000657111534141606013522 0ustar vignavigna# JOE syntax highlight file for Progress 4GL # by Gediminas http://proc.w3.lt # Version 1.04 # bold inverse blink dim underline # 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-2.5/syntax/ada.jsf0000644000076600007660000000477611534141606013566 0ustar vignavigna# 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-2.5/syntax/asm.jsf0000644000076600007660000001010611534141606013601 0ustar vignavigna# JOE syntax highlight file for assembly language # Highlights ';' comments, C-preprocessor stuff and C-like constants. # Some assembly languages do not use C-preprocessor and use '#' for # comments. For them, eliminate all states up to ':idle'. and change the # ";" line in 'idle' to '#'. # For UNIX assembly ';' actually separates instructions, but I've only seen # this for 'rep; movsb'. If it bothers you eliminate the ';' line in # ':idle'. # This highlighter is too strict for numeric constants. Many assembly # languages allow '000009', which looks like a bad octal constant in C/UNIX. =Idle =Bad bold red =Preproc blue =Define bold blue =Comment green =IncLocal cyan =IncSystem bold cyan =Constant cyan =Escape bold cyan =Type bold =Keyword bold =CppKeyword bold =Brace =Control :reset Idle * first noeat " \t" reset :first Idle * idle noeat "#" pre recolor=-1 :pre Preproc * preproc noeat " \t" pre "a-z" preident recolor=-1 buffer :preident Preproc * preproc noeat strings "define" predef "include" preinc done "a-z" preident :preinc Preproc * preinc " \t" preinc_ws "\n" reset :preinc_ws Preproc * prebad recolor=-1 " \t" preinc_ws "\"" preinc_local recolor=-1 "<" preinc_system recolor=-1 :preinc_local IncLocal * preinc_local "\"\n" reset :preinc_system IncSystem * preinc_system ">\n" reset :prebad Bad * prebad "\n" reset :predef Preproc * predef " \t" predef_ws "\n" reset :predef_ws Preproc * prebad recolor=-1 " \t" predef_ws "a-zA-Z0-9_" predef_ident recolor=-1 :predef_ident Define * idle noeat "a-zA-Z0-9_" predef_ident :preproc Preproc * preproc "\n" reset "\\" preproc_cont "/" preproc_slash :preproc_slash Preproc * preproc noeat "*" comment recolor=-2 "/" line_comment recolor=-2 :preproc_cont Preproc * preproc_cont "\n" preproc # All following states are for when we're not in a preprocessor line :idle Idle * idle ";" line_comment recolor=-1 "\n" reset "/" slash "0" first_digit recolor=-1 "1-9" decimal recolor=-1 "." maybe_float "\"" string recolor=-1 "'" char recolor=-1 "a-zA-Z_" ident buffer "{}" brace recolor=-1 ",:=()><[]*&|!~+\-%^" control recolor=-1 :brace Brace * idle noeat :control Control * idle noeat :slash Idle * idle noeat recolor=-2 # Not sure about this "*" comment recolor=-2 "/" line_comment recolor=-2 :comment Comment * comment "*" maybe_end_comment :maybe_end_comment Comment * comment "/" idle "*" maybe_end_comment :line_comment Comment * line_comment "\n" reset :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_control "\n" reset "diouxXeEfFgGaAcspn%SC" string :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 "a-zA-Z0-9_" ident ne-2.5/syntax/awk.jsf0000644000076600007660000000631011534141606013605 0ustar vignavigna# JOE syntax highlight file for AWK # Resync whole file - =Idle =Bad bold red =Preproc blue =Define bold blue =Comment green =IncLocal cyan =IncSystem bold cyan =Constant cyan =Escape bold cyan =Type bold =Keyword bold =CppKeyword bold =Brace magenta =Control :reset Idle * first noeat " \t" reset :first Idle * idle noeat "/" regex "#" reset_line_comment recolor=-1 :regex Idle * regex "/" idle "\\" regex_skip :regex_skip Idle * regex :reset_line_comment Comment * reset_line_comment "\n" reset :idle Idle * idle "\n" reset "#" line_comment recolor=-1 "0" first_digit recolor=-1 "1-9" decimal recolor=-1 "." maybe_float "\"" string recolor=-1 "'" char recolor=-1 "a-zA-Z_" ident buffer "{}" brace recolor=-1 "/,:;=()><[]*&|!~+\-%^" control recolor=-1 :brace Brace * idle noeat :control Control * idle noeat :line_comment Comment * line_comment "\n" reset :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 "if" kw "else" kw "while" kw "do" kw "for" kw "break" kw "continue" kw "delete" kw "exit" kw "close" kw "getline" kw "next" kw "nextfile" kw "print" kw "printf" kw "system" kw "fflush" kw "atan2" kw "cos" kw "exp" kw "int" kw "log" kw "rand" kw "sin" kw "sqrt" kw "srand" kw "asort" kw "asorti" kw "gensub" kw "gsub" kw "index" kw "length" kw "match" kw "split" kw "sprintf" kw "strtonum" kw "sub" kw "substr" kw "tolower" kw "toupper" kw "mktime" kw "strftime" kw "systime" kw "and" kw "compl" kw "lshift" kw "or" kw "rshift" kw "xor" kw "bindtextdomain" kw "dcgettext" kw "dcngettext" kw "function" kw "return" kw done "a-zA-Z0-9_" ident :kw Keyword * idle noeat ne-2.5/syntax/c.jsf0000644000076600007660000003036511534141606013254 0ustar vignavigna# JOE syntax highlight file for C and C++ # A (deterministic) state machine which performs lexical analysis of C. # (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: [