pax_global_header00006660000000000000000000000064135441514330014515gustar00rootroot0000000000000052 comment=cae7c45ea5a563c022001a54d1eee71c268f62b4 dte-1.9.1/000077500000000000000000000000001354415143300123015ustar00rootroot00000000000000dte-1.9.1/CHANGELOG.md000066400000000000000000000273141354415143300141210ustar00rootroot00000000000000Releases ======== v1.9.1 (latest release) ----------------------- Released on 2019-09-29. **Changes:** * Fixed `make check` when running from a release tarball. **Downloads:** * [dte-1.9.1.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.9.1.tar.gz) * [dte-1.9.1.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.9.1.tar.gz.sig) v1.9 ---- Released on 2019-09-21. **Additions:** * Added a new `pipe-to` command, to complement the existing `pipe-from` and `filter` commands. * Added a new `show` command, which can be used to introspect the current values of aliases and bindings. * Added a `-k` flag to the `copy` command, to allow keeping the current selection after copying. * Added a man page entry for the (previously undocumented) `eval` command. * Added new `$FILETYPE` and `$LINENO` special variables. * Added a `display-invisible` global option, to allow visible rendering of otherwise invisible Unicode characters. * Added an `-s` command-line flag, for validating custom syntax files. * Added a compile-time `ICONV_DISABLE=1` option, which disables linking to the system iconv library (but makes the editor UTF-8 only). * Added a Desktop Entry file, which can be installed using `make install-desktop-file`. **Improvements:** * Updated Unicode support to version 12.1. * Modified the `errorfmt` command, to allow sub-match groups in the regexp pattern to be ignored. * Various improvements to syntax highlighting and filetype detection. * Various performance optimizations. **Fixes:** * Fixed a bug that would sometimes cause files to be saved as UTF-8, even if another encoding was specified. **Downloads:** * [dte-1.9.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.9.tar.gz) * [dte-1.9.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.9.tar.gz.sig) v1.8.2 ------ Released on 2019-04-23. **Changes:** * Fixed makefile to work with GNU Make 3.81 (which is still used by OS X and Ubuntu 14.04). **Downloads:** * [dte-1.8.2.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.8.2.tar.gz) * [dte-1.8.2.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.8.2.tar.gz.sig) v1.8.1 ------ Released on 2019-04-22. **Fixes:** * Fixed parsing of escaped special characters in command arguments (which was causing Lua syntax highlighting to fail). * Removed use of `rep` (repeat character) control sequence, due to problems caused by certain terminal emulators that claim to be "xterm" but don't support the full set of features in the xterm `terminfo(5)` entry (notably, the FreeBSD 12 console). **Additions:** * Show a confirmation prompt if Ctrl+q (quit) is pressed with unsaved changes, instead of a cryptic error message. **Downloads:** * [dte-1.8.1.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.8.1.tar.gz) * [dte-1.8.1.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.8.1.tar.gz.sig) v1.8 ---- Released on 2019-04-18. **Additions:** * Added support for 24-bit RGB terminal colors. * Added support for `strikethrough` terminal attribute. * Added support for `alias` names containing multi-byte Unicode characters. * Added `refresh` command (to force a full screen redraw). * Added `dte -K` command-line option (for keycode debugging). * Added support for reading a buffer from `stdin` at startup. * Added support for binding Ctrl/Alt/Shift + F1-F12 on xterm-like terminals. * Added `-s` flag to `bol` command, to allow moving to beginning of indented text, before moving to beginning of line (a.k.a "smart home"). * Added `-c` flag to all cursor movement commands, to allow selecting characters while moving. * Added `-l` flag to `up`, `down`, `pgup` and `pgdown` commands, to allow selecting whole lines while moving. * Added default bindings for various Shift+key combinations, for doing GUI-style text selections. * Added key bindings to command mode for deleting/erasing whole words (Alt+Delete and Alt+Backspace). **Improvements:** * Optimized built-in filetype detection. * Fixed cursor interaction with Unicode combining characters. * Improved handling of Unicode whitespace and unprintable characters. * Updated character class lookup tables to Unicode 11. * Expanded documentation for `hi` and `compile` commands. * Optimized code to reduce editor startup and input latency. **Breaking changes:** * Changed the `bind` command to be much more strict when parsing key strings and show an error message when invalid. The caret (`^`) modifier can now only be used alone (not in combination with other modifiers) and the `C-`, `M-` and `S-` modifiers *must* be uppercase. * Removed support for chained key bindings (e.g. `bind '^X c' ...`). Commands that aren't bound to simple key combinations can just be accessed via command mode. * Removed support for recognizing some Ctrl/Alt/Shift key combinations produced by the `rxvt` terminal emulator. The key codes produced by `rxvt` violate the [ECMA-48] specification. Users of such terminals are encouraged to configure the key codes to mimic `xterm` instead. **Downloads:** * [dte-1.8.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.8.tar.gz) * [dte-1.8.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.8.tar.gz.sig) v1.7 ---- Released on 2018-05-08. **Changes:** * Added support for opening multiple files using glob patterns (e.g. `open -g *.[ch]`). * Added support for binding more xterm extended key combinations (Ctrl/Meta/Shift + PageUp/PageDown/Home/End). * Improved compiler error parsing for newer versions of GCC. * Improved handling of underline/dim/italic terminal attributes (including support for the `ncv` terminfo capability). * Many other small bug fixes and code improvements. **Downloads:** * [dte-1.7.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.7.tar.gz) * [dte-1.7.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.7.tar.gz.sig) v1.6 ---- Released on 2017-12-20. **Changes:** * Added new, default `dark` color scheme. * Added Ctrl+G key binding to exit command mode. * Added Ctrl+H key binding for `erase` command. * Added syntax highlighting for TeX and roff (man page) files. * Improved syntax highlighting of Python numeric literals. * Improved syntax highlighting for CSS files. * Added `ft -b` command for detecting file types from file basenames. * Converted user documentation to Markdown format. * Created new [website] for online documentation. * Added support for terminfo extended (or "user-defined") capabilities. * Added built-in support for `st` and `rxvt` terminals. * Fixed some built-in regex patterns to avoid non-portable features. * Fixed compiler warnings on NetBSD. * Removed tilde expansion of `~username` from command mode, in order to avoid using `getpwnam(3)` and thereby allow static linking with GNU libc on Linux. **Downloads:** * [dte-1.6.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.6.tar.gz) * [dte-1.6.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.6.tar.gz.sig) v1.5 ---- Released on 2017-11-03. **Changes:** * Added syntax highlighting for Nginx config files. * Added some POSIX and C11 features to the C syntax highlighter. * Added new command-line flags for listing (`-B`) and dumping (`-b`) built-in rc files. * Moved some of the documentation from the `dte(1)` man page to a separate `dterc(5)` page. * Fixed a terminal input bug triggered by redirecting `stdin`. * Fixed some memory and file descriptor leaks. * Fixed a few portability issues. **Downloads:** * [dte-1.5.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.5.tar.gz) * [dte-1.5.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.5.tar.gz.sig) v1.4 ---- Released on 2017-10-16. **Changes:** * Changed the build system to compile all default configs and syntax highlighting files into the `dte` binary instead of installing and loading them from disk. The `$PKGDATADIR` variable is now removed. * Added syntax highlighting for the Vala and D languages. * Added the ability to bind additional, xterm-style key combinations (e.g. `bind C-M-S-left ...`) when `$TERM` is `tmux` (previously only available for `xterm` and `screen`). * Added an option to compile without linking to the curses/terminfo library (`make TERMINFO_DISABLE=1`), to make it easier to create portable, statically-linked builds. **Downloads:** * [dte-1.4.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.4.tar.gz) * [dte-1.4.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.4.tar.gz.sig) v1.3 ---- Released on 2017-08-27. **Changes:** * Added support for binding Ctrl+Alt+Shift+arrows in xterm/screen/tmux. * Added support for binding Ctrl+delete/Shift+delete in xterm/screen/tmux. * Added the ability to override the default user config directory via the `DTE_HOME` environment variable. * Added syntax highlighting for the Markdown, Meson and Ruby languages. * Improved syntax highlighting for the C, awk and HTML languages. * Fixed a bug with the `close -wq` command when using split frames (`wsplit`). * Fixed a segfault bug in `git-open` mode when not inside a git repo. * Fixed a few cases of undefined behaviour and potential buffer overflow inherited from [dex]. * Fixed all compiler warnings when building on OpenBSD 6. * Fixed and clarified various details in the man pages. * Renamed `inc-home` and `inc-end` commands to `bolsf` and `eolsf`, for consistency with other similar commands. * Renamed `dte-syntax.7` man page to `dte-syntax.5` (users with an existing installation may want to manually delete `dte-syntax.7`). **Downloads:** * [dte-1.3.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.3.tar.gz) * [dte-1.3.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.3.tar.gz.sig) v1.2 ---- Released on 2017-07-30. **Changes:** * Unicode 10 rendering support. * Various build system fixes. * Coding style fixes. **Downloads:** * [dte-1.2.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.2.tar.gz) * [dte-1.2.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.2.tar.gz.sig) v1.1 ---- Released on 2017-07-29. **Changes:** * Renamed project from "dex" to "dte". * Changed default key bindings to be more like most GUI applications. * Added `-n` flag to `delete-eol` command, to enable deleting newlines if the cursor is at the of the line. * Added `-p` flag to `save` command, to open a prompt if the current buffer has no existing filename. * Added `inc-end` and `inc-home` commands that move the cursor incrementally to the end/beginning of the line/screen/file. * Added a command-line option to jump to a specific line number after opening a file. * Added syntax highlighting for `ini`, `robots.txt` and `Dockerfile` languages. * Fixed a compilation error on OpenBSD. * Replaced quirky command-line option parser with POSIX `getopt(3)`. **Downloads:** * [dte-1.1.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.1.tar.gz) * [dte-1.1.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.1.tar.gz.sig) v1.0 ---- Released on 2015-04-28. This is identical to the `v1.0` [release][dex v1.0] of [dex][] (the editor from which this project was forked). **Downloads:** * [dte-1.0.tar.gz](https://craigbarnes.gitlab.io/dist/dte/dte-1.0.tar.gz) * [dte-1.0.tar.gz.sig](https://craigbarnes.gitlab.io/dist/dte/dte-1.0.tar.gz.sig) Public Key ========== A detached PGP signature file is provided for each release. The public key for verifying these signatures is available to download at or from the keyserver at `hkps://hkps.pool.sks-keyservers.net`. Checksums ========= A list of SHA256 checksums for all release tarballs and signatures is available at . [website]: https://craigbarnes.gitlab.io/dte/ [dex]: https://github.com/tihirvon/dex [dex v1.0]: https://github.com/tihirvon/dex/releases/tag/v1.0 [ECMA-48]: https://www.ecma-international.org/publications/standards/Ecma-048.htm dte-1.9.1/GNUmakefile000066400000000000000000000036461354415143300143640ustar00rootroot00000000000000include mk/compat.mk -include Config.mk include mk/util.mk include mk/build.mk include mk/docs.mk include mk/gen.mk -include mk/dev.mk prefix ?= /usr/local bindir ?= $(prefix)/bin datadir ?= $(prefix)/share mandir ?= $(datadir)/man man1dir ?= $(mandir)/man1 man5dir ?= $(mandir)/man5 appdir ?= $(datadir)/applications INSTALL = install INSTALL_PROGRAM = $(INSTALL) INSTALL_DATA = $(INSTALL) -m 644 INSTALL_DESKTOP_FILE = desktop-file-install RM = rm -f all: $(dte) check: $(test) all $(E) TEST $< $(Q) ./$< install: all $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(bindir)' $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(man1dir)' $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(man5dir)' $(E) INSTALL '$(DESTDIR)$(bindir)/$(dte)' $(Q) $(INSTALL_PROGRAM) '$(dte)' '$(DESTDIR)$(bindir)' $(E) INSTALL '$(DESTDIR)$(man1dir)/dte.1' $(Q) $(INSTALL_DATA) docs/dte.1 '$(DESTDIR)$(man1dir)' $(E) INSTALL '$(DESTDIR)$(man5dir)/dterc.5' $(Q) $(INSTALL_DATA) docs/dterc.5 '$(DESTDIR)$(man5dir)' $(E) INSTALL '$(DESTDIR)$(man5dir)/dte-syntax.5' $(Q) $(INSTALL_DATA) docs/dte-syntax.5 '$(DESTDIR)$(man5dir)' uninstall: $(RM) '$(DESTDIR)$(bindir)/$(dte)' $(RM) '$(DESTDIR)$(man1dir)/dte.1' $(RM) '$(DESTDIR)$(man5dir)/dterc.5' $(RM) '$(DESTDIR)$(man5dir)/dte-syntax.5' install-desktop-file: $(E) INSTALL '$(DESTDIR)$(appdir)/dte.desktop' $(Q) $(INSTALL_DESKTOP_FILE) \ --dir='$(DESTDIR)$(appdir)' \ --set-key=TryExec --set-value='$(bindir)/$(dte)' \ --set-key=Exec --set-value='$(bindir)/$(dte) %F' \ $(if $(DESTDIR),, --rebuild-mime-info-cache) \ dte.desktop uninstall-desktop-file: $(RM) '$(DESTDIR)$(appdir)/dte.desktop' $(if $(DESTDIR),, update-desktop-database -q '$(appdir)' || :) tags: ctags $$(find src/ test/ -type f -name '*.[ch]') clean: $(RM) $(CLEANFILES) $(if $(CLEANDIRS),$(RM) -r $(CLEANDIRS)) .DEFAULT_GOAL = all .PHONY: all check install uninstall tags clean .PHONY: install-desktop-file uninstall-desktop-file .DELETE_ON_ERROR: dte-1.9.1/LICENSE000066400000000000000000000432541354415143300133160ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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. dte-1.9.1/README.md000066400000000000000000000142121354415143300135600ustar00rootroot00000000000000dte === dte is a small and easy to use console text editor. Features -------- * Multiple buffers/tabs * Unlimited undo/redo * Regex search and replace * Syntax highlighting * Customizable color schemes * Customizable key bindings * Support for all xterm Ctrl/Alt/Shift key codes * Command language with auto-completion * Unicode 12.1 compatible text rendering * Support for multiple encodings (using [iconv]) * Jump to definition (using [ctags]) * Jump to compiler error * [EditorConfig] support Screenshot ---------- ![dte screenshot](https://craigbarnes.gitlab.io/dte/screenshot.png) Requirements ------------ * [GCC] 4.6+ or [Clang] * [GNU Make] 3.81+ * [terminfo] library (may be provided by [ncurses], depending on OS) * [iconv] library (may be included in libc, depending on OS) * [POSIX]-compatible [`sh`] and [`awk`] Installation ------------ To build `dte` from source, first install the requirements listed above, then use the following commands: curl -LO https://craigbarnes.gitlab.io/dist/dte/dte-1.9.1.tar.gz tar -xzf dte-1.9.1.tar.gz cd dte-1.9.1 make -j8 && sudo make install Documentation ------------- After installing, you can access the documentation in man page format via `man 1 dte`, `man 5 dterc` and `man 5 dte-syntax`. Online documentation is also available at . Testing ------- `dte` is tested on the following platforms: | Platform | Testing Method | |-------------------|-----------------------| | Debian | [GitLab CI] | | CentOS | GitLab CI | | Alpine Linux | GitLab CI | | Void Linux (musl) | GitLab CI | | Mac OS X | [Travis CI] | | Ubuntu | GitLab CI + Travis CI | | FreeBSD | Manual testing | | NetBSD | Manual testing | | OpenBSD | Manual testing | | Cygwin | Manual testing | Other [POSIX] 2008 compatible platforms should also work, but may require build system changes. Packaging --------- **Stable releases**: The [releases] page contains a short summary of changes for each stable version and links to the corresponding source tarballs. Note: auto-generated tarballs from GitHub/GitLab can (and [do][libgit issue #4343]) change over time and cannot be guaranteed to have long-term stable checksums. Use the tarballs from the [releases] page, unless you're prepared to deal with future checksum failures. **Build variables**: The following build variables may be useful when packaging `dte`: * `prefix`: Top-level installation prefix (defaults to `/usr/local`). * `bindir`: Installation prefix for program binary (defaults to `$prefix/bin`). * `mandir`: Installation prefix for manual pages (defaults to `$prefix/share/man`). * `DESTDIR`: Standard variable used for [staged installs]. * `V=1`: Enable verbose build output. * `TERMINFO_DISABLE=1`: Use built-in terminal support, instead of linking to the system [terminfo]/curses library. This makes it much easier to build a portable, statically linked binary. The built-in terminal support currently works with `tmux`, `screen`, `st`, `xterm` (and many other `xterm`-compatible terminals) and falls back to [ECMA-48] mode for other terminals. * `ICONV_DISABLE=1`: Disable support for all file encodings except UTF-8, to avoid the need to link with the system [iconv] library. This can significantly reduce the size of statically linked builds, but is generally not recommended. Example usage: make V=1 make install V=1 prefix=/usr DESTDIR=PKG **Persistent configuration**: Build variables can also be configured persistently by adding them to a `Config.mk` file, for example: prefix = /usr mandir = $(prefix)/man DESTDIR = ~/buildroot V = 1 The `Config.mk` file should be in the project base directory alongside `GNUmakefile` and *must* be valid GNU make syntax. **Desktop menu entry**: A desktop menu entry for `dte` can be added by running: make install-desktop-file Any variable overrides specified for `make install` must also be specified for `make install-desktop-file`. The easiest way to do this is simply to run both at the same time, e.g.: make install install-desktop-file V=1 prefix=/usr DESTDIR=PKG **Note**: the `install-desktop-file` target requires [desktop-file-utils] to be installed. License ------- Copyright (C) 2017-2019 Craig Barnes. Copyright (C) 2010-2015 Timo Hirvonen. This program is free software; you can redistribute it and/or modify it under the terms of the GNU [General Public License version 2], as published by the Free Software Foundation. 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 version 2 for more details. [ctags]: https://en.wikipedia.org/wiki/Ctags [EditorConfig]: https://editorconfig.org/ [GCC]: https://gcc.gnu.org/ [Clang]: https://clang.llvm.org/ [GNU Make]: https://www.gnu.org/software/make/ [ncurses]: https://www.gnu.org/software/ncurses/ [terminfo]: https://en.wikipedia.org/wiki/Terminfo [ECMA-48]: https://www.ecma-international.org/publications/standards/Ecma-048.htm "ANSI X3.64 / ECMA-48 / ISO/IEC 6429" [desktop-file-utils]: https://www.freedesktop.org/wiki/Software/desktop-file-utils [`GNUmakefile`]: https://gitlab.com/craigbarnes/dte/blob/master/GNUmakefile [syntax files]: https://gitlab.com/craigbarnes/dte/tree/master/config/syntax [staged installs]: https://www.gnu.org/prep/standards/html_node/DESTDIR.html [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/ [iconv]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/iconv.h.html [`sh`]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html [`awk`]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/awk.html [GitLab CI]: https://gitlab.com/craigbarnes/dte/pipelines [Travis CI]: https://travis-ci.org/craigbarnes/dte [General Public License version 2]: https://www.gnu.org/licenses/gpl-2.0.html [releases]: https://craigbarnes.gitlab.io/dte/releases.html [libgit issue #4343]: https://github.com/libgit2/libgit2/issues/4343 dte-1.9.1/config/000077500000000000000000000000001354415143300135465ustar00rootroot00000000000000dte-1.9.1/config/README.md000066400000000000000000000007041354415143300150260ustar00rootroot00000000000000dte built-in configs ==================== The files in this directory are embedded into the `dte` program binary at compile-time. The `rc` file is loaded by default when the editor starts. When `dte` is installed, the full list of embedded files can be printed using `dte -B` and the contents of any embedded file can be printed using, for example, `dte -b binding/default`. For more information, see . dte-1.9.1/config/binding/000077500000000000000000000000001354415143300151605ustar00rootroot00000000000000dte-1.9.1/config/binding/default000066400000000000000000000044031354415143300165300ustar00rootroot00000000000000bind left left bind right right bind up up bind down down bind home bol bind end eol bind pgup pgup bind pgdown pgdown bind delete delete bind C-left 'word-bwd -s' bind C-right 'word-fwd -s' bind C-up up bind C-down down bind M-left bol bind M-right eol bind M-up pgup bind M-down pgdown bind S-left 'left -c' bind S-right 'right -c' bind S-up 'up -c' bind S-down 'down -c' bind S-home 'bol -c' bind S-end 'eol -c' bind S-pgup 'pgup -c' bind S-pgdown 'pgdown -c' bind C-S-left 'word-bwd -cs' bind C-S-right 'word-fwd -cs' bind C-S-up 'up -l' bind C-S-down 'down -l' bind C-S-pgup 'pgup -l' bind C-S-pgdown 'pgdown -l' bind C-S-delete 'delete-eol' bind M-S-left 'bol -c' bind M-S-right 'eol -c' bind M-S-up 'pgup -c' bind M-S-down 'pgdown -c' bind M-S-delete 'delete-eol' bind C-M-S-left 'bol -c' bind C-M-S-right 'eol -c' bind C-M-S-up 'pgup -l' bind C-M-S-down 'pgdown -l' bind C-M-S-pgup 'pgup -l' bind C-M-S-pgdown 'pgdown -l' bind C-M-S-delete 'delete-eol' # Either of these 2 may be equivalent to Backspace, depending on the # terminal, so they're both bound to erase by default for portability. # On some terminals one may be Backspace and the other Ctrl+Backspace. bind ^H erase bind ^? erase bind ^C copy bind ^F search bind ^G 'search -n' # ^I == Tab bind ^L 'command line\ ' # ^M == Enter bind ^N open bind ^O 'command open\ ' bind ^Q 'quit -p' bind ^R 'command replace\ ' bind ^S 'save -p' bind ^T open bind ^V 'paste -c' bind ^W 'close -wq' bind ^X cut bind ^Y redo bind ^Z undo bind ^] tag bind ^[ unselect bind M-b bof bind M-c 'toggle -v case-sensitive-search' bind M-e eof bind M-f 'search -w' bind M-F 'search -wr' bind M-j wrap-paragraph bind M-n 'msg -n' bind M-p 'msg -p' bind M-t 'insert -m "\t"' bind M-x command bind M-z suspend bind M-1 'view 1' bind M-2 'view 2' bind M-3 'view 3' bind M-4 'view 4' bind M-5 'view 5' bind M-6 'view 6' bind M-7 'view 7' bind M-8 'view 8' bind M-9 'view 9' bind M-0 'view last' bind M-, prev bind M-. next bind M-< wprev bind M-> wnext bind M-- 'shift -- -1' bind M-= 'shift +1' bind M-C-? 'erase-word -s' bind M-delete 'delete-word -s' bind M-\; command bind M-: command bind M-/ search bind M-? 'search -r' bind M-! 'move-tab left' bind M-@ 'move-tab right' bind F1 'run man dte' bind F3 'search -n' bind F4 'search -p' bind F5 refresh dte-1.9.1/config/binding/shift-select000066400000000000000000000002771354415143300175030ustar00rootroot00000000000000# This file is kept for backwards compatibility purposes. # It was used to bind Shift+move selection keys, until they were # integrated into the editor as a built-in feature (e.g `left -c`). dte-1.9.1/config/color/000077500000000000000000000000001354415143300146645ustar00rootroot00000000000000dte-1.9.1/config/color/darkgray000066400000000000000000000017531354415143300164210ustar00rootroot00000000000000# Reset hi hi default 250 235 hi nontext 5/3/0 keep hi noline 234 234 hi wserror default 5/5/3 hi selection keep 238 keep hi linenumber 250 237 hi statusline 252 239 hi errormsg 5/3/0 hi infomsg 0/4/5 hi tabbar 250 237 hi activetab 255 hi inactivetab 250 237 hi text hi code hi ident hi keyword 5/5/4 hi type 0/4/5 hi label 5/5/2 hi notice 5/3/0 hi numeric 5/2/5 hi constant 5/2/5 hi char 5/2/5 hi string 3/3/2 hi special 3/5/2 hi comment 242 hi error 232 216 hi variable 0/4/5 hi c.preproc 0/3/5 hi c.preproccond 27 hi css.id 0/2/5 hi css.class 0/3/5 hi css.pseudoclass 5/2/5 hi css.pseudoelement 0/5/0 hi diff.header 255 hi diff.chunk 250 239 hi diff.added 0/3/5 hi diff.removed 5/1/1 hi diff.context keep keep hi gitcommit.added 0/3/5 hi gitcommit.modified 202 hi gitcommit.untracked 202 hi gitcommit.newfile 0/4/0 hi go.builtin 0/3/5 hi html.tag 3/5/2 hi html.attr 0/3/5 hi javascript.regexp 5/2/5 hi make.builtin 5/5/4 hi sh.builtin 5/5/4 hi xml.tag 3/5/2 hi xml.attr 0/3/5 hi ini.section bold dte-1.9.1/config/color/default000066400000000000000000000020561354415143300162360ustar00rootroot00000000000000# This is the color scheme that gets loaded by default when the editor # starts. It only uses the basic color palette, in order to maximize # compatibility and remain suitable for both light and dark terminals. hi hi linenumber gray darkgray hi selection default blue hi text hi code hi ident hi keyword bold hi type green hi comment darkgray hi numeric blue hi string yellow hi variable bold yellow hi constant bold cyan hi special magenta hi char magenta hi label bold red hi notice bold red hi error black yellow hi tex.macro bold yellow hi ini.section bold hi make.builtin bold magenta hi make.special-target green hi markdown.heading bold magenta hi markdown.emphasis italic cyan hi markdown.strong-emphasis bold cyan hi markdown.code-block yellow hi markdown.code-span yellow hi markdown.block-quote green hi markdown.reference bold blue hi markdown.url blue hi html.tag bold magenta hi html.attr magenta hi xml.tag bold magenta hi xml.attr magenta hi diff.header cyan hi diff.chunk magenta hi diff.added bold green hi diff.removed bold red hi zig.builtin cyan dte-1.9.1/config/color/reset000066400000000000000000000005311354415143300157300ustar00rootroot00000000000000# Reset for terminals that support at least 8 colors hi default hi nontext blue keep hi noline blue hi wserror default yellow hi selection keep gray keep hi currentline keep keep keep hi linenumber hi statusline black gray hi commandline hi errormsg bold red hi infomsg bold blue hi tabbar black gray hi activetab bold hi inactivetab black gray dte-1.9.1/config/color/reset-basic000066400000000000000000000005201354415143300170050ustar00rootroot00000000000000# Reset for terminals that support basic attributes but not colors (e.g. vt220) hi default hi nontext reverse hi noline hi wserror reverse hi selection reverse hi currentline keep keep keep hi linenumber reverse hi statusline reverse hi commandline hi errormsg bold hi infomsg hi tabbar reverse hi activetab bold hi inactivetab reverse dte-1.9.1/config/compiler/000077500000000000000000000000001354415143300153605ustar00rootroot00000000000000dte-1.9.1/config/compiler/gcc000066400000000000000000000021131354415143300160340ustar00rootroot00000000000000errorfmt -i gcc 'error: \(Each undeclared identifier is reported only once' errorfmt -i gcc 'error: for each function it appears in.\)' errorfmt -i gcc '^([^:]+):([0-9]+): (error|warning): \(near ' errorfmt -i gcc '^([^:]+):([0-9]+):([0-9]+):$' errorfmt gcc '^([^:]+):([0-9]+):([0-9]+): (.*)' file line column message errorfmt gcc '^([^:]+):([0-9]+): (.*)' file line message errorfmt gcc '^.* at (.+):([0-9]+):$' file line errorfmt gcc " *inlined from '.*' at (.*):([0-9]+):" file line errorfmt -i gcc '^In file included from (.+):([0-9]+)[,:]' errorfmt -i gcc '^ +from (.+):([0-9]+)[,:]' errorfmt -i gcc '^([^:]+): (In function .*:)$' errorfmt -i gcc "^In function '.*',$" errorfmt -i gcc '^([^:]+): At top level:$' errorfmt -i gcc '^cc1: (all |some )?warnings being treated as errors$' errorfmt -i gcc '^make: \*\*\* \[.*\] Error [0-9]+$' errorfmt -i gcc '^make: \*\*\* Waiting for unfinished jobs' errorfmt -i gcc '^make: \*\*\* wait: No child processes. Stop' errorfmt -i gcc '^collect2: ld returned [0-9]+ exit status$' errorfmt -i gcc '^[ \t]' errorfmt -i gcc '^[0-9]+ warnings? generated.$' dte-1.9.1/config/compiler/go000066400000000000000000000002161354415143300157070ustar00rootroot00000000000000errorfmt go '^(.+):([0-9]+):([0-9]+): (.*)' file line column message errorfmt go '^(.+):([0-9]+): (.*)' file line message errorfmt go -i '^#' dte-1.9.1/config/rc000066400000000000000000000044401354415143300140770ustar00rootroot00000000000000include -b binding/default include -b color/default include -b compiler/gcc include -b compiler/go alias builtin 'include -b' alias help 'run man dte' alias make 'compile gcc make' alias man 'run man' alias read 'pipe-from cat' alias reverse-lines 'filter tac' alias sort-lines 'filter sh -c "LC_COLLATE=C sort"' alias split-words 'replace -g "[ \t]+" "\n"' alias trim-lines 'replace "[ \t]+$" ""' alias xsel 'pipe-to xsel -b' errorfmt gitgrep '^([^:]+):([0-9]+):' file line alias git-grep 'compile -1s gitgrep git grep -n' # Remove possible 'F' from $LESS so that less will always wait # for keypress and "run" can be used without "-p". # ('R' = allow ANSI colors, 'S' = chop long lines) setenv LESS RS ft -f dte '/\.?dte/(config/)?(rc|((syntax|color|binding|compiler)/.*))$' ft -f mail '/tmpmsg-.*\.txt$|pico\.[0-9]+|mutt(ng)?-*-\w+|mutt[a-zA-Z0-9_-]{6}' # Don't remember cursor position for git temporary files option gitcommit,gitrebase file-history false option -r '/\.git/.*\.diff$' file-history false # Try to detect indent settings based on existing file contents option c,css,d,java,javascript,lua,nginx,php,sh,vala detect-indent 2,4,8 # Scan for braces to determine next auto-ident size option c,css,d,go,java,javascript,lua,nginx,php,sh,vala brace-indent true # Override indent options for Makefiles, since they almost always require tabs option make indent-width 8 expand-tab false emulate-tab false # Use space indents in YAML files, since the language spec doesn't allow tabs option yaml \ expand-tab true \ emulate-tab true \ ws-error tab-indent,tab-after-indent,trailing,special # Patterns used to detect indent size when "auto-indent" option is enabled option c indent-regex '^[ \t]*(case|default|do|else|for|if|while)\b' option java indent-regex '^[ \t]*(case|default|do|else|for|if|while)\b' option lua indent-regex '(then|do|repeat|else) *$|function *[A-Za-z0-9_.:]* *\([A-Za-z0-9_, ]*\) *$' option php indent-regex '^[ \t]*(case|default|do|else|for|foreach|if|while)\b' option python indent-regex ':[ \t]*(#.*)?$' option sh indent-regex '(^|;)[ \t]*(do|else|then)\b' # Don't highlight whitespace errors in diff and mail files option diff,mail ws-error 0 # Aliases for renamed built-in commands (for backwards compatibility) alias format-paragraph wrap-paragraph alias pass-through pipe-from dte-1.9.1/config/syntax/000077500000000000000000000000001354415143300150745ustar00rootroot00000000000000dte-1.9.1/config/syntax/awk000066400000000000000000000040051354415143300156000ustar00rootroot00000000000000# Escapes common to string and regexp syntax .awk-esc state special char 0-3 oct1 char 4-7 oct2 char x hex0 # Anything but \n char -n "\n" END error # Don't eat \n noeat END state oct1 special char 0-7 oct2 noeat END state oct2 special char 0-7 END special noeat END state hex0 special char 0-9a-fA-F hex1 noeat END state hex1 special char 0-9a-fA-F hex2 noeat END # "\xaff" is an error but "\xafg" is not state hex2 special char 0-9a-fA-F END error noeat END syntax awk state code char \" string char / regexp char # comment char -b a-zA-Z_ ident eat this state string char \\ dqescape char \" code string # Avoid highlighting rest of the file again char "\n" code eat this state regexp char \\ regexpescape char / code regexp # Avoid highlighting rest of the file again char "\n" code eat this state dqescape special char abfnrtv\\\" string special noeat .awk-esc:string state regexpescape special char abfnrtv.[({*+?|^/\\\$ regexp special noeat .awk-esc:regexp state comment char "\n" code eat this state ident char -b a-zA-Z0-9_ this inlist keyword code inlist builtin code inlist pattern code inlist variable code noeat code # gawk reserved words are separated by an empty line list keyword \ break continue delete do else exit for function getline if next \ print printf return while \ \ func nextfile list builtin \ atan2 close cos exp fflush gsub index int length log match rand sin \ split sprintf sqrt srand sub substr system \ \ and asort bindtextdomain compl dcgettext gensub lshift mktime or \ rshift strftime strtonum systime tolower toupper xor list pattern BEGIN END list variable \ ARGC ARGV FILENAME FNR FS NF NR OFMT OFS ORS RLENGTH RS RSTART SUBSEP \ \ ARGIND BINMODE CONVFMT ENVIRON ERRNO FIELDWIDTHS IGNORECASE LINT \ PROCINFO RLENGTH RT TEXTDOMAIN default string regexp default keyword pattern dte-1.9.1/config/syntax/c000066400000000000000000000167611354415143300152540ustar00rootroot00000000000000syntax .c-esc # TODO: C11 Unicode escapes (e.g. "\u00ff" and "\U00ff99cc") state start special char "abfnrtv'\\\"" END special char 0-3 oct1 char 4-7 oct2 char x hex0 # Anything but \n char -n "\n" END error # Don't eat \n noeat END state oct1 special char 0-7 oct2 noeat END state oct2 special char 0-7 END special noeat END state hex0 special char 0-9a-fA-F hex1 noeat END state hex1 special char 0-9a-fA-F hex2 noeat END # In strings "\xaff" is an error but "\xafg" is not. # In chars both are errors. state hex2 special char 0-9a-fA-F END error noeat END syntax .c-string state string char '"' END string # Avoid highlighting rest of the file again char "\n" END error char "\\" .c-esc:this eat this syntax .c-char state char char "'\n" END error char \\ .c-esc:char-end eat char-end state char-end char char "'" END char eat END error syntax .c-comment state comment char '*' star eat this state star comment char / END comment noeat comment syntax .cpp-comment state comment char "\n" END char \\ backslash eat this state backslash comment # Multi-line comment? char "\n" comment noeat comment syntax c state start code char " \t" this char # preproc char -b a-z_ ident-label char -b A-Z ident-upper-label noeat c state preproc char " \t" this char -b a-zA-Z_ preproc-ident noeat preproc-misc state preproc-ident preproc char -b a-zA-Z0-9_ this bufis 'include' preproc-include inlist preproccond preproccond noeat preproc-misc state preproccond # Recolor to start of the line recolor preproccond 100 noeat preproc-misc state preproc-misc preproc char "\n" start char "\\" preproc-cont char \" .c-string:this char \' .c-char:this str '/*' .c-comment:this str '//' .cpp-comment:start eat this state preproc-include preproc char " \t" this char "\n" start char "\\" preproc-cont char '"' local-include char '<' system-include # Not an error: #include DEFINE noeat preproc-misc state preproc-cont preproc char "\n" preproc-misc noeat preproc-misc state local-include string char '"' end-include string char "\n" start eat this state system-include string char '>' end-include string char "\n" start eat this state end-include preproc char "\n" start char " \t" this str '/*' .c-comment:this str '//' .cpp-comment:start eat preproc-error state preproc-error error char "\n" start eat this state c code char -b a-z_ ident char -b A-Z ident-upper char 0 zero char 1-9 dec char . dot char \" .c-string:this char \' .c-char:this char "\n" start char ';' semicolon str '/*' .c-comment:this str '//' .cpp-comment:start eat this state semicolon code char " \t" this char -b a-zA-Z_ ident-label noeat c state ident-label ident char -b a-zA-Z0-9_ this char -b : label noeat -b ident state label recolor label noeat c state ident char -b a-zA-Z0-9_ this inlist keyword c inlist c11-keyword c inlist type c inlist common-typedef c inlist posix-type c inlist constant c inlist cpp-keyword c noeat c state ident-upper ident char -b a-z class-name char -b A-Z0-9_ ident noeat c state class-name recolor class-name char a-zA-Z0-9_ this noeat c state ident-upper-label ident char -b a-z class-name-label char -b A-Z0-9_ ident-label char -b : label noeat c state class-name-label class-name char -b a-zA-Z0-9_ this char -b : label noeat -b class-name state zero numeric char xX hex char 0-7 oct char . float noeat check-suffix state oct numeric char 0-7 this noeat check-suffix state dec numeric char 0-9 this char eE exp char . float noeat check-suffix state hex numeric char 0-9a-fA-F this noeat check-suffix state check-suffix error char -b a-zA-Z0-9_ this inlist numeric-suffix c numeric noeat c state dot numeric char 0-9 float recolor code 1 noeat c state float numeric char 0-9 this char eE exp char fFlL float-suffix char a-zA-Z0-9_ error-ident noeat c state float-suffix numeric char a-zA-Z0-9_ error-ident noeat c state exp numeric char +- exp-digit char 0-9 exp-digit char a-zA-Z0-9_ error-ident noeat c state exp-digit numeric char 0-9 this char a-zA-Z0-9_ error-ident noeat c state error-ident error char a-zA-Z0-9_ this noeat c list keyword \ if else for while do continue switch case break default return goto \ asm const static inline register extern volatile auto restrict sizeof \ typedef list c11-keyword \ _Alignas _Alignof _Noreturn \ alignas alignof noreturn list type \ char short int long float double void signed unsigned enum struct union \ bool size_t va_list jmp_buf FILE _Bool _Complex _Imaginary \ clock_t div_t double_t fenv_t fexcept_t float_t fpos_t imaxdiv_t ldiv_t \ lldiv_t mbstate_t ptrdiff_t sig_atomic_t time_t wchar_t wctrans_t wint_t \ int8_t uint8_t int_fast8_t uint_fast8_t int_least8_t uint_least8_t \ int16_t uint16_t int_fast16_t uint_fast16_t int_least16_t uint_least16_t \ int32_t uint32_t int_fast32_t uint_fast32_t int_least32_t uint_least32_t \ int64_t uint64_t int_fast64_t uint_fast64_t int_least64_t uint_least64_t \ intptr_t uintptr_t intmax_t uintmax_t __int128 char32_t char16_t char8_t list common-typedef \ u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 s8 s16 s32 s64 s128 \ uchar ushort uint ulong ullong u_char u_short u_int u_long u_llong list posix-type \ blkcnt_t blksize_t clockid_t dev_t fsblkcnt_t fsfilcnt_t gid_t id_t \ ino_t key_t mode_t nlink_t off_t pid_t regex_t regmatch_t regoff_t \ siginfo_t sigjmp_buf sigset_t ssize_t suseconds_t timer_t uid_t DIR \ trace_attr_t trace_event_id_t trace_event_set_t trace_id_t \ pthread_t pthread_attr_t pthread_barrier_t pthread_barrierattr_t \ pthread_cond_t pthread_condattr_t pthread_key_t pthread_mutex_t \ pthread_mutexattr_t pthread_once_t pthread_rwlock_t \ pthread_rwlockattr_t pthread_spinlock_t list constant \ NULL stdin stdout stderr false true \ E2BIG EACCES EADDRINUSE EADDRNOTAVAIL EAFNOSUPPORT EAGAIN EALREADY \ EBADF EBADMSG EBUSY ECANCELED ECHILD ECONNABORTED ECONNREFUSED \ ECONNRESET EDEADLK EDESTADDRREQ EDOM EDQUOT EEXIST EFAULT EFBIG \ EHOSTUNREACH EIDRM EILSEQ EINPROGRESS EINTR EINVAL EIO EISCONN \ EISDIR ELOOP EMFILE EMLINK EMSGSIZE EMULTIHOP ENAMETOOLONG ENETDOWN \ ENETRESET ENETUNREACH ENFILE ENOBUFS ENODATA ENODEV ENOENT ENOEXEC \ ENOLCK ENOLINK ENOMEM ENOMSG ENOPROTOOPT ENOSPC ENOSR ENOSTR ENOSYS \ ENOTCONN ENOTDIR ENOTEMPTY ENOTRECOVERABLE ENOTSOCK ENOTSUP ENOTTY \ ENXIO EOPNOTSUPP EOVERFLOW EOWNERDEAD EPERM EPIPE EPROTO \ EPROTONOSUPPORT EPROTOTYPE ERANGE EROFS ESPIPE ESRCH ESTALE ETIME \ ETIMEDOUT ETXTBSY EWOULDBLOCK EXDEV ENOTBLK list preproccond \ elif else endif if ifdef ifndef list -i numeric-suffix \ ULL UL U LLU LU LL L list cpp-keyword \ catch class const_cast delete dynamic_cast explicit friend \ mutable namespace new operator private protected public \ reinterpret_cast static_cast template this throw try typeid \ typeof typename using virtual default ident class-name default keyword c11-keyword cpp-keyword default preproc preproccond default type posix-type dte-1.9.1/config/syntax/config000066400000000000000000000003541354415143300162660ustar00rootroot00000000000000# This is a generic syntax that just highlights shell-style comments, # as found in many simple, line-based config files in /etc. syntax config state code char # comment eat this state comment char "\n" code eat this dte-1.9.1/config/syntax/css000066400000000000000000000450401354415143300156120ustar00rootroot00000000000000syntax .css-comment state comment char "*" star eat this state star comment char / END comment noeat comment # Eat erroneous statement; stop at ; or \n syntax .css-statementerror state error char ";\n" END code eat this # Eat erroneous selector; stop at whitespace or { syntax .css-selectorerror state error char -n " \t\n\r{" this noeat END # Hex escape: [0-9a-fA-F]{1,6} # One hex digit already eaten # Can be terminated with whitespace character that is skipped syntax .css-hex state hex1 special char 0-9a-fA-F hex2 noeat ws state hex2 special char 0-9a-fA-F hex3 noeat ws state hex3 special char 0-9a-fA-F hex4 noeat ws state hex4 special char 0-9a-fA-F hex5 noeat ws state hex5 special char 0-9a-fA-F ws special noeat ws state ws # Whitespace is always skipped, even if there are 6 hex digits! char "\t\n\r " END special noeat END # Double quote syntax .css-dq state string char \" END string char \\ esc char "\n" END error eat this state esc special # \\ + \n -> continue to next line char "\n\\\"" string special char 0-9a-fA-F .css-hex:string eat string error # Single quote syntax .css-sq state string char \' END string char \\ esc char "\n" END error eat this state esc special # \\ + \n -> continue to next line char "\n\\'" string special char 0-9a-fA-F .css-hex:string eat string error syntax .css-ident state ident char "a-zA-Z0-9\xa1-\xff_-" this noeat END # Attribute selector [x=y], [x|=y], [x~=y] syntax .css-attributeselector state attributeselector code char -b a-zA-Z attribute noeat close state attribute char -b a-zA-Z this char "|~" op char = value code noeat close state op code char = value code eat END error state value char \" .css-dq:close char \' .css-sq:close char a-zA-Z_- .css-ident:close noeat close state close char ] END code eat END error # url(...) syntax .css-url state url char \" .css-dq:close char \' .css-sq:close noeat plain state plain code char ) END url char "\n" END error eat this state close char ) END url eat END error # List of words syntax .css-medialist state list code char "\t\n " this char -b a-zA-Z0-9_- name noeat END state name code char -b a-zA-Z0-9_ this inlist mediatype next noeat next state next code char "\t\n " this char , must noeat END state must code char "\t\n " this char -b a-zA-Z0-9_ name eat END error list -i mediatype \ all braille embossed handheld print projection screen speech tty tv syntax .css-import state import atkeyword char "\t\n " arg noeat error state arg code char "\t\n " this char \" .css-dq:medialist char \' .css-sq:medialist str -i "url(" .css-url:medialist noeat error state medialist noeat .css-medialist:mediaend state mediaend char \; END code noeat error state error noeat .css-statementerror:END syntax .css-namespace state namespace atkeyword char "\t\n " arg1 noeat error state arg1 code char "\t\n " this str -i "url(" .css-url:namespace-end char a-zA-Z prefix noeat arg2 state prefix code char a-zA-Z0-9- this char "\t\n " arg2 noeat error state arg2 code char "\t\n " this char \" .css-dq:namespace-end char \' .css-sq:namespace-end str -i "url(" .css-url:namespace-end noeat error state namespace-end char "\t\n " this char \; END code noeat error state error noeat .css-statementerror:END syntax css state verybeginning # These 9 chars exactly + double quote str -i '@charset ' charset noeat start state charset atkeyword char \" .css-dq:atend eat .css-statementerror:start state atend code char "\t\n " this char \; start eat .css-statementerror:start state start code char " \t\n" this char -b a-zA-Z maybe-element char . class char # id char -b : pseudo char [ .css-attributeselector:this char { block char 0-9_- .css-selectorerror:this str "/*" .css-comment:this char -b @ atrule eat this state maybe-element char -b a-zA-Z0-9 this inlist element start tag char _- .css-selectorerror:start noeat start state pseudo code char -b a-zA-Z0-9:- this inlist pseudoclass start inlist exprpseudo nth pseudoclass inlist pseudoelement start inlist pagepseudoclass start pseudoclass bufis -i :lang complexpseudo bufis -i :not complexpseudo noeat start # lang(indetifier) { ... } # not(table) { ... } state complexpseudo pseudoclass char ( complexexpr eat .css-selectorerror:start # nth-child(odd|even|an+b) etc. state nth char ( nthexpr eat .css-selectorerror:start state nthexpr expr char a-z0-9+- this char ) start expr eat .css-selectorerror:start state complexexpr expr char ) start expr char -n "\n" this noeat start state class char a-zA-Z0-9_- this noeat start state id char a-zA-Z0-9_- this noeat start state atrule code char -b a-zA-Z0-9_- this # Selectors inlist atkeyword start # @import { URI | string } [ media type, ...]; bufis -i "@import" .css-import:start atkeyword # @namespace [prefix] { URI | string }; bufis -i "@namespace" .css-namespace:start atkeyword # FIXME: @media not allowed inside @media bufis -i @media mediatypes atkeyword noeat start state mediatypes noeat .css-medialist:mediablock state mediablock code char "\t\n " this char { start eat start error state block code char " \t\n;" this char -b a-zA-Z- property char 0-9_- property-error char } start str "/*" .css-comment:this eat this state property code char -b a-zA-Z- this char 0-9_ property-error inlist property property-end inlist fontfaceproperty property-end property # Could be unknown property noeat property-end state property-end code char : values char " \t\n" this char ";" block char } start eat property-error state values code char " \t\n" this char 0-9 int char -- - minus char # hexcolor char ";" block char \" .css-dq:this char \' .css-sq:this str -i "url(" .css-url:this char -b a-zA-Z_ value char } start str "/*" .css-comment:this eat this state minus numeric char 0-9 int char . float noeat values state int numeric char 0-9 this char . float char -b a-zA-Z% unit noeat values state float numeric char 0-9 this char -b a-zA-Z% unit noeat values state unit error char -b a-zA-Z% this inlist unit values numeric char ";" block char } start eat value-error state value code char -b a-zA-Z0-9_- this inlist value values inlist color values inlist func func value noeat values # FIXME: length must be 3 or 6 state hexcolor color char 0-9a-fA-F this char g-zG-Z_- value-error noeat values state func code char " \t" this char ( params eat value-error state params code char ) values char ";" block char } start eat this state property-error error char a-zA-Z0-9_- this noeat property state value-error error char a-zA-Z0-9_- this noeat values list -i element \ a abbr acronym address area b base bdo big blockquote body br \ button caption cite code col colgroup dd del dfn div dl dt em \ fieldset form h1 h2 h3 h4 h5 h6 head hr html i iframe img input \ ins kbd label legend li link map meta noscript object ol optgroup \ option p param pre q samp script select small span strong style \ sub sup table tbody td textarea tfoot th thead title tr tt ul var \ \ article aside audio canvas command datalist details embed figure \ footer header hgroup keygen main mark meter nav output progress ruby \ section time video wbr # https://www.w3.org/Style/CSS/all-properties.en.json list -i property \ align-content align-items alignment-baseline align-self all \ animation animation-delay animation-direction animation-duration \ animation-fill-mode animation-iteration-count animation-name \ animation-play-state animation-timing-function appearance azimuth \ background background-attachment background-blend-mode \ background-clip background-color background-image \ background-image-transform background-origin background-position \ background-repeat background-size baseline-shift block-size \ block-step block-step-align block-step-insert block-step-round \ block-step-size bookmark-label bookmark-level bookmark-state border \ border-block border-block-color border-block-end \ border-block-end-color border-block-end-style border-block-end-width \ border-block-start border-block-start-color border-block-start-style \ border-block-start-width border-block-style border-block-width \ border-bottom border-bottom-color border-bottom-left-radius \ border-bottom-right-radius border-bottom-style border-bottom-width \ border-boundary border-collapse border-color border-image \ border-image-outset border-image-repeat border-image-slice \ border-image-source border-image-transform border-image-width \ border-inline border-inline-color border-inline-end \ border-inline-end-color border-inline-end-style \ border-inline-end-width border-inline-start \ border-inline-start-color border-inline-start-style \ border-inline-start-width border-inline-style border-inline-width \ border-left border-left-color border-left-style border-left-width \ border-radius border-right border-right-color border-right-style \ border-right-width border-spacing border-style border-top \ border-top-color border-top-left-radius border-top-right-radius \ border-top-style border-top-width border-width bottom \ box-decoration-break box-shadow box-sizing box-snap break-after \ break-before break-inside caption-side caret caret-animation \ caret-color caret-shape chains clear clip clip-path clip-rule color \ color-adjust color-interpolation-filters column-count column-fill \ column-gap column-rule column-rule-color column-rule-style \ column-rule-width columns column-span column-width contain content \ continue counter-increment counter-reset counter-set cue cue-after \ cue-before cursor direction display dominant-baseline elevation \ empty-cells fill fill-break fill-color fill-image fill-opacity \ fill-origin fill-position fill-repeat fill-rule fill-size filter \ flex flex-basis flex-direction flex-flow flex-grow flex-shrink \ flex-wrap float float-defer float-offset float-reference flood-color \ flood-opacity flow flow-from flow-into font font-family \ font-feature-settings font-kerning font-language-override \ font-max-size font-min-size font-optical-sizing font-palette \ font-presentation font-size font-size-adjust font-stretch font-style \ font-synthesis font-variant font-variant-alternates \ font-variant-caps font-variant-east-asian font-variant-ligatures \ font-variant-numeric font-variant-position font-variation-settings \ font-weight footnote-display footnote-policy gap \ glyph-orientation-vertical grid grid-area grid-auto-columns \ grid-auto-flow grid-auto-rows grid-column grid-column-end \ grid-column-gap grid-column-start grid-gap grid-row grid-row-end \ grid-row-gap grid-row-start grid-template grid-template-areas \ grid-template-columns grid-template-rows hanging-punctuation height \ hyphenate-character hyphenate-limit-chars hyphenate-limit-last \ hyphenate-limit-lines hyphenate-limit-zone hyphens image-orientation \ image-resolution initial-letter initial-letter-align \ initial-letter-wrap inline-size inset inset-block inset-block-end \ inset-block-start inset-inline inset-inline-end inset-inline-start \ isolation justify-content justify-items justify-self left \ letter-spacing lighting-color line-break line-grid line-height \ line-height-step line-snap list-style list-style-image \ list-style-position list-style-type margin margin-block \ margin-block-end margin-block-start margin-bottom margin-inline \ margin-inline-end margin-inline-start margin-left margin-right \ margin-top marker marker-end marker-knockout-left \ marker-knockout-right marker-mid marker-pattern marker-segment \ marker-side marker-start marquee-direction marquee-loop \ marquee-speed marquee-style mask mask-border mask-border-mode \ mask-border-outset mask-border-repeat mask-border-slice \ mask-border-source mask-border-width mask-clip mask-composite \ mask-image mask-mode mask-origin mask-position mask-repeat mask-size \ mask-type max-block-size max-height max-inline-size max-lines \ max-width min-block-size min-height min-inline-size min-width \ mix-blend-mode nav-down nav-left nav-right nav-up object-fit \ object-position offset offset-after offset-anchor offset-before \ offset-distance offset-end offset-path offset-position offset-rotate \ offset-start opacity order orphans outline outline-color \ outline-offset outline-style outline-width overflow overflow-style \ overflow-wrap overflow-x overflow-y padding padding-block \ padding-block-end padding-block-start padding-bottom padding-inline \ padding-inline-end padding-inline-start padding-left padding-right \ padding-top page page-break-after page-break-before \ page-break-inside pause pause-after pause-before pitch pitch-range \ place-content place-items place-self play-during position \ presentation-level quotes region-fragment resize rest rest-after \ rest-before richness right rotation rotation-point row-gap \ ruby-align ruby-merge ruby-position running scrollbar-gutter \ scroll-behavior scroll-padding scroll-padding-block \ scroll-padding-block-end scroll-padding-block-start \ scroll-padding-bottom scroll-padding-inline \ scroll-padding-inline-end scroll-padding-inline-start \ scroll-padding-left scroll-padding-right scroll-padding-top \ scroll-snap-align scroll-snap-margin scroll-snap-margin-block \ scroll-snap-margin-block-end scroll-snap-margin-block-start \ scroll-snap-margin-bottom scroll-snap-margin-inline \ scroll-snap-margin-inline-end scroll-snap-margin-inline-start \ scroll-snap-margin-left scroll-snap-margin-right \ scroll-snap-margin-top scroll-snap-stop scroll-snap-type \ shape-image-threshold shape-inside shape-margin shape-outside size \ speak speak-as speak-header speak-numeral speak-punctuation \ speech-rate stress string-set stroke stroke-align stroke-alignment \ stroke-break stroke-color stroke-dashadjust stroke-dasharray \ stroke-dashcorner stroke-dash-corner stroke-dash-justify \ stroke-dashoffset stroke-image stroke-linecap stroke-linejoin \ stroke-miterlimit stroke-opacity stroke-origin stroke-position \ stroke-repeat stroke-size stroke-width table-layout tab-size \ text-align text-align-all text-align-last text-combine-upright \ text-decoration text-decoration-color text-decoration-line \ text-decoration-skip text-decoration-style text-emphasis \ text-emphasis-color text-emphasis-position text-emphasis-style \ text-indent text-justify text-orientation text-overflow text-shadow \ text-space-collapse text-space-trim text-spacing text-transform \ text-underline-position text-wrap top transform transform-box \ transform-origin transition transition-delay transition-duration \ transition-property transition-timing-function unicode-bidi \ user-select vertical-align visibility voice-balance voice-duration \ voice-family voice-pitch voice-range voice-rate voice-stress \ voice-volume volume white-space widows width will-change word-break \ word-spacing word-wrap wrap-after wrap-before wrap-flow wrap-inside \ wrap-through writing-mode z-index list -i value \ absolute always armenian auto avoid baseline bidi-override blink \ block bold bolder both bottom capitalize center circle \ cjk-ideographic collapse compact condensed crop cross crosshair \ dashed decimal decimal-leading-zero default disc dotted double \ e-resize embed expanded extra-condensed extra-expanded fixed \ georgian groove hebrew help hidden hide hiragana hiragana-iroha \ inline inline-table inset inside italic justify katakana \ katakana-iroha landscape large larger left lighter line-through \ list-item lower-alpha lower-greek lower-latin lower-roman lowercase \ ltr marker medium middle move n-resize narrower ne-resize no-repeat \ none normal nowrap nw-resize oblique outset outside overline pointer \ portrait pre relative repeat repeat-x repeat-y ridge right rtl run-in \ s-resize scroll se-resize semi-condensed semi-expanded separate show \ small small-caps smaller solid square static sub super sw-resize \ table table-caption table-cell table-column table-column-group \ table-footer-group table-header-group table-row table-row-group text \ text-bottom text-top thick thin top ultra-condensed ultra-expanded \ underline upper-alpha upper-latin upper-roman uppercase visible \ w-resize wait wider x-large x-small xx-large xx-small list -i color \ aqua black blue fuchsia gray green lime maroon navy olive \ purple red silver teal white yellow # Simple pseudo-classes list -i pseudoclass \ :active :checked :disabled :empty :enabled :first-child \ :first-of-type :focus :hover :last-child :last-of-type :link \ :only-child :only-of-type :root :target :visited # nth-child(odd) etc. list -i exprpseudo :nth-child :nth-last-child :nth-last-of-type :nth-of-type # CSS1 and CSS2 pseudo-elements can be prefixed with single colon # CSS2.1 changed pseudo-elements start with a double colon # support only double colon for CSS3 pseudo-elements list -i pseudoelement \ :after :before :first-letter :first-line \ ::after ::before ::first-letter ::first-line \ ::selection # @page list -i pagepseudoclass \ :left :right :first # @font-face list -i fontfaceproperty \ font-family font-stretch font-style font-weight src unicode-range # %, distance, angle, time, frequency, resolution list -i unit \ % fr \ ch cm em ex in mm pc pt px rem vh vw vmin vmax \ deg grad rad turn \ ms s \ hz khz \ dpcm dpi dppx list -i func \ attr clip counter rect rgb # at-rules that work as selectors list -i atkeyword @page @font-face default keyword property default type class id pseudoclass pseudoelement attribute default special expr default constant value color url mediatype default special atkeyword dte-1.9.1/config/syntax/d000066400000000000000000000104531354415143300152450ustar00rootroot00000000000000syntax .d-esc state start special char "abfnrtv'\\\"" END special char 0-3 oct1 char 4-7 oct2 char x hex0 char -n "\n" END error noeat END state oct1 special char 0-7 oct2 noeat END state oct2 special char 0-7 END special noeat END state hex0 special char 0-9a-fA-F hex1 noeat END state hex1 special char 0-9a-fA-F hex2 noeat END # In strings "\xaff" is an error but "\xafg" is not. # In chars both are errors. state hex2 special char 0-9a-fA-F END error noeat END syntax .d-string state string char "\"" END string char "\n" END error char "\\" .d-esc:this eat this syntax .d-char state char char "'\n" END error char \\ .d-esc:char-end eat char-end state char-end char char "'" END char eat END error syntax .d-long-comment state comment char "*" star eat this state star comment char / END comment noeat comment syntax .d-line-comment state comment char "\n" END char \\ backslash eat this state backslash comment # Multi-line comment? char "\n" comment noeat comment syntax d state start code char " \t" this char -b a-z_ ident-label char -b A-Z ident-upper-label noeat code # TODO: Fix up to match the D syntax spec (originally copied from C) state code code char -b a-z_ ident char -b A-Z ident-upper char 0 zero char 1-9 dec char . dot char \" .d-string:this char \' .d-char:this char "\n" start char ';' semicolon str "/*" .d-long-comment:this str "//" .d-line-comment:start eat this state semicolon code char " \t" this char -b a-zA-Z_ ident-label noeat code state ident-label ident char -b a-zA-Z0-9_ this char -b : label noeat -b ident state label recolor label noeat code state ident char -b a-zA-Z0-9_ this inlist keyword code inlist deprecated-keyword code error inlist constant code inlist type code inlist special-token code noeat code state ident-upper ident char -b a-z class-name char -b A-Z0-9_ ident noeat code state class-name recolor class-name char a-zA-Z0-9_ this noeat code state ident-upper-label ident char -b a-z class-name-label char -b A-Z0-9_ ident-label char -b : label noeat code state class-name-label class-name char -b a-zA-Z0-9_ this char -b : label noeat -b class-name state zero numeric char xX hex char 0-7 oct char . float noeat code state oct numeric char 0-7 this noeat code state dec numeric char 0-9 this char eE exp char . float noeat code state hex numeric char 0-9a-fA-F this noeat code state dot numeric char 0-9 float recolor code 1 noeat code state float numeric char 0-9 this char eE exp char fFlL float-suffix char a-zA-Z0-9_ error-ident noeat code state float-suffix numeric char a-zA-Z0-9_ error-ident noeat code state exp numeric char +- exp-digit char 0-9 exp-digit char a-zA-Z0-9_ error-ident noeat code state exp-digit numeric char 0-9 this char a-zA-Z0-9_ error-ident noeat code state error-ident error char a-zA-Z0-9_ this noeat code list keyword \ abstract alias align asm assert auto body break case cast catch \ class const continue debug default deprecated do else enum export \ extern final finally for foreach foreach_reverse goto if immutable \ import in inout interface invariant is lazy macro mixin module new \ nothrow out override package pragma private protected public pure \ ref return scope shared static struct super switch synchronized \ template this throw try typeid typeof union unittest version while \ with __FILE__ __FILE_FULL_PATH__ __MODULE__ __LINE__ __FUNCTION__ \ __PRETTY_FUNCTION__ __gshared __traits __vector __parameters list deprecated-keyword \ delete typedef volatile list constant \ true false null list type \ bool byte ubyte short ushort int uint long ulong char wchar dchar \ float double real ifloat idouble ireal cfloat cdouble creal void \ cent ucent size_t ptrdiff_t function delegate # TODO: union/enum? list special-token \ __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__ default ident class-name default macro special-token dte-1.9.1/config/syntax/diff000066400000000000000000000012171354415143300157300ustar00rootroot00000000000000syntax diff state headers header str 'diff ' header str 'index ' header str 'new ' header str -- '--- ' header str '+++ ' header str '@@ ' chunk char # comment noeat text state header char "\n" headers eat this state chunk char "\n" diff chunk eat this state diff code char ' ' context char + added char -- - removed str '@@ ' chunk noeat headers state context char "\n" diff eat this state added char "\n" diff eat this state removed char "\n" diff eat this state comment char "\n" headers eat this state text char "\n" headers eat this dte-1.9.1/config/syntax/docker000066400000000000000000000010611354415143300162640ustar00rootroot00000000000000syntax docker list -i instruction \ ADD CMD COPY ENTRYPOINT ENV EXPOSE FROM MAINTAINER ONBUILD RUN \ USER VOLUME WORKDIR state start char -b a-zA-Z ident char # comment char -n " \t\n" bad eat this state ident char -b a-zA-Z this char -bn " \t\n" bad error inlist instruction arguments keyword noeat -b bad state arguments str "\\\n" this char "\n" start eat this state comment char "\n" start eat this state bad error recolor error char " \t" arguments char "\n" start eat this dte-1.9.1/config/syntax/dte000066400000000000000000000015531354415143300155770ustar00rootroot00000000000000syntax dte state start code char " \t" this char # comment noeat command state comment char "\n" start eat this state command code char "'" sq char '"' dq char "\n" start char "\\" escape char -b \$ variable char ";" this special eat this state sq string char "'" command string char "\n" start eat this state dq string char -b "\\" dq-escape char '"' command string char "\n" start eat this state dq-escape special char "abfnrtv'\\\"" dq special char x hex1 eat dq error state hex1 special char 0-9a-fA-F hex2 char '"' command error eat dq error state hex2 special char 0-9a-fA-F dq special char '"' command error eat dq error state escape special char "\n" command eat command special state variable char -b a-zA-Z_ this noeat command dte-1.9.1/config/syntax/gitcommit000066400000000000000000000014251354415143300170150ustar00rootroot00000000000000syntax gitcommit state start text char # comment-start noeat text state text char "\n" start eat this state comment-start comment char "\n" start char "\t" newfile comment str ' On branch ' branch comment str ' Changes to be committed:' comment added str ' Changed but not updated:' comment modified str ' Untracked files:' comment untracked eat comment state comment char "\n" start eat this state branch notice char "\n" start eat this # Label or new file, not known yet state newfile # Buffer anything but \n or : char "\n" start # Anything else but : (-n inverts bitmap) char -bn : this # Recolor buffered bytes to label recolor label noeat file state file char "\n" start eat this dte-1.9.1/config/syntax/gitrebase000066400000000000000000000013641354415143300167700ustar00rootroot00000000000000syntax gitrebase state start text char "\n" this char # comment char -b a-zA-Z0-9_ word eat error state word text char -b a-zA-Z0-9_ this inlist keyword separator1 bufis x exec keyword bufis exec exec keyword noeat error # FIXME: can be annoying state error char "\n" start eat this state separator1 code char " \t" space1 noeat error state space1 code char " \t" this noeat sha1 state sha1 constant char 0-9a-fA-F this char "\n" start char " \t" title noeat error state title text char "\n" start eat this state exec code char "\n" start eat this state comment char "\n" start eat this list keyword \ e f p r s edit fixup pick reword squash dte-1.9.1/config/syntax/go000066400000000000000000000070101354415143300154220ustar00rootroot00000000000000syntax .go-esc state special char 0-3 oct1 # >255 byte value char 4-7 END error char x 2left char u 4left char U 8left noeat short state oct1 special char 0-7 oct2 noeat short state oct2 special char 0-7 END special noeat short state 8left special char 0-9a-fA-F 7left noeat short state 7left special char 0-9a-fA-F 6left noeat short state 6left special char 0-9a-fA-F 5left noeat short state 5left special char 0-9a-fA-F 4left noeat short state 4left special char 0-9a-fA-F 3left noeat short state 3left special char 0-9a-fA-F 2left noeat short state 2left special char 0-9a-fA-F 1left noeat short state 1left special char 0-9a-fA-F END special noeat short state short special char "\x80-\xff" not-ascii # Any ASCII character but \n char -n "\n" END error # Don't eat \n noeat END # Eats (at least) one multibyte UTF-8 character state not-ascii error char "\x80-\xff" this noeat END syntax go state go code char -b A-Z exported char -b a-z_ ident char 0 zero char 1-9 dec char . dot char "\"" string char "'" char char ` raw-string str "//" cpp-comment str "/*" c-comment eat this state exported char -b a-zA-Z0-9_ this noeat go state ident char -b a-zA-Z0-9_ this inlist keyword go inlist type go inlist constant go inlist builtin go noeat go state zero numeric char xX hex char 0-7 oct char . float noeat check-imag state oct numeric char 0-7 this noeat check-suffix state dec numeric char 0-9 this char eE exp char . float noeat check-imag state hex numeric char 0-9a-fA-F this noeat check-suffix state check-suffix error char a-zA-Z0-9_ this noeat go state check-imag code char i check-suffix numeric noeat check-suffix state dot numeric char 0-9 float recolor code 1 noeat go state float numeric char 0-9 this char eE exp noeat check-imag state exp numeric char +- exp-digit char 0-9 exp-digit noeat check-imag state exp-digit numeric char 0-9 this noeat check-imag state string char "\"" go string char "\n" go char "\\" string-esc eat this state string-esc special # \' is illegal inside string literal char "abfnrtv\"\\" string special noeat .go-esc:string state char char "'" go error char "\\" char-esc char "\n" go # Assume multiple non-ASCII bytes are one UTF-8 character char "\x80-\xff" not-ascii eat char-end state char-esc special # \" is illegal inside character literal char "abfnrtv'\\" char-end special noeat .go-esc:char-end state not-ascii char char "\x80-\xff" this noeat char-end state char-end char char "'" go char eat go error state raw-string string char ` go string eat this state cpp-comment comment char "\n" go eat this state c-comment comment str "*/" go comment eat this list keyword \ break case chan const continue default defer else fallthrough \ for func go goto if import interface map package range return \ select struct switch type var list constant \ false true iota nil list builtin \ append cap close complex delete copy imag len \ make new panic print println real recover list type \ bool byte complex64 complex128 float32 float64 \ int8 int16 int32 int64 string uint8 uint16 uint32 uint64 \ int uint uintptr error rune default ident exported dte-1.9.1/config/syntax/html000066400000000000000000000074131354415143300157700ustar00rootroot00000000000000syntax .html-entity state entity char # hash char -b a-zA-Z name recolor error 1 char "\n" END eat END error state hash entity char xX x char 0-9 dec eat END error state x entity char 0-9a-fA-F hex eat END error state hex entity char 0-9a-fA-F this char ";" END entity eat END error state dec entity char 0-9 this char ";" END entity eat END error state name entity char -b a-zA-Z0-9 this inlist entity optional-semicolon noeat -b semicolon state optional-semicolon char ";" END entity noeat END state semicolon char ";" END entity recolor error eat END error # Generated by tools/get-html-entities.sh list entity \ aacute Aacute acirc Acirc acute aelig AElig agrave Agrave amp AMP \ aring Aring atilde Atilde auml Auml brvbar ccedil Ccedil cedil cent \ copy COPY curren deg divide eacute Eacute ecirc Ecirc egrave Egrave \ eth ETH euml Euml frac12 frac14 frac34 gt GT iacute Iacute icirc \ Icirc iexcl igrave Igrave iquest iuml Iuml laquo lt LT macr micro \ middot nbsp not ntilde Ntilde oacute Oacute ocirc Ocirc ograve \ Ograve ordf ordm oslash Oslash otilde Otilde ouml Ouml para plusmn \ pound quot QUOT raquo reg REG sect shy sup1 sup2 sup3 szlig thorn \ THORN times uacute Uacute ucirc Ucirc ugrave Ugrave uml uuml Uuml \ yacute Yacute yen yuml syntax html # TODO: Allow comments before doctype # TODO: Don't highlight tags inside RCDATA elements (e.g. style, script, etc.) state start text char " \t\n" this str " text comment char < text error eat this state text str " text comment eat this state tag-start tag char / close-tag char -b a-zA-Z0-9_ tag-name char " \t\n" this char > text tag eat this error state close-tag tag char -b a-zA-Z0-9_ close-tag-name char " \t\n" this eat text error state tag-name tag-unknown char -b a-zA-Z0-9_ this inlist tag attrs inlist tag-deprecated attrs noeat attrs state close-tag-name tag-unknown char -b a-zA-Z0-9_ this inlist tag close-tag-end inlist tag-deprecated close-tag-end noeat close-tag-end state close-tag-end tag char " \t\n" this char > text tag eat text error state attrs code char " \t\n" this char -b a-zA-Z attr-name char > text tag char / short-close eat this error state short-close tag char > text tag eat text error state attr-name attr char a-zA-Z:_- this char = attr-eq noeat attrs state attr-eq attr char \" dq char \' sq noeat attrs state dq string char \" attrs string char "\n" attrs char "&" .html-entity:this eat this state sq string char \' attrs string char "\n" attrs char "&" .html-entity:this eat this list -i tag \ a abbr acronym address area b base bdo big blockquote body br \ button caption cite code col colgroup dd del dfn div dl dt em \ fieldset form h1 h2 h3 h4 h5 h6 head hr html i iframe img input \ ins kbd label legend li link map meta noscript object ol optgroup \ option p param pre q samp script select small span strong style \ sub sup table tbody td textarea tfoot th thead title tr tt ul var \ \ article aside audio canvas command datalist details embed figure \ footer header hgroup keygen main mark meter nav output progress ruby \ section time video wbr list -i tag-deprecated \ acronym applet basefont big blink center dir font frame frameset \ isindex marquee menu noframes s strike tt u default code tag-unknown default error tag-deprecated default special entity dte-1.9.1/config/syntax/ini000066400000000000000000000017701354415143300156030ustar00rootroot00000000000000syntax ini list constant true false state line-start char " \t\n" this char [ section char '#;' comment char = value error noeat text state section ini.section char ] section-end ini.section char "\n" line-start error eat this state section-end char "\n" line-start char # comment char " \t" this eat this error state text char # comment char -b = key operator char -b -n "\n" text noeat line-start state key recolor variable recolor operator 1 noeat value state value char \' single-quote char \" double-quote char -b a-z this # FIXME: does this highlight constants with trailing characters? inlist constant this constant char # comment char "\n" line-start eat this state single-quote string char \' value string char "\n" line-start eat this state double-quote string char \" value string char "\n" line-start eat this state comment char "\n" line-start eat this dte-1.9.1/config/syntax/java000066400000000000000000000056061354415143300157470ustar00rootroot00000000000000syntax .java-esc state char-esc special char "bfnrt\\'\"" END special char 0 oct char u hex4 noeat short state oct special char 0-3 oct2 char 4-7 oct1 noeat END state oct2 special char 0-7 oct1 noeat END state oct1 special char 0-7 END special noeat END state hex4 special char 0-9a-fA-F hex3 noeat END state hex3 special char 0-9a-fA-F hex2 noeat END state hex2 special char 0-9a-fA-F hex1 noeat END state hex1 special char 0-9a-fA-F END special noeat END state short special char "\x80-\xff" not-ascii # Any ASCII character but \n char -n "\n" END error # Don't eat \n noeat END # Eats (at least) one multibyte UTF-8 character state not-ascii error char "\x80-\xff" this noeat END syntax java state code char -b a-zA-Z_ ident char 0 zero char 1-9 dec char . dot char \" string char \' char str "//" cpp-comment str "/*" c-comment eat this state cpp-comment comment char "\n" code eat this state c-comment comment str "*/" code comment eat this state ident char -b a-zA-Z0-9_ this inlist keyword code inlist type code inlist constant code noeat code state zero numeric char xX hex char 0-7 oct char . float noeat num-suffix state oct numeric char 0-7 this noeat num-suffix state dec numeric char 0-9 this char eE exp char . float noeat num-suffix state hex numeric char 0-9a-fA-F this noeat num-suffix state num-suffix char lLfFdD check-suffix numeric noeat check-suffix state float-suffix char fFdD check-suffix numeric noeat check-suffix state check-suffix error char a-zA-Z0-9_ this noeat code state dot numeric char 0-9 float recolor code 1 noeat code state float numeric char 0-9 this char eE exp noeat float-suffix state exp numeric char +- exp-digit char 0-9 exp-digit noeat float-suffix state exp-digit numeric char 0-9 this noeat float-suffix state string char \\ .java-esc:this char \" code string eat this state char char "\\" .java-esc:char-end char "\n" code char \' code error char "\x80-\xff" not-ascii eat char-end # Eats (at least) one multibyte UTF-8 character state not-ascii char char "\x80-\xff" this noeat char-end state char-end char char \' code char eat code error list keyword \ abstract assert boolean break byte case catch char class const \ continue default do double else enum extends final finally float \ for goto if implements import instanceof int interface long native \ new package private protected public return short static strictfp \ super switch synchronized this throw throws transient try void \ volatile while list type \ byte short int long float double boolean char list constant \ false true null dte-1.9.1/config/syntax/javascript000066400000000000000000000113731354415143300171720ustar00rootroot00000000000000syntax .js-esc state special char "'\"\\bfnrtv" END special char 0 3n char x 2left char u 4left noeat short state 4left special char 0-9a-fA-F 3left noeat short state 3left special char 0-9a-fA-F 2left noeat short state 2left special char 0-9a-fA-F 1left noeat short state 1left special char 0-9a-fA-F END special noeat short state 3n special char 0-9 2n noeat short state 2n special char 0-9 1n noeat short state 1n special char 0-9 END special noeat short state short special # Don't eat \n char -n "\n" END error noeat END syntax .js-regexp state regexp char "\\" regexp-esc char "[" regexp-char-list-start char "{" regexp-range char "/" regexp-flags regexp char "^$|()?:*+." this regexp-special char "\n" END error eat this state regexp-esc regexp-special char "\n" END error eat regexp regexp-special state regexp-char-list-start regexp-special char ^ regexp-char-list regexp-special char ] regexp regexp-special char "\n" END error eat regexp-char-list state regexp-char-list regexp char "-" this regexp-special char "\\" regexp-char-list-esc char ] regexp regexp-special char "\n" END error eat this state regexp-char-list-esc regexp-special char "\n" END error eat regexp-char-list regexp-special state regexp-range regexp-special char "0-9" this regexp char "," regexp-range-second char "}" regexp regexp-special char "\n" END error eat this error state regexp-range-second regexp-special char "0-9" this regexp char "}" regexp regexp-special char "\n" END error eat this error state regexp-flags regexp-special char gimuy this noeat END syntax javascript state first-line char " \t" this char # line-comment noeat code state code char -b a-zA-Z_ ident char 0 zero char 1-9 dec char . dot str "//" line-comment str "/*" long-comment char \' sq char \" dq char "(;:,=*/%&|!?+-" foo eat this # who knows where regexps are allowed? state foo code char " \t(;:,=*%&|!?+-" this str "//" line-comment str "/*" long-comment char / .js-regexp:code noeat code state zero numeric char xX hex char . float noeat check-suffix state dec numeric char 0-9 this char eE exp char . float noeat check-suffix state hex numeric char 0-9a-fA-F this noeat check-suffix state dot numeric char 0-9 float recolor code 1 noeat code state float numeric char 0-9 this char eE exp noeat check-suffix state exp numeric char +- exp-digit char 0-9 exp-digit noeat check-suffix state exp-digit numeric char 0-9 this noeat check-suffix state check-suffix error char a-zA-Z0-9_ this noeat code state ident char -b a-zA-Z0-9_ this inlist keyword code inlist reserved code keyword inlist type code inlist errortype code type inlist constant code inlist builtin-namespace code constant inlist function code builtin inlist message code builtin inlist global code builtin inlist member code builtin inlist deprecated code noeat code state line-comment comment char "\n" code eat this state long-comment comment str "*/" code comment eat this state sq string char \' code string char "\n" code char "\\" .js-esc:this eat this state dq string char \" code string char "\n" code char "\\" .js-esc:this eat this list keyword \ break case catch continue debugger default delete do else finally for \ function if in instanceof new return switch this throw try typeof var \ void while with list reserved \ class const enum export extends implements import interface let \ package private protected public static super yield list type \ Array Boolean Date Function Number Object RegExp String \ Map WeakMap Set WeakSet Symbol Promise Proxy ArrayBuffer DataView \ Float32Array Float64Array Int8Array Int16Array Int32Array \ Uint8Array Uint16Array Uint32Array Uint8ClampedArray # Non-constructible, built-in objects (used and referred to as "namespaces") list builtin-namespace \ Intl JSON Math Reflect WebAssembly list errortype \ Error EvalError RangeError ReferenceError SyntaxError TypeError URIError list constant \ arguments \ Infinity NaN false null true undefined list function \ decodeURI decodeURIComponent encodeURI encodeURIComponent eval \ isFinite isNaN parseFloat parseInt # builtin? list message \ alert confirm prompt status list global \ self window top parent list member \ document event location list deprecated \ escape unescape default special regexp default special regexp-special dte-1.9.1/config/syntax/lua000066400000000000000000000077051354415143300156110ustar00rootroot00000000000000syntax .lua-long-string state body string char ] end-match eat this state end-match string heredocend end noeat body state end string char ] END string noeat body syntax .lua-long-comment state body comment char ] end-match eat this state end-match comment heredocend end noeat body state end comment char ] END comment noeat body syntax .lua-escape state escape char "abfnrtv\\\"'" END special char x hex1 char 0-1 decimal-low char 2 decimal-two char 3-9 decimal-high noeat short state hex1 special char 0-9a-fA-F hex2 noeat short state hex2 special char 0-9a-fA-F END special noeat short state decimal-low special char 0-9 decimal-last noeat END state decimal-last special char 0-9 END special noeat END state decimal-two special char 0-4 decimal-last char 5 decimal-two-five char 6-9 decimal-short noeat END state decimal-two-five special char 0-5 END special char 6-9 END error noeat END state decimal-high special char 0-9 decimal-short noeat END state decimal-short special char 0-9 END error noeat END state short special char -n "\n" END error noeat END syntax lua list constant false true nil list opword and or not list keyword \ do end while repeat until if elseif then else for in \ function local return goto break state first-line char " \t" this char # line-comment noeat code state code char -b a-zA-Z_ identifier char 0 zero char 1-9 decimal char \' single-quote char \" double-quote char \- dash char [ bracket char -b : colon str '~=' this operator char =/+*#<>^%|&~ this operator str '...' this special str '..' this operator char . dot char '!@`$?' this error eat this state bracket string char [ .lua-long-string:code string char -b = bracket-equals recolor default 1 noeat code state colon char -b : colon-colon noeat code state colon-colon char -b a-zA-Z_ colon-colon-name eat code error state colon-colon-name char -b a-zA-Z0-9_ this char -b : colon-colon-name-colon eat code error state colon-colon-name-colon char -b : label eat code error state label recolor label noeat code state bracket-equals string char -b = this char -n [ code error heredocbegin .lua-long-string code state dash operator char \- dash-dash noeat code state dash-dash comment recolor comment 2 char "\n" code char [ dash-dash-bracket eat line-comment comment state dash-dash-bracket comment char [ .lua-long-comment:code comment char -b = dash-dash-bracket-equals comment eat line-comment state dash-dash-bracket-equals char -b = this comment char -n [ line-comment heredocbegin .lua-long-comment code state line-comment comment char "\n" code eat this comment state zero numeric char 0-9 decimal char xX hex char . float noeat check-suffix state decimal numeric char 0-9 this char eE exponent char . float noeat check-suffix state hex numeric char 0-9a-fA-F this noeat check-suffix state float numeric char 0-9 this char eE exponent noeat check-suffix state exponent numeric char +- exponent-digit char 0-9 exponent-digit noeat check-suffix state exponent-digit numeric char 0-9 this noeat check-suffix state check-suffix error char a-zA-Z0-9_ this noeat code state dot char 0-9 dot-float noeat code state dot-float recolor numeric 2 noeat float state identifier char -b a-zA-Z0-9_ this inlist keyword code inlist constant code inlist opword code operator noeat code state single-quote string char \' code string char "\n" code char "\\" .lua-escape:this special eat this state double-quote string char \" code string char "\n" code char "\\" .lua-escape:this special eat this dte-1.9.1/config/syntax/mail000066400000000000000000000002541354415143300157420ustar00rootroot00000000000000syntax mail state bol text char '>' quote noeat text state text char "\n" bol eat this state quote char "\n" bol eat this default comment quote dte-1.9.1/config/syntax/make000066400000000000000000000042331354415143300157360ustar00rootroot00000000000000syntax make state start code char "\n" this char "\t" tab-indent char ' ' space-indent noeat not-recipe # "^\t[ \t]*" state tab-indent code char " \t" this char "\n" start char -b a-zA-Z_- not-target noeat make # "^ [ \t]*" state space-indent code char " \t" this noeat not-recipe # Can't be target but can be recipe, variable or keyword state not-target code char -b a-zA-Z_- this inlist keyword make noeat make # Can't be recipe but can be target, variable or keyword state not-recipe code char -b . special-target char -b a-zA-Z_- this inlist keyword make noeat make state special-target code char -b A-Z_ this # FIXME: ".PHONY := ..." is valid inlist special-target make noeat make state make code char # comment char \$ dollar char "\n" start char \\ esc # this is a hack char ')}' this variable eat this state comment char "\n" start char \\ comment-esc eat this state comment-esc comment char "\\\n" comment noeat comment state dollar variable char '({' lp char a-zA-Z make variable char '@% block-quote char [ maybe-link-def char -b 0-9 digit str "**" double-asterisk char * bullet-asterisk emphasis char +- bullet char -b "~" tilde char -b "`" backtick noeat text state text char "\n" line-start char '`' code-span char [ reference str '**' double-asterisk str '__' double-underline char * asterisk char _ underscore char "\\" .markdown-escape:this eat this state bullet char "\n" line-start char -n " \t" text recolor unordered-list 2 noeat text state bullet-asterisk char "\n" line-start char -n " \t" asterisk emphasis recolor unordered-list 2 noeat text state code-block char "\n" line-start eat this state backtick code-block char -b '`' this bufis '`' code-span bufis '``' text code-span heredocbegin .markdown-fenced-code-block line-start state tilde code-block char -b '~' this bufis '~' text bufis '~~' text heredocbegin .markdown-fenced-code-block line-start state block-quote char "\n" line-start eat this state digit char "\n" line-start char -b 0-9 this char -n . text recolor ordered-list eat text ordered-list state hash-heading heading char "\n" line-start eat this state code-span char "\n" line-start char ` text code-span eat this state asterisk emphasis char * text emphasis char -n "\n" this noeat line-start state double-asterisk strong-emphasis char "\n" line-start str '**' text strong-emphasis eat this state underscore emphasis char _ text emphasis char -n "\n" this noeat line-start state double-underline strong-emphasis char "\n" line-start str '__' text strong-emphasis eat this state reference char "\\" .markdown-escape:this char ] reference-close reference eat this state reference-close char "\n" line-start # char ( reference-link # char [ reference-pointer noeat text state maybe-link-def reference char "\\" .markdown-escape:this char ] maybe-link-def-close eat this state maybe-link-def-close reference char : link-def reference noeat reference-close state link-def char " \t" this char "\n" line-start noeat link-def-url state link-def-url url char "\n" line-start char " \t" link-def-after-url eat this state link-def-after-url text char "\n" line-start eat this default comment yaml default special unordered-list default numeric ordered-list dte-1.9.1/config/syntax/meson000066400000000000000000000013131354415143300161360ustar00rootroot00000000000000syntax meson list keyword if elif else endif foreach endforeach list constant true false state start code char # comment str "'''" longstring char "'" string char -b a-zA-Z_ ident char 0-9 number eat this state comment char "\n" start eat this state longstring string str "'''" start string eat this state string string char "'" start string char "\n" start char -b "\\" string-escape eat this state string-escape special char "n'\\" string special recolor string noeat string state ident char -b a-zA-Z_0-9 this inlist keyword start inlist constant start noeat start state number numeric char 0-9 this noeat start dte-1.9.1/config/syntax/nginx000066400000000000000000000236561354415143300161560ustar00rootroot00000000000000syntax .nginx-var state variable char '{' brace char A-Za-z0-9_ this noeat END state brace variable char A-Za-z0-9_ this char '}' END variable eat END error syntax .nginx-sq-string state string char "\\" escape char \$ .nginx-var:this char "'" END string eat this state escape special char "'$" string special eat string error syntax .nginx-dq-string state string char "\\" escape char \$ .nginx-var:this char "\"" END string eat this state escape special char "\"$" string special eat string error syntax nginx state line-start char -b a-z_ first-word char ";\n\t " this noeat nginx state nginx char \$ .nginx-var:this char "\"" .nginx-dq-string:this char "'" .nginx-sq-string:this char \\ escape char # comment char ";\n{" line-start eat this state first-word char -b a-z_ this inlist directive nginx keyword inlist deprecated_directive nginx error noeat nginx # TODO: check the list of escape chars is complete/correct state escape special char "\"\\'trn.$" nginx special recolor error 1 noeat nginx state comment char "\n" line-start eat this list directive \ accept_mutex accept_mutex_delay access_log add_after_body \ add_before_body add_header addition_types aio alias allow \ ancient_browser ancient_browser_value auth_basic \ auth_basic_user_file auth_http auth_http_header auth_http_timeout \ auth_request auth_request_set autoindex autoindex_exact_size \ autoindex_localtime break charset charset_map charset_types \ chunked_transfer_encoding client_body_buffer_size \ client_body_in_file_only client_body_in_single_buffer \ client_body_temp_path client_body_timeout client_header_buffer_size \ client_header_timeout client_max_body_size connection_pool_size \ create_full_put_path daemon dav_access dav_methods debug_connection \ debug_points default_type deny directio directio_alignment \ disable_symlinks empty_gif env error_log error_page etag events \ expires f fastcgi_bind fastcgi_buffering fastcgi_buffers \ fastcgi_buffer_size fastcgi_busy_buffers_size fastcgi_cache \ fastcgi_cache_bypass fastcgi_cache_key fastcgi_cache_lock \ fastcgi_cache_lock_timeout fastcgi_cache_methods \ fastcgi_cache_min_uses fastcgi_cache_path fastcgi_cache_purge \ fastcgi_cache_revalidate fastcgi_cache_use_stale fastcgi_cache_valid \ fastcgi_catch_stderr fastcgi_connect_timeout fastcgi_hide_header \ fastcgi_ignore_client_abort fastcgi_ignore_headers fastcgi_index \ fastcgi_intercept_errors fastcgi_keep_conn \ fastcgi_max_temp_file_size fastcgi_next_upstream \ fastcgi_next_upstream_timeout fastcgi_next_upstream_tries \ fastcgi_no_cache fastcgi_param fastcgi_pass fastcgi_pass_header \ fastcgi_pass_request_body fastcgi_pass_request_headers \ fastcgi_read_timeout fastcgi_send_lowat fastcgi_send_timeout \ fastcgi_split_path_info fastcgi_store fastcgi_store_access \ fastcgi_temp_file_write_size fastcgi_temp_path flv geo geoip_city \ geoip_country geoip_org geoip_proxy geoip_proxy_recursive gunzip \ gunzip_buffers gzip gzip_buffers gzip_comp_level gzip_disable \ gzip_http_version gzip_min_length gzip_proxied gzip_static \ gzip_types gzip_vary hash health_check hls hls_buffers \ hls_forward_args hls_fragment hls_mp http if if_modified_since \ ignore_invalid_headers image_filter image_filter_buffer \ image_filter_interlace image_filter_jpeg_quality \ image_filter_sharpen image_filter_transparency imap_auth \ imap_capabilities imap_client_buffer include index internal ip_hash \ keepalive keepalive_disable keepalive_requests keepalive_timeout \ large_client_header_buffers least_conn limit_conn \ limit_conn_log_level limit_conn_status limit_conn_zone limit_except \ limit_rate limit_rate_after limit_req limit_req_log_level \ limit_req_status limit_req_zone limit_zone lingering_close \ lingering_time lingering_timeout listen location lock_file \ log_format log_not_found log_subrequest mail map \ map_hash_bucket_size map_hash_max_size master_process match \ max_ranges memcached_bind memcached_buffer_size \ memcached_connect_timeout memcached_gzip_flag \ memcached_next_upstream memcached_next_upstream_timeout \ memcached_next_upstream_tries memcached_pass memcached_read_timeout \ memcached_send_timeout merge_slashes min_delete_depth modern_browser \ modern_browser_value mp msie_padding msie_refresh multi_accept \ open_file_cache open_file_cache_errors open_file_cache_min_uses \ open_file_cache_valid open_log_file_cache \ output_buffers override_charset pcre_jit perl perl_modules \ perl_require perl_set pid pop port_in_redirect postpone_output \ protocol proxy_bind proxy_buffer proxy_buffering proxy_buffers \ proxy_buffer_size proxy_busy_buffers_size proxy_cache \ proxy_cache_bypass proxy_cache_key proxy_cache_lock \ proxy_cache_lock_timeout proxy_cache_methods proxy_cache_min_uses \ proxy_cache_path proxy_cache_purge proxy_cache_revalidate \ proxy_cache_use_stale proxy_cache_valid proxy_connect_timeout \ proxy_cookie_domain proxy_cookie_path proxy_headers_hash_bucket_size \ proxy_headers_hash_max_size proxy_hide_header proxy_http_version \ proxy_ignore_client_abort proxy_ignore_headers \ proxy_intercept_errors proxy_max_temp_file_size proxy_method \ proxy_next_upstream proxy_next_upstream_timeout \ proxy_next_upstream_tries proxy_no_cache proxy_pass \ proxy_pass_error_message proxy_pass_header proxy_pass_request_body \ proxy_pass_request_headers proxy_read_timeout proxy_redirect \ proxy_send_lowat proxy_send_timeout proxy_set_body proxy_set_header \ proxy_ssl_ciphers proxy_ssl_crl proxy_ssl_name proxy_ssl_protocols \ proxy_ssl_server_name proxy_ssl_session_reuse \ proxy_ssl_trusted_certificate proxy_ssl_verify \ proxy_ssl_verify_depth proxy_store proxy_store_access \ proxy_temp_file_write_size proxy_temp_path proxy_timeout queue \ random_index read_ahead real_ip_header real_ip_recursive \ recursive_error_pages referer_hash_bucket_size referer_hash_max_size \ request_pool_size reset_timedout_connection resolver \ resolver_timeout return rewrite rewrite_log root satisfy \ scgi_bind scgi_buffering scgi_buffers scgi_buffer_size \ scgi_busy_buffers_size scgi_cache scgi_cache_bypass scgi_cache_key \ scgi_cache_lock scgi_cache_lock_timeout scgi_cache_methods \ scgi_cache_min_uses scgi_cache_path scgi_cache_purge \ scgi_cache_revalidate scgi_cache_use_stale scgi_cache_valid \ scgi_connect_timeout scgi_hide_header scgi_ignore_client_abort \ scgi_ignore_headers scgi_intercept_errors scgi_max_temp_file_size \ scgi_next_upstream scgi_next_upstream_timeout \ scgi_next_upstream_tries scgi_no_cache scgi_param scgi_pass \ scgi_pass_header scgi_pass_request_body scgi_pass_request_headers \ scgi_read_timeout scgi_send_timeout scgi_store scgi_store_access \ scgi_temp_file_write_size scgi_temp_path secure_link secure_link_md5 \ secure_link_secret sendfile sendfile_max_chunk send_lowat \ send_timeout server server_name server_name_in_redirect \ server_names_hash_bucket_size server_names_hash_max_size \ server_tokens session_log session_log_format session_log_zone set \ set_real_ip_from smtp_auth smtp_capabilities so_keepalive \ source_charset spdy_chunk_size spdy_headers_comp split_clients ssi \ ssi_last_modified ssi_min_file_chunk ssi_silent_errors ssi_types \ ssi_value_length ssl ssl_buffer_size ssl_certificate \ ssl_certificate_key ssl_ciphers ssl_client_certificate ssl_crl \ ssl_dhparam ssl_ecdh_curve ssl_engine ssl_password_file \ ssl_prefer_server_ciphers ssl_protocols ssl_session_cache \ ssl_session_ticket_key ssl_session_tickets ssl_session_timeout \ ssl_stapling ssl_stapling_file ssl_stapling_responder \ ssl_stapling_verify ssl_trusted_certificate ssl_verify_client \ ssl_verify_depth starttls status status_format status_zone sticky \ sticky_cookie_insert stub_status sub_filter sub_filter_last_modified \ sub_filter_once sub_filter_types tcp_nodelay tcp_nopush timeout \ timer_resolution try_files types types_hash_bucket_size \ types_hash_max_size underscores_in_headers \ uninitialized_variable_warn upstream upstream_conf use user userid \ userid_domain userid_expires userid_mark userid_name userid_p3p \ userid_path userid_service uwsgi_bind uwsgi_buffering uwsgi_buffers \ uwsgi_buffer_size uwsgi_busy_buffers_size uwsgi_cache \ uwsgi_cache_bypass uwsgi_cache_key uwsgi_cache_lock \ uwsgi_cache_lock_timeout uwsgi_cache_methods uwsgi_cache_min_uses \ uwsgi_cache_path uwsgi_cache_purge uwsgi_cache_revalidate \ uwsgi_cache_use_stale uwsgi_cache_valid uwsgi_connect_timeout \ uwsgi_hide_header uwsgi_ignore_client_abort uwsgi_ignore_headers \ uwsgi_intercept_errors uwsgi_max_temp_file_size uwsgi_modifier1 \ uwsgi_modifier2 uwsgi_next_upstream uwsgi_next_upstream_timeout \ uwsgi_next_upstream_tries uwsgi_no_cache uwsgi_param uwsgi_pass \ uwsgi_pass_header uwsgi_pass_request_body uwsgi_pass_request_headers \ uwsgi_read_timeout uwsgi_send_timeout uwsgi_ssl_ciphers \ uwsgi_ssl_crl uwsgi_ssl_name uwsgi_ssl_protocols \ uwsgi_ssl_server_name uwsgi_ssl_session_reuse \ uwsgi_ssl_trusted_certificate uwsgi_ssl_verify \ uwsgi_ssl_verify_depth uwsgi_store uwsgi_store_access \ uwsgi_temp_file_write_size uwsgi_temp_path valid_referers \ variables_hash_bucket_size variables_hash_max_size \ worker_aio_requests worker_connections worker_cpu_affinity \ worker_priority worker_processes worker_rlimit_core \ worker_rlimit_nofile worker_rlimit_sigpending working_directory \ xclient xml_entities xslt_last_modified xslt_param xslt_string_param \ xslt_stylesheet xslt_types zone list deprecated_directive \ imap open_file_cache_retest optimize_server_names satisfy_any dte-1.9.1/config/syntax/ninja000066400000000000000000000007371354415143300161250ustar00rootroot00000000000000syntax ninja state line-start code char " \t" this char # comment noeat code state comment char "\n" line-start eat this state code char "\n" line-start str "$\n" line-start special char -b \$ dollar eat this state dollar variable char -b ' $:' escape noeat variable state variable char " \t" code char "\n" line-start char "\x20-\x7e\xa1-\xff" this eat code error state escape recolor special noeat code dte-1.9.1/config/syntax/php000066400000000000000000000123151354415143300156100ustar00rootroot00000000000000# "simple" variable inside string syntax .php-strvar state start char a-zA-Z_ strvar noeat END state strvar char a-zA-Z_0-9 this noeat END # Curly braces inside string syntax .php-strcurly state strvar char } END strvar eat this # Common escapes inside double quotes and heredocs syntax .php-esc state special char x hex # 1-3 octal digits, \777 (0x1ff) overflows to 0xff char 0-7 oct1 recolor string 1 eat END state hex special char 0-9a-fA-F hex2 # "\xq" is not an error, it's literal recolor string 2 noeat END state hex2 special char 0-9a-fA-F END special # Single hex digit is ok noeat END state oct1 special char 0-7 oct2 noeat END state oct2 special char 0-7 END special noeat END # Common to << syntax .php state code char -b a-zA-Z_ word char \$ variable char \" dq char \' sq char 0 zero char 1-9 dec char . dot char ? qm str "//" cpp-comment str "/*" c-comment char # sh-comment str <<< heredoc eat this state word code char -b a-zA-Z_0-9 this inlist keyword code inlist constant code inlist lang code noeat code state variable char -b a-zA-Z_ variable2 noeat code state variable2 variable char -b a-zA-Z_0-9 this bufis this this-keyword noeat code state this-keyword recolor this 5 noeat code state dq string char \" code string char \\ dqesc char \$ .php-strvar:this strvar str '{$' .php-strcurly:this strvar eat this state dqesc special char 'nrtvf\$"' dq special noeat .php-esc:dq state sq string char \' code string char \\ sqesc eat this state sqesc string # Not \ or ' char -n "\\'" sq recolor special 1 eat sq special state zero numeric char xX hex char 0-7 oct char . float noeat check-suffix state oct numeric char 0-7 this noeat check-suffix state dec numeric char 0-9 this char eE exp char . float noeat check-suffix state hex numeric char 0-9a-fA-F this noeat check-suffix state check-suffix error char a-zA-Z0-9_ this noeat code state dot numeric char 0-9 float recolor code 1 noeat code state float numeric char 0-9 this char eE exp noeat check-suffix state exp numeric char +- exp-digit char 0-9 exp-digit noeat check-suffix state exp-digit numeric char 0-9 this noeat check-suffix state qm tag char > END tag recolor code 1 noeat code state cpp-comment comment char "\n" code eat this state c-comment comment str "*/" code comment eat this state sh-comment comment char "\n" code eat this state heredoc special char -b a-zA-Z_ heredoc-plain char \" heredoc-dq-start char \' nowdoc-start noeat code state heredoc-plain special char -b a-zA-Z0-9_ this heredocbegin .php-heredoc code state heredoc-dq-start special char -b a-zA-Z_ heredoc-dq noeat code state heredoc-dq special char -b a-zA-Z0-9_ this heredocbegin .php-heredoc-dq code state nowdoc-start special char -b a-zA-Z_ nowdoc noeat code state nowdoc special char -b a-zA-Z0-9_ this heredocbegin .php-nowdoc code list keyword \ abstract and array as break case catch cfunction class clone \ const continue declare default do else elseif enddeclare \ endfor endforeach endif endswitch endwhile extends final for \ foreach function global goto if implements interface \ instanceof namespace new old_function or private protected \ public static switch throw try use var while xor self parent list -i constant \ __CLASS__ __DIR__ __FILE__ __FUNCTION__ __METHOD__ __NAMESPACE__ \ false null true # Language constructs list lang \ die echo empty exit eval include include_once isset list require \ require_once return print unset syntax php state code char < lab eat this state lab tag char ? tag-php recolor code 1 noeat code state tag-php tag str "php" tag-ver noeat .php:code state tag-ver tag char 3-5 .php:code tag noeat .php:code default special tag default keyword lang default special strvar default variable this dte-1.9.1/config/syntax/python000066400000000000000000000060131354415143300163400ustar00rootroot00000000000000syntax .python-esc # TODO: \N{name} \uxxxx \Uxxxxxxxx state esc special char "abfnrtv'\\\"" END special char 0-7 oct1 char x hex0 noeat END state oct1 special char 0-7 oct2 noeat END state oct2 special char 0-7 END special noeat END state hex0 special char 0-9a-fA-F hex1 noeat END state hex1 special char 0-9a-fA-F END special noeat END syntax python state start code char # comment str '"""' longdq str "'''" longsq char '"' dq char "'" sq # TODO: [uU][rR]" [uU]" [rR]" [bB]" [bB][rR]" char -b a-zA-Z_ ident char 0 zero char 1-9 dec char . dot char " \t\f\r\n" whitespace eat this state comment char "\n" start eat this state longdq string str '"""' start string char -b "\\" .python-esc:this eat this state longsq string str "'''" start string char -b "\\" .python-esc:this eat this state dq string char "\"" start string char "\n" start char -b "\\" .python-esc:this eat this state sq string char "'" start string char "\n" start char -b "\\" .python-esc:this eat this state ident char -b a-zA-Z_0-9 this inlist keyword start inlist constant start noeat start state zero numeric char bB bin char oO oct char xX hex char 0 dec-zero char . float char eE exponent noeat int-suffix state bin numeric char 01 this noeat int-suffix state oct numeric char 0-7 this noeat int-suffix state dec numeric char 0-9 this char eE exponent char . float noeat int-suffix state dec-zero numeric char 0 this char eE exponent char . float # TODO: digits 1-9 are allowed here but only if the number becomes a float noeat int-suffix state hex numeric char 0-9a-fA-F this noeat int-suffix state int-suffix code # TODO: Remove long int suffixes? (removed in Python 3) char lL int-end numeric char jJ float-end numeric noeat int-end state int-end error char a-zA-Z0-9_ this noeat start state float numeric char 0-9 this char eE exponent noeat float-suffix state exponent numeric char +- exponent-sign char 0-9 exponent-digit recolor error 1 noeat float-end state exponent-sign numeric char 0-9 exponent-digit recolor error 2 noeat float-end state exponent-digit numeric char 0-9 this noeat float-suffix state float-suffix code char jJ float-end numeric noeat int-end state float-end error char a-zA-Z_ this noeat start state whitespace char " \t\f\r\n" this char . whitespace-dot noeat start state whitespace-dot code char 0-9 dot-float noeat start state dot-float numeric recolor numeric 2 noeat float state dot code char 0-9 this error noeat start list keyword \ and as assert break class continue def del elif else except exec \ finally for from global if import in is lambda not or pass print \ raise return try while with yield list constant \ None False True dte-1.9.1/config/syntax/robotstxt000066400000000000000000000010641354415143300170700ustar00rootroot00000000000000syntax robotstxt list -i field user-agent: disallow: allow: crawl-delay: sitemap: host: state main code char -b A-Za-z firstword char # comment char " \t" this noeat remainder state firstword char -b A-Za-z- this char -b : field noeat -b remainder state field inlist field value keyword recolor error noeat remainder state value char # comment char "\n" main eat this state remainder error recolor error char # comment char "\n" main eat this state comment char "\n" main eat this dte-1.9.1/config/syntax/roff000066400000000000000000000021221354415143300157500ustar00rootroot00000000000000syntax roff state line-start char -b '.' request char "\n" this noeat text # TODO: Highlight .TH request in bold # TODO: Highlight "quoted strings" in request arguments state request char -b a-zA-Z this inlist section section noeat text state escape-start escape char -b '"#' comment char -b f font-selection char -b ( 2char-escape char " \\.%!|^&)/~:{}acdeEprtu-" text escape eat text error state 2char-escape escape char -b "A-Za-z0-9_<>=!?$%*|/'\":^,~.`+-" 2char-escape-2 recolor error char "\n" line-start eat text error state 2char-escape-2 escape char "A-Za-z0-9_<>=!?$%*|/'\":^,~.`+-" text escape recolor error char "\n" line-start eat text error state font-selection escape char -b RBI text escape noeat text state text char -b '\\' escape-start char "\n" line-start eat this state comment recolor comment char "\n" line-start eat this state section char "\n" line-start eat this list section .SH .SS default special escape default string request default keyword section dte-1.9.1/config/syntax/ruby000066400000000000000000000066431354415143300160110ustar00rootroot00000000000000syntax .ruby-esc # TODO: \unnnn Unicode codepoint, where nnnn is exactly 4 hex digits # TODO: \u{nnnn ...} Unicode codepoint(s), where each nnnn is 1-6 hex digits state esc special char "abefnrstv'\\\"" END special char 0-7 oct1 char x hex0 char c control str 'C-' control str 'M-' meta noeat END state oct1 special char 0-7 oct2 noeat END state oct2 special char 0-7 END special noeat END state hex0 special char 0-9a-fA-F hex1 noeat END state hex1 special char 0-9a-fA-F END special noeat END state control special str "\\M-" ascii-print noeat ascii-print state meta special str "\\c" ascii-print str "\\C-" ascii-print noeat ascii-print state ascii-print special char "\x21-\x7e" END special noeat END syntax ruby # TODO: %Q() strings # TODO: Expression substitution in strings (e.g. "my name is #{$ruby}") # TODO: Here document strings (e.g. print <*\$?:\" start global-variable noeat start state maybe-instance-variable instance-variable char -b a-zA-Z_ instance-variable recolor code noeat start state instance-variable char a-zA-Z0-9_ this noeat start state maybe-symbol symbol char -b a-zA-Z_ symbol char -b : double-colon recolor code noeat start state double-colon recolor double-colon noeat start state symbol char a-zA-Z0-9_ this noeat start state comment char "\n" line-start eat this state line-start-eq comment str 'begin' =begin recolor code 1 noeat start state =begin comment char "\n" =begin-line-start char " \t" =begin-text recolor code 6 noeat start state =begin-text comment char "\n" =begin-line-start eat this state =begin-line-start comment char "\n" this str '=end' =begin-maybe-end noeat =begin-text state =begin-maybe-end comment char "\n" line-start char " \t" =end noeat =begin-text state =end comment char "\n" line-start eat this state ident char -b a-zA-Z0-9_!? this inlist keyword start inlist true-false-nil start noeat start state dq string char "\"" start string char "\n" line-start char -b "\\" .ruby-esc:this eat this state sq string char "'" start string char "\n" line-start char -b "\\" .ruby-esc:this eat this list keyword \ __ENCODING__ __LINE__ __FILE__ BEGIN END \ alias and begin break case class def defined? do else elsif end \ ensure for if in module next not or redo rescue retry return self \ super then undef unless until when while yield # These keywords could be in the same list as above but they are kept # separate here to allow the option of highlighting with a different color list true-false-nil \ true false nil default keyword true-false-nil default numeric symbol default variable global-variable default variable instance-variable default operator double-colon dte-1.9.1/config/syntax/sed000066400000000000000000000002741354415143300155750ustar00rootroot00000000000000syntax sed state line-start char " \t\n" this char # comment noeat code state code char "\n" line-start eat this state comment char "\n" line-start eat this dte-1.9.1/config/syntax/sh000066400000000000000000000137221354415143300154360ustar00rootroot00000000000000# Single quoted string syntax .sh-sq state string char "'" END string eat this # Escape inside double quotes syntax .sh-dq-escape state special char '"$\`'"\n" END special recolor string 1 eat END # Escape outside double quotes syntax .sh-command-escape state special eat END special # ${var}, $var, $1 etc. ($ already eaten) syntax .sh-var state variable char '{' brace1 char '*@#?$!0-9-' END variable char a-zA-Z_ name noeat END state name variable char a-zA-Z_0-9 this noeat END state brace1 variable char } END variable str '${' brace2 eat this state brace2 variable char } brace1 variable str '${' brace3 eat this state brace3 variable char } brace2 variable str '${' END error eat this # $((expr)) "$((" already eaten syntax .sh-expression state expression special # FIXME: there can be almost anything in $(( ... )) char ')' expression-end eat this state expression-end special char ')' END special eat END error # Double quoted string. $(cmd) and $((expr) not properly parsed syntax .sh-simple-dq state string char \" END string char \\ .sh-dq-escape:this char \$ special eat this state special char '(' paren recolor variable 1 noeat .sh-var:string state paren special char '(' .sh-expression:string noeat command state command char ')' string special char -n "\n" this noeat string # $(cmd) syntax .sh-pcommand state command char ')' END special char \' .sh-sq:this char \" .sh-simple-dq:this char \\ .sh-command-escape:this char \$ .sh-var:this eat this # $var, $(command), $((expression)) syntax .sh-dollar state special char '(' paren recolor variable 1 noeat .sh-var:END state paren special char '(' .sh-expression:END noeat .sh-pcommand:END # `command` syntax .sh-backtick state special # NOTE: you can't put "`" inside `...`, it's not possible char \' .sh-sq:this char \" .sh-simple-dq:this char \\ .sh-command-escape:this char \$ .sh-var:this char ` END special eat this syntax .sh-dq # Double quoted string state string char '"' END string char \\ .sh-dq-escape:this char ` .sh-backtick:this char \$ .sh-dollar:this eat this # Skip redirect etc. after < /tmp/file syntax .sh-heredoc-skip-line state start char "\n" END eat this code # Common to <<"EOF" and <<'EOF' syntax .sh-heredoc-common state start noeat .sh-heredoc-skip-line:bol state bol string heredocend match noeat string state string char "\n" bol eat this state match special char "\n" END # Length clamped to bol recolor string 1000 noeat string # << EOF syntax .sh-heredoc state start code noeat .sh-heredoc-skip-line:bol state bol string heredocend match noeat string state string char \\ esc char "\n" bol char \` .sh-backtick:this char \$ .sh-dollar:this eat this state esc special char "\n\\`$" string special recolor error 1 noeat string state match special char "\n" END # Length clamped to bol recolor string 1000 noeat string # << "EOF" syntax .sh-heredoc-dq state start string char -n \" END error eat .sh-heredoc-common:END # << 'EOF' syntax .sh-heredoc-sq state start string char -n \' END error eat .sh-heredoc-common:END # Commands can be at beginning of line or after any of && || ; & | { ` $( # also beginning of line can contain ( after which command is allowed syntax sh state start code char -b a-zA-Z0-9_/.- command char "\t " this char '({' this special noeat args state command code # Eat all garbage to distinguish test/run.sh from builtin test char -b a-zA-Z0-9_/.- this inlist keyword1 start keyword inlist keyword2 args keyword inlist builtin args bufis for for char \( function noeat args state function special # Don't try to validate syntax because there can be comments # between "()" and "{" char \) start special eat start error state for keyword char " \t" this char a-zA-Z_ forvar noeat forerror state forvar ident char a-zA-Z_0-9 this char " \t" forspace noeat forerror state forspace code char " \t" this str in in noeat forerror state in keyword char " \t" args noeat forerror state forerror code char a-zA-Z_0-9 args error noeat args state args code char # comment char "'" .sh-sq:this char '"' .sh-dq:this char \\ .sh-command-escape:this char ` .sh-backtick:this char \$ .sh-dollar:this char "\n" start # && is same as two &, same for |. does not matter char ';&|' start special # This might be error char ()} this special char < lt eat this state comment char "\n" start eat this state lt special char < lt2 noeat args state lt2 special char < lt3 char -- - heredoc-start special noeat heredoc-start state lt3 special # cat <<<"string $var" # cat <<< $(echo $USER) noeat args state heredoc-start special char " \t" this char \" heredoc-dq char \' heredoc-sq char "\n" start error noeat heredoc state heredoc-dq string char -bn "\"\n" this heredocbegin .sh-heredoc-dq start state heredoc-sq string char -bn "\'\n" this heredocbegin .sh-heredoc-sq start state heredoc special char -bn " \t\n;|>" heredoc heredocbegin .sh-heredoc start # Command allowed after these list keyword1 \ do else elif exec if then until while # Command not allowed after these list keyword2 \ break case continue done esac exit export fi function in return shift list builtin \ alias bg bind cd caller command compgen complete test kill source \ time declare typeset dirs disown echo enable eval fc fg getopts hash \ help history jobs let local logout popd printf pushd pwd read \ readonly shopt set trap type ulimit umask unalias unset wait "." default special expression dte-1.9.1/config/syntax/sql000066400000000000000000000117651354415143300156300ustar00rootroot00000000000000syntax .sql-esc state esc special char "0'\"bnrtZ\\%_" END special noeat END syntax sql state start code char "'" sq char '"' dq char -b bB b char -b xX x char -b a-zA-Z_\$ word char 0 zero char 1-9 int char . float char @ at char ` backtick char -- - dash char # comment char / slash eat this state zero numeric char 0-9 int char . float # Only lowercase. Inconsistent with X'10ff'! char b bin char x hex noeat trailing state int numeric char 0-9 this char . float noeat trailing state float numeric char 0-9 this noeat trailing state bin numeric char 01 bindigit eat start error state bindigit numeric char 01 this noeat trailing state hex numeric char 0-9a-fA-F hexdigit eat start error state hexdigit numeric char 0-9a-fA-F this noeat trailing state b code # Single quote only char \' bitstr noeat -b word state bitstr recolor string 2 noeat bitstrbit state bitstrbit special char 01 this char "\n'" start string eat this error state x code # Single quote only char \' hexstr noeat -b word state hexstr recolor string 2 noeat hexstreven state hexstreven special char 0-9a-fA-F hexstrodd char "\n'" start string eat hexstrodd error state hexstrodd special char 0-9a-fA-F hexstreven char "\n'" start error eat hexstreven error state word code char -b a-zA-Z0-9_\$ this inlist keyword start inlist identifier start char . column code noeat start state column char -b a-zA-Z0-9_\$ this noeat start state at variable char a-zA-Z0-9._\$ variable char "'" sq char '"' dq char ` backtick eat start error state variable char a-zA-Z0-9._\$ this noeat start state sq string char "\n'" start string char \\ .sql-esc:this eat this state dq string char "\n"\" start string char \\ .sql-esc:this eat this state backtick constant char "\n`" start eat this state dash comment char -- - maybe-comment recolor code 1 noeat start state maybe-comment comment char "\n" start # Control character or space required! char "\x01- " comment recolor code 2 noeat start state comment char "\n" start eat this state slash comment char "*" maybe-c-comment recolor code 1 noeat start state maybe-c-comment comment char ! begin-special-comment noeat c-comment state begin-special-comment recolor special 3 noeat special-comment state special-comment special char "*" scstar eat this state scstar special char / start special noeat special-comment state c-comment comment char "*" star eat this state star comment char / start comment noeat c-comment state trailing error char a-zA-Z0-9_\$ this noeat start # FIXME: types should probably go to separate list list -i keyword \ ACCESSIBLE \ ADD ALL ALTER ANALYZE AND AS ASC ASENSITIVE BEFORE BETWEEN \ BIGINT BINARY BLOB BOTH BY CALL CASCADE CASE CHANGE CHAR \ CHARACTER CHECK COLLATE COLUMN CONDITION CONSTRAINT CONTINUE \ CONVERT CREATE CROSS CURRENT_DATE CURRENT_TIME \ CURRENT_TIMESTAMP CURRENT_USER CURSOR DATABASE DATABASES \ DAY_HOUR DAY_MICROSECOND DAY_MINUTE DAY_SECOND DEC DECIMAL \ DECLARE DEFAULT DELAYED DELETE DESC DESCRIBE DETERMINISTIC \ DISTINCT DISTINCTROW DIV DOUBLE DROP DUAL EACH ELSE ELSEIF \ ENCLOSED ESCAPED EXISTS EXIT EXPLAIN FALSE FETCH FLOAT FLOAT4 \ FLOAT8 FOR FORCE FOREIGN FROM FULLTEXT GRANT GROUP HAVING \ HIGH_PRIORITY HOUR_MICROSECOND HOUR_MINUTE HOUR_SECOND IF \ IGNORE IN INDEX INFILE INNER INOUT INSENSITIVE INSERT INT INT1 \ INT2 INT3 INT4 INT8 INTEGER INTERVAL INTO IS ITERATE JOIN KEY \ KEYS KILL LEADING LEAVE LEFT LIKE LIMIT LINEAR LINES LOAD LOCALTIME \ LOCALTIMESTAMP LOCK LONG LONGBLOB LONGTEXT LOOP LOW_PRIORITY \ MASTER_BIND MASTER_SSL_VERIFY_SERVER_CERT \ MATCH MAXVALUE MEDIUMBLOB MEDIUMINT MEDIUMTEXT MIDDLEINT \ MINUTE_MICROSECOND MINUTE_SECOND MOD MODIFIES NATURAL NOT \ NO_WRITE_TO_BINLOG NULL NUMERIC ON ONE_SHOT OPTIMIZE OPTION OPTIONALLY \ OR ORDER OUT OUTER OUTFILE PARTITION PRECISION PRIMARY PROCEDURE PURGE RANGE \ READ READS READ_WRITE REAL REFERENCES REGEXP RELEASE RENAME REPEAT \ REPLACE REQUIRE RESIGNAL RESTRICT RETURN REVOKE RIGHT RLIKE SCHEMA \ SCHEMAS SECOND_MICROSECOND SELECT SENSITIVE SEPARATOR SET \ SHOW SIGNAL SMALLINT SPATIAL SPECIFIC SQL SQLEXCEPTION \ SQLSTATE SQLWARNING SQL_BIG_RESULT SQL_CALC_FOUND_ROWS \ SQL_SMALL_RESULT SSL STARTING STRAIGHT_JOIN TABLE TERMINATED \ THEN TINYBLOB TINYINT TINYTEXT TO TRAILING TRIGGER TRUE UNDO \ UNION UNIQUE UNLOCK UNSIGNED UPDATE USAGE USE USING UTC_DATE \ UTC_TIME UTC_TIMESTAMP VALUES VARBINARY VARCHAR VARCHARACTER \ VARYING WHEN WHERE WHILE WITH WRITE XOR YEAR_MONTH ZEROFILL list -i identifier \ ACTION BIT DATE ENUM NO TEXT TIME TIMESTAMP default keyword identifier default code column dte-1.9.1/config/syntax/tex000066400000000000000000000005761354415143300156270ustar00rootroot00000000000000syntax tex # TODO: Inline math ($...$) # TODO: Display math ($$...$$, \[...\]) state text char \\ backslash char % comment eat this state backslash macro char A-Za-z macro char "\\,;{}" escape eat text error state macro char A-Za-z this noeat text state escape recolor special 2 noeat text state comment char "\n" text eat this dte-1.9.1/config/syntax/texmfcnf000066400000000000000000000010071354415143300166270ustar00rootroot00000000000000syntax texmfcnf state line-start char "\n" this char % comment noeat text state text char "\n" line-start char " \t" space char \$ dollar eat this state space char " \t" this char % comment noeat text state comment char "\n" line-start eat this state dollar variable char A-Za-z_ variable char { brace noeat text state variable char A-Za-z0-9_ this noeat text state brace variable char "\n" line-start char } text variable eat this dte-1.9.1/config/syntax/vala000066400000000000000000000063231354415143300157460ustar00rootroot00000000000000syntax .vala-esc state char-esc special char "bfnrt\\'\"" END special char 0 oct char u hex4 noeat short state oct special char 0-3 oct2 char 4-7 oct1 noeat END state oct2 special char 0-7 oct1 noeat END state oct1 special char 0-7 END special noeat END state hex4 special char 0-9a-fA-F hex3 noeat END state hex3 special char 0-9a-fA-F hex2 noeat END state hex2 special char 0-9a-fA-F hex1 noeat END state hex1 special char 0-9a-fA-F END special noeat END state short special char "\x80-\xff" not-ascii # Any ASCII character but \n char -n "\n" END error # Don't eat \n noeat END # Eats (at least) one multibyte UTF-8 character state not-ascii error char "\x80-\xff" this noeat END # TODO: Check against the Vala spec (was copied from the Java syntax) syntax vala state code char -b a-zA-Z_ ident char 0 zero char 1-9 dec char . dot char \" string char \' char str "//" cpp-comment str "/*" c-comment eat this state cpp-comment comment char "\n" code eat this state c-comment comment str "*/" code comment eat this state ident char -b a-zA-Z0-9_ this inlist keyword code inlist type code inlist constant code noeat code state zero numeric char xX hex char 0-7 oct char . float noeat num-suffix state oct numeric char 0-7 this noeat num-suffix state dec numeric char 0-9 this char eE exp char . float noeat num-suffix state hex numeric char 0-9a-fA-F this noeat num-suffix state num-suffix char lLfFdD check-suffix numeric noeat check-suffix state float-suffix char fFdD check-suffix numeric noeat check-suffix state check-suffix error char a-zA-Z0-9_ this noeat code state dot numeric char 0-9 float recolor code 1 noeat code state float numeric char 0-9 this char eE exp noeat float-suffix state exp numeric char +- exp-digit char 0-9 exp-digit noeat float-suffix state exp-digit numeric char 0-9 this noeat float-suffix state string char \\ .vala-esc:this char \" code string eat this state char char "\\" .vala-esc:char-end char "\n" code char \' code error char "\x80-\xff" not-ascii eat char-end # Eats (at least) one multibyte UTF-8 character state not-ascii char char "\x80-\xff" this noeat char-end state char-end char char \' code char eat code error list keyword \ if else switch case default do while for foreach in break continue \ return try catch finally throw lock class interface struct enum \ delegate errordomain const weak unowned dynamic abstract virtual \ override signal extern static async inline new public private \ protected internal out ref throws requires ensures namespace using \ as is in new delete sizeof typeof this base null true false get set \ construct default value construct static construct class construct \ void var yield global owned list type \ bool char uchar short ushort int uint long ulong size_t ssize_t int8 \ uint8 int16 uint16 int32 uint32 int64 uint64 unichar float double string list constant \ false true null dte-1.9.1/config/syntax/xml000066400000000000000000000042041354415143300156170ustar00rootroot00000000000000syntax .xml-entity state entity char # hash char -b a-zA-Z0-9 name recolor error 1 char "\n" END eat END error state hash entity char xX x char 0-9 dec eat END error state x entity char 0-9a-fA-F hex eat END error state hex entity char 0-9a-fA-F this char ";" END entity eat END error state dec entity char 0-9 this char ";" END entity eat END error state name error char -b a-zA-Z0-9 this inlist entity semicolon noeat semicolon state semicolon error char ";" END entity eat END error list entity \ quot apos amp lt gt syntax xml state line1 text char " \t\n" this str " state xml comment char > line2 comment char < line2 error eat this state doctype comment char > text comment char < text error eat this state text str " text comment eat this state tag-start tag char / close-tag char -b a-zA-Z_: tag-name char > text tag eat this error state close-tag tag char -b a-zA-Z_: close-tag-name eat text error state tag-name tag char -b a-zA-Z0-9_:.- this noeat attrs state close-tag-name tag char -b a-zA-Z0-9_:.- this noeat close-tag-end state close-tag-end special char " \t\n" this char > text tag eat text error state attrs code char " \t\n" this char -b a-zA-Z:_ attr-name char > text tag char / short-close eat this error state short-close tag char > text tag eat text error state attr-name attr char a-zA-Z0-9_:.- this char = attr-eq noeat attrs state attr-eq attr char \" dq char \' sq noeat attrs state dq string char \" attrs string char "\n" attrs char "&" .xml-entity:this eat this state sq string char \' attrs string char "\n" attrs char "&" .xml-entity:this eat this default special entity dte-1.9.1/config/syntax/xresources000066400000000000000000000002741354415143300172240ustar00rootroot00000000000000syntax xresources state start code char " \t" this char ! comment noeat line state line code char "\n" start eat this state comment char "\n" start eat this dte-1.9.1/config/syntax/zig000066400000000000000000000053241354415143300156140ustar00rootroot00000000000000syntax .zig-esc # TODO: \xNN \uNNNN \UNNNNNN state esc special char "nrt'\\\"" END special noeat END syntax .zig-string state string char '"' END string char "\n" END error char "\\" .zig-esc:this eat this syntax zig state start code char -b iu maybe-int-type char -b a-z_@ ident char -b A-Z ident-upper # char 0 zero # char 1-9 dec char \" .zig-string:this # char \' .c-char:this str '//' comment eat this state ident char -b a-zA-Z0-9_ this inlist keyword start inlist type start inlist constant start inlist builtin start noeat start state ident-upper ident char -b a-z class-name char -b A-Z0-9_ ident noeat start state class-name recolor class-name char a-zA-Z0-9_ this noeat start state maybe-int-type type char 1-9 int-type recolor ident 1 noeat -b ident state int-type type char 0-9 this noeat start state comment char "\n" start eat this list keyword \ align allowzero and asm async await break cancel catch comptime \ const continue defer else enum errdefer error export extern fn \ for if inline linksection nakedcc noalias or orelse packed pub \ resume return stdcallcc struct suspend switch test threadlocal \ try union unreachable usingnamespace var volatile while list type \ bool f16 f32 f64 f128 void noreturn type anyerror promise \ i0 u0 isize usize comptime_int comptime_float \ c_short c_ushort c_int c_uint c_long c_ulong c_longlong \ c_ulonglong c_longdouble c_void list constant \ null undefined true false list builtin \ @ArgType @IntType @OpaqueType @TagType @This @Vector \ @addWithOverflow @alignCast @alignOf @atomicLoad @atomicRmw \ @bitCast @bitOffsetOf @bitReverse @boolToInt @breakpoint \ @byteOffsetOf @byteSwap @bytesToSlice @cDefine @cImport \ @cInclude @cUndef @ceil @clz @cmpxchgStrong @cmpxchgWeak \ @compileError @compileLog @cos @ctz @divExact @divFloor \ @divTrunc @embedFile @enumToInt @errSetCast @errorName \ @errorReturnTrace @errorToInt @exp @exp2 @export @fabs \ @fence @field @fieldParentPtr @floatCast @floatToInt @floor \ @frameAddress @handle @hasDecl @import @inlineCall @intCast \ @intToEnum @intToError @intToFloat @intToPtr @ln @log10 @log2 \ @memberCount @memberName @memberType @memcpy @memset @mod \ @mulAdd @mulWithOverflow @newStackCall @noInlineCall @panic \ @popCount @ptrCast @ptrToInt @rem @returnAddress @round \ @setAlignStack @setCold @setEvalBranchQuota @setFloatMode \ @setRuntimeSafety @shlExact @shlWithOverflow @shrExact @sin \ @sizeOf @sliceToBytes @sqrt @subWithOverflow @tagName @trunc \ @truncate @typeId @typeInfo @typeName @typeOf dte-1.9.1/docs/000077500000000000000000000000001354415143300132315ustar00rootroot00000000000000dte-1.9.1/docs/dte-syntax.5000066400000000000000000000141361354415143300154240ustar00rootroot00000000000000.TH DTE\-SYNTAX 5 "November 2017" .nh .ad l . .SH NAME dte\-syntax \- Format of syntax highlighting files used by \fBdte\fR(1) .SH SYNOPSIS .P Commands: .br .P Main commands: .br \fBsyntax\fR \fIname\fR .br \fBstate\fR \fIname\fR [\fIemit\-color\fR] .br \fBdefault\fR \fIcolor\fR \fIname\fR... .br \fBlist\fR [\fB\-i\fR] \fIname\fR \fIstring\fR... .br .P Conditionals: .br \fBbufis\fR [\fB\-i\fR] \fIstring\fR \fIdestination\fR [\fIemit\-name\fR] .br \fBchar\fR [\fB\-bn\fR] \fIcharacters\fR \fIdestination\fR [\fIemit\-name\fR] .br \fBheredocend\fR \fIdestination\fR .br \fBinlist\fR \fIlist\fR \fIdestination\fR [\fIemit\-name\fR] .br \fBstr\fR [\fB\-i\fR] \fIstring\fR \fIdestination\fR [\fIemit\-name\fR] .br .P Default actions: .br \fBeat\fR \fIdestination\fR [\fIemit\-name\fR] .br \fBheredocbegin\fR \fIsubsyntax\fR \fIreturn\-state\fR .br \fBnoeat\fR [\fB\-b\fR] \fIdestination\fR .br .P Other commands: .br \fBrecolor\fR \fIcolor\fR [\fIcount\fR] .br .SH DESCRIPTION A dte syntax file consists of multiple states. A state consists of optional conditionals and one default action. The best way understand the syntax is to read through some of the built\-in syntax files, which can be printed with \fBdte \-b\fR, for example: .P .IP .nf \f[C] dte\ \-b\ syntax/dte \f[] .fi .PP The basic syntax used is the same as in \fBdterc\fR(5) files, but the available commands are different. .P Conditionals and default actions have a destination state. The special destination state \fBthis\fR can be used to jump to the current state. .P .SH COMMANDS .SS Main commands \fBsyntax\fR \fIname\fR .RS Begin a new syntax. One syntax file can contain multiple syntax definitions, but you should only define one real syntax in one syntax file. .P See also: sub\-syntaxes. .P .RE \fBstate\fR \fIname\fR [\fIemit\-color\fR] .RS Add new state. Conditionals (if any) and one default action must follow. The first state is the \fBstart\fR state. .P .RE \fBdefault\fR \fIcolor\fR \fIname\fR... .RS Set default \fIcolor\fR for emitted \fIname\fR. .P Example: .P .IP .nf \f[C] default\ numeric\ oct\ dec\ hex \f[] .fi .PP If there is no color defined for \fIoct\fR, \fIdec\fR or \fIhex\fR then color \fInumeric\fR is used instead. .P .RE \fBlist\fR [\fB\-i\fR] \fIname\fR \fIstring\fR... .RS Define a list of strings. .P Example: .P .IP .nf \f[C] list\ keyword\ if\ else\ for\ while\ do\ continue\ switch\ case \f[] .fi .PP Use the \fBinlist\fR command to test if a buffered string is in a list. .P .TP \fB\-i\fR Make list case\-insensitive. .PP .RE .SS Conditionals \fBbufis\fR [\fB\-i\fR] \fIstring\fR \fIdestination\fR [\fIemit\-name\fR] .RS Test if buffered bytes are same as \fIstring\fR. If they are, emit \fIemit\-name\fR and jump to \fIdestination\fR state. .P .TP \fB\-i\fR Case\-insensitive. .PP .RE \fBchar\fR [\fB\-bn\fR] \fIcharacters\fR \fIdestination\fR [\fIemit\-name\fR] .RS Test if current byte is in the \fIcharacters\fR list. If it is then emit \fIemit\-color\fR and jump to \fIdestination\fR state. If \fIemit\-name\fR is not given then the \fIdestination\fR state's emit name is used. .P \fIcharacters\fR is a list of strings. Ranges are supported (\fBa\-d\fR is the same as \fBabcd\fR). .P .TP \fB\-b\fR Add byte to buffer. .PP .TP \fB\-n\fR Invert character bitmap. .PP .RE \fBheredocend\fR \fIdestination\fR .RS Compare following characters to heredoc end delimiter and go to destination state if comparison is true. .P .RE \fBinlist\fR \fIlist\fR \fIdestination\fR [\fIemit\-name\fR] .RS Test if buffered bytes are found in \fIlist\fR. If found, emit \fIemit\-name\fR and jump to \fIdestination\fR state. .P .RE \fBstr\fR [\fB\-i\fR] \fIstring\fR \fIdestination\fR [\fIemit\-name\fR] .RS See if following bytes are same as \fIstring\fR. If they are, emit \fIemit\-name\fR and jump to \fIdestination\fR state. .P .TP \fB\-i\fR Case\-insensitive. .PP NOTE: This conditional can be slow, especially if \fIstring\fR is longer than two bytes. .P .RE .SS Default actions The last command of every state must be a default action. It is an unconditional jump. .P \fBeat\fR \fIdestination\fR [\fIemit\-name\fR] .RS Consume byte, emit \fIemit\-name\fR color and continue to \fIdestination\fR state. .P .RE \fBheredocbegin\fR \fIsubsyntax\fR \fIreturn\-state\fR .RS Store buffered bytes as heredoc end delimiter and go to \fIsubsyntax\fR. Sub\-syntax is like any other sub\-syntax but it must contain a \fIheredocend\fR conditional. .P .RE \fBnoeat\fR [\fB\-b\fR] \fIdestination\fR .RS Continue to \fIdestination\fR state without emitting color or consuming byte. .P .TP \fB\-b\fR Don't stop buffering. .PP .RE .SS Other commands \fBrecolor\fR \fIcolor\fR [\fIcount\fR] .RS If \fIcount\fR is given, recolor \fIcount\fR previous bytes, otherwise recolor buffered bytes. .P .RE .SH SUB\-SYNTAXES Sub\-syntaxes are useful when the same states are needed in many contexts. .P Sub\-syntax names must be prefixed with \fB.\fR. It's recommended to also use the main syntax name in the prefix. For example \fB.c\-comment\fR if \fBc\fR is the main syntax. .P A sub\-syntax is a syntax in which some destination state's name is \fBEND\fR. \fBEND\fR is a special state name that is replaced by the state specified in another syntax. .P Example: .P .IP .nf \f[C] #\ Sub\-syntax syntax\ .c\-comment state\ comment \ \ \ \ char\ "*"\ star \ \ \ \ eat\ comment state\ star\ comment \ \ \ \ #\ END\ is\ a\ special\ state\ name \ \ \ \ char\ /\ END\ comment \ \ \ \ noeat\ comment #\ Main\ syntax syntax\ c state\ c\ code \ \ \ \ char\ "\ \\t\\n"\ c \ \ \ \ char\ \-b\ a\-zA\-Z_\ ident \ \ \ \ char\ "\\""\ string \ \ \ \ char\ "'"\ char \ \ \ \ #\ Call\ sub\-syntax \ \ \ \ str\ "/*"\ .c\-comment:c \ \ \ \ eat\ c #\ Other\ states\ removed \f[] .fi .PP In this example the destination state \fB.c\-comment:c\fR is a special syntax for calling a sub\-syntax. \fB.c\-comment\fR is the name of the sub\-syntax and \fBc\fR is the return state defined in the main syntax. The whole sub\-syntax tree is copied into the main syntax and all destination states in the sub\-syntax whose name is \fBEND\fR are replaced with \fBc\fR. .P . .SH SEE ALSO \fBdte\fR(1), \fBdterc\fR(5) .SH AUTHORS Craig Barnes .br Timo Hirvonen dte-1.9.1/docs/dte-syntax.md000066400000000000000000000112451354415143300156560ustar00rootroot00000000000000--- title: dte-syntax section: 5 date: November 2017 description: Format of syntax highlighting files used by `dte` author: [Craig Barnes, Timo Hirvonen] seealso: ["`dte`", "`dterc`"] --- # dte-syntax A dte syntax file consists of multiple states. A state consists of optional conditionals and one default action. The best way understand the syntax is to read through some of the [built-in syntax files], which can be printed with `dte -b`, for example: dte -b syntax/dte The basic syntax used is the same as in [`dterc`] files, but the available commands are different. Conditionals and default actions have a destination state. The special destination state `this` can be used to jump to the current state. # Commands ## Main commands ### **syntax** _name_ Begin a new syntax. One syntax file can contain multiple syntax definitions, but you should only define one real syntax in one syntax file. See also: sub-syntaxes. ### **state** _name_ [_emit-color_] Add new state. Conditionals (if any) and one default action must follow. The first state is the `start` state. ### **default** _color_ _name_... Set default _color_ for emitted _name_. Example: default numeric oct dec hex If there is no color defined for _oct_, _dec_ or _hex_ then color _numeric_ is used instead. ### **list** [**-i**] _name_ _string_... Define a list of strings. Example: list keyword if else for while do continue switch case Use the `inlist` command to test if a buffered string is in a list. `-i` : Make list case-insensitive. ## Conditionals ### **bufis** [**-i**] _string_ _destination_ [_emit-name_] Test if buffered bytes are same as _string_. If they are, emit _emit-name_ and jump to _destination_ state. `-i` : Case-insensitive. ### **char** [**-bn**] _characters_ _destination_ [_emit-name_] Test if current byte is in the _characters_ list. If it is then emit _emit-color_ and jump to _destination_ state. If _emit-name_ is not given then the _destination_ state's emit name is used. _characters_ is a list of strings. Ranges are supported (`a-d` is the same as `abcd`). `-b` : Add byte to buffer. `-n` : Invert character bitmap. ### **heredocend** _destination_ Compare following characters to heredoc end delimiter and go to destination state if comparison is true. ### **inlist** _list_ _destination_ [_emit-name_] Test if buffered bytes are found in _list_. If found, emit _emit-name_ and jump to _destination_ state. ### **str** [**-i**] _string_ _destination_ [_emit-name_] See if following bytes are same as _string_. If they are, emit _emit-name_ and jump to _destination_ state. `-i` : Case-insensitive. NOTE: This conditional can be slow, especially if _string_ is longer than two bytes. ## Default actions The last command of every state must be a default action. It is an unconditional jump. ### **eat** _destination_ [_emit-name_] Consume byte, emit _emit-name_ color and continue to _destination_ state. ### **heredocbegin** _subsyntax_ _return-state_ Store buffered bytes as heredoc end delimiter and go to _subsyntax_. Sub-syntax is like any other sub-syntax but it must contain a _heredocend_ conditional. ### **noeat** [**-b**] _destination_ Continue to _destination_ state without emitting color or consuming byte. `-b` : Don't stop buffering. ## Other commands ### **recolor** _color_ [_count_] If _count_ is given, recolor _count_ previous bytes, otherwise recolor buffered bytes. # Sub-syntaxes Sub-syntaxes are useful when the same states are needed in many contexts. Sub-syntax names must be prefixed with `.`. It's recommended to also use the main syntax name in the prefix. For example `.c-comment` if `c` is the main syntax. A sub-syntax is a syntax in which some destination state's name is `END`. `END` is a special state name that is replaced by the state specified in another syntax. Example: ```sh # Sub-syntax syntax .c-comment state comment char "*" star eat comment state star comment # END is a special state name char / END comment noeat comment # Main syntax syntax c state c code char " \t\n" c char -b a-zA-Z_ ident char "\"" string char "'" char # Call sub-syntax str "/*" .c-comment:c eat c # Other states removed ``` In this example the destination state `.c-comment:c` is a special syntax for calling a sub-syntax. `.c-comment` is the name of the sub-syntax and `c` is the return state defined in the main syntax. The whole sub-syntax tree is copied into the main syntax and all destination states in the sub-syntax whose name is `END` are replaced with `c`. [`dterc`]: https://craigbarnes.gitlab.io/dte/dterc.html [built-in syntax files]: https://gitlab.com/craigbarnes/dte/tree/master/config/syntax dte-1.9.1/docs/dte.1000066400000000000000000000112421354415143300140670ustar00rootroot00000000000000.TH DTE 1 "November 2017" .nh .ad l . .SH NAME . dte \- A small and flexible text editor . .SH SYNOPSIS . .B dte .RB [ \-hBHKRV ] [\fB\-c\fR \fIcommand\fR] [\fB\-t\fR \fIctag\fR] [\fB\-r\fR \fIrcfile\fR] [\fB\-b\fR \fIrcname\fR] .RI [[+ line ] " file" ]... . .SH OPTIONS . .TP .BI \-c " command" Run \fIcommand\fR, after reading the rc file and opening any \fIfile\fR arguments. See \fBdterc\fR(5) for available commands. . .TP .BI \-t " ctag" Jump to source location of \fIctag\fR. Requires \fBtags\fR file generated by \fBctags\fR(1). . .TP .BI \-r " rcfile" Read configuration from \fIrcfile\fR instead of \fB~/.dte/rc\fR. . .TP .BI \-s " file" Load \fIfile\fR as a \fBdte\-syntax\fR(5) file and exit. Any errors encountered are printed to \fBstderr\fR(3) and the exit status is set appropriately. . .TP .BI \-b " rcname" Dump the contents of the built\-in rc or syntax file named \fIrcname\fR and exit. . .TP .B \-B Print a list of all built\-in config names that can be used with the \fB\-b\fR option and exit. . .TP .B \-H Don't load history files at startup or save history files on exit (see \fBFILES\fR section below). History features will work as usual but will be in-memory only and not persisted to the filesystem. . .TP .B \-R Don't read the rc file. . .TP .B \-K Start in a special mode that continuously reads input and prints the name and numeric code of each pressed key. . .TP .B \-h Display the help summary and exit. . .TP .B \-V Display the version number and exit. . .SH BASIC USAGE . Here are some of the default key bindings. \fBM\-x\fR is Alt+x, \fB^V\fR (or \fBC\-V\fR) is Ctrl+v and \fBS\-left\fR is Shift+left. . .TP .BR S\-up ", " S\-down ", " S\-left ", " S\-right Move cursor and select characters . .TP .BR C\-S\-left ", " C\-S\-right Move cursor and select whole words . .TP .BR C\-S\-up ", " C\-S\-down Move cursor and select whole lines . .TP .B ^C Copy current line or selection . .TP .B ^X Cut current line or selection . .TP .B ^V Paste . .TP .B ^Z Undo . .TP .B ^Y Redo . .TP .B M\-x Enter command mode . .TP .B ^F Enter search mode . .TP .B F3 Search next . .TP .B F4 Search previous . .TP .B ^T Open new tab . .TP .BI M\- N Activate \fIN\fRth tab . .TP .B ^W Close tab . .TP .B ^S Save file . .TP .B ^Q Quit . .SH COMMAND MODE . Command mode allows you to run various editor commands by using a language similar to Unix shell. The \fBnext\fR and \fBprev\fR commands switch to the next/previous file. The \fBopen\fR, \fBsave\fR and \fBquit\fR commands should be self\-explanatory. For a full list of available commands, see \fBdterc\fR(5). .P The default key bindings for command mode are: . .TP .BR up ", " down Browse previous command history. . .TP .B tab Auto\-complete current command or argument . .TP .B ^A Go to beginning of command line . .TP .B ^B Move left . .TP .B ^C Exit command mode . .TP .B ^D Delete . .TP .B ^E Go to end of command line . .TP .B ^F Move right . .TP .B ^K Delete to end of command line . .TP .B ^U Delete to beginning of command line . .TP .B ^W Erase word . .TP .B Esc Exit command mode . .SH SEARCH MODE . Search mode allows you to enter an extended regular expression to search in the current buffer. .P The key bindings for search mode are mostly the same as in command mode, plus these additional keys: . .TP .B M\-c Toggle case sensitive search option. . .TP .B M\-r Reverse search direction. . .SH ENVIRONMENT . .TP .B DTE_HOME User configuration directory. Defaults to \fB$HOME/.dte\fR if not set. . .TP .B DTE_FORCE_TERMINFO Force \fBdte\fR to use \fBterminfo\fR(5) database, even for terminals with built-in support. Enabled if set. . .TP .B TERM Identifier used to determine which \fBterminfo\fR(5) entry or built-in terminal support to use. . .SH FILES . .TP .B $DTE_HOME/rc Your personal configuration file. See \fBdterc\fR(5) for a full list of available commands and options or run "dte \-b rc" to see the built\-in, default config. . .TP .B $DTE_HOME/syntax/* Your personal syntax files. These override the syntax files that come with the program. See \fBdte\-syntax\fR(5) for more information or run "dte \-b syntax/dte" for a basic example. . .TP .B $DTE_HOME/file\-locks Records open files to protect you from accidentally editing files opened in another process. Used only if the \fBlock\-files\fR option is enabled. . .TP .B $DTE_HOME/file\-history History of edited files and cursor positions. Used only if the \fBfile\-history\fR option is enabled. . .TP .B $DTE_HOME/command\-history History of \fBdterc\fR(5) commands used while in command mode. . .TP .B $DTE_HOME/search\-history History of search patterns used while in search mode. . .SH SEE ALSO . .BR dterc (5), .BR dte\-syntax (5) . .SH AUTHORS . Craig Barnes .br Timo Hirvonen dte-1.9.1/docs/dterc.5000066400000000000000000001032101354415143300144150ustar00rootroot00000000000000.TH DTERC 5 "March 2019" .nh .ad l . .SH NAME dterc \- Command and configuration language used by \fBdte\fR(1) .SH SYNOPSIS .P Commands: .br .P Configuration Commands: .br \fBalias\fR \fIname\fR \fIcommand\fR .br \fBbind\fR \fIkey\fR [\fIcommand\fR] .br \fBset\fR [\fB\-gl\fR] \fIoption\fR [\fIvalue\fR] ... .br \fBsetenv\fR \fIname\fR \fIvalue\fR .br \fBhi\fR \fIname\fR [\fIfg\-color\fR [\fIbg\-color\fR]] [\fIattribute\fR]... .br \fBft\fR [\fB\-bcfi\fR] \fIfiletype\fR \fIstring\fR... .br \fBoption\fR [\fB\-r\fR] \fIfiletype\fR \fIoption\fR \fIvalue\fR... .br \fBinclude\fR [\fB\-b\fR] \fIfile\fR .br \fBerrorfmt\fR [\fB\-i\fR] \fIcompiler\fR \fIregexp\fR [file|line|column|message]... .br \fBload\-syntax\fR \fIfilename\fR|\fIfiletype\fR .br .P Editor Commands: .br \fBquit\fR [\fB\-fp\fR] .br \fBsuspend\fR .br \fBcd\fR \fIdirectory\fR .br \fBcommand\fR [\fItext\fR] .br \fBsearch\fR [\fB\-Hnprw\fR] [\fIpattern\fR] .br \fBgit\-open\fR .br \fBrefresh\fR .br .P Buffer Management Commands: .br \fBopen\fR [\fB\-g\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR]... .br \fBsave\fR [\fB\-dfup\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR] .br \fBclose\fR [\fB\-fqw\fR] .br \fBnext\fR .br \fBprev\fR .br \fBview\fR \fIN\fR|last .br \fBmove\-tab\fR \fIN\fR|left|right .br .P Window Management Commands: .br \fBwsplit\fR [\fB\-bhr\fR] [\fIfile\fR]... .br \fBwclose\fR [\fB\-f\fR] .br \fBwnext\fR .br \fBwprev\fR .br \fBwresize\fR [\fB\-hv\fR] [\fIN\fR|+\fIN\fR|\-\- \-\fIN\fR] .br \fBwflip\fR .br \fBwswap\fR .br .P Movement Commands: .br \fBleft\fR [\fB\-c\fR] .br \fBright\fR [\fB\-c\fR] .br \fBup\fR [\fB\-cl\fR] .br \fBdown\fR [\fB\-cl\fR] .br \fBpgup\fR [\fB\-cl\fR] .br \fBpgdown\fR [\fB\-cl\fR] .br \fBword\-fwd\fR [\fB\-cs\fR] .br \fBword\-bwd\fR [\fB\-cs\fR] .br \fBbol\fR [\fB\-cs\fR] .br \fBeol\fR [\fB\-c\fR] .br \fBbof\fR .br \fBeof\fR .br \fBbolsf\fR .br \fBeolsf\fR .br \fBscroll\-up\fR .br \fBscroll\-down\fR .br \fBscroll\-pgup\fR .br \fBscroll\-pgdown\fR .br \fBcenter\-view\fR .br \fBline\fR \fInumber\fR .br \fBtag\fR [\fB\-r\fR] [\fItag\fR] .br \fBmsg\fR [\fB\-np\fR] .br .P Editing Commands: .br \fBcut\fR .br \fBcopy\fR [\fB\-k\fR] .br \fBpaste\fR [\fB\-c\fR] .br \fBundo\fR .br \fBredo\fR [\fIchoice\fR] .br \fBclear\fR .br \fBjoin\fR .br \fBnew\-line\fR .br \fBdelete\fR .br \fBerase\fR .br \fBdelete\-eol\fR [\fB\-n\fR] .br \fBerase\-bol\fR .br \fBdelete\-word\fR [\fB\-s\fR] .br \fBerase\-word\fR [\fB\-s\fR] .br \fBcase\fR [\fB\-lu\fR] .br \fBinsert\fR [\fB\-km\fR] \fItext\fR .br \fBreplace\fR [\fB\-bcgi\fR] \fIpattern\fR \fIreplacement\fR .br \fBshift\fR \fIcount\fR .br \fBwrap\-paragraph\fR [\fIwidth\fR] .br \fBselect\fR [\fB\-bkl\fR] .br \fBunselect\fR .br .P External Commands: .br \fBfilter\fR \fIcommand\fR [\fIparameter\fR]... .br \fBpipe\-from\fR [\fB\-ms\fR] \fIcommand\fR [\fIparameter\fR]... .br \fBpipe\-to\fR \fIcommand\fR [\fIparameter\fR]... .br \fBrun\fR [\fB\-ps\fR] \fIcommand\fR [\fIparameters\fR]... .br \fBcompile\fR [\fB\-1ps\fR] \fIerrorfmt\fR \fIcommand\fR [\fIparameters\fR]... .br \fBeval\fR \fIcommand\fR [\fIparameter\fR]... .br .P Other Commands: .br \fBrepeat\fR \fIcount\fR \fIcommand\fR [\fIparameters\fR]... .br \fBtoggle\fR [\fB\-gv\fR] \fIoption\fR [\fIvalues\fR]... .br \fBshow\fR [\fB\-c\fR] \fItype\fR [\fIkey\fR] .br .P Options: .br .P Global options: .br \fBcase\-sensitive\-search\fR [true] .br \fBdisplay\-invisible\fR [false] .br \fBdisplay\-special\fR [false] .br \fBesc\-timeout\fR [100] 0...2000 .br \fBfilesize\-limit\fR [250] .br \fBlock\-files\fR [true] .br \fBnewline\fR [unix] .br \fBscroll\-margin\fR [0] .br \fBset\-window\-title\fR [false] .br \fBshow\-line\-numbers\fR [false] .br \fBstatusline\-left\fR [" %f%s%m%r%s%M"] .br \fBstatusline\-right\fR [" %y,%X %u %E %n %t %p "] .br \fBtab\-bar\fR [horizontal] .br \fBtab\-bar\-max\-components\fR [0] .br \fBtab\-bar\-width\fR [25] .br .P Local options: .br \fBbrace\-indent\fR [false] .br \fBfiletype\fR [none] .br \fBindent\-regex\fR [""] .br .P Local and global options: .br \fBauto\-indent\fR [true] .br \fBdetect\-indent\fR [""] .br \fBemulate\-tab\fR [false] .br \fBexpand\-tab\fR [false] .br \fBfile\-history\fR [true] .br \fBindent\-width\fR [8] .br \fBsyntax\fR [true] .br \fBtab\-width\fR [8] .br \fBtext\-width\fR [72] .br \fBws\-error\fR [special] .br .SH DESCRIPTION dterc is the language used in \fBdte\fR(1) configuration files (\fB~/.dte/rc\fR) and also in the command mode of the editor (Alt+x). The syntax of the language is quite similar to shell, but much simpler. .P Commands are separated either by a newline or \fB;\fR character. To make a command span multiple lines in an rc file, escape the newline (put \fB\\\fR at the end of the line). .P Rc files can contain comments at the start of a line. Comments begin with a \fB#\fR character and can be indented, but they can't be put on the same line as a command. .P Commands can contain environment variables. Variables always expand into a single argument even if they contain whitespace. Variables inside single or double quotes are NOT expanded. This makes it possible to bind keys to commands that contain variables (inside single or double quotes), which will be expanded just before the command is executed. .P Example: .P .IP .nf \f[C] alias\ x\ "run\ chmod\ 755\ $FILE" \f[] .fi .PP \fB$FILE\fR is expanded when the alias \fIx\fR is executed. The command works even if \fB$FILE\fR contains whitespace. .P .SS Special variables These variables are always defined and override environment variables of the same name. .P \fB$FILE\fR .RS The filename of the current buffer (or an empty string if unsaved). .P .RE \fB$FILETYPE\fR .RS The value of the \fBfiletype\fR option for the current buffer. .P .RE \fB$LINENO\fR .RS The line number of the cursor in the current buffer. .P .RE \fB$WORD\fR .RS The selected text or the word under the cursor. .P .RE .SS Single quoted strings Single quoted strings can't contain single quotes or escaped characters. .P .SS Double quoted strings Double quoted strings may contain the following escapes: .P .TP \fB\\a\fR, \fB\\b\fR, \fB\\t\fR, \fB\\n\fR, \fB\\v\fR, \fB\\f\fR, \fB\\r\fR Control characters (same as in C) .PP .TP \fB\\\\\fR Escaped backslash .PP .TP \fB\\x0a\fR Hexadecimal byte value 0x0a. Note that \fB\\x00\fR is not supported because strings are NUL\-terminated. .PP .TP \fB\\u20ac\fR Four hex digit Unicode code point U+20AC. .PP .TP \fB\\U000020ac\fR Eight hex digit Unicode code point U+20AC. .PP .SH COMMANDS .SS Configuration Commands Configuration commands are used to customize certain aspects of the editor, for example adding key bindings, setting options, etc. These are the only commands allowed in user config files. .P \fBalias\fR \fIname\fR \fIcommand\fR .RS Create an alias \fIname\fR for \fIcommand\fR. .P Example: .P .IP .nf \f[C] alias\ read\ 'pipe\-from\ cat' \f[] .fi .PP Now you can run \fBread file.txt\fR to insert \fBfile.txt\fR into the current buffer. .P .RE \fBbind\fR \fIkey\fR [\fIcommand\fR] .RS Bind \fIcommand\fR to \fIkey\fR. If no \fIcommand\fR is given then any existing binding for \fIkey\fR is removed. .P Special keys: .P \(bu \fBleft\fR .br \(bu \fBright\fR .br \(bu \fBup\fR .br \(bu \fBdown\fR .br \(bu \fBinsert\fR .br \(bu \fBdelete\fR .br \(bu \fBhome\fR .br \(bu \fBend\fR .br \(bu \fBpgup\fR .br \(bu \fBpgdown\fR .br \(bu \fBenter\fR .br \(bu \fBtab\fR .br \(bu \fBspace\fR .br \(bu \fBF1\fR..\fBF12\fR .br .P Modifiers: .P .TP Ctrl: \fBC\-X\fR or \fB^X\fR .PP .TP Alt: \fBM\-X\fR .PP .TP Shift: \fBS\-left\fR .PP .RE \fBset\fR [\fB\-gl\fR] \fIoption\fR [\fIvalue\fR] ... .RS Set \fIvalue\fR for \fIoption\fR. Value can be omitted for boolean option to set it true. Multiple options can be set at once but then \fIvalue\fR must be given for every option. .P There are three kinds of options. .P 1. Global options. .P . .br 2. Local options. These are file specific options. Each open file has its own copies of the option values. .P . .br 3. Options that have both global and local values. The Global value is just a default local value for opened files and is never used for anything else. Changing the global value does not affect any already opened files. .P . .br .P By default \fBset\fR changes both global and local values. .P .TP \fB\-g\fR Change only global option value .PP .TP \fB\-l\fR Change only local option value of current file .PP In configuration files only global options can be set (no need to specify the \fB\-g\fR flag). .P See also: \fBtoggle\fR and \fBoption\fR commands. .P .RE \fBsetenv\fR \fIname\fR \fIvalue\fR .RS Set environment variable. .P .RE \fBhi\fR \fIname\fR [\fIfg\-color\fR [\fIbg\-color\fR]] [\fIattribute\fR]... .RS Set highlight color. .P The \fIname\fR argument can be a token name defined by a \fBdte\-syntax\fR(5) file or one of the following, built\-in highlight names: .P \(bu \fBdefault\fR .br \(bu \fBnontext\fR .br \(bu \fBnoline\fR .br \(bu \fBwserror\fR .br \(bu \fBselection\fR .br \(bu \fBcurrentline\fR .br \(bu \fBlinenumber\fR .br \(bu \fBstatusline\fR .br \(bu \fBcommandline\fR .br \(bu \fBerrormsg\fR .br \(bu \fBinfomsg\fR .br \(bu \fBtabbar\fR .br \(bu \fBactivetab\fR .br \(bu \fBinactivetab\fR .br .P The \fIfg\-color\fR and \fIbg\-color\fR arguments can be one of the following: .P \(bu No value (equivalent to \fBdefault\fR) .br \(bu A numeric value between \fB\-2\fR and \fB255\fR .br \(bu A 256\-color palette value in R/G/B notation (e.g. \fB0/3/5\fR) .br \(bu A true color value in CSS\-style #RRGGBB notation (e.g. \fB#ab90df\fR) .br \(bu \fBkeep\fR (\fB\-2\fR) .br \(bu \fBdefault\fR (\fB\-1\fR) .br \(bu \fBblack\fR (\fB0\fR) .br \(bu \fBred\fR (\fB1\fR) .br \(bu \fBgreen\fR (\fB2\fR) .br \(bu \fByellow\fR (\fB3\fR) .br \(bu \fBblue\fR (\fB4\fR) .br \(bu \fBmagenta\fR (\fB5\fR) .br \(bu \fBcyan\fR (\fB6\fR) .br \(bu \fBgray\fR (\fB7\fR) .br \(bu \fBdarkgray\fR (\fB8\fR) .br \(bu \fBlightred\fR (\fB9\fR) .br \(bu \fBlightgreen\fR (\fB10\fR) .br \(bu \fBlightyellow\fR (\fB11\fR) .br \(bu \fBlightblue\fR (\fB12\fR) .br \(bu \fBlightmagenta\fR (\fB13\fR) .br \(bu \fBlightcyan\fR (\fB14\fR) .br \(bu \fBwhite\fR (\fB15\fR) .br .P Colors \fB16\fR to \fB231\fR correspond to R/G/B colors. Colors \fB232\fR to \fB255\fR are grayscale values. .P If the terminal has limited support for rendering colors, the \fIfg\-color\fR and \fIbg\-color\fR arguments will fall back to the nearest supported color, which may be less precise than the value specified. .P The \fIattribute\fR argument(s) can be any combination of the following: .P \(bu \fBbold\fR .br \(bu \fBdim\fR .br \(bu \fBitalic\fR .br \(bu \fBunderline\fR .br \(bu \fBstrikethrough\fR .br \(bu \fBblink\fR .br \(bu \fBreverse\fR .br \(bu \fBinvisible\fR .br \(bu \fBkeep\fR .br .P The color and attribute value \fBkeep\fR is useful in selected text to keep \fIfg\-color\fR and attributes and change only \fIbg\-color\fR. .P NOTE: Because \fBkeep\fR is both a color and an attribute you need to specify both \fIfg\-color\fR and \fIbg\-color\fR if you want to set the \fBkeep\fR \fIattribute\fR. .P Unset fg/bg colors are inherited from highlight color \fBdefault\fR. If you don't set fg/bg for the highlight color \fBdefault\fR then terminal's default fg/bg is used. .P .RE \fBft\fR [\fB\-bcfi\fR] \fIfiletype\fR \fIstring\fR... .RS Add a filetype association. Filetypes are used to determine which syntax highlighter and local options to use when opening files. .P By default \fIstring\fR is interpreted as one or more filename extensions. .P .TP \fB\-b\fR Interpret \fIstring\fR as a file basename .PP .TP \fB\-c\fR Interpret \fIstring\fR as a regex pattern and match against the contents of the first line of the file .PP .TP \fB\-f\fR Interpret \fIstring\fR as a regex pattern and match against the full (absolute) filename .PP .TP \fB\-i\fR Interpret \fIstring\fR as a command interpretter name and match against the Unix shebang line (after removing any path prefix and/or version suffix) .PP Examples: .P .IP .nf \f[C] ft\ c\ c\ h ft\ \-b\ make\ Makefile\ GNUmakefile ft\ \-c\ xml\ '<\\?xml' ft\ \-f\ mail\ '/tmpmsg\-.*\\.txt$' ft\ \-i\ lua\ lua\ luajit \f[] .fi .PP See also: .P \(bu The \fBoption\fR command (below) .br \(bu The \fBfiletype\fR option (below) .br \(bu The \fBdte\-syntax\fR(5) man page .br .P .RE \fBoption\fR [\fB\-r\fR] \fIfiletype\fR \fIoption\fR \fIvalue\fR... .RS Add automatic \fIoption\fR for \fIfiletype\fR (as previously registered with the \fBft\fR command). Automatic options are set when files are are opened. .P .TP \fB\-r\fR Interpret \fIfiletype\fR argument as a regex pattern instead of a filetype and match against full filenames .PP .RE \fBinclude\fR [\fB\-b\fR] \fIfile\fR .RS Read and execute commands from \fIfile\fR. .P .TP \fB\-b\fR Read built\-in \fIfile\fR instead of reading from the filesystem .PP Note: "built\-in files" are config files bundled into the program binary. See the \fB\-B\fR and \fB\-b\fR flags in the \fBdte\fR(1) man page for more information. .P .RE \fBerrorfmt\fR [\fB\-i\fR] \fIcompiler\fR \fIregexp\fR [file|line|column|message]... .RS .TP \fB\-i\fR Ignore this error .PP See \fBcompile\fR and \fBmsg\fR commands for more information. .P .RE \fBload\-syntax\fR \fIfilename\fR|\fIfiletype\fR .RS Load a \fBdte\-syntax\fR(5) file into the editor. If the argument contains a \fB/\fR character it's considered a filename. .P Note: this command only loads a syntax file ready for later use. To actually apply a syntax highlighter to the current buffer, use the \fBset\fR command to change the \fBfiletype\fR of the buffer instead, e.g. \fBset filetype html\fR. .P .RE .SS Editor Commands \fBquit\fR [\fB\-fp\fR] .RS Quit the editor. .P .TP \fB\-f\fR Force quit, even if there are unsaved files .PP .TP \fB\-p\fR Prompt for confirmation if there are unsaved files .PP .RE \fBsuspend\fR .RS Suspend the editor (run \fBfg\fR in the shell to resume). .P .RE \fBcd\fR \fIdirectory\fR .RS Change the working directory and update \fB$PWD\fR and \fB$OLDPWD\fR. Running \fBcd \-\fR changes to the previous directory (\fB$OLDPWD\fR). .P .RE \fBcommand\fR [\fItext\fR] .RS Enter command mode. If \fItext\fR is given then it is written to the command line (see the default \fB^L\fR key binding for why this is useful). .P .RE \fBsearch\fR [\fB\-Hnprw\fR] [\fIpattern\fR] .RS If no flags or just \fB\-r\fR and no \fIpattern\fR given then dte changes to search mode where you can type a regular expression to search. .P .TP \fB\-H\fR Don't add \fIpattern\fR to search history .PP .TP \fB\-n\fR Search next .PP .TP \fB\-p\fR Search previous .PP .TP \fB\-r\fR Start searching backwards .PP .TP \fB\-w\fR Search word under cursor .PP .RE \fBgit\-open\fR .RS Interactive file opener. Lists all files in a git repository. .P Same keys work as in command mode, but with these changes: .P .TP \fBup\fR Move up in file list. .PP .TP \fBdown\fR Move down in file list. .PP .TP \fBenter\fR Open file. .PP .TP \fB^O\fR Open file but don't close git\-open. .PP .TP \fBM\-e\fR Go to end of file list. .PP .TP \fBM\-t\fR Go to top of file list. .PP .RE \fBrefresh\fR .RS Trigger a full redraw of the screen. .P .RE .SS Buffer Management Commands \fBopen\fR [\fB\-g\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR]... .RS Open file. If \fIfilename\fR is omitted, a new file is opened. .P .TP \fB\-e\fR \fIencoding\fR Set file \fIencoding\fR. See \fBiconv \-l\fR for list of supported encodings. .PP .TP \fB\-g\fR Perform \fBglob\fR(3) expansion on \fIfilename\fR. .PP .RE \fBsave\fR [\fB\-dfup\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR] .RS Save file. By default line\-endings (LF vs CRLF) are preserved. .P .TP \fB\-d\fR Save with DOS/CRLF line\-endings .PP .TP \fB\-f\fR Force saving read\-only file .PP .TP \fB\-u\fR Save with Unix/LF line\-endings .PP .TP \fB\-p\fR Open a command prompt if there's no specified or existing \fIfilename\fR .PP .TP \fB\-e\fR \fIencoding\fR Set file \fIencoding\fR. See \fBiconv \-l\fR for list of supported encodings. .PP .RE \fBclose\fR [\fB\-fqw\fR] .RS Close file. .P .TP \fB\-f\fR Close file even if it hasn't been saved after last modification .PP .TP \fB\-q\fR Quit if closing the last open file .PP .TP \fB\-w\fR Close parent window if closing its last contained file .PP .RE \fBnext\fR .RS Display next file. .P .RE \fBprev\fR .RS Display previous file. .P .RE \fBview\fR \fIN\fR|last .RS Display _N_th or last open file. .P .RE \fBmove\-tab\fR \fIN\fR|left|right .RS Move current tab to position \fIN\fR or 1 position left or right. .P .RE .SS Window Management Commands \fBwsplit\fR [\fB\-bhr\fR] [\fIfile\fR]... .RS Like \fBopen\fR but at first splits current window vertically. .P .TP \fB\-b\fR Add new window before current instead of after. .PP .TP \fB\-h\fR Split horizontally instead of vertically. .PP .TP \fB\-r\fR Split root instead of current window. .PP .RE \fBwclose\fR [\fB\-f\fR] .RS Close window. .P .TP \fB\-f\fR Close even if there are unsaved files in the window .PP .RE \fBwnext\fR .RS Next window. .P .RE \fBwprev\fR .RS Previous window. .P .RE \fBwresize\fR [\fB\-hv\fR] [\fIN\fR|+\fIN\fR|\-\- \-\fIN\fR] .RS If no parameter given, equalize window sizes in current frame. .P .TP \fB\-h\fR Resize horizontally .PP .TP \fB\-v\fR Resize vertically .PP .TP \fIN\fR Set size of current window to \fIN\fR characters. .PP .TP \fB+\fR\fIN\fR Increase size of current window by \fIN\fR characters. .PP .TP \fB\-\fR\fIN\fR Decrease size of current window by \fIN\fR characters. Use \fB\-\-\fR to prevent the minus symbol being parsed as an option flag, e.g. \fBwresize \-\- \-5\fR. .PP .RE \fBwflip\fR .RS Change from vertical layout to horizontal and vice versa. .P .RE \fBwswap\fR .RS Swap positions of this and next frame. .P .RE .SS Movement Commands \fBleft\fR [\fB\-c\fR] .RS Move left. .P .TP \fB\-c\fR Select characters .PP .RE \fBright\fR [\fB\-c\fR] .RS Move right. .P .TP \fB\-c\fR Select characters .PP .RE \fBup\fR [\fB\-cl\fR] .RS Move cursor up. .P .TP \fB\-c\fR Select characters .PP .TP \fB\-l\fR Select whole lines .PP .RE \fBdown\fR [\fB\-cl\fR] .RS Move cursor down. .P .TP \fB\-c\fR Select characters .PP .TP \fB\-l\fR Select whole lines .PP .RE \fBpgup\fR [\fB\-cl\fR] .RS Move cursor page up. See also \fBscroll\-pgup\fR. .P .TP \fB\-c\fR Select characters .PP .TP \fB\-l\fR Select whole lines .PP .RE \fBpgdown\fR [\fB\-cl\fR] .RS Move cursor page down. See also \fBscroll\-pgdown\fR. .P .TP \fB\-c\fR Select characters .PP .TP \fB\-l\fR Select whole lines .PP .RE \fBword\-fwd\fR [\fB\-cs\fR] .RS Move cursor forward one word. .P .TP \fB\-c\fR Select characters .PP .TP \fB\-s\fR Skip special characters .PP .RE \fBword\-bwd\fR [\fB\-cs\fR] .RS Move cursor backward one word. .P .TP \fB\-c\fR Select characters .PP .TP \fB\-s\fR Skip special characters .PP .RE \fBbol\fR [\fB\-cs\fR] .RS Move to beginning of line. .P .TP \fB\-c\fR Select characters .PP .TP \fB\-s\fR Move to beginning of indented text or beginning of line, depending on current cursor position. .PP .RE \fBeol\fR [\fB\-c\fR] .RS Move cursor to end of line. .P .TP \fB\-c\fR Select characters .PP .RE \fBbof\fR .RS Move to beginning of file. .P .RE \fBeof\fR .RS Move cursor to end of file. .P .RE \fBbolsf\fR .RS Incrementally move cursor to beginning of line, then beginning of screen, then beginning of file. .P .RE \fBeolsf\fR .RS Incrementally move cursor to end of line, then end of screen, then end of file. .P .RE \fBscroll\-up\fR .RS Scroll view up one line. Keeps cursor position unchanged if possible. .P .RE \fBscroll\-down\fR .RS Scroll view down one line. Keeps cursor position unchanged if possible. .P .RE \fBscroll\-pgup\fR .RS Scroll page up. Cursor position relative to top of screen is maintained. See also \fBpgup\fR. .P .RE \fBscroll\-pgdown\fR .RS Scroll page down. Cursor position relative to top of screen is maintained. See also \fBpgdown\fR. .P .RE \fBcenter\-view\fR .RS Center view to cursor. .P .RE \fBline\fR \fInumber\fR .RS Go to line. .P .RE \fBtag\fR [\fB\-r\fR] [\fItag\fR] .RS Save current location to stack and go to the location of \fItag\fR. Requires tags file generated by Exuberant Ctags. If no \fItag\fR is given then word under cursor is used as a tag instead. .P .TP \fB\-r\fR return back to previous location .PP Tag files are searched from current working directory and its parent directories. .P See also \fBmsg\fR command. .P .RE \fBmsg\fR [\fB\-np\fR] .RS Show latest, next (\fB\-n\fR) or previous (\fB\-p\fR) message. If its location is known (compile error or tag message) then the file will be opened and cursor moved to the location. .P .TP \fB\-n\fR Next message .PP .TP \fB\-p\fR Previous message .PP See also \fBcompile\fR and \fBtag\fR commands. .P .RE .SS Editing Commands \fBcut\fR .RS Cut current line or selection. .P .RE \fBcopy\fR [\fB\-k\fR] .RS Copy current line or selection. .P .TP \fB\-k\fR Keep selection (by default, selections are lost after copying) .PP .RE \fBpaste\fR [\fB\-c\fR] .RS Paste text previously copied by the \fBcopy\fR or \fBcut\fR commands. .P .TP \fB\-c\fR Paste at the cursor position .PP .RE \fBundo\fR .RS Undo latest change. .P .RE \fBredo\fR [\fIchoice\fR] .RS Redo changes done by the \fBundo\fR command. If there are multiple possibilities a message is displayed: .P .IP .nf \f[C] Redoing\ newest\ (2)\ of\ 2\ possible\ changes. \f[] .fi .PP If the change was not the one you wanted, just run \fBundo\fR and then, for example, \fBredo 1\fR. .P .RE \fBclear\fR .RS Clear current line. .P .RE \fBjoin\fR .RS Join selection or next line to current. .P .RE \fBnew\-line\fR .RS Insert empty line under current line. .P .RE \fBdelete\fR .RS Delete character after cursor (or selection). .P .RE \fBerase\fR .RS Delete character before cursor (or selection). .P .RE \fBdelete\-eol\fR [\fB\-n\fR] .RS Delete to end of line. .P .TP \fB\-n\fR Delete newline if cursor is at end of line .PP .RE \fBerase\-bol\fR .RS Erase to beginning of line. .P .RE \fBdelete\-word\fR [\fB\-s\fR] .RS Delete word after cursor. .P .TP \fB\-s\fR Be more "aggressive" .PP .RE \fBerase\-word\fR [\fB\-s\fR] .RS Erase word before cursor. .P .TP \fB\-s\fR Be more "aggressive" .PP .RE \fBcase\fR [\fB\-lu\fR] .RS Change text case. The default is to change lower case to upper case and vice versa. .P .TP \fB\-l\fR Lower case .PP .TP \fB\-u\fR Upper case .PP .RE \fBinsert\fR [\fB\-km\fR] \fItext\fR .RS Insert \fItext\fR into the buffer. .P .TP \fB\-k\fR Insert one character at a time as if it has been typed .PP .TP \fB\-m\fR Move after inserted text .PP .RE \fBreplace\fR [\fB\-bcgi\fR] \fIpattern\fR \fIreplacement\fR .RS Replace all instances of text matching \fIpattern\fR with the \fIreplacement\fR text. .P The \fIpattern\fR is a POSIX extended \fBregex\fR(7). .P .TP \fB\-b\fR Use basic instead of extended regex syntax .PP .TP \fB\-c\fR Ask for confirmation before each replacement .PP .TP \fB\-g\fR Replace all matches for each line (instead of just the first) .PP .TP \fB\-i\fR Ignore case .PP .RE \fBshift\fR \fIcount\fR .RS Shift current or selected lines by \fIcount\fR indentation levels. Count is usually \fB\-1\fR (decrease indent) or \fB1\fR (increase indent). .P To specify a negative number, it's necessary to first disable option parsing with \fB\-\-\fR, e.g. \fBshift \-\- \-1\fR. .P .RE \fBwrap\-paragraph\fR [\fIwidth\fR] .RS Format the current selection or paragraph under the cursor. If paragraph \fIwidth\fR is not given then the \fBtext\-width\fR option is used. .P This command merges the selection into one paragraph. To format multiple paragraphs use the external \fBfmt\fR(1) program with the \fBfilter\fR command, e.g. \fBfilter fmt \-w 60\fR. .P .RE \fBselect\fR [\fB\-bkl\fR] .RS Enter selection mode. All movement commands while in this mode extend the selected area. .P Note: A better way to create selections is to hold the Shift key whilst moving the cursor. The \fBselect\fR command exists mostly as a fallback, for terminals with limited key binding support. .P .TP \fB\-b\fR Select block between opening \fB{\fR and closing \fB}\fR curly braces .PP .TP \fB\-k\fR Keep existing selections .PP .TP \fB\-l\fR Select whole lines .PP .RE \fBunselect\fR .RS Unselect. .P .RE .SS External Commands \fBfilter\fR \fIcommand\fR [\fIparameter\fR]... .RS Filter selected text or whole file through external \fIcommand\fR. .P Example: .P .IP .nf \f[C] filter\ sort\ \-r \f[] .fi .PP Note that \fIcommand\fR is executed directly using \fBexecvp\fR(3). To use shell features like pipes or redirection, use a shell interpreter as the \fIcommand\fR. For example: .P .IP .nf \f[C] filter\ sh\ \-c\ 'tr\ a\-z\ A\-Z\ |\ sed\ s/foo/bar/' \f[] .fi .PP .RE \fBpipe\-from\fR [\fB\-ms\fR] \fIcommand\fR [\fIparameter\fR]... .RS Run external \fIcommand\fR and insert its standard output. .P .TP \fB\-m\fR Move after the inserted text .PP .TP \fB\-s\fR Strip newline from end of output .PP .RE \fBpipe\-to\fR \fIcommand\fR [\fIparameter\fR]... .RS Run external \fIcommand\fR and pipe the selected text (or whole file) to its standard input. .P Can be used to e.g. write text to the system clipboard: .P .IP .nf \f[C] pipe\-to\ xsel\ \-b \f[] .fi .PP .RE \fBrun\fR [\fB\-ps\fR] \fIcommand\fR [\fIparameters\fR]... .RS Run external \fIcommand\fR. .P .TP \fB\-p\fR Display "Press any key to continue" prompt .PP .TP \fB\-s\fR Silent \-\- both \fBstderr\fR and \fBstdout\fR are redirected to \fB/dev/null\fR .PP .RE \fBcompile\fR [\fB\-1ps\fR] \fIerrorfmt\fR \fIcommand\fR [\fIparameters\fR]... .RS Run external \fIcommand\fR and collect output messages. This can be used to run e.g. compilers, build systems, code search utilities, etc. and then jump to a file/line position for each message. .P The \fIerrorfmt\fR argument corresponds to a regex capture pattern previously specified by the \fBerrorfmt\fR command. After \fIcommand\fR exits successfully, parsed messages can be navigated using the \fBmsg\fR command. .P .TP \fB\-1\fR Read error messages from stdout instead of stderr .PP .TP \fB\-p\fR Display "Press any key to continue" prompt .PP .TP \fB\-s\fR Silent. Both \fBstderr\fR and \fBstdout\fR are redirected to \fB/dev/null\fR .PP See also: \fBerrorfmt\fR and \fBmsg\fR commands. .P .RE \fBeval\fR \fIcommand\fR [\fIparameter\fR]... .RS Run external \fIcommand\fR and execute its standard output text as dterc commands. .P .RE .SS Other Commands \fBrepeat\fR \fIcount\fR \fIcommand\fR [\fIparameters\fR]... .RS Run \fIcommand\fR \fIcount\fR times. .P .RE \fBtoggle\fR [\fB\-gv\fR] \fIoption\fR [\fIvalues\fR]... .RS Toggle \fIoption\fR. If list of \fIvalues\fR is not given then the option must be either boolean or enum. .P .TP \fB\-g\fR toggle global option instead of local .PP .TP \fB\-v\fR display new value .PP If \fIoption\fR has both local and global values then local is toggled unless \fB\-g\fR is used. .P .RE \fBshow\fR [\fB\-c\fR] \fItype\fR [\fIkey\fR] .RS Display current values for various configurable types. .P The \fItype\fR argument can be one of: .P .TP \fBalias\fR show command aliases .PP .TP \fBbind\fR show key bindings .PP The \fIkey\fR argument is the name of the entry to lookup (i.e. alias name or key string). If this argument is specified, the value will be displayed in the status line. If omitted, a pager will be opened displaying all entries of the specified type. .P .TP \fB\-c\fR write value to command line instead of status line .PP .RE .SH OPTIONS Options can be changed using the \fBset\fR command. Enumerated options can also be \fBtoggle\fRd. To see which options are enumerated, type "toggle " in command mode and press the tab key. You can also use the \fBoption\fR command to set default options for specific file types. .P .SS Global options \fBcase\-sensitive\-search\fR [true] .RS .TP \fBfalse\fR Search is case\-insensitive. .PP .TP \fBtrue\fR Search is case\-sensitive. .PP .TP \fBauto\fR If search string contains an uppercase letter search is case\-sensitive, otherwise it is case\-insensitive. .PP .RE \fBdisplay\-invisible\fR [false] .RS Display invisible characters. .P .RE \fBdisplay\-special\fR [false] .RS Display special characters. .P .RE \fBesc\-timeout\fR [100] 0...2000 .RS When single escape is read from the terminal dte waits some time before treating the escape as a single keypress. The timeout value is in milliseconds. .P Too long timeout makes escape key feel slow and too small timeout can cause escape sequences of for example arrow keys to be split and treated as multiple key presses. .P .RE \fBfilesize\-limit\fR [250] .RS Refuse to open any file with a size larger than this value (in mebibytes). Useful to prevent accidentally opening very large files, which can take a long time on some systems. .P .RE \fBlock\-files\fR [true] .RS Lock files using \fB$DTE_HOME/file\-locks\fR. Only protects from your own mistakes (two processes editing same file). .P .RE \fBnewline\fR [unix] .RS Whether to use LF (\fIunix\fR) or CRLF (\fIdos\fR) line\-endings. This is just a default value for new files. .P .RE \fBscroll\-margin\fR [0] .RS Minimum number of lines to keep visible before and after cursor. .P .RE \fBset\-window\-title\fR [false] .RS Set the window title to the filename of the current buffer (if the terminal supports it). .P .RE \fBshow\-line\-numbers\fR [false] .RS Show line numbers. .P .RE \fBstatusline\-left\fR [" %f%s%m%r%s%M"] .RS Format string for the left aligned part of status line. .P .TP \fB%f\fR Filename. .PP .TP \fB%m\fR Prints \fB*\fR if file is has been modified since last save. .PP .TP \fB%r\fR Prints \fBRO\fR if file is read\-only. .PP .TP \fB%y\fR Cursor row. .PP .TP \fB%Y\fR Total rows in file. .PP .TP \fB%x\fR Cursor display column. .PP .TP \fB%X\fR Cursor column as characters. If it differs from cursor display column then both are shown (e.g. \fB2\-9\fR). .PP .TP \fB%p\fR Position in percentage. .PP .TP \fB%E\fR File encoding. .PP .TP \fB%M\fR Miscellaneous status information. .PP .TP \fB%n\fR Line\-ending (LF or CRLF). .PP .TP \fB%s\fR Add separator. .PP .TP \fB%t\fR File type. .PP .TP \fB%u\fR Hexadecimal Unicode value value of character under cursor. .PP .TP \fB%%\fR Literal \fB%\fR. .PP .RE \fBstatusline\-right\fR [" %y,%X %u %E %n %t %p "] .RS Format string for the right aligned part of status line. .P .RE \fBtab\-bar\fR [horizontal] .RS .TP \fBhidden\fR Hide tab bar. .PP .TP \fBhorizontal\fR Show tab bar on top. .PP .TP \fBvertical\fR Show tab bar on left if there's enough space, hide otherwise. .PP .TP \fBauto\fR Show tab bar on left if there's enough space, on top otherwise. .PP .RE \fBtab\-bar\-max\-components\fR [0] .RS Maximum number of path components displayed in vertical tab bar. Set to \fB0\fR to disable. .P .RE \fBtab\-bar\-width\fR [25] .RS Width of vertical tab bar. Note that width of tab bar is automatically reduced to keep editing area at least 80 characters wide. Vertical tab bar is shown only if there's enough space. .P .RE .SS Local options \fBbrace\-indent\fR [false] .RS Scan for \fB{\fR and \fB}\fR characters when calculating indentation size. Depends on the \fBauto\-indent\fR option. .P .RE \fBfiletype\fR [none] .RS Type of file. Value must be previously registered using the \fBft\fR command. .P .RE \fBindent\-regex\fR [""] .RS If this regular expression matches current line when enter is pressed and \fBauto\-indent\fR is true then indentation is increased. Set to \fB""\fR to disable. .P .RE .SS Local and global options The global values for these options serve as the default values for local (per\-file) options. .P \fBauto\-indent\fR [true] .RS Automatically insert indentation when pressing enter. Indentation is copied from previous non\-empty line. If also the \fBindent\-regex\fR local option is set then indentation is automatically increased if the regular expression matches current line. .P .RE \fBdetect\-indent\fR [""] .RS Comma\-separated list of indent widths (\fB1\fR\-\fB8\fR) to detect automatically when a file is opened. Set to \fB""\fR to disable. Tab indentation is detected if the value is not \fB""\fR. Adjusts the following options if indentation style is detected: \fBemulate\-tab\fR, \fBexpand\-tab\fR, \fBindent\-width\fR. .P Example: .P .IP .nf \f[C] set\ detect\-indent\ 2,3,4,8 \f[] .fi .PP .RE \fBemulate\-tab\fR [false] .RS Make \fBdelete\fR, \fBerase\fR and moving \fBleft\fR and \fBright\fR inside indentation feel as if there were tabs instead of spaces. .P .RE \fBexpand\-tab\fR [false] .RS Convert tab to spaces on insert. .P .RE \fBfile\-history\fR [true] .RS Save line and column for each file to \fB$DTE_HOME/file\-history\fR. .P .RE \fBindent\-width\fR [8] .RS Size of indentation in spaces. .P .RE \fBsyntax\fR [true] .RS Use syntax highlighting. .P .RE \fBtab\-width\fR [8] .RS Width of tab. Recommended value is \fB8\fR. If you use other indentation size than \fB8\fR you should use spaces to indent. .P .RE \fBtext\-width\fR [72] .RS Preferred width of text. Used as the default argument for the \fBwrap\-paragraph\fR command. .P .RE \fBws\-error\fR [special] .RS Comma\-separated list of flags that describe which whitespace errors should be highlighted. Set to \fB""\fR to disable. .P .TP \fBauto\-indent\fR If the \fBexpand\-tab\fR option is enabled then this is the same as \fBtab\-after\-indent,tab\-indent\fR. Otherwise it's the same as \fBspace\-indent\fR. .PP .TP \fBspace\-align\fR Highlight spaces used for alignment after tab indents as errors. .PP .TP \fBspace\-indent\fR Highlight space indents as errors. Note that this still allows using less than \fBtab\-width\fR spaces at the end of indentation for alignment. .PP .TP \fBtab\-after\-indent\fR Highlight tabs used anywhere other than indentation as errors. .PP .TP \fBtab\-indent\fR Highlight tabs in indentation as errors. If you set this you most likely want to set "tab\-after\-indent" too. .PP .TP \fBspecial\fR Display all characters that look like regular space as errors. One of these characters is no\-break space (U+00A0), which is often accidentally typed (AltGr+space in some keyboard layouts). .PP .TP \fBtrailing\fR Highlight trailing whitespace characters at the end of lines as errors. .PP . .SH SEE ALSO \fBdte\fR(1), \fBdte\-syntax\fR(5) .SH AUTHORS Craig Barnes .br Timo Hirvonen dte-1.9.1/docs/dterc.md000066400000000000000000000625771354415143300146750ustar00rootroot00000000000000--- title: dterc section: 5 date: March 2019 description: Command and configuration language used by `dte` author: [Craig Barnes, Timo Hirvonen] seealso: ["`dte`", "`dte-syntax`"] --- # dterc dterc is the language used in `dte` configuration files (`~/.dte/rc`) and also in the command mode of the editor (Alt+x). The syntax of the language is quite similar to shell, but much simpler. Commands are separated either by a newline or `;` character. To make a command span multiple lines in an rc file, escape the newline (put `\` at the end of the line). Rc files can contain comments at the start of a line. Comments begin with a `#` character and can be indented, but they can't be put on the same line as a command. Commands can contain environment variables. Variables always expand into a single argument even if they contain whitespace. Variables inside single or double quotes are NOT expanded. This makes it possible to bind keys to commands that contain variables (inside single or double quotes), which will be expanded just before the command is executed. Example: alias x "run chmod 755 $FILE" `$FILE` is expanded when the alias _x_ is executed. The command works even if `$FILE` contains whitespace. ## Special variables These variables are always defined and override environment variables of the same name. ### **$FILE** The filename of the current buffer (or an empty string if unsaved). ### **$FILETYPE** The value of the `filetype` option for the current buffer. ### **$LINENO** The line number of the cursor in the current buffer. ### **$WORD** The selected text or the word under the cursor. ## Single quoted strings Single quoted strings can't contain single quotes or escaped characters. ## Double quoted strings Double quoted strings may contain the following escapes: `\a`, `\b`, `\t`, `\n`, `\v`, `\f`, `\r` : Control characters (same as in C) `\\` : Escaped backslash `\x0a` : Hexadecimal byte value 0x0a. Note that `\x00` is not supported because strings are NUL-terminated. `\u20ac` : Four hex digit Unicode code point U+20AC. `\U000020ac` : Eight hex digit Unicode code point U+20AC. # Commands ## Configuration Commands Configuration commands are used to customize certain aspects of the editor, for example adding key bindings, setting options, etc. These are the only commands allowed in user config files. ### **alias** _name_ _command_ Create an alias _name_ for _command_. Example: alias read 'pipe-from cat' Now you can run `read file.txt` to insert `file.txt` into the current buffer. ### **bind** _key_ [_command_] Bind _command_ to _key_. If no _command_ is given then any existing binding for _key_ is removed. Special keys: * `left` * `right` * `up` * `down` * `insert` * `delete` * `home` * `end` * `pgup` * `pgdown` * `enter` * `tab` * `space` * `F1`..`F12` Modifiers: Ctrl: : `C-X` or `^X` Alt: : `M-X` Shift: : `S-left` ### **set** [**-gl**] _option_ [_value_] ... Set _value_ for _option_. Value can be omitted for boolean option to set it true. Multiple options can be set at once but then _value_ must be given for every option. There are three kinds of options. 1. Global options. 2. Local options. These are file specific options. Each open file has its own copies of the option values. 3. Options that have both global and local values. The Global value is just a default local value for opened files and is never used for anything else. Changing the global value does not affect any already opened files. By default `set` changes both global and local values. `-g` : Change only global option value `-l` : Change only local option value of current file In configuration files only global options can be set (no need to specify the `-g` flag). See also: `toggle` and `option` commands. ### **setenv** _name_ _value_ Set environment variable. ### **hi** _name_ [_fg-color_ [_bg-color_]] [_attribute_]... Set highlight color. The _name_ argument can be a token name defined by a `dte-syntax` file or one of the following, built-in highlight names: * `default` * `nontext` * `noline` * `wserror` * `selection` * `currentline` * `linenumber` * `statusline` * `commandline` * `errormsg` * `infomsg` * `tabbar` * `activetab` * `inactivetab` The _fg-color_ and _bg-color_ arguments can be one of the following: * No value (equivalent to `default`) * A numeric value between `-2` and `255` * A 256-color palette value in R/G/B notation (e.g. `0/3/5`) * A true color value in CSS-style #RRGGBB notation (e.g. `#ab90df`) * `keep` (`-2`) * `default` (`-1`) * `black` (`0`) * `red` (`1`) * `green` (`2`) * `yellow` (`3`) * `blue` (`4`) * `magenta` (`5`) * `cyan` (`6`) * `gray` (`7`) * `darkgray` (`8`) * `lightred` (`9`) * `lightgreen` (`10`) * `lightyellow` (`11`) * `lightblue` (`12`) * `lightmagenta` (`13`) * `lightcyan` (`14`) * `white` (`15`) Colors `16` to `231` correspond to R/G/B colors. Colors `232` to `255` are grayscale values. If the terminal has limited support for rendering colors, the _fg-color_ and _bg-color_ arguments will fall back to the nearest supported color, which may be less precise than the value specified. The _attribute_ argument(s) can be any combination of the following: * `bold` * `dim` * `italic` * `underline` * `strikethrough` * `blink` * `reverse` * `invisible` * `keep` The color and attribute value `keep` is useful in selected text to keep _fg-color_ and attributes and change only _bg-color_. NOTE: Because `keep` is both a color and an attribute you need to specify both _fg-color_ and _bg-color_ if you want to set the `keep` _attribute_. Unset fg/bg colors are inherited from highlight color `default`. If you don't set fg/bg for the highlight color `default` then terminal's default fg/bg is used. ### **ft** [**-bcfi**] _filetype_ _string_... Add a filetype association. Filetypes are used to determine which syntax highlighter and local options to use when opening files. By default _string_ is interpreted as one or more filename extensions. `-b` : Interpret _string_ as a file basename `-c` : Interpret _string_ as a regex pattern and match against the contents of the first line of the file `-f` : Interpret _string_ as a regex pattern and match against the full (absolute) filename `-i` : Interpret _string_ as a command interpretter name and match against the Unix shebang line (after removing any path prefix and/or version suffix) Examples: ft c c h ft -b make Makefile GNUmakefile ft -c xml '<\?xml' ft -f mail '/tmpmsg-.*\.txt$' ft -i lua lua luajit See also: * The `option` command (below) * The `filetype` option (below) * The [`dte-syntax`] man page ### **option** [**-r**] _filetype_ _option_ _value_... Add automatic _option_ for _filetype_ (as previously registered with the `ft` command). Automatic options are set when files are are opened. `-r` : Interpret _filetype_ argument as a regex pattern instead of a filetype and match against full filenames ### **include** [**-b**] _file_ Read and execute commands from _file_. `-b` : Read built-in _file_ instead of reading from the filesystem Note: "built-in files" are config files bundled into the program binary. See the `-B` and `-b` flags in the `dte` man page for more information. ### **errorfmt** [**-i**] _compiler_ _regexp_ [file|line|column|message]... `-i` : Ignore this error See `compile` and `msg` commands for more information. ### **load-syntax** _filename_|_filetype_ Load a `dte-syntax` file into the editor. If the argument contains a `/` character it's considered a filename. Note: this command only loads a syntax file ready for later use. To actually apply a syntax highlighter to the current buffer, use the `set` command to change the `filetype` of the buffer instead, e.g. `set filetype html`. ## Editor Commands ### **quit** [**-fp**] Quit the editor. `-f` : Force quit, even if there are unsaved files `-p` : Prompt for confirmation if there are unsaved files ### **suspend** Suspend the editor (run `fg` in the shell to resume). ### **cd** _directory_ Change the working directory and update `$PWD` and `$OLDPWD`. Running `cd -` changes to the previous directory (`$OLDPWD`). ### **command** [_text_] Enter command mode. If _text_ is given then it is written to the command line (see the default `^L` key binding for why this is useful). ### **search** [**-Hnprw**] [_pattern_] If no flags or just `-r` and no _pattern_ given then dte changes to search mode where you can type a regular expression to search. `-H` : Don't add _pattern_ to search history `-n` : Search next `-p` : Search previous `-r` : Start searching backwards `-w` : Search word under cursor ### **git-open** Interactive file opener. Lists all files in a git repository. Same keys work as in command mode, but with these changes: `up` : Move up in file list. `down` : Move down in file list. `enter` : Open file. `^O` : Open file but don't close git-open. `M-e` : Go to end of file list. `M-t` : Go to top of file list. ### **refresh** Trigger a full redraw of the screen. ## Buffer Management Commands ### **open** [**-g**] [**-e** _encoding_] [_filename_]... Open file. If _filename_ is omitted, a new file is opened. `-e` _encoding_ : Set file _encoding_. See `iconv -l` for list of supported encodings. `-g` : Perform [`glob`] expansion on _filename_. ### **save** [**-dfup**] [**-e** _encoding_] [_filename_] Save file. By default line-endings (LF vs CRLF) are preserved. `-d` : Save with DOS/CRLF line-endings `-f` : Force saving read-only file `-u` : Save with Unix/LF line-endings `-p` : Open a command prompt if there's no specified or existing _filename_ `-e` _encoding_ : Set file _encoding_. See `iconv -l` for list of supported encodings. ### **close** [**-fqw**] Close file. `-f` : Close file even if it hasn't been saved after last modification `-q` : Quit if closing the last open file `-w` : Close parent window if closing its last contained file ### **next** Display next file. ### **prev** Display previous file. ### **view** _N_|last Display _N_th or last open file. ### **move-tab** _N_|left|right Move current tab to position _N_ or 1 position left or right. ## Window Management Commands ### **wsplit** [**-bhr**] [_file_]... Like `open` but at first splits current window vertically. `-b` : Add new window before current instead of after. `-h` : Split horizontally instead of vertically. `-r` : Split root instead of current window. ### **wclose** [**-f**] Close window. `-f` : Close even if there are unsaved files in the window ### **wnext** Next window. ### **wprev** Previous window. ### **wresize** [**-hv**] [_N_|+_N_|-- -_N_] If no parameter given, equalize window sizes in current frame. `-h` : Resize horizontally `-v` : Resize vertically _N_ : Set size of current window to _N_ characters. `+`_N_ : Increase size of current window by _N_ characters. `-`_N_ : Decrease size of current window by _N_ characters. Use `--` to prevent the minus symbol being parsed as an option flag, e.g. `wresize -- -5`. ### **wflip** Change from vertical layout to horizontal and vice versa. ### **wswap** Swap positions of this and next frame. ## Movement Commands ### **left** [**-c**] Move left. `-c` : Select characters ### **right** [**-c**] Move right. `-c` : Select characters ### **up** [**-cl**] Move cursor up. `-c` : Select characters `-l` : Select whole lines ### **down** [**-cl**] Move cursor down. `-c` : Select characters `-l` : Select whole lines ### **pgup** [**-cl**] Move cursor page up. See also `scroll-pgup`. `-c` : Select characters `-l` : Select whole lines ### **pgdown** [**-cl**] Move cursor page down. See also `scroll-pgdown`. `-c` : Select characters `-l` : Select whole lines ### **word-fwd** [**-cs**] Move cursor forward one word. `-c` : Select characters `-s` : Skip special characters ### **word-bwd** [**-cs**] Move cursor backward one word. `-c` : Select characters `-s` : Skip special characters ### **bol** [**-cs**] Move to beginning of line. `-c` : Select characters `-s` : Move to beginning of indented text or beginning of line, depending on current cursor position. ### **eol** [**-c**] Move cursor to end of line. `-c` : Select characters ### **bof** Move to beginning of file. ### **eof** Move cursor to end of file. ### **bolsf** Incrementally move cursor to beginning of line, then beginning of screen, then beginning of file. ### **eolsf** Incrementally move cursor to end of line, then end of screen, then end of file. ### **scroll-up** Scroll view up one line. Keeps cursor position unchanged if possible. ### **scroll-down** Scroll view down one line. Keeps cursor position unchanged if possible. ### **scroll-pgup** Scroll page up. Cursor position relative to top of screen is maintained. See also `pgup`. ### **scroll-pgdown** Scroll page down. Cursor position relative to top of screen is maintained. See also `pgdown`. ### **center-view** Center view to cursor. ### **line** _number_ Go to line. ### **tag** [**-r**] [_tag_] Save current location to stack and go to the location of _tag_. Requires tags file generated by Exuberant Ctags. If no _tag_ is given then word under cursor is used as a tag instead. `-r` : return back to previous location Tag files are searched from current working directory and its parent directories. See also `msg` command. ### **msg** [**-np**] Show latest, next (`-n`) or previous (`-p`) message. If its location is known (compile error or tag message) then the file will be opened and cursor moved to the location. `-n` : Next message `-p` : Previous message See also `compile` and `tag` commands. ## Editing Commands ### **cut** Cut current line or selection. ### **copy** [**-k**] Copy current line or selection. `-k` : Keep selection (by default, selections are lost after copying) ### **paste** [**-c**] Paste text previously copied by the `copy` or `cut` commands. `-c` : Paste at the cursor position ### **undo** Undo latest change. ### **redo** [_choice_] Redo changes done by the `undo` command. If there are multiple possibilities a message is displayed: Redoing newest (2) of 2 possible changes. If the change was not the one you wanted, just run `undo` and then, for example, `redo 1`. ### **clear** Clear current line. ### **join** Join selection or next line to current. ### **new-line** Insert empty line under current line. ### **delete** Delete character after cursor (or selection). ### **erase** Delete character before cursor (or selection). ### **delete-eol** [**-n**] Delete to end of line. `-n` : Delete newline if cursor is at end of line ### **erase-bol** Erase to beginning of line. ### **delete-word** [**-s**] Delete word after cursor. `-s` : Be more "aggressive" ### **erase-word** [**-s**] Erase word before cursor. `-s` : Be more "aggressive" ### **case** [**-lu**] Change text case. The default is to change lower case to upper case and vice versa. `-l` : Lower case `-u` : Upper case ### **insert** [**-km**] _text_ Insert _text_ into the buffer. `-k` : Insert one character at a time as if it has been typed `-m` : Move after inserted text ### **replace** [**-bcgi**] _pattern_ _replacement_ Replace all instances of text matching _pattern_ with the _replacement_ text. The _pattern_ is a POSIX extended **regex**(7). `-b` : Use basic instead of extended regex syntax `-c` : Ask for confirmation before each replacement `-g` : Replace all matches for each line (instead of just the first) `-i` : Ignore case ### **shift** _count_ Shift current or selected lines by _count_ indentation levels. Count is usually `-1` (decrease indent) or `1` (increase indent). To specify a negative number, it's necessary to first disable option parsing with `--`, e.g. `shift -- -1`. ### **wrap-paragraph** [_width_] Format the current selection or paragraph under the cursor. If paragraph _width_ is not given then the `text-width` option is used. This command merges the selection into one paragraph. To format multiple paragraphs use the external `fmt`(1) program with the `filter` command, e.g. `filter fmt -w 60`. ### **select** [**-bkl**] Enter selection mode. All movement commands while in this mode extend the selected area. Note: A better way to create selections is to hold the Shift key whilst moving the cursor. The `select` command exists mostly as a fallback, for terminals with limited key binding support. `-b` : Select block between opening `{` and closing `}` curly braces `-k` : Keep existing selections `-l` : Select whole lines ### **unselect** Unselect. ## External Commands ### **filter** _command_ [_parameter_]... Filter selected text or whole file through external _command_. Example: filter sort -r Note that _command_ is executed directly using [`execvp`]. To use shell features like pipes or redirection, use a shell interpreter as the _command_. For example: filter sh -c 'tr a-z A-Z | sed s/foo/bar/' ### **pipe-from** [**-ms**] _command_ [_parameter_]... Run external _command_ and insert its standard output. `-m` : Move after the inserted text `-s` : Strip newline from end of output ### **pipe-to** _command_ [_parameter_]... Run external _command_ and pipe the selected text (or whole file) to its standard input. Can be used to e.g. write text to the system clipboard: pipe-to xsel -b ### **run** [**-ps**] _command_ [_parameters_]... Run external _command_. `-p` : Display "Press any key to continue" prompt `-s` : Silent -- both `stderr` and `stdout` are redirected to `/dev/null` ### **compile** [**-1ps**] _errorfmt_ _command_ [_parameters_]... Run external _command_ and collect output messages. This can be used to run e.g. compilers, build systems, code search utilities, etc. and then jump to a file/line position for each message. The _errorfmt_ argument corresponds to a regex capture pattern previously specified by the `errorfmt` command. After _command_ exits successfully, parsed messages can be navigated using the `msg` command. `-1` : Read error messages from stdout instead of stderr `-p` : Display "Press any key to continue" prompt `-s` : Silent. Both `stderr` and `stdout` are redirected to `/dev/null` See also: `errorfmt` and `msg` commands. ### **eval** _command_ [_parameter_]... Run external _command_ and execute its standard output text as dterc commands. ## Other Commands ### **repeat** _count_ _command_ [_parameters_]... Run _command_ _count_ times. ### **toggle** [**-gv**] _option_ [_values_]... Toggle _option_. If list of _values_ is not given then the option must be either boolean or enum. `-g` : toggle global option instead of local `-v` : display new value If _option_ has both local and global values then local is toggled unless `-g` is used. ### **show** [**-c**] _type_ [_key_] Display current values for various configurable types. The _type_ argument can be one of: `alias` : show command aliases `bind` : show key bindings The _key_ argument is the name of the entry to lookup (i.e. alias name or key string). If this argument is specified, the value will be displayed in the status line. If omitted, a pager will be opened displaying all entries of the specified type. `-c` : write value to command line instead of status line # Options Options can be changed using the `set` command. Enumerated options can also be `toggle`d. To see which options are enumerated, type "toggle " in command mode and press the tab key. You can also use the `option` command to set default options for specific file types. ## Global options ### **case-sensitive-search** [true] `false` : Search is case-insensitive. `true` : Search is case-sensitive. `auto` : If search string contains an uppercase letter search is case-sensitive, otherwise it is case-insensitive. ### **display-invisible** [false] Display invisible characters. ### **display-special** [false] Display special characters. ### **esc-timeout** [100] 0...2000 When single escape is read from the terminal dte waits some time before treating the escape as a single keypress. The timeout value is in milliseconds. Too long timeout makes escape key feel slow and too small timeout can cause escape sequences of for example arrow keys to be split and treated as multiple key presses. ### **filesize-limit** [250] Refuse to open any file with a size larger than this value (in mebibytes). Useful to prevent accidentally opening very large files, which can take a long time on some systems. ### **lock-files** [true] Lock files using `$DTE_HOME/file-locks`. Only protects from your own mistakes (two processes editing same file). ### **newline** [unix] Whether to use LF (_unix_) or CRLF (_dos_) line-endings. This is just a default value for new files. ### **scroll-margin** [0] Minimum number of lines to keep visible before and after cursor. ### **set-window-title** [false] Set the window title to the filename of the current buffer (if the terminal supports it). ### **show-line-numbers** [false] Show line numbers. ### **statusline-left** [" %f%s%m%r%s%M"] Format string for the left aligned part of status line. `%f` : Filename. `%m` : Prints `*` if file is has been modified since last save. `%r` : Prints `RO` if file is read-only. `%y` : Cursor row. `%Y` : Total rows in file. `%x` : Cursor display column. `%X` : Cursor column as characters. If it differs from cursor display column then both are shown (e.g. `2-9`). `%p` : Position in percentage. `%E` : File encoding. `%M` : Miscellaneous status information. `%n` : Line-ending (LF or CRLF). `%s` : Add separator. `%t` : File type. `%u` : Hexadecimal Unicode value value of character under cursor. `%%` : Literal `%`. ### **statusline-right** [" %y,%X %u %E %n %t %p "] Format string for the right aligned part of status line. ### **tab-bar** [horizontal] `hidden` : Hide tab bar. `horizontal` : Show tab bar on top. `vertical` : Show tab bar on left if there's enough space, hide otherwise. `auto` : Show tab bar on left if there's enough space, on top otherwise. ### **tab-bar-max-components** [0] Maximum number of path components displayed in vertical tab bar. Set to `0` to disable. ### **tab-bar-width** [25] Width of vertical tab bar. Note that width of tab bar is automatically reduced to keep editing area at least 80 characters wide. Vertical tab bar is shown only if there's enough space. ## Local options ### **brace-indent** [false] Scan for `{` and `}` characters when calculating indentation size. Depends on the `auto-indent` option. ### **filetype** [none] Type of file. Value must be previously registered using the `ft` command. ### **indent-regex** [""] If this regular expression matches current line when enter is pressed and `auto-indent` is true then indentation is increased. Set to `""` to disable. ## Local and global options The global values for these options serve as the default values for local (per-file) options. ### **auto-indent** [true] Automatically insert indentation when pressing enter. Indentation is copied from previous non-empty line. If also the `indent-regex` local option is set then indentation is automatically increased if the regular expression matches current line. ### **detect-indent** [""] Comma-separated list of indent widths (`1`-`8`) to detect automatically when a file is opened. Set to `""` to disable. Tab indentation is detected if the value is not `""`. Adjusts the following options if indentation style is detected: `emulate-tab`, `expand-tab`, `indent-width`. Example: set detect-indent 2,3,4,8 ### **emulate-tab** [false] Make `delete`, `erase` and moving `left` and `right` inside indentation feel as if there were tabs instead of spaces. ### **expand-tab** [false] Convert tab to spaces on insert. ### **file-history** [true] Save line and column for each file to `$DTE_HOME/file-history`. ### **indent-width** [8] Size of indentation in spaces. ### **syntax** [true] Use syntax highlighting. ### **tab-width** [8] Width of tab. Recommended value is `8`. If you use other indentation size than `8` you should use spaces to indent. ### **text-width** [72] Preferred width of text. Used as the default argument for the `wrap-paragraph` command. ### **ws-error** [special] Comma-separated list of flags that describe which whitespace errors should be highlighted. Set to `""` to disable. `auto-indent` : If the `expand-tab` option is enabled then this is the same as `tab-after-indent,tab-indent`. Otherwise it's the same as `space-indent`. `space-align` : Highlight spaces used for alignment after tab indents as errors. `space-indent` : Highlight space indents as errors. Note that this still allows using less than `tab-width` spaces at the end of indentation for alignment. `tab-after-indent` : Highlight tabs used anywhere other than indentation as errors. `tab-indent` : Highlight tabs in indentation as errors. If you set this you most likely want to set "tab-after-indent" too. `special` : Display all characters that look like regular space as errors. One of these characters is no-break space (U+00A0), which is often accidentally typed (AltGr+space in some keyboard layouts). `trailing` : Highlight trailing whitespace characters at the end of lines as errors. [`dte-syntax`]: dte-syntax.html [`execvp`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/execvp.html [`glob`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html [`xterm`]: https://invisible-island.net/xterm/ dte-1.9.1/docs/keys.md000066400000000000000000000046541354415143300145370ustar00rootroot00000000000000Default Key Bindings -------------------- ### Editor `Alt`+`x` : Enter command mode (see [`dterc`] for available commands) `Ctrl`+`q` : Quit editor (use `quit -f` in command mode to quit with unsaved buffers) `Alt`+`z` : Suspend editor (resume by running `fg` in the shell) ### Buffer `Ctrl`+`n`, `Ctrl`+`t` : Open new, empty buffer `Ctrl`+`o` : Open file (prompt) `Ctrl`+`s` : Save current buffer `Ctrl`+`w` : Close current buffer `Alt`+`1`, `Alt`+`2` ... `Alt`+`9` : Switch to buffer 1 (or 2, 3, 4, etc.) `Alt`+`0` : Switch to last buffer `Alt`+`,` : Switch to previous buffer `Alt`+`.` : Switch to next buffer ### Navigation `Alt`+`b` : Move cursor to beginning of file `Alt`+`e` : Move cursor to end of file `Ctrl`+`l` : Go to line (prompt) `Ctrl`+`f`, `Alt`+`/` : Find text (prompt) `Ctrl`+`g`, `F3` : Find next `F4` : Find previous `Alt`+`f` : Find next occurence of word under cursor `Alt`+`Shift`+`f` : Find previous occurence of word under cursor ### Selection `Shift`+`left`, `Shift`+`right` : Select one character left/right `Ctrl`+`Shift`+`left`, `Ctrl`+`Shift`+`right` : Select one word left/right `Shift`+`up`, `Shift`+`down` : Select to column above/below cursor `Ctrl`+`Shift`+`up`, `Ctrl`+`Shift`+`down` : Select one whole line up/down `Alt`+`Shift`+`left`, `Shift`+`Home` : Select from cursor to beginning of line `Alt`+`Shift`+`right`, `Shift`+`End` : Select from cursor to end of line `Shift`+`Page Up`, `Alt`+`Shift`+`up` : Select one page above cursor `Shift`+`Page Down`, `Alt`+`Shift`+`down` : Select one page below cursor `Ctrl`+`Shift`+`Page Up`, `Ctrl`+`Alt`+`Shift`+`up` : Select whole lines one page up `Ctrl`+`Shift`+`Page Down`, `Ctrl`+`Alt`+`Shift`+`down` : Select whole lines one page down `Tab`, `Shift`+`Tab` : Increase/decrease indentation level of selected (whole) lines ### Editing `Ctrl`+`c` : Copy selection (or line) `Ctrl`+`x` : Cut selection (or line) `Ctrl`+`v` : Paste previously copied or cut text `Ctrl`+`z` : Undo last action `Ctrl`+`y` : Redo last undone action `Alt`+`j` : Wrap paragraph under cursor to value of `text-width` option (72 by default) `Alt`+`t` : Insert a tab character (useful when using the `expand-tab` option) `Alt`+`-` : Decrease indent level of selection (or line) `Alt`+`=` : Increase indent level of selection (or line) [`dterc`]: https://craigbarnes.gitlab.io/dte/dterc.html dte-1.9.1/docs/layout.css000066400000000000000000000024071354415143300152630ustar00rootroot00000000000000#main-grid > main { grid-area: content; color: #222; } #main-grid > header { grid-area: header; padding: 10px; margin-bottom: 1em; border-bottom: 1px solid #ddd; } #main-grid > footer { grid-area: footer; color: #eee; font-size: small; text-align: right; } #top-title { float: left; font-size: 1.4em; } #top-nav { float: right; user-select: none; } #toc { grid-area: toc; border-bottom: 2px dashed #ccc; margin-bottom: 1.5em; padding-bottom: 1.8em; } #toc ul { margin: 0 0 0 1.2em; padding: 0.1em; } #main-grid { display: grid; grid-row-gap: 10px; grid-template-areas: "header" "toc" "content" "footer"; width: 100%; max-width: 1080px; margin: 0 auto; } @media only screen and (min-width: 1000px) and (min-device-width: 700px) { #main-grid { grid-template-areas: "header header" "toc content" "footer footer"; grid-column-gap: 2.5em; grid-row-gap: 20px; grid-row-gap: 1em; grid-template-columns: 1fr 4fr; } #toc { border-bottom: none; font-size: 0.9em; } #main-grid > main > h1:first-child { margin-top: 0; } } dte-1.9.1/docs/lcov-extra.css000066400000000000000000000005041354415143300160260ustar00rootroot00000000000000td.coverPerLo, td.coverNumLo, td.headerCovTableEntryLo { background-color: rgba(255, 53, 47, 0.55); } td.coverPerMed, td.coverNumMed, td.headerCovTableEntryMed { background-color: rgba(255, 224, 80, 0.55); } td.coverPerHi, td.coverNumHi, td.headerCovTableEntryHi { background-color: rgba(27, 234, 89, 0.55); } dte-1.9.1/docs/lcov-orig.css000066400000000000000000000242001354415143300156420ustar00rootroot00000000000000/* All views: initial background and text color */ body { color: #000000; background-color: #FFFFFF; } /* All views: standard link format*/ a:link { color: #284FA8; text-decoration: underline; } /* All views: standard link - visited format */ a:visited { color: #00CB40; text-decoration: underline; } /* All views: standard link - activated format */ a:active { color: #FF0040; text-decoration: underline; } /* All views: main title format */ td.title { text-align: center; padding-bottom: 10px; font-family: sans-serif; font-size: 20pt; font-style: italic; font-weight: bold; } /* All views: header item format */ td.headerItem { text-align: right; padding-right: 6px; font-family: sans-serif; font-weight: bold; vertical-align: top; white-space: nowrap; } /* All views: header item value format */ td.headerValue { text-align: left; color: #284FA8; font-family: sans-serif; font-weight: bold; white-space: nowrap; } /* All views: header item coverage table heading */ td.headerCovTableHead { text-align: center; padding-right: 6px; padding-left: 6px; padding-bottom: 0px; font-family: sans-serif; font-size: 80%; white-space: nowrap; } /* All views: header item coverage table entry */ td.headerCovTableEntry { text-align: right; color: #284FA8; font-family: sans-serif; font-weight: bold; white-space: nowrap; padding-left: 12px; padding-right: 4px; background-color: #DAE7FE; } /* All views: header item coverage table entry for high coverage rate */ td.headerCovTableEntryHi { text-align: right; color: #000000; font-family: sans-serif; font-weight: bold; white-space: nowrap; padding-left: 12px; padding-right: 4px; background-color: #A7FC9D; } /* All views: header item coverage table entry for medium coverage rate */ td.headerCovTableEntryMed { text-align: right; color: #000000; font-family: sans-serif; font-weight: bold; white-space: nowrap; padding-left: 12px; padding-right: 4px; background-color: #FFEA20; } /* All views: header item coverage table entry for ow coverage rate */ td.headerCovTableEntryLo { text-align: right; color: #000000; font-family: sans-serif; font-weight: bold; white-space: nowrap; padding-left: 12px; padding-right: 4px; background-color: #FF0000; } /* All views: header legend value for legend entry */ td.headerValueLeg { text-align: left; color: #000000; font-family: sans-serif; font-size: 80%; white-space: nowrap; padding-top: 4px; } /* All views: color of horizontal ruler */ td.ruler { background-color: #6688D4; } /* All views: version string format */ td.versionInfo { text-align: center; padding-top: 2px; font-family: sans-serif; font-style: italic; } /* Directory view/File view (all)/Test case descriptions: table headline format */ td.tableHead { text-align: center; color: #FFFFFF; background-color: #6688D4; font-family: sans-serif; font-size: 120%; font-weight: bold; white-space: nowrap; padding-left: 4px; padding-right: 4px; } span.tableHeadSort { padding-right: 4px; } /* Directory view/File view (all): filename entry format */ td.coverFile { text-align: left; padding-left: 10px; padding-right: 20px; color: #284FA8; background-color: #DAE7FE; font-family: monospace; } /* Directory view/File view (all): bar-graph entry format*/ td.coverBar { padding-left: 10px; padding-right: 10px; background-color: #DAE7FE; } /* Directory view/File view (all): bar-graph outline color */ td.coverBarOutline { background-color: #000000; } /* Directory view/File view (all): percentage entry for files with high coverage rate */ td.coverPerHi { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #A7FC9D; font-weight: bold; font-family: sans-serif; } /* Directory view/File view (all): line count entry for files with high coverage rate */ td.coverNumHi { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #A7FC9D; white-space: nowrap; font-family: sans-serif; } /* Directory view/File view (all): percentage entry for files with medium coverage rate */ td.coverPerMed { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #FFEA20; font-weight: bold; font-family: sans-serif; } /* Directory view/File view (all): line count entry for files with medium coverage rate */ td.coverNumMed { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #FFEA20; white-space: nowrap; font-family: sans-serif; } /* Directory view/File view (all): percentage entry for files with low coverage rate */ td.coverPerLo { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #FF0000; font-weight: bold; font-family: sans-serif; } /* Directory view/File view (all): line count entry for files with low coverage rate */ td.coverNumLo { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #FF0000; white-space: nowrap; font-family: sans-serif; } /* File view (all): "show/hide details" link format */ a.detail:link { color: #B8D0FF; font-size:80%; } /* File view (all): "show/hide details" link - visited format */ a.detail:visited { color: #B8D0FF; font-size:80%; } /* File view (all): "show/hide details" link - activated format */ a.detail:active { color: #FFFFFF; font-size:80%; } /* File view (detail): test name entry */ td.testName { text-align: right; padding-right: 10px; background-color: #DAE7FE; font-family: sans-serif; } /* File view (detail): test percentage entry */ td.testPer { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #DAE7FE; font-family: sans-serif; } /* File view (detail): test lines count entry */ td.testNum { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #DAE7FE; font-family: sans-serif; } /* Test case descriptions: test name format*/ dt { font-family: sans-serif; font-weight: bold; } /* Test case descriptions: description table body */ td.testDescription { padding-top: 10px; padding-left: 30px; padding-bottom: 10px; padding-right: 30px; background-color: #DAE7FE; } /* Source code view: function entry */ td.coverFn { text-align: left; padding-left: 10px; padding-right: 20px; color: #284FA8; background-color: #DAE7FE; font-family: monospace; } /* Source code view: function entry zero count*/ td.coverFnLo { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #FF0000; font-weight: bold; font-family: sans-serif; } /* Source code view: function entry nonzero count*/ td.coverFnHi { text-align: right; padding-left: 10px; padding-right: 10px; background-color: #DAE7FE; font-weight: bold; font-family: sans-serif; } /* Source code view: source code format */ pre.source { font-family: monospace; white-space: pre; margin-top: 2px; } /* Source code view: line number format */ span.lineNum { background-color: #EFE383; } /* Source code view: format for lines which were executed */ td.lineCov, span.lineCov { background-color: #CAD7FE; } /* Source code view: format for Cov legend */ span.coverLegendCov { padding-left: 10px; padding-right: 10px; padding-bottom: 2px; background-color: #CAD7FE; } /* Source code view: format for lines which were not executed */ td.lineNoCov, span.lineNoCov { background-color: #FF6230; } /* Source code view: format for NoCov legend */ span.coverLegendNoCov { padding-left: 10px; padding-right: 10px; padding-bottom: 2px; background-color: #FF6230; } /* Source code view (function table): standard link - visited format */ td.lineNoCov > a:visited, td.lineCov > a:visited { color: black; text-decoration: underline; } /* Source code view: format for lines which were executed only in a previous version */ span.lineDiffCov { background-color: #B5F7AF; } /* Source code view: format for branches which were executed * and taken */ span.branchCov { background-color: #CAD7FE; } /* Source code view: format for branches which were executed * but not taken */ span.branchNoCov { background-color: #FF6230; } /* Source code view: format for branches which were not executed */ span.branchNoExec { background-color: #FF6230; } /* Source code view: format for the source code heading line */ pre.sourceHeading { white-space: pre; font-family: monospace; font-weight: bold; margin: 0px; } /* All views: header legend value for low rate */ td.headerValueLegL { font-family: sans-serif; text-align: center; white-space: nowrap; padding-left: 4px; padding-right: 2px; background-color: #FF0000; font-size: 80%; } /* All views: header legend value for med rate */ td.headerValueLegM { font-family: sans-serif; text-align: center; white-space: nowrap; padding-left: 2px; padding-right: 2px; background-color: #FFEA20; font-size: 80%; } /* All views: header legend value for hi rate */ td.headerValueLegH { font-family: sans-serif; text-align: center; white-space: nowrap; padding-left: 2px; padding-right: 4px; background-color: #A7FC9D; font-size: 80%; } /* All views except source code view: legend format for low coverage */ span.coverLegendCovLo { padding-left: 10px; padding-right: 10px; padding-top: 2px; background-color: #FF0000; } /* All views except source code view: legend format for med coverage */ span.coverLegendCovMed { padding-left: 10px; padding-right: 10px; padding-top: 2px; background-color: #FFEA20; } /* All views except source code view: legend format for hi coverage */ span.coverLegendCovHi { padding-left: 10px; padding-right: 10px; padding-top: 2px; background-color: #A7FC9D; } dte-1.9.1/docs/pdman.lua000066400000000000000000000110121354415143300150260ustar00rootroot00000000000000local concat = table.concat local Buffer = {} Buffer.__index = Buffer function Buffer:write(...) local length = self.length for i = 1, select("#", ...) do length = length + 1 self[length] = select(i, ...) end self.length = length end function Buffer:tostring() return concat(self) end local function new_buffer() return setmetatable({length = 0}, Buffer) end setmetatable(Buffer, {__call = new_buffer}) local function escape(s, in_attribute) return (s:gsub("[\\-]", "\\%1")) end local toc = Buffer() local template = [[ .TH %s %s "%s" .nh .ad l . .SH NAME %s \- %s .SH SYNOPSIS %s%s. .SH SEE ALSO %s .SH AUTHORS %s]] function Doc(body, metadata, variables) local title = assert(metadata.title) local section = assert(metadata.section) local description = assert(metadata.description) local date = assert(metadata.date) local seealso = assert(metadata.seealso) local authors = assert(metadata.author) return template:format ( title:upper(), section, date, title, description, toc:tostring(), body, concat(seealso, ",\n"), concat(authors, "\n.br\n") ) end local generate_toc = false local in_toc_heading = false local prev_heading_level = 0 function Header(level, s, attr) assert(level < 4) local prefix = "" if prev_heading_level > 2 then prefix = ".RE\n" end prev_heading_level = level if level == 1 then if s == "dterc" or s == "dte\\-syntax" then generate_toc = true return ".SH DESCRIPTION\n" end if generate_toc then if s == "Commands" or s == "Options" then toc:write(".P\n", s, ":\n.br\n") in_toc_heading = true else in_toc_heading = false end end return prefix .. ".SH " .. s:upper() .. "\n" elseif level == 2 then if in_toc_heading then toc:write(".P\n", s, ":\n.br\n") end return prefix .. ".SS " .. s .. "\n" elseif level == 3 then if in_toc_heading then toc:write(" ", s, "\n.br\n") end return prefix .. s .. "\n.RS\n" end end function DefinitionList(items) local buffer = Buffer() for i, item in ipairs(items) do local term, definitions = next(item) assert(type(term) == "string") assert(type(definitions) == "table") assert(#definitions == 1) local definition = assert(definitions[1]) buffer:write(".TP\n", term, "\n", definition, "\n.PP\n") end return buffer:tostring() end function BulletList(items) local buffer = Buffer() for i, item in ipairs(items) do buffer:write("\\(bu ", item, "\n.br\n") end buffer:write(".P\n") return buffer:tostring() end function OrderedList(items) local buffer = Buffer() for i, item in ipairs(items) do buffer:write(tostring(i), ". ", item, "\n.br\n") end buffer:write(".P\n") return (buffer:tostring():gsub("\n\n+", "\n.\n")) end function CodeBlock(s, attr) local code = s:gsub("[ \\-]", "\\%1") return ".IP\n.nf\n\\f[C]\n" .. code .. "\n\\f[]\n.fi\n.PP\n" end function Code(s, attr) local crossrefs = { dte = "(1)", dterc = "(5)", ["dte-syntax"] = "(5)", execvp = "(3)", glob = "(3)" } return "\\fB" .. escape(s) .. "\\fR" .. (crossrefs[s] or "") end function Strong(s) return "\\fB" .. s .. "\\fR" end function Emph(s) return "\\fI" .. s .. "\\fR" end function Link(text, href, title, attr) return text end function Str(s) return escape(s) end function Para(s) return s .. "\n.P\n" end function Plain(s) return s end function Blocksep() return "" end function LineBreak() return "\n.br\n" end function SoftBreak() return "\n" end function Space() return " " end setmetatable(_G, { __index = function(_, key) error(("Error: undefined function '%s'"):format(key)) end }) --[[ Not implemented: function Subscript(s) function Superscript(s) function SmallCaps(s) function Strikeout(s) function Image(s, src, tit, attr) function InlineMath(s) function DisplayMath(s) function Note(s) function Span(s, attr) function RawInline(format, str) function Table(caption, aligns, widths, headers, rows) function BlockQuote(s) function HorizontalRule() function LineBlock(ls) function CaptionedImage(src, tit, caption, attr) function RawBlock(output_format, str) function Div(s, attr) function Cite(s, cs) function DoubleQuoted(s) --]] dte-1.9.1/docs/screenshot.png000066400000000000000000000625161354415143300161260ustar00rootroot00000000000000PNG  IHDR _.PLTEGpL___]]]荏ّ333!!!%%% uuu.46((( ABA{|zaba665HIFefbUVUjki-.-mmmQRPSTS???+++wxw111MML^__npmJJJstr998EFFcecqrp/0/}z4Ġĥ]]\OPOұfgeXZY###vws<<DÊ~~~}Ǡqhig6<>gTN MRR1799/.Z,9qD8~%;fkj[K-%(1`#BjWWIOO6i -F1OEKK&Iu "zFF@~OA(Mxc*//['2ى8Z& JL4553d+Tyy :(Ϲpig8~?/r~'P2@1@" @, @US8@[LPT%JBI~@t"͆"LHGPuH+ !۰ i"HtHa_$ P>Pd b  :Q. ZPWx*QBA HJ A 9JU L"Av! RNEPڛ(P45S%DDRB1&5T)".*$L *B{lAk=UIkkCS;TIIriB)d2Z3C]¢ LI ZE NPum فp8)$ %jjj/^D r*C ]ŗ/@i!RHz `SIA Gd.S4,p \*$Bq'_fΟ.7* m @i.\m#yI,@#1)k'&v3ƕ Ĵ UHQ))7yfZ"Ѵp4gZUH&K蘁zKɴ_>BmPjnArpT5IYLPgOiAAqHZP -m[+$YȩB>"I4TJT4I M>jk @*ZԐWkZZǗ 4 ɾ9 kNBDN 6 oίBk @*}s eiږ_gfY60?o-`VA5V7KH| c}}+++K~ۋYUyoU8xY8Q> -'&12ǽQp >w! ūP_[ud&RU={\\ET&T0:8nKإ$T('P: PW Ov:010&Lk @V=zB&pw0*fʺe}}3^r0Ƚ0>(F'l@}U0fv-x+HbT|>T(h _? 544tOdxxfJً hއQ_OЭcv>Plݻ7nvQAc;s"ێ*./1'QnL{ob%f]OpN<;Rեт*>bټ*NphL{L)u`ƛ}&OȘB}ELGgxe40% DMa~2*QP`cPᰰDDgV] D>+XqW#s/ sNB*h4; ƑqfOT_fmdOxn\#$%ԟQ̔Փ oc1qLA"ʨe1L^AP9>8Xmp..o[z}߃ JF爉!n M1r'-Yސ[&cA pc% cw 6V+ͼt1.g*#do,@dYMNe1 ƢuFv$ :LY|.&}ks8P uVaj}\YkxU %Me5mV?nBcF(1Y19\,]d0;Nzd2ݩVjd"We TKr۬ct+r*(J"!]I@c5IF.20Y*qycμILI䃬^a[\<2faI*8mz 뭬ECDBaT8'#ʰJ `ING%52f< 0'lP[ Ƒ5,n*\gYFա jA1[ׄɈ2: L 3qɟ&Ɖ O>^+t-yW^aIcm66Gz*(c϶v˫c)VPPawO-ƩQn7mI;eq400fct}&>2hd4˯UƋeUVԚИ}i҆y>$/n' &:] yCAZL=e~Kx<.޿@sTk ` ƽ\Pa;;*uE*nÝ-HT>e4_wBPAS d5@Te*w=miS"6 RNě8Y`+sd&>՘ BiAHG|fvfֵir|Ucv>;- Q@v9Ѥ[_" BI7ʑt{tQ@Yڕɣ0;}/&߳Hϣge 1DAt%ooS )J&YyMw_M5ɡꃩ$!UZX;]6 _?WL_-埤<<|uW) YTΞ+ |G>hYA# ;Bq[O_5}3l7?2 Qko-侲㣲qQ1)%9Q@%[¢z9]׾s8(\޿V)܇uu@x"TpDu>A2+(mByByJZF#f&ggRG1_G#8Q@ >Uu9@fN]=V#/ _&k, ԭFa/SQH( (DQ@! (B! (B! (B! (B! (B! (B! (B! (B! (B! (B! (B! (B! (8,4Fbl)zz 4G,߾F .m8>a5l6*{K;Y.u%:0ΰG} 2>aw5<%ԙcv ;h.P.XMg _y\Q(:}gL(] 錢%D:(tFٵlEUufJyv1[qү7awm8-BZfo18v] 54EP;4 EaoV)-92.@YOe%7r\hvxtYZiZ2vLE^) %L3;^LBbcQ802;)3^(ٴ9i;ANf}9 Zd]vV_ؕP{N:9Ϡ!P휫ewﳭkYpy v8O02tptOCZ#:풍) !8ۙB}~Zf)(0aܸG ;0슬Ό(pGEGn/ f:CCOz/,2؞"{) AB3ԏgP8 q`_Jܶ?ݢ}uUBFu>v ꜸN^( ~R_9bQV@ Ȍud$!&bvxt 3iۆ8L3=JOԍl+`6jx<4ea4gJLx Jk B.B}- 9 +3ɣkrg`ػBuZ3ԝ)h]Ku}Hba=/Ie Qxb^)huC흦,FgJLx y[6 zC}j;*EA&޳[/ni-k٩ӧuYt#2aᖳVy̔v,n}9hP9kyN)|]a3z Fk Rrlz_BɹF%ߌG =;kԗB8v^j֢Xt>4TS#ZN'3_H˧LfZaӡ4DԙQt,OjvDqwHY g &S/1B!5BvlXgfJӹ:Olm)N4Y>?2sTXQ苢'q;V3 srY/Oҕޢ.GArk07SM#jzWEvxH)L#,Q8(_dc0>Ի802;L~<\o+yp#ṋi# dY__4ӛQ@Hc7oQ7B! (B! (B( !efɥ_ ϟ)uZks#}P:e6TU5/az]v~Ћx^Wbtє!vNu(Ub%ӔD)W;?Q~_![BwI4k= ׫(?jrHgDV柊c`1}$Lqhwh]RmAIZ.%b"Ek=! ?STӔ=GRq%ǽ2 :f _q %Ƚd4yю)EμZzbڸUi/_L/ܻHc!A6^M 5xgWuB;q`/}*1OLp*X jāf_ݾ>g^G=RS* cg_Ν@¥f'H;*TqgWuBu+@,"`\"&$+ ^C][A8X׻ձi?}yHVp4nUZ/j ǑpAlimGʏ<2A}]'&$+ ޝ i0YA8Xl/  *̫(G*樍[V=@9!$͑pZw nd.^9Ϯ" ™'hb&!ۛ/8dHyd%@cmM櫰3֣6n5Zbz6<;ojL);Vyx9x:Nzp?WGǃ?v#gu5@W3IYA:mb?.u+f ;e1&[͡~ Q/Czhuuij.% u 0]V"ND f(FVr&~ Q'/~]VsajfSAu 01Kѭ2ew˘}a-RߩAޣOOiu>#ѻ5o5 abw[-ᐑ͑Z9tkFǓwSZ]g1;@[Ve@ABQ_N˰ <}䴒Cfyzǵ<ڟT]'H%Qt,;wj7 Xᇵ5aig)n@ +@A D{ eh4(V(skX0>@\t}/ {\͞=i]EA‰aj??xw&#szu SRh_pnlvRؐ1vp`K/=shˍV(,bLv;^custӐ۳_EAn7F}beiϮVF3ްGƺZ, +@BXڿ6˜b1m<ص-CA҄(߼!(nN -o2؍:aۭh7(xV5Y#Cllp. ujƼ1OnP/ [!\gIʿe)?/~ ݠYaAuw_]_Lѳ d"m3 +WҌ%.#Wٺv5'7[xlX\w)70i}]v~PY&jӺcn.ԃ띠Ӊ.4t1A +@Af #`ڱ"K.D@hK)_mkM _rb_j+>H诵,u]o%& ேʀ*^av(_rQ+5+TV+<ͫ0|ZalhX|ڷ,cZ i>JOk%OF,)QS5TN+dтLO^qraD:#a$%@LK+1iQ-k+@z8{)XkRaqg#oJ '?5CbNƐn* N7AM)4eX}|+7QZPZ fRR(0.5&͑Vl}3yhpF5+TVdWUha=x\+lp_G[CjN &Z]kmֽPYm>AډuV侇p1јǧҜ۝͇M #*glr J8Tf٬4ga2`YHPy/dnJO{hkrP K.) КE\R)&* +@A +zPk.<1 q:. A $+L- z93g޺ar+)t? Lv;H xBEݏ͡^ 54Fd+0 ex\)Z=߳79Mp&+lnv2<.@Y!:3.p|QN 3 _qt? sPNB$>YrS߲9xm/;-qLWmgx lehI 4-'H[e2o$3 &n_3.@' U +@A NMFDO/ X XX!Z:D&#szHk2Hi>JO;BTV/m,Fd&#s_"ǗVbZ`X~Kf@!b2&6NkdԔ0 ,Dc 4QTIfchKRb5ƧZŇj 7_Bi-b,w.̜M꽵~?qs3sg\c>@=?Q6 T,Lj%PթP~_uMB;]?U0 WN ulu MTT2RY %K:{ݓV \;Hu_~-<˯`TAcTο 3^SK3n/?˯`TAcTDujTTp? PAST* @[z,G8*NqT 뺍  ' >N3 Eguhr=3 4a 0 5qT \yVkyg@PT* @TP*T *@ P@TP* @PTSAP(΢צ/7Vϟ9~kXLeTN\vB,2)MB\8Qj)'?.ETL܌(/xr\*(͝򙚗T(-yI8MþX_5*HTxGr$l:mPV=ghj|FK 3e;VˤFQ jsJRp $@! Qȷ쉎+5kqB-Gռ}oSUOu璭K"ZʕkV7POں{{RⓁ. ,RYIRAx}V*Hp5֛r;_IB"{V1֛ ,u:nݎrX"HʑTIUe?} $aT! mIҩ"*7$J92L[tېsWSLIOP )Gˢ)Rh m`-6# U͓%6F◶g U%Z)ڡTCdM~kUY)R(kn*nש+K4ўrdr$QI&$"jȍǒ RABdmWXEqn*_hy>V;9J+z g"刢r*X*L=ڡ󍫛" vg(hTFP*`səWe> fAqqjSgٹR$c6 Zԕk+xFfdzZ NVY|'yIQШ "vT@~R.6yO5$)G=VqrXn*͊(7ᥦRZoދWo5Kďѩ "bx(4U ZDdn [:v$dphUuoxvDvfZyxyn~z$^g'syB4*עmv%ގ^/m,ؤE]twx$jkjl9D* "Y?H+i\9Z^X4Qg7F3˽Gwxws˟K֫*7}=bÂ?βXU$:I\R }}I d8O[wnU .V9mKQ!s_xOk!箨 ?pZXCA@Ȣs'*6i9 @PT* @TP*T *@ P@TP*T @POޥזxJ!1U(\ypBRu.oBH`NNy ։De^}#s6wyOk~TsEF:+j=LCz g=el_X8Ov`fGZ5>Դk[V=9*[ J{Z,蛋s-羸f4~/tw3c擙ٝwOƞKJ6:EO^N ۼ+$4yt(G8$? nG^Ah$s tƼ- ҧ L- {1{j@|u\k3/_i~5EKdp(V s >y`jJgq: oiuPP3zgD%MO_>/BtvP0oHןD 'jP@S ƬW| 3.>Z&=F}S|L@BYgAU|+$nEEqHD3+a+ vvqg1fn|OV 'Yz<(jC~@ ;7K <ݾÍ"/cq ߗg=>>ܦQ ڧ*@<"gjAe>/;*Zf{ |=N\Ea!I4ӽsyH!p+SkotD]?pzx7 WN\?Kv gHS{(WOJ^=SH;wǺA@, 3G$WmO|+ b$=UHw o (@@d- l 9؈$$l.5'){1S ~P&yvPP9WDA>E {?ݵa9eL e\Ru@+P<,P B)uWh`V=%4]T96W0zX88AA#O&dMLDu@( gQ.{! 1M<)עڧw Z'J@T?? c @40BLIkz ^3Pc>~j^!}Eo EdO"3{gEx >8N Ұ 0AA7(A8)ܙ8_AP i@"zVVM 4`?Q,3\7k M)TUvD tz%4~N ;X(led\!lB%t|^I (ر @G i$+;B6(hzxk:cumޟxJT?? ! BЌDڻB!Vva!HIB]kj: Q JᎊYQ+w]B‹Gf.YPJoT91@  > &4%<ҮLʉy@p?s(@@ pA8I-Iq j[ǩ@%-Y9!m iO Y \=]n6P B§o(,(Kp$\Ba>]n(d eV&ҿ<.JŎ_x/ªϻ_M{v6S)ȷ/;>Gv[[dWZHA/ih`dq`S\rHP:p / s k*q_@a7é#%66966R|" )BW]qL1R2Z}_4Rl{Ł46C v GQ![4gPq?^cs̡2}51v]vp=zJHnhuY270/yiwoXXNI@! !  $lzv൪o#ƌcʦ;\? sDjKgvHˏ|ɕB1Ph5O冊ŁEh2wHRvЉߋnqeq o, `~c䴙G.pϤq^(3-? G]j_BYޣZb;v!P ~-/<dž5\͍Apc ;GP?6yLMGIs  PP-L ۀ[ B.cq+7R\Z1Zo\q7np\ˏ&b/4#P\sH R.A UykZ{n~GXH>AoonfjhBy̴=V(f @60m> elkmdi,R/k/\ ܉+UR(8+C-ӥHK bj5ܐ80 @A+S݈iHXdq pvA8fpŁ{ ͹ (@@ (@@(LuҪ[͕چhO+yYꅼM-5zWVI-=B}C E)ϣ( &яs%8^vx,$i镋BOjg?x(NLQ}CA# _kӿ⬒GKQB2/r7ՋռBԯj纷)mU>w7-~< ڞ߯GOӴ|y<)+0{d+yS$-zU.ndy4(d^Y WwB:Uu'_;$]y[(4y( Sɼ)ހt2oE<`ïԹ;C`&(؛M%I^63~R~ y,-^rBiTi,AuouB0iNO~˼)#wBkMLYz_vy]9 Ŝ=߿obǿPv–cvv:e>}<m˿4._l+<=Sy %p/X?UW~mCu^aPI@ci̓qV~#sP(AGW屴uV`ZO(8 P*'@@ @@ @t %).ERŨt,T6*LdeJ$ˈA"`T, qJF!XZ> Q r  k!E [1bQ(S_d\g "@?($(OYQ4a3]=JZa!@L9S::ZT۠iglS>1Pń>PQ2%T$%B(q Z%);SZB!)*%b)a<  Gh_B:PwpTgʮl`_P($b6'yA\6mZ舘12QH%bSZ P&RꃎJ9z=pQ fb)E "(^Cx8q ԢhM&IEI=T27 _^E(9̓ :>4tP=3l$|)P!&.:4;R3C-#3aW#/I* ω:IVYCaBeV60B"fsb77h@huP=3lޒesȞgfOCP03/ۊI a(6n$U@,fᐐc+@2+UT lN,ubzԟlnx€L=I F+LP&d*t,u琥xd(TfeBBQ9qun1( zOm PM3~HC$8 "z3C6㵢 x?Iy'QL#ƨNI?JZ &. %m(Py4=;, `C_ &v4WH1W039QFJQ IU bԭ]PHlnG@sڀ=P"aG"bR%D! 3HBq@!$&HʂB8,RKr@d=Pq|XZff*d.x6WR8pl@ʣ<{ /㤈/AsL9XK e}}MGe^R`yUW{ƹ@(@(@(dJ}&(>] BݚKP*X/+"A1@m/Pj+dYq2±aKL< Sw=>BO|AD;AՂ7v"(:{Dm#ZPX7nsCۭAòyAbwx\'Z q5;AURȺt;~Ĕ 2+GP` uocP9x_ NGP`g埜8nOp7 PNbA!¿ΗO 4 3MS5W F gAU,~X T%(j飰b -MT%*   P ^Rum7v4˳N'}DevqX~X#-^a9D/kEluQ8S*Zjp"aHevmr8֩\(<_ν~$+'>E¬%鮠d4<_\X:0CaiB*MQ`sP_PNTKufsnWD!7]w X<;(8K v660ެI3dV@a%L7"Ț|f*OP@I| [+x~b~P;9SK&OBqjhぬV޸ wS]$|E_MgOʹ{L$mH\.WTΩ>t1J@6nL( ;dkk W|Ǜ^HTGGNPP*%:zO=Q?( ޛj{l( uik0̇x*>~o9vlOu(ߔ|d9s9?Gh+8k>`ߠoV|+ALu  ;L|h?+Np@Ġ>)$L*{FV*: 5}:  `+5s kNUI[.usN[-l{VvwSL]Źyd1wOe=(BtY2nGgݩ?joض-ǼP8ԵBsv.OWoAp~ΆǯGǼp(t;ǎ觍%,ZP0{j`/uG%Ԛ[$t\_2 (N=RꔸOsf(z K_ȤbEk9 @P$kQnP.xkyb.K--19Sc @0QR~7C {nϏB}S@!sPsG: @ሡ@]&(hR\!5@KY` G ꂏz\=A}-EeBhe<Cc`bgvx*WZT/|G9 @Pw63 > F=Sy^w>r~Z=E6x]/Q 6t)y`l 3#ӷ) ֗;33]J(X(WWwqS?Bj q󓲂!o8ӥA gYz ޸avS3^/ g<PXԯH= hz eS_ψyxL 3=׹ƦGúZ=E5mGEx'g<PNWFF xRS?BuY DZz^6( SKg<P8uik`D |)yp4pA.o   ptU$Z =TZq2: Hr?QA+^dJGHhAJ@/@Cؘ " nK53 Zc!LS>b761~LRD!v?|pRPH|^3s]>}aP3ƦWs[SO] 1KgV"D:j~J;Pρ LZ8Nb}T Q0öNR H+G$RGvb?77G$c ! ~i@J@}ngxcMj /`Vǘ :@2-4 y RP8nҧ*t)PPPȜRՃ@'qVHVOa -` ?; s6>~A([i9)|TB0@ i u :%6c@ḣ\ZZ&s(sB!s(BZ !D$p"PHdVX^T& 'XwTe%‰A!>/6qq"DCJ`$m4UJ31!TJe\\QB̶k\VSȌ39Xm.r#}w11NC8gy<μt /1g/*T PP*0*@ P@TP**xN=;Z @sǝP@KT ~Gs@=ǍeFh0[^*p\& @} Tv 5\*pgDsϣh- @PNVKT0~ @,P%*0@~XKT`` ? ,P@KT`` ? @PT`ǫ%*` ? X~XKT`` ? ,P%*0@FX%*0@PT* P@TP*T *@ P@T* @ګ6* M_*^I**f'z[r%OϾX_ HJqi8I15kfdkC5A(zQP~ˆeѭ^Hɴ͎.ǶAAU'൪EH^91_LƖ,r gnf VFB54ddt!eq)$U/rZ%$&@U_ ˲}]WIͼHOL$ѵN:Va]CƊ U3tBT `x3F>i~-vw>Y_VYJnϱRdTS1K"ߧI9;_A$0+*a#PXR̋kPۢŠD0E*8[7B[{W Rb1m=kXX[yȾKT)!+")),s7TГќ Y5^Ljrf/̖UP%q*X='-?e*e1zw[([jq!B9RۢB:& Q) bO{JDCP֫I 6d^.+LPrvܲvW%E+d \׵t˰Q ov-V1s i1jP<-[ia|J; LM Z@utJTF9S)<֪k[T(=_Qm&6c^U=RbtU%Eդ/qO%YRNLyRJMo%-۬*kRXX1/zlBFYVbI Rk|%isUg+ 5x+*f^rV"=S!(?ʸ1$)e 5߲,TgEEoUT<ٺp**m\VN6LqKVfd= UATW0Vhd[?6i$~C TvF?_;? *@*D#ΎӓPlkNU8cl̎~ Vw {Pt S38_yS}qg枮iօ:Yٹi¨˅w5|G31/S8~s :T8߀ `;p 7{Lj3Dw *\\ 5 q\"7oŨ..Nx/ssߵw/}M> RKp.9s߸fɟNUqsPt } U:vQ!~%2TT|K AUZO G{o`hpSNgZ-T$U;ڻw#TO0i#>RaW *4e,}wOxTe1c i *pHLA0`L 3Tx wȸL X tLo6z *c@fCy*fUaC].Ly^,TQdL ;lϫ`B:zH]D=]tl :N~X*zH{FFzz τ?Ёܥ9`ȞZ !so`p<'8h>px𾁾{WGաf*=$,>7<}|!{x𾏩 fQGT0G5Mt0Kx˄E d`HY ]$ž.*ax @14D="t;M 0l5ڊ4.zPr`pQ?l}dO,'roնp2=Z@$P>&Gv>ih B| b (Q(f.nO ;x2 @…0N"$h8f' hg!,ap Tֺ3sl)H|/Tsm܃RY A Ǫ;?F)\-ۙL84.B5K B wE jfi &!$RXah #A(6paeI N!,J|A@($ dte text editor
dte text editor
$body$

© Craig Barnes 2017

dte-1.9.1/dte.desktop000066400000000000000000000006331354415143300144520ustar00rootroot00000000000000[Desktop Entry] Name = dte GenericName = Text Editor Comment = Edit text files Icon = text-editor Categories = Utility;TextEditor; Keywords = text;editor; Type = Application Terminal = true StartupNotify = false TryExec = dte Exec = dte %F MimeType = text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++; dte-1.9.1/makefile000066400000000000000000000002211354415143300137740ustar00rootroot00000000000000# This shim makefile directs non-GNU makes to run gmake .POSIX: .SUFFIXES: all: +gmake -f GNUmakefile all .DEFAULT: +gmake -f GNUmakefile $< dte-1.9.1/mk/000077500000000000000000000000001354415143300127105ustar00rootroot00000000000000dte-1.9.1/mk/README.md000066400000000000000000000003151354415143300141660ustar00rootroot00000000000000dte build system ================ This directory contains the [GNU Make] based build system for `dte`, along with various scripts used during compilation. [GNU Make]: https://www.gnu.org/software/make/ dte-1.9.1/mk/build.mk000066400000000000000000000156101354415143300143430ustar00rootroot00000000000000CC ?= gcc CFLAGS ?= -O2 LDFLAGS ?= AWK = awk VERSION = 1.9.1 WARNINGS = \ -Wall -Wextra -Wformat -Wformat-security \ -Wmissing-prototypes -Wstrict-prototypes \ -Wold-style-definition -Wwrite-strings -Wundef -Wshadow \ -Werror=div-by-zero -Werror=implicit-function-declaration \ -Wno-sign-compare -Wno-pointer-sign WARNINGS_EXTRA = \ -Wformat-signedness -Wformat-truncation -Wformat-overflow \ -Wstringop-truncation -Wstringop-overflow -Wshift-overflow=2 \ -Wframe-larger-than=32768 -Wvla BUILTIN_SYNTAX_FILES := \ awk c config css d diff docker dte gitcommit gitrebase go html \ ini java javascript lua mail make markdown meson nginx ninja php \ python robotstxt roff ruby sed sh sql tex texmfcnf vala xml \ xresources zig BUILTIN_CONFIGS := $(addprefix config/, \ rc compiler/gcc compiler/go \ binding/default binding/shift-select \ $(addprefix color/, reset reset-basic default darkgray) \ $(addprefix syntax/, $(BUILTIN_SYNTAX_FILES)) ) TEST_CONFIGS := $(addprefix test/data/, $(addsuffix .dterc, \ env thai crlf fuzz1 )) build_subdirs := $(addprefix build/, $(addsuffix /, \ editorconfig encoding syntax terminal util test )) util_objects := $(call prefix-obj, build/util/, \ ascii exec hashset path ptr-array readfile string strtonum \ unicode utf8 wbuf xmalloc xreadwrite xsnprintf ) editorconfig_objects := $(call prefix-obj, build/editorconfig/, \ editorconfig ini match ) encoding_objects := $(call prefix-obj, build/encoding/, \ bom convert decoder encoder encoding ) syntax_objects := $(call prefix-obj, build/syntax/, \ bitset color highlight state syntax ) terminal_objects := $(call prefix-obj, build/terminal/, \ color ecma48 input key no-op output terminal terminfo xterm xterm-keys ) editor_objects := $(call prefix-obj, build/, \ alias bind block block-iter buffer change cmdline command \ command-parse command-run compiler completion config ctags debug \ edit editor env error file-history file-option filetype frame \ history indent load-save lock main mode-command mode-git-open \ mode-normal mode-search move msg options parse-args regexp \ screen screen-cmdline screen-status screen-tabbar screen-view \ search selection spawn tag view window ) \ $(editorconfig_objects) \ $(encoding_objects) \ $(syntax_objects) \ $(terminal_objects) \ $(util_objects) test_objects := $(call prefix-obj, build/test/, \ cmdline command config editorconfig encoding filetype main \ syntax terminal test util ) all_objects := $(editor_objects) $(test_objects) editor_sources := $(patsubst build/%.o, src/%.c, $(editor_objects)) test_sources := $(patsubst build/test/%.o, test/%.c, $(test_objects)) ifdef WERROR WARNINGS += -Werror endif CWARNS = $(WARNINGS) $(foreach W,$(WARNINGS_EXTRA),$(call cc-option,$(W))) CSTD = $(call cc-option,-std=gnu11,-std=gnu99) $(call make-lazy,CWARNS) $(call make-lazy,CSTD) ifeq "$(KERNEL)" "Darwin" LDLIBS_ICONV += -liconv else ifeq "$(OS)" "Cygwin" LDLIBS_ICONV += -liconv EXEC_SUFFIX = .exe else ifeq "$(KERNEL)" "OpenBSD" LDLIBS_ICONV += -liconv BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib else ifeq "$(KERNEL)" "NetBSD" ifeq ($(shell expr "`uname -r`" : '[01]\.'),2) LDLIBS_ICONV += -liconv endif BASIC_CFLAGS += -I/usr/pkg/include BASIC_LDFLAGS += -L/usr/pkg/lib endif ifdef ICONV_DISABLE build/encoding/convert.o: BASIC_CFLAGS += -DICONV_DISABLE=1 else LDLIBS += $(LDLIBS_ICONV) endif ifdef TERMINFO_DISABLE build/terminal/terminfo.o: BASIC_CFLAGS += -DTERMINFO_DISABLE=1 else LDLIBS += $(or $(call pkg-libs, tinfo), $(call pkg-libs, ncurses), -lcurses) endif dte = dte$(EXEC_SUFFIX) test = build/test/test$(EXEC_SUFFIX) ifdef USE_SANITIZER SANITIZER_FLAGS := \ -fsanitize=address,undefined -fsanitize-address-use-after-scope \ -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common CC_SANITIZER_FLAGS := $(or \ $(call cc-option, $(SANITIZER_FLAGS)), \ $(warning USE_SANITIZER set but compiler doesn't support ASan/UBSan) ) $(all_objects): BASIC_CFLAGS += $(CC_SANITIZER_FLAGS) $(dte) $(test): BASIC_LDFLAGS += $(CC_SANITIZER_FLAGS) export ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 DEBUG = 3 else # 0: Disable debugging # 1: Enable BUG_ON() and light-weight sanity checks # 3: Enable expensive sanity checks DEBUG ?= 0 endif ifeq "$(DEBUG)" "0" UNWIND = $(call cc-option,-fno-asynchronous-unwind-tables) $(call make-lazy,UNWIND) endif $(all_objects): BASIC_CFLAGS += $(CSTD) -DDEBUG=$(DEBUG) $(CWARNS) $(UNWIND) # If "make install" with no other named targets ifeq "" "$(filter-out install,$(or $(MAKECMDGOALS),all))" OPTCHECK = : else OPTCHECK = SILENT_BUILD='$(MAKE_S)' mk/optcheck.sh endif ifndef NO_DEPS ifneq '' '$(call cc-option,-MMD -MP -MF /dev/null)' $(all_objects): DEPFLAGS = -MMD -MP -MF $(patsubst %.o, %.mk, $@) else ifneq '' '$(call cc-option,-MD -MF /dev/null)' $(all_objects): DEPFLAGS = -MD -MF $(patsubst %.o, %.mk, $@) endif -include $(patsubst %.o, %.mk, $(all_objects)) endif $(dte): $(editor_objects) $(test): $(filter-out build/main.o, $(all_objects)) $(util_objects): | build/util/ $(editorconfig_objects): | build/editorconfig/ $(encoding_objects): | build/encoding/ $(syntax_objects): | build/syntax/ $(terminal_objects): | build/terminal/ build/builtin-config.h: build/builtin-config.mk build/config.o: build/builtin-config.h build/test/config.o: build/test/data.h build/editor.o: build/version.h build/terminal/terminfo.o: build/terminal/terminfo.cflags build/encoding/convert.o: build/encoding/convert.cflags build/terminal/terminfo.cflags: | build/terminal/ build/encoding/convert.cflags: | build/encoding/ CFLAGS_ALL = $(CPPFLAGS) $(CFLAGS) $(BASIC_CFLAGS) LDFLAGS_ALL = $(CFLAGS) $(LDFLAGS) $(BASIC_LDFLAGS) $(dte) $(test): build/all.ldflags $(E) LINK $@ $(Q) $(CC) $(LDFLAGS_ALL) -o $@ $(filter %.o, $^) $(LDLIBS) $(editor_objects): build/%.o: src/%.c build/all.cflags | build/ $(E) CC $@ $(Q) $(CC) $(CFLAGS_ALL) $(DEPFLAGS) -c -o $@ $< $(test_objects): build/test/%.o: test/%.c build/all.cflags | build/test/ $(E) CC $@ $(Q) $(CC) $(CFLAGS_ALL) $(DEPFLAGS) -c -o $@ $< build/all.ldflags: FORCE | build/ @$(OPTCHECK) '$(CC) $(LDFLAGS_ALL) $(LDLIBS)' $@ build/%.cflags: FORCE | build/ @$(OPTCHECK) '$(CC) $(CFLAGS_ALL)' $@ build/version.h: FORCE | build/ @$(OPTCHECK) 'static const char version[] = "$(VERSION)";' $@ build/builtin-config.mk: FORCE | build/ @$(OPTCHECK) '$(@:.mk=.h): $(BUILTIN_CONFIGS)' $@ build/builtin-config.h: $(BUILTIN_CONFIGS) mk/config2c.awk | build/ $(E) GEN $@ $(Q) $(AWK) -f mk/config2c.awk $(BUILTIN_CONFIGS) > $@ build/test/data.h: $(TEST_CONFIGS) mk/config2c.awk | build/test/ $(E) GEN $@ $(Q) $(AWK) -f mk/config2c.awk $(TEST_CONFIGS) > $@ $(build_subdirs): | build/ $(Q) mkdir -p $@ build/: $(Q) mkdir -p $@ CLEANFILES += $(dte) CLEANDIRS += build/ .PHONY: FORCE .SECONDARY: build/ dte-1.9.1/mk/compat.mk000066400000000000000000000006301354415143300145230ustar00rootroot00000000000000REQUIRE = $(if $(filter $(1), $(.FEATURES)),, $(error $(REQERROR))) REQERROR = Required feature "$(strip $(1))" not supported by $(MAKE) $(call REQUIRE, target-specific) $(call REQUIRE, else-if) $(call REQUIRE, order-only) ifeq "$(MAKE_VERSION)" "3.81" $(warning Disabling build optimization to work around a bug in GNU Make 3.81) else make-lazy = $(eval $1 = $$(eval $1 := $(value $(1)))$$($1)) endif dte-1.9.1/mk/config2c.awk000077500000000000000000000025251354415143300151150ustar00rootroot00000000000000#!/usr/bin/awk -f function escape_ident(s) { gsub(/[+\/.-]/, "_", s) return s } function escape_string(s) { gsub(/\\/, "\\134", s) gsub(/"/, "\\042", s) return s } function escape_syntax(s) { gsub(/^ +/, "", s) gsub(/\\/, "\\134", s) gsub(/"/, "\\042", s) return s } BEGIN { print "#ifdef __linux__" print "#define CONFIG_SECTION SECTION(\".dte.config\") ALIGNED(8)" print "#else" print "#define CONFIG_SECTION" print "#endif\n" print "IGNORE_WARNING(\"-Woverlength-strings\")\n" } FNR == 1 { if (NR != 1) { print ";\n" } name = FILENAME gsub(/^config\//, "", name) ident = "builtin_" escape_ident(name) print "static CONFIG_SECTION const char " ident "[] =" names[++nfiles] = name idents[nfiles] = ident } name ~ /syntax\// { if ($0 !~ /^ *#/) { print "\"" escape_syntax($0) "\\n\"" } next } { print "\"" escape_string($0) "\\n\"" } END { print ";\n\nUNIGNORE_WARNINGS\n" print \ "#define cfg(n, t) { \\\n" \ " .name = n, \\\n" \ " .text = {.data = t, .length = sizeof(t) - 1} \\\n" \ "}\n" print "static CONFIG_SECTION const BuiltinConfig builtin_configs[] = {" for (i = 1; i <= nfiles; i++) { print " cfg(\"" names[i] "\", " idents[i] ")," } print "};" } dte-1.9.1/mk/docs.mk000066400000000000000000000037351354415143300142010ustar00rootroot00000000000000PANDOC = pandoc PANDOC_FLAGS = -f markdown_github+definition_lists+auto_identifiers+yaml_metadata_block-hard_line_breaks PDMAN = $(PANDOC) $(PANDOC_FLAGS) -t docs/pdman.lua PDHTML = $(PANDOC) $(PANDOC_FLAGS) -t html5 --toc --template=docs/template.html -Voutput_basename=$(@F) FINDLINKS = sed -n 's|^.*\(https\?://[A-Za-z0-9_/.-]*\).*|\1|gp' CHECKURL = curl -sSI -w '%{http_code} @1 %{redirect_url}\n' -o /dev/null @1 XARGS_P_FLAG = $(call try-run, printf "1\n2" | xargs -P2 -I@ echo '@', -P$(NPROC)) html-man = public/dterc.html public/dte-syntax.html html = public/index.html public/releases.html $(html-man) docs: man html gz man: docs/dterc.5 docs/dte-syntax.5 html: $(html) pdf: public/dte.pdf gz: $(patsubst %, %.gz, $(html) public/style.css) $(html): docs/template.html | public/style.css docs/%.5: docs/%.md docs/pdman.lua $(E) PANDOC $@ $(Q) $(PDMAN) -o $@ $< public/dte.pdf: docs/dte.1 docs/dterc.5 docs/dte-syntax.5 | public/ $(E) GROFF $@ $(Q) groff -mandoc -Tpdf $^ > $@ public/index.html: build/docs/index.md | public/screenshot.png $(E) PANDOC $@ $(Q) $(PDHTML) -Mtitle=_ -o $@ $< build/docs/index.md: README.md docs/keys.md | build/docs/ $(E) GEN $@ $(Q) sed '/^Online documentation is/,/^Public License/d' README.md > $@ $(Q) sed '/^`/s|`\([^`]\+\)`|\1|g' docs/keys.md >> $@ public/releases.html: CHANGELOG.md | public/ $(E) PANDOC $@ $(Q) $(PDHTML) -Mtitle=_ -o $@ $< $(html-man): public/%.html: docs/%.md $(E) PANDOC $@ $(Q) $(PDHTML) -o $@ $< public/style.css: docs/layout.css docs/style.css | public/ $(E) CSSCAT $@ $(Q) cat $^ > $@ public/screenshot.png: docs/screenshot.png | public/ $(E) CP $@ $(Q) cp $< $@ public/%.gz: public/% $(E) GZIP $@ $(Q) gzip -9 < $< > $@ public/: $(Q) mkdir -p $@ build/docs/: build/ $(Q) mkdir -p $@ check-docs: README.md CHANGELOG.md docs/contributing.md docs/dterc.md docs/dte-syntax.md @$(FINDLINKS) $^ | xargs -I@1 $(XARGS_P_FLAG) $(CHECKURL) CLEANDIRS += public/ .PHONY: docs man html pdf gz check-docs dte-1.9.1/mk/gen.mk000066400000000000000000000006261354415143300140160ustar00rootroot00000000000000FETCH = curl -LSs -o $@ LUA = lua UCD_FILES = $(addprefix .cache/, \ UnicodeData.txt EastAsianWidth.txt DerivedCoreProperties.txt ) gen-wcwidth: $(UCD_FILES) $(E) GEN src/util/wcwidth.c $(Q) $(LUA) src/util/wcwidth.lua $(UCD_FILES) > src/util/wcwidth.c $(UCD_FILES): | .cache/ $(E) FETCH $@ $(Q) $(FETCH) https://unicode.org/Public/12.1.0/ucd/$(@F) .cache/: @mkdir -p $@ .PHONY: gen-wcwidth dte-1.9.1/mk/nproc.sh000077500000000000000000000003651354415143300143740ustar00rootroot00000000000000#!/bin/sh case "$(uname)" in Linux) exec getconf _NPROCESSORS_ONLN;; FreeBSD|NetBSD|OpenBSD) exec /sbin/sysctl -n hw.ncpu;; Darwin) exec /usr/sbin/sysctl -n hw.activecpu;; SunOS) exec /usr/sbin/psrinfo -p;; *) echo 1;; esac dte-1.9.1/mk/optcheck.sh000077500000000000000000000003071354415143300150470ustar00rootroot00000000000000#!/bin/sh value="$1" file="$2" current=$(cat "$file" 2>/dev/null) if test "$current" != "$value"; then test "$SILENT_BUILD" || printf ' %7s %s\n' UPDATE "$file" echo "$value" > "$file" fi dte-1.9.1/mk/util.mk000066400000000000000000000034461354415143300142250ustar00rootroot00000000000000PKGCONFIG = $(shell command -v pkg-config || command -v pkgconf || echo ':') $(call make-lazy,PKGCONFIG) streq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) try-run = $(if $(shell $(1) >/dev/null 2>&1 && echo 1),$(2),$(3)) cc-option = $(call try-run,$(CC) $(1) -Werror -c -x c -o /dev/null /dev/null,$(1),$(2)) prefix-obj = $(addprefix $(1), $(addsuffix .o, $(2))) pkg-libs = $(shell $(PKGCONFIG) --libs $(1) 2>/dev/null) MAKEFLAGS += -r KERNEL := $(shell sh -c 'uname -s 2>/dev/null || echo not') OS := $(shell sh -c 'uname -o 2>/dev/null || echo not') DISTRO = $(shell . /etc/os-release && echo "$$NAME $$VERSION_ID") ARCH = $(shell uname -m 2>/dev/null) NPROC = $(shell sh mk/nproc.sh) _POSIX_VERSION = $(shell getconf _POSIX_VERSION 2>/dev/null) _XOPEN_VERSION = $(shell getconf _XOPEN_VERSION 2>/dev/null) TPUT = $(shell sh -c 'command -v tput') TPUT-V = $(if $(TPUT), $(shell $(TPUT) -V 2>/dev/null)) CC_VERSION = $(shell $(CC) --version 2>/dev/null | head -n1) MAKE_S = $(findstring s,$(firstword -$(MAKEFLAGS)))$(filter -s,$(MAKEFLAGS)) PRINTVAR = printf '\033[1m%15s\033[0m = %s$(2)\n' '$(1)' '$(strip $($(1)))' $(3) PRINTVARX = $(call PRINTVAR,$(1), \033[32m(%s)\033[0m, '$(origin $(1))') USERVARS = CC CFLAGS CPPFLAGS LDFLAGS LDLIBS DEBUG AUTOVARS = \ VERSION KERNEL \ $(if $(call streq,$(KERNEL),Linux), DISTRO) \ ARCH NPROC _POSIX_VERSION _XOPEN_VERSION \ TERM SHELL PKGCONFIG TPUT TPUT-V MAKE_VERSION CC_VERSION vars: @echo @$(foreach VAR, $(AUTOVARS), $(call PRINTVAR,$(VAR));) @$(foreach VAR, $(USERVARS), $(call PRINTVARX,$(VAR));) @echo .PHONY: vars ifneq "$(MAKE_S)" "" # Make "-s" flag was used (silent build) Q = @ E = @: else ifeq "$(V)" "1" # "V=1" variable was set (verbose build) Q = E = @: else # Normal build Q = @ E = @printf ' %7s %s\n' endif dte-1.9.1/mk/version.sh000077500000000000000000000012611354415143300147340ustar00rootroot00000000000000#!/bin/sh # This script generates the version string, as printed by "dte -V". set -eu export VPREFIX="$1" # These values are filled automatically for git-archive(1) tarballs. # See: "export-subst" in gitattributes(5). distinfo_commit_full='cae7c45ea5a563c022001a54d1eee71c268f62b4' distinfo_commit_short='cae7c45e' distinfo_author_date='2019-09-29 16:51:55 +0100' if expr "$distinfo_commit_short" : '[0-9a-f]\{7,40\}$' >/dev/null; then echo "${VPREFIX}-g${distinfo_commit_short}-dist" exit 0 fi git_describe_ver=$(git describe --match="v$VPREFIX" 2>/dev/null || true) if test -n "$git_describe_ver"; then echo "${git_describe_ver#v}" exit 0 fi echo "$VPREFIX-unknown" dte-1.9.1/src/000077500000000000000000000000001354415143300130705ustar00rootroot00000000000000dte-1.9.1/src/README.md000066400000000000000000000011551354415143300143510ustar00rootroot00000000000000dte source code =============== This directory contains the `dte` source code. It makes liberal use of ISO C99 features and POSIX 2008 APIs, but generally requires very little else. The main editor code is in the base directory and various other (somewhat reusable) parts are in sub-directories: * `editorconfig/` - [EditorConfig] implementation * `encoding/` - charset encoding/decoding/conversion * `filetype/` - filetype detection * `syntax/` - syntax highlighting * `terminal/` - terminal control and response parsing * `util/` - data structures, string utilities, etc. [EditorConfig]: https://editorconfig.org/ dte-1.9.1/src/alias.c000066400000000000000000000053431354415143300143320ustar00rootroot00000000000000#include #include #include #include "alias.h" #include "command.h" #include "completion.h" #include "editor.h" #include "error.h" #include "util/ascii.h" #include "util/macros.h" #include "util/ptr-array.h" #include "util/str-util.h" #include "util/xmalloc.h" typedef struct { char *name; char *value; } CommandAlias; static PointerArray aliases = PTR_ARRAY_INIT; static void CONSTRUCTOR prealloc(void) { ptr_array_init(&aliases, 32); } static bool is_valid_alias_name(const char *name) { if (unlikely(name[0] == '\0')) { error_msg("Empty alias name not allowed"); return false; } for (unsigned char c; (c = *name); name++) { if (is_word_byte(c) || c == '-' || c == '?' || c == '!') { continue; } error_msg("Invalid byte in alias name: 0x%02x", (unsigned int)c); return false; } return true; } void add_alias(const char *name, const char *value) { if (!is_valid_alias_name(name)) { return; } if (find_command(commands, name)) { error_msg("Can't replace existing command %s with an alias", name); return; } // Replace existing alias for (size_t i = 0; i < aliases.count; i++) { CommandAlias *alias = aliases.ptrs[i]; if (streq(alias->name, name)) { free(alias->value); alias->value = xstrdup(value); return; } } CommandAlias *alias = xnew(CommandAlias, 1); alias->name = xstrdup(name); alias->value = xstrdup(value); ptr_array_add(&aliases, alias); if (editor.status != EDITOR_INITIALIZING) { sort_aliases(); } } static int alias_cmp(const void *ap, const void *bp) { const CommandAlias *const *a = ap; const CommandAlias *const *b = bp; return strcmp((*a)->name, (*b)->name); } void sort_aliases(void) { ptr_array_sort(&aliases, alias_cmp); } const char *find_alias(const char *const name) { const CommandAlias key = {.name = (char*) name}; const void *ptr = ptr_array_bsearch(aliases, &key, alias_cmp); if (ptr) { const CommandAlias *alias = *(const CommandAlias **) ptr; return alias->value; } return NULL; } void collect_aliases(const char *const prefix) { for (size_t i = 0; i < aliases.count; i++) { const CommandAlias *const alias = aliases.ptrs[i]; if (str_has_prefix(alias->name, prefix)) { add_completion(xstrdup(alias->name)); } } } String dump_aliases(void) { String buf = string_new(4096); for (size_t i = 0; i < aliases.count; i++) { const CommandAlias *alias = aliases.ptrs[i]; string_sprintf(&buf, " %s -> %s\n", alias->name, alias->value); } return buf; } dte-1.9.1/src/alias.h000066400000000000000000000004001354415143300143240ustar00rootroot00000000000000#ifndef ALIAS_H #define ALIAS_H #include "util/string.h" void add_alias(const char *name, const char *value); void sort_aliases(void); const char *find_alias(const char *name); void collect_aliases(const char *prefix); String dump_aliases(void); #endif dte-1.9.1/src/bind.c000066400000000000000000000150571354415143300141600ustar00rootroot00000000000000#include #include "bind.h" #include "change.h" #include "command.h" #include "debug.h" #include "error.h" #include "parse-args.h" #include "util/ascii.h" #include "util/ptr-array.h" #include "util/str-util.h" #include "util/xmalloc.h" typedef struct { KeyCode key; KeyBinding *bind; } KeyBindingEntry; // Fast lookup table for most common key combinations (Ctrl or Meta // with ASCII keys or any combination of modifiers with special keys) static KeyBinding *bindings_lookup_table[(2 * 128) + (8 * NR_SPECIAL_KEYS)]; // Fallback for all other keys (Unicode combos etc.) static PointerArray bindings_ptr_array = PTR_ARRAY_INIT; static ssize_t key_lookup_index(KeyCode k) { const KeyCode modifiers = keycode_get_modifiers(k); const KeyCode key = keycode_get_key(k); static_assert(MOD_MASK >> 24 == (1 | 2 | 4)); if (key >= KEY_SPECIAL_MIN && key <= KEY_SPECIAL_MAX) { const size_t mod_offset = (modifiers >> 24) * NR_SPECIAL_KEYS; return (2 * 128) + mod_offset + (key - KEY_SPECIAL_MIN); } if (key >= 0x20 && key <= 0x7E) { switch (modifiers) { case MOD_CTRL: return key; case MOD_META: return key + 128; default: break; } } return -1; } UNITTEST { const size_t size = ARRAY_COUNT(bindings_lookup_table); const KeyCode min = KEY_SPECIAL_MIN; const KeyCode max = KEY_SPECIAL_MAX; const KeyCode nsk = NR_SPECIAL_KEYS; BUG_ON(key_lookup_index(MOD_MASK | max) != size - 1); BUG_ON(key_lookup_index(min) != 256); BUG_ON(key_lookup_index(max) != 256 + nsk - 1); BUG_ON(key_lookup_index(MOD_CTRL | min) != 256 + nsk); BUG_ON(key_lookup_index(MOD_SHIFT | max) != 256 + (5 * nsk) - 1); BUG_ON(key_lookup_index(MOD_CTRL | ' ') != 32); BUG_ON(key_lookup_index(MOD_META | ' ') != 32 + 128); BUG_ON(key_lookup_index(MOD_CTRL | '~') != 126); BUG_ON(key_lookup_index(MOD_META | '~') != 126 + 128); BUG_ON(key_lookup_index(MOD_CTRL | MOD_META | 'a') != -1); BUG_ON(key_lookup_index(MOD_META | 0x0E01) != -1); } static KeyBinding *key_binding_new(const char *cmd_str) { const size_t cmd_str_len = strlen(cmd_str); KeyBinding *b = xmalloc(sizeof(*b) + cmd_str_len + 1); b->cmd = NULL; memcpy(b->cmd_str, cmd_str, cmd_str_len + 1); PointerArray array = PTR_ARRAY_INIT; CommandParseError err = 0; if (!parse_commands(&array, cmd_str, &err)) { goto out; } ptr_array_trim_nulls(&array); if (array.count < 2 || ptr_array_idx(&array, NULL) != (array.count - 1)) { // Only single commands can be cached goto out; } const Command *cmd = find_command(commands, array.ptrs[0]); if (!cmd) { // Aliases or non-existent commands can't be cached goto out; } if (memchr(cmd_str, '$', cmd_str_len)) { // Commands containing variables can't be cached goto out; } free(ptr_array_remove_idx(&array, 0)); CommandArgs a = {.args = (char**)array.ptrs}; suppress_error_msg(); bool ok = parse_args(cmd, &a); unsuppress_error_msg(); if (!ok) { goto out; } // Command can be cached; binding takes ownership of args array b->cmd = cmd; b->a = a; return b; out: ptr_array_free(&array); return b; } static void key_binding_free(KeyBinding *binding) { if (binding) { if (binding->cmd) { free_string_array(binding->a.args); } free(binding); } } void add_binding(const char *keystr, const char *command) { KeyCode key; if (!parse_key(&key, keystr)) { error_msg("invalid key string: %s", keystr); return; } const ssize_t idx = key_lookup_index(key); if (idx >= 0) { key_binding_free(bindings_lookup_table[idx]); bindings_lookup_table[idx] = key_binding_new(command); return; } KeyBindingEntry *b = xnew(KeyBindingEntry, 1); b->key = key; b->bind = key_binding_new(command); ptr_array_add(&bindings_ptr_array, b); } void remove_binding(const char *keystr) { KeyCode key; if (!parse_key(&key, keystr)) { return; } const ssize_t idx = key_lookup_index(key); if (idx >= 0) { key_binding_free(bindings_lookup_table[idx]); bindings_lookup_table[idx] = NULL; return; } size_t i = bindings_ptr_array.count; while (i > 0) { KeyBindingEntry *b = bindings_ptr_array.ptrs[--i]; if (b->key == key) { ptr_array_remove_idx(&bindings_ptr_array, i); key_binding_free(b->bind); free(b); return; } } } const KeyBinding *lookup_binding(KeyCode key) { const ssize_t idx = key_lookup_index(key); if (idx >= 0) { const KeyBinding *b = bindings_lookup_table[idx]; if (b) { return b; } } for (size_t i = bindings_ptr_array.count; i > 0; i--) { KeyBindingEntry *b = bindings_ptr_array.ptrs[i - 1]; if (b->key == key) { return b->bind; } } return NULL; } void handle_binding(KeyCode key) { const KeyBinding *b = lookup_binding(key); if (!b) { return; } if (!b->cmd) { // Command isn't cached; parse and run command string handle_command(commands, b->cmd_str); return; } // Command is cached; call it directly begin_change(CHANGE_MERGE_NONE); b->cmd->cmd(&b->a); end_change(); } static void append_lookup_table_binding(String *buf, KeyCode key) { const ssize_t i = key_lookup_index(key); BUG_ON(i < 0); const KeyBinding *b = bindings_lookup_table[i]; if (b) { const char *keystr = key_to_string(key); string_sprintf(buf, " %-10s %s\n", keystr, b->cmd_str); } } String dump_bindings(void) { String buf = string_new(4096); for (KeyCode k = 0x20; k < 0x7E; k++) { append_lookup_table_binding(&buf, MOD_CTRL | k); } for (KeyCode k = 0x20; k < 0x7E; k++) { append_lookup_table_binding(&buf, MOD_META | k); } static_assert(MOD_CTRL == (1 << 24)); for (KeyCode m = 0, modifiers = 0; m <= 7; modifiers = ++m << 24) { for (KeyCode k = KEY_SPECIAL_MIN; k <= KEY_SPECIAL_MAX; k++) { append_lookup_table_binding(&buf, modifiers | k); } } for (size_t i = 0, nbinds = bindings_ptr_array.count; i < nbinds; i++) { const KeyBindingEntry *b = bindings_ptr_array.ptrs[i]; const char *keystr = key_to_string(b->key); string_sprintf(&buf, " %-10s %s\n", keystr, b->bind->cmd_str); } return buf; } dte-1.9.1/src/bind.h000066400000000000000000000010071354415143300141530ustar00rootroot00000000000000#ifndef BIND_H #define BIND_H #include "command.h" #include "terminal/key.h" #include "util/string.h" typedef struct { // The cached command and parsed arguments (NULL if not cached) const Command *cmd; CommandArgs a; // The original command string char cmd_str[]; } KeyBinding; void add_binding(const char *keystr, const char *command); void remove_binding(const char *keystr); const KeyBinding *lookup_binding(KeyCode key); void handle_binding(KeyCode key); String dump_bindings(void); #endif dte-1.9.1/src/block-iter.c000066400000000000000000000202411354415143300152660ustar00rootroot00000000000000#include "block-iter.h" #include "debug.h" #include "util/str-util.h" #include "util/utf8.h" #include "util/xmalloc.h" void block_iter_normalize(BlockIter *bi) { const Block *blk = bi->blk; if (bi->offset == blk->size && blk->node.next != bi->head) { bi->blk = BLOCK(blk->node.next); bi->offset = 0; } } /* * Move after next newline (beginning of next line or end of file). * Returns number of bytes iterator advanced. */ size_t block_iter_eat_line(BlockIter *bi) { block_iter_normalize(bi); const size_t offset = bi->offset; if (offset == bi->blk->size) { return 0; } // There must be at least one newline if (bi->blk->nl == 1) { bi->offset = bi->blk->size; } else { const unsigned char *end; end = memchr(bi->blk->data + offset, '\n', bi->blk->size - offset); bi->offset = end + 1 - bi->blk->data; } return bi->offset - offset; } /* * Move to beginning of next line. * If there is no next line iterator is not advanced. * Returns number of bytes iterator advanced. */ size_t block_iter_next_line(BlockIter *bi) { block_iter_normalize(bi); const size_t offset = bi->offset; if (offset == bi->blk->size) { return 0; } // There must be at least one newline size_t new_offset; if (bi->blk->nl == 1) { new_offset = bi->blk->size; } else { const unsigned char *end; end = memchr(bi->blk->data + offset, '\n', bi->blk->size - offset); new_offset = end + 1 - bi->blk->data; } if (new_offset == bi->blk->size && bi->blk->node.next == bi->head) { return 0; } bi->offset = new_offset; return bi->offset - offset; } /* * Move to beginning of previous line. * Returns number of bytes moved, which is zero if there's no previous line. */ size_t block_iter_prev_line(BlockIter *bi) { Block *blk = bi->blk; size_t offset = bi->offset; size_t start = offset; while (offset && blk->data[offset - 1] != '\n') { offset--; } if (!offset) { if (blk->node.prev == bi->head) { return 0; } bi->blk = blk = BLOCK(blk->node.prev); offset = blk->size; start += offset; } offset--; while (offset && blk->data[offset - 1] != '\n') { offset--; } bi->offset = offset; return start - offset; } size_t block_iter_get_char(BlockIter *bi, CodePoint *up) { BlockIter tmp = *bi; return block_iter_next_char(&tmp, up); } size_t block_iter_next_char(BlockIter *bi, CodePoint *up) { size_t offset = bi->offset; if (offset == bi->blk->size) { if (bi->blk->node.next == bi->head) { return 0; } bi->blk = BLOCK(bi->blk->node.next); bi->offset = offset = 0; } // Note: this block can't be empty *up = bi->blk->data[offset]; if (*up < 0x80) { bi->offset++; return 1; } *up = u_get_nonascii(bi->blk->data, bi->blk->size, &bi->offset); return bi->offset - offset; } size_t block_iter_prev_char(BlockIter *bi, CodePoint *up) { size_t offset = bi->offset; if (!offset) { if (bi->blk->node.prev == bi->head) { return 0; } bi->blk = BLOCK(bi->blk->node.prev); bi->offset = offset = bi->blk->size; } // Note: this block can't be empty *up = bi->blk->data[offset - 1]; if (*up < 0x80) { bi->offset--; return 1; } *up = u_prev_char(bi->blk->data, &bi->offset); return offset - bi->offset; } size_t block_iter_next_column(BlockIter *bi) { CodePoint u; size_t size = block_iter_next_char(bi, &u); while (block_iter_get_char(bi, &u) && u_is_zero_width(u)) { size += block_iter_next_char(bi, &u); } return size; } size_t block_iter_prev_column(BlockIter *bi) { CodePoint u; size_t skip, total = 0; do { skip = block_iter_prev_char(bi, &u); total += skip; } while (skip && u_is_zero_width(u)); return total; } size_t block_iter_bol(BlockIter *bi) { block_iter_normalize(bi); size_t offset = bi->offset; if (!offset || offset == bi->blk->size) { return 0; } if (bi->blk->nl == 1) { offset = 0; } else { while (offset && bi->blk->data[offset - 1] != '\n') { offset--; } } const size_t ret = bi->offset - offset; bi->offset = offset; return ret; } size_t block_iter_eol(BlockIter *bi) { block_iter_normalize(bi); const Block *blk = bi->blk; const size_t offset = bi->offset; if (offset == blk->size) { // Cursor at end of last block return 0; } if (blk->nl == 1) { bi->offset = blk->size - 1; return bi->offset - offset; } const unsigned char *end; end = memchr(blk->data + offset, '\n', blk->size - offset); bi->offset = end - blk->data; return bi->offset - offset; } void block_iter_back_bytes(BlockIter *bi, size_t count) { while (count > bi->offset) { count -= bi->offset; bi->blk = BLOCK(bi->blk->node.prev); bi->offset = bi->blk->size; } bi->offset -= count; } void block_iter_skip_bytes(BlockIter *bi, size_t count) { size_t avail = bi->blk->size - bi->offset; while (count > avail) { count -= avail; bi->blk = BLOCK(bi->blk->node.next); bi->offset = 0; avail = bi->blk->size; } bi->offset += count; } void block_iter_goto_offset(BlockIter *bi, size_t offset) { Block *blk; block_for_each(blk, bi->head) { if (offset <= blk->size) { bi->blk = blk; bi->offset = offset; return; } offset -= blk->size; } } void block_iter_goto_line(BlockIter *bi, size_t line) { Block *blk = BLOCK(bi->head->next); size_t nl = 0; while (blk->node.next != bi->head && nl + blk->nl < line) { nl += blk->nl; blk = BLOCK(blk->node.next); } bi->blk = blk; bi->offset = 0; while (nl < line) { if (!block_iter_eat_line(bi)) { break; } nl++; } } size_t block_iter_get_offset(const BlockIter *bi) { const Block *blk; size_t offset = 0; block_for_each(blk, bi->head) { if (blk == bi->blk) { break; } offset += blk->size; } return offset + bi->offset; } bool block_iter_is_bol(const BlockIter *bi) { const size_t offset = bi->offset; if (offset == 0) { return true; } return bi->blk->data[offset - 1] == '\n'; } char *block_iter_get_bytes(const BlockIter *bi, size_t len) { if (len == 0) { return NULL; } const Block *blk = bi->blk; size_t offset = bi->offset; size_t pos = 0; char *buf = xmalloc(len); while (pos < len) { const size_t avail = blk->size - offset; size_t count = len - pos; if (count > avail) { count = avail; } memcpy(buf + pos, blk->data + offset, count); pos += count; BUG_ON(pos < len && blk->node.next == bi->head); blk = BLOCK(blk->node.next); offset = 0; } return buf; } // bi should be at bol void fill_line_ref(BlockIter *bi, LineRef *lr) { block_iter_normalize(bi); lr->line = bi->blk->data + bi->offset; const size_t max = bi->blk->size - bi->offset; if (max == 0) { // Cursor at end of last block lr->size = 0; return; } if (bi->blk->nl == 1) { lr->size = max - 1; return; } const unsigned char *nl = memchr(lr->line, '\n', max); lr->size = nl - lr->line; } void fill_line_nl_ref(BlockIter *bi, LineRef *lr) { block_iter_normalize(bi); lr->line = bi->blk->data + bi->offset; const size_t max = bi->blk->size - bi->offset; if (max == 0) { // Cursor at end of last block lr->size = 0; return; } if (bi->blk->nl == 1) { lr->size = max; return; } const unsigned char *nl = memchr(lr->line, '\n', max); lr->size = nl - lr->line + 1; } size_t fetch_this_line(const BlockIter *bi, LineRef *lr) { BlockIter tmp = *bi; const size_t count = block_iter_bol(&tmp); fill_line_ref(&tmp, lr); return count; } dte-1.9.1/src/block-iter.h000066400000000000000000000035371354415143300153040ustar00rootroot00000000000000#ifndef BLOCK_ITER_H #define BLOCK_ITER_H #include #include #include "block.h" #include "util/macros.h" #include "util/unicode.h" typedef struct { Block *blk; ListHead *head; size_t offset; } BlockIter; typedef struct { const unsigned char NONSTRING *line; size_t size; } LineRef; #define BLOCK_ITER_INIT(head_) { \ .blk = BLOCK((head_)->next), \ .head = (head_), \ .offset = 0 \ } static inline void block_iter_bof(BlockIter *bi) { bi->blk = BLOCK(bi->head->next); bi->offset = 0; } static inline void block_iter_eof(BlockIter *bi) { bi->blk = BLOCK(bi->head->prev); bi->offset = bi->blk->size; } static inline bool block_iter_is_eof(const BlockIter *const bi) { return bi->offset == bi->blk->size && bi->blk->node.next == bi->head; } void block_iter_normalize(BlockIter *bi); size_t block_iter_eat_line(BlockIter *bi); size_t block_iter_next_line(BlockIter *bi); size_t block_iter_prev_line(BlockIter *bi); size_t block_iter_get_char(BlockIter *bi, CodePoint *up); size_t block_iter_next_char(BlockIter *bi, CodePoint *up); size_t block_iter_prev_char(BlockIter *bi, CodePoint *up); size_t block_iter_next_column(BlockIter *bi); size_t block_iter_prev_column(BlockIter *bi); size_t block_iter_bol(BlockIter *bi); size_t block_iter_eol(BlockIter *bi); void block_iter_back_bytes(BlockIter *bi, size_t count); void block_iter_skip_bytes(BlockIter *bi, size_t count); void block_iter_goto_offset(BlockIter *bi, size_t offset); void block_iter_goto_line(BlockIter *bi, size_t line); size_t block_iter_get_offset(const BlockIter *bi); bool block_iter_is_bol(const BlockIter *bi); char *block_iter_get_bytes(const BlockIter *bi, size_t len); void fill_line_ref(BlockIter *bi, LineRef *lr); void fill_line_nl_ref(BlockIter *bi, LineRef *lr); size_t fetch_this_line(const BlockIter *bi, LineRef *lr); #endif dte-1.9.1/src/block.c000066400000000000000000000253421354415143300143340ustar00rootroot00000000000000#include "block.h" #include "buffer.h" #include "debug.h" #include "syntax/highlight.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "view.h" #define BLOCK_EDIT_SIZE 512 static void sanity_check(void) { #if DEBUG >= 1 BUG_ON(list_empty(&buffer->blocks)); bool cursor_seen = false; Block *blk; block_for_each(blk, &buffer->blocks) { BUG_ON(!blk->size && buffer->blocks.next->next != &buffer->blocks); BUG_ON(blk->size > blk->alloc); BUG_ON(blk->size && blk->data[blk->size - 1] != '\n'); if (blk == view->cursor.blk) { cursor_seen = true; } if (DEBUG > 2) { BUG_ON(count_nl(blk->data, blk->size) != blk->nl); } } BUG_ON(!cursor_seen); BUG_ON(view->cursor.offset > view->cursor.blk->size); #endif } static size_t ALLOC_ROUND(size_t size) { return ROUND_UP(size, 64); } Block *block_new(size_t alloc) { Block *blk = xnew0(Block, 1); alloc = ALLOC_ROUND(alloc); blk->data = xmalloc(alloc); blk->alloc = alloc; return blk; } static void delete_block(Block *blk) { list_del(&blk->node); free(blk->data); free(blk); } static size_t copy_count_nl(char *dst, const char *const src, size_t len) { size_t nl = 0; for (size_t i = 0; i < len; i++) { dst[i] = src[i]; if (src[i] == '\n') { nl++; } } return nl; } static size_t insert_to_current(const char *buf, size_t len) { Block *blk = view->cursor.blk; size_t offset = view->cursor.offset; size_t size = blk->size + len; if (size > blk->alloc) { blk->alloc = ALLOC_ROUND(size); xrenew(blk->data, blk->alloc); } memmove(blk->data + offset + len, blk->data + offset, blk->size - offset); size_t nl = copy_count_nl(blk->data + offset, buf, len); blk->nl += nl; blk->size = size; return nl; } /* * Combine current block and new data into smaller blocks: * - Block _must_ contain whole lines * - Block _must_ contain at least one line * - Preferred maximum size of block is BLOCK_EDIT_SIZE * - Size of any block can be larger than BLOCK_EDIT_SIZE * only if there's a very long line */ static size_t split_and_insert(const char *buf, size_t len) { Block *blk = view->cursor.blk; ListHead *prev_node = blk->node.prev; const char *buf1 = blk->data; const char *buf2 = buf; const char *buf3 = blk->data + view->cursor.offset; size_t size1 = view->cursor.offset; size_t size2 = len; size_t size3 = blk->size - size1; size_t total = size1 + size2 + size3; size_t start = 0; // Beginning of new block size_t size = 0; // Size of new block size_t pos = 0; // Current position size_t nl_added = 0; while (start < total) { // Size of new block if next line would be added size_t new_size = 0; size_t copied = 0; if (pos < size1) { const char *nl = memchr(buf1 + pos, '\n', size1 - pos); if (nl) { new_size = nl - buf1 + 1 - start; } } if (!new_size && pos < size1 + size2) { size_t offset = 0; if (pos > size1) { offset = pos - size1; } const char *nl = memchr(buf2 + offset, '\n', size2 - offset); if (nl) { new_size = size1 + nl - buf2 + 1 - start; } } if (!new_size && pos < total) { size_t offset = 0; if (pos > size1 + size2) { offset = pos - size1 - size2; } const char *nl = memchr(buf3 + offset, '\n', size3 - offset); if (nl) { new_size = size1 + size2 + nl - buf3 + 1 - start; } else { new_size = total - start; } } if (new_size <= BLOCK_EDIT_SIZE) { // Fits size = new_size; pos = start + new_size; if (pos < total) { continue; } } else { // Does not fit if (!size) { // One block containing one very long line size = new_size; pos = start + new_size; } } BUG_ON(!size); Block *new = block_new(size); if (start < size1) { size_t avail = size1 - start; size_t count = size; if (count > avail) { count = avail; } new->nl += copy_count_nl(new->data, buf1 + start, count); copied += count; start += count; } if (start >= size1 && start < size1 + size2) { size_t offset = start - size1; size_t avail = size2 - offset; size_t count = size - copied; if (count > avail) { count = avail; } new->nl += copy_count_nl(new->data + copied, buf2 + offset, count); copied += count; start += count; } if (start >= size1 + size2) { size_t offset = start - size1 - size2; size_t avail = size3 - offset; size_t count = size - copied; BUG_ON(count > avail); new->nl += copy_count_nl(new->data + copied, buf3 + offset, count); copied += count; start += count; } new->size = size; BUG_ON(copied != size); list_add_before(&new->node, &blk->node); nl_added += new->nl; size = 0; } view->cursor.blk = BLOCK(prev_node->next); while (view->cursor.offset > view->cursor.blk->size) { view->cursor.offset -= view->cursor.blk->size; view->cursor.blk = BLOCK(view->cursor.blk->node.next); } nl_added -= blk->nl; delete_block(blk); return nl_added; } static size_t insert_bytes(const char *buf, size_t len) { // Blocks must contain whole lines. // Last char of buf might not be newline. block_iter_normalize(&view->cursor); Block *blk = view->cursor.blk; size_t new_size = blk->size + len; if (new_size <= blk->alloc || new_size <= BLOCK_EDIT_SIZE) { return insert_to_current(buf, len); } if (blk->nl <= 1 && !memchr(buf, '\n', len)) { // Can't split this possibly very long line. // insert_to_current() is much faster than split_and_insert(). return insert_to_current(buf, len); } return split_and_insert(buf, len); } void do_insert(const char *buf, size_t len) { size_t nl = insert_bytes(buf, len); buffer->nl += nl; sanity_check(); view_update_cursor_y(view); buffer_mark_lines_changed(view->buffer, view->cy, nl ? INT_MAX : view->cy); if (buffer->syn) { hl_insert(buffer, view->cy, nl); } } static bool only_block(const Block *blk) { return blk->node.prev == &buffer->blocks && blk->node.next == &buffer->blocks; } char *do_delete(size_t len) { ListHead *saved_prev_node = NULL; Block *blk = view->cursor.blk; size_t offset = view->cursor.offset; size_t pos = 0; size_t deleted_nl = 0; if (!len) { return NULL; } if (!offset) { // The block where cursor is can become empty and thereby may be deleted saved_prev_node = blk->node.prev; } char *buf = xmalloc(len); while (pos < len) { ListHead *next = blk->node.next; size_t avail = blk->size - offset; size_t count = len - pos; if (count > avail) { count = avail; } size_t nl = copy_count_nl(buf + pos, blk->data + offset, count); if (count < avail) { memmove ( blk->data + offset, blk->data + offset + count, avail - count ); } deleted_nl += nl; buffer->nl -= nl; blk->nl -= nl; blk->size -= count; if (!blk->size && !only_block(blk)) { delete_block(blk); } offset = 0; pos += count; blk = BLOCK(next); BUG_ON(pos < len && next == &buffer->blocks); } if (saved_prev_node) { // Cursor was at beginning of a block that was possibly deleted if (saved_prev_node->next == &buffer->blocks) { view->cursor.blk = BLOCK(saved_prev_node); view->cursor.offset = view->cursor.blk->size; } else { view->cursor.blk = BLOCK(saved_prev_node->next); } } blk = view->cursor.blk; if ( blk->size && blk->data[blk->size - 1] != '\n' && blk->node.next != &buffer->blocks ) { Block *next = BLOCK(blk->node.next); size_t size = blk->size + next->size; if (size > blk->alloc) { blk->alloc = ALLOC_ROUND(size); xrenew(blk->data, blk->alloc); } memcpy(blk->data + blk->size, next->data, next->size); blk->size = size; blk->nl += next->nl; delete_block(next); } sanity_check(); view_update_cursor_y(view); buffer_mark_lines_changed ( view->buffer, view->cy, deleted_nl ? INT_MAX : view->cy ); if (buffer->syn) { hl_delete(buffer, view->cy, deleted_nl); } return buf; } char *do_replace(size_t del, const char *buf, size_t ins) { block_iter_normalize(&view->cursor); Block *blk = view->cursor.blk; size_t offset = view->cursor.offset; size_t avail = blk->size - offset; if (del >= avail) { goto slow; } size_t new_size = blk->size + ins - del; if (new_size > BLOCK_EDIT_SIZE) { // Should split if (blk->nl > 1 || memchr(buf, '\n', ins)) { // Most likely can be split goto slow; } } if (new_size > blk->alloc) { blk->alloc = ALLOC_ROUND(new_size); xrenew(blk->data, blk->alloc); } // Modification is limited to one block char *ptr = blk->data + offset; char *deleted = xmalloc(del); size_t del_nl = copy_count_nl(deleted, ptr, del); blk->nl -= del_nl; buffer->nl -= del_nl; if (del != ins) { memmove(ptr + ins, ptr + del, avail - del); } size_t ins_nl = copy_count_nl(ptr, buf, ins); blk->nl += ins_nl; buffer->nl += ins_nl; blk->size = new_size; sanity_check(); view_update_cursor_y(view); if (del_nl == ins_nl) { // Some line(s) changed but lines after them did not move up or down buffer_mark_lines_changed(view->buffer, view->cy, view->cy + del_nl); } else { buffer_mark_lines_changed(view->buffer, view->cy, INT_MAX); } if (buffer->syn) { hl_delete(buffer, view->cy, del_nl); hl_insert(buffer, view->cy, ins_nl); } return deleted; slow: deleted = do_delete(del); do_insert(buf, ins); return deleted; } dte-1.9.1/src/block.h000066400000000000000000000015421354415143300143350ustar00rootroot00000000000000#ifndef BLOCK_H #define BLOCK_H #include #include "util/list.h" #include "util/macros.h" // Blocks always contain whole lines. // There's one zero-sized block when the file is empty. // Otherwise zero-sized blocks are forbidden. typedef struct { ListHead node; unsigned char NONSTRING *data; size_t size; size_t alloc; size_t nl; } Block; static inline Block *BLOCK(const ListHead *item) { static_assert(offsetof(Block, node) == 0); return (Block*)item; } #define block_for_each(block_, list_head_) \ for ( \ block_ = BLOCK((list_head_)->next); \ &block_->node != (list_head_); \ block_ = BLOCK(block_->node.next) \ ) Block *block_new(size_t size); void do_insert(const char *buf, size_t len); char *do_delete(size_t len); char *do_replace(size_t del, const char *buf, size_t ins); #endif dte-1.9.1/src/buffer.c000066400000000000000000000231771354415143300145170ustar00rootroot00000000000000#include #include #include #include "block.h" #include "buffer.h" #include "editor.h" #include "file-option.h" #include "filetype.h" #include "lock.h" #include "syntax/state.h" #include "util/hashset.h" #include "util/path.h" #include "util/str-util.h" #include "util/string-view.h" #include "util/xmalloc.h" Buffer *buffer; PointerArray buffers = PTR_ARRAY_INIT; static void set_display_filename(Buffer *b, char *name) { free(b->display_filename); b->display_filename = name; } /* * Mark line range min...max (inclusive) "changed". These lines will be * redrawn when screen is updated. This is called even when content has not * been changed, but selection has or line has been deleted and all lines * after the deleted line move up. * * Syntax highlighter has different logic. It cares about contents of the * lines, not about selection or if the lines have been moved up or down. */ void buffer_mark_lines_changed(Buffer *b, long min, long max) { if (min > max) { long tmp = min; min = max; max = tmp; } if (min < b->changed_line_min) { b->changed_line_min = min; } if (max > b->changed_line_max) { b->changed_line_max = max; } } const char *buffer_filename(const Buffer *b) { return b->display_filename; } Buffer *buffer_new(const Encoding *encoding) { static unsigned int id; Buffer *b = xnew0(Buffer, 1); list_init(&b->blocks); b->cur_change = &b->change_head; b->saved_change = &b->change_head; b->id = ++id; b->newline = editor.options.newline; if (encoding) { b->encoding = *encoding; } else { b->encoding.type = ENCODING_AUTODETECT; } memcpy(&b->options, &editor.options, sizeof(CommonOptions)); b->options.brace_indent = 0; b->options.filetype = str_intern("none"); b->options.indent_regex = NULL; ptr_array_add(&buffers, b); return b; } Buffer *open_empty_buffer(void) { Buffer *b = buffer_new(&editor.charset); // At least one block required Block *blk = block_new(1); list_add_before(&blk->node, &b->blocks); set_display_filename(b, xmemdup_literal("(No name)")); return b; } void free_buffer(Buffer *b) { ptr_array_remove(&buffers, b); if (b->locked) { unlock_file(b->abs_filename); } ListHead *item = b->blocks.next; while (item != &b->blocks) { ListHead *next = item->next; Block *blk = BLOCK(item); free(blk->data); free(blk); item = next; } free_changes(&b->change_head); free(b->line_start_states.ptrs); free(b->views.ptrs); free(b->display_filename); free(b->abs_filename); free(b); } static bool same_file(const struct stat *const a, const struct stat *const b) { return a->st_dev == b->st_dev && a->st_ino == b->st_ino; } Buffer *find_buffer(const char *abs_filename) { struct stat st; bool st_ok = stat(abs_filename, &st) == 0; for (size_t i = 0; i < buffers.count; i++) { Buffer *b = buffers.ptrs[i]; const char *f = b->abs_filename; if ( (f != NULL && streq(f, abs_filename)) || (st_ok && same_file(&st, &b->st)) ) { return b; } } return NULL; } Buffer *find_buffer_by_id(unsigned long id) { for (size_t i = 0; i < buffers.count; i++) { Buffer *b = buffers.ptrs[i]; if (b->id == id) { return b; } } return NULL; } bool buffer_detect_filetype(Buffer *b) { const char *ft = NULL; if (BLOCK(b->blocks.next)->size) { BlockIter bi = BLOCK_ITER_INIT(&b->blocks); LineRef lr; fill_line_ref(&bi, &lr); const StringView line = string_view(lr.line, lr.size); ft = find_ft(b->abs_filename, line); } else if (b->abs_filename) { const StringView line = STRING_VIEW_INIT; ft = find_ft(b->abs_filename, line); } if (ft && !streq(ft, b->options.filetype)) { b->options.filetype = str_intern(ft); return true; } return false; } static char *short_filename_cwd(const char *absolute, const char *cwd) { char *f = relative_filename(absolute, cwd); size_t home_len = strlen(editor.home_dir); size_t abs_len = strlen(absolute); size_t f_len = strlen(f); if (f_len >= abs_len) { // Prefer absolute if relative isn't shorter free(f); f = xstrdup(absolute); f_len = abs_len; } if ( abs_len > home_len && !memcmp(absolute, editor.home_dir, home_len) && absolute[home_len] == '/' ) { size_t len = abs_len - home_len + 1; if (len < f_len) { char *filename = xmalloc(len + 1); filename[0] = '~'; memcpy(filename + 1, absolute + home_len, len); free(f); return filename; } } return f; } char *short_filename(const char *absolute) { char cwd[8192]; if (getcwd(cwd, sizeof(cwd))) { return short_filename_cwd(absolute, cwd); } return xstrdup(absolute); } void update_short_filename_cwd(Buffer *b, const char *cwd) { if (b->abs_filename) { if (cwd) { set_display_filename(b, short_filename_cwd(b->abs_filename, cwd)); } else { // getcwd() failed set_display_filename(b, xstrdup(b->abs_filename)); } } } void update_short_filename(Buffer *b) { set_display_filename(b, short_filename(b->abs_filename)); } void buffer_update_syntax(Buffer *b) { Syntax *syn = NULL; if (b->options.syntax) { // Even "none" can have syntax syn = find_syntax(b->options.filetype); if (!syn) { syn = load_syntax_by_filetype(b->options.filetype); } } if (syn == b->syn) { return; } b->syn = syn; if (syn) { // Start state of first line is constant PointerArray *s = &b->line_start_states; if (!s->alloc) { s->alloc = 64; s->ptrs = xnew(void *, s->alloc); } s->ptrs[0] = syn->states.ptrs[0]; s->count = 1; } mark_all_lines_changed(b); } static bool allow_odd_indent(const Buffer *b) { // 1, 3, 5 and 7 space indent const unsigned int odd = 1 << 0 | 1 << 2 | 1 << 4 | 1 << 6; return (b->options.detect_indent & odd) ? true : false; } static int indent_len(const Buffer *b, const char *line, int len, bool *tab_indent) { bool space_before_tab = false; int spaces = 0; int tabs = 0; int pos = 0; while (pos < len) { if (line[pos] == ' ') { spaces++; } else if (line[pos] == '\t') { tabs++; if (spaces) { space_before_tab = true; } } else { break; } pos++; } *tab_indent = false; if (pos == len) { // Whitespace only return -1; } if (pos == 0) { // Not indented return 0; } if (space_before_tab) { // Mixed indent return -2; } if (tabs) { // Tabs and possible spaces after tab for alignment *tab_indent = true; return tabs * 8; } if (len > spaces && line[spaces] == '*') { // '*' after indent, could be long C style comment if (spaces % 2 || allow_odd_indent(b)) { return spaces - 1; } } return spaces; } static bool detect_indent(Buffer *b) { BlockIter bi = BLOCK_ITER_INIT(&b->blocks); int current_indent = 0; int counts[9] = {0}; int tab_count = 0; int space_count = 0; for (unsigned int i = 0; i < 200; i++) { LineRef lr; int indent; bool tab; fill_line_ref(&bi, &lr); indent = indent_len(b, lr.line, lr.size, &tab); if (indent == -2) { // Ignore mixed indent because tab width might not be 8 } else if (indent == -1) { // Empty line, no change in indent } else if (indent == 0) { current_indent = 0; } else { // Indented line int change; // Count only increase in indentation because indentation // almost always grows one level at time, whereas it can // can decrease multiple levels all at once. if (current_indent == -1) { current_indent = 0; } change = indent - current_indent; if (change > 0 && change <= 8) { counts[change]++; } if (tab) { tab_count++; } else { space_count++; } current_indent = indent; } if (!block_iter_next_line(&bi)) { break; } } if (tab_count == 0 && space_count == 0) { return false; } if (tab_count > space_count) { b->options.emulate_tab = false; b->options.expand_tab = false; b->options.indent_width = b->options.tab_width; } else { size_t m = 0; for (size_t i = 1; i < ARRAY_COUNT(counts); i++) { if (b->options.detect_indent & 1 << (i - 1)) { if (counts[i] > counts[m]) { m = i; } } } if (m == 0) { return false; } b->options.emulate_tab = true; b->options.expand_tab = true; b->options.indent_width = m; } return true; } void buffer_setup(Buffer *b) { b->setup = true; buffer_detect_filetype(b); set_file_options(b); set_editorconfig_options(b); buffer_update_syntax(b); if (b->options.detect_indent && b->abs_filename != NULL) { detect_indent(b); } } dte-1.9.1/src/buffer.h000066400000000000000000000040411354415143300145110ustar00rootroot00000000000000#ifndef BUFFER_H #define BUFFER_H #include #include #include #include #include "block-iter.h" #include "change.h" #include "encoding/encoding.h" #include "options.h" #include "syntax/syntax.h" #include "util/list.h" #include "util/macros.h" #include "util/ptr-array.h" #include "util/unicode.h" typedef struct Buffer { ListHead blocks; Change change_head; Change *cur_change; // Used to determine if buffer is modified Change *saved_change; struct stat st; // Needed for identifying buffers whose filename is NULL unsigned long id; size_t nl; // Views pointing to this buffer PointerArray views; char *display_filename; char *abs_filename; bool readonly; bool locked; bool setup; LineEndingType newline; // Encoding of the file. Buffer always contains UTF-8. Encoding encoding; LocalOptions options; Syntax *syn; // Index 0 is always syn->states.ptrs[0]. // Lowest bit of an invalidated value is 1. PointerArray line_start_states; long changed_line_min; long changed_line_max; } Buffer; // buffer = view->buffer = window->view->buffer extern struct View *view; extern Buffer *buffer; extern PointerArray buffers; static inline void mark_all_lines_changed(Buffer *b) { b->changed_line_min = 0; b->changed_line_max = INT_MAX; } static inline bool buffer_modified(const Buffer *b) { return b->saved_change != b->cur_change; } void buffer_mark_lines_changed(Buffer *b, long min, long max); const char *buffer_filename(const Buffer *b); char *short_filename(const char *absolute) XSTRDUP; void update_short_filename_cwd(Buffer *b, const char *cwd); void update_short_filename(Buffer *b); Buffer *find_buffer(const char *abs_filename); Buffer *find_buffer_by_id(unsigned long id); Buffer *buffer_new(const Encoding *encoding); Buffer *open_empty_buffer(void); void free_buffer(Buffer *b); bool buffer_detect_filetype(Buffer *b); void buffer_update_syntax(Buffer *b); void buffer_setup(Buffer *b); #endif dte-1.9.1/src/change.c000066400000000000000000000243771354415143300144760ustar00rootroot00000000000000#include "change.h" #include "buffer.h" #include "debug.h" #include "error.h" #include "util/xmalloc.h" #include "view.h" static ChangeMergeEnum change_merge; static ChangeMergeEnum prev_change_merge; static Change *alloc_change(void) { return xcalloc(sizeof(Change)); } static void add_change(Change *change) { Change *head = buffer->cur_change; change->next = head; xrenew(head->prev, head->nr_prev + 1); head->prev[head->nr_prev++] = change; buffer->cur_change = change; } // This doesn't need to be local to buffer because commands are atomic static Change *change_barrier; static bool is_change_chain_barrier(const Change *change) { return !change->ins_count && !change->del_count; } static Change *new_change(void) { if (change_barrier) { /* * We are recording series of changes (:replace for example) * and now we have just made the first change so we have to * mark beginning of the chain. * * We could have done this before when starting the change * chain but then we may have ended up with an empty chain. * We don't want to record empty changes ever. */ add_change(change_barrier); change_barrier = NULL; } Change *change = alloc_change(); add_change(change); return change; } static size_t buffer_offset(void) { return block_iter_get_offset(&view->cursor); } static void record_insert(size_t len) { Change *change = buffer->cur_change; BUG_ON(!len); if ( change_merge == prev_change_merge && change_merge == CHANGE_MERGE_INSERT ) { BUG_ON(change->del_count); change->ins_count += len; return; } change = new_change(); change->offset = buffer_offset(); change->ins_count = len; } static void record_delete(char *buf, size_t len, bool move_after) { BUG_ON(!len); BUG_ON(!buf); Change *change = buffer->cur_change; if (change_merge == prev_change_merge) { if (change_merge == CHANGE_MERGE_DELETE) { xrenew(change->buf, change->del_count + len); memcpy(change->buf + change->del_count, buf, len); change->del_count += len; free(buf); return; } if (change_merge == CHANGE_MERGE_ERASE) { xrenew(buf, len + change->del_count); memcpy(buf + len, change->buf, change->del_count); change->del_count += len; free(change->buf); change->buf = buf; change->offset -= len; return; } } change = new_change(); change->offset = buffer_offset(); change->del_count = len; change->move_after = move_after; change->buf = buf; } static void record_replace(char *deleted, size_t del_count, size_t ins_count) { BUG_ON(del_count && !deleted); BUG_ON(!del_count && deleted); BUG_ON(!del_count && !ins_count); Change *change = new_change(); change->offset = buffer_offset(); change->ins_count = ins_count; change->del_count = del_count; change->buf = deleted; } void begin_change(ChangeMergeEnum m) { change_merge = m; } void end_change(void) { prev_change_merge = change_merge; } void begin_change_chain(void) { BUG_ON(change_barrier); // Allocate change chain barrier but add it to the change tree only if // there will be any real changes change_barrier = alloc_change(); change_merge = CHANGE_MERGE_NONE; } void end_change_chain(void) { if (change_barrier) { // There were no changes in this change chain. free(change_barrier); change_barrier = NULL; } else { // There were some changes. Add end of chain marker. add_change(alloc_change()); } } static void fix_cursors(size_t offset, size_t del, size_t ins) { for (size_t i = 0, n = buffer->views.count; i < n; i++) { View *v = buffer->views.ptrs[i]; if (v != view && offset < v->saved_cursor_offset) { if (offset + del <= v->saved_cursor_offset) { v->saved_cursor_offset -= del; v->saved_cursor_offset += ins; } else { v->saved_cursor_offset = offset; } } } } static void reverse_change(Change *change) { if (buffer->views.count > 1) { fix_cursors(change->offset, change->ins_count, change->del_count); } block_iter_goto_offset(&view->cursor, change->offset); if (!change->ins_count) { // Convert delete to insert do_insert(change->buf, change->del_count); if (change->move_after) { block_iter_skip_bytes(&view->cursor, change->del_count); } change->ins_count = change->del_count; change->del_count = 0; free(change->buf); change->buf = NULL; } else if (change->del_count) { // Reverse replace size_t del_count = change->ins_count; size_t ins_count = change->del_count; char *buf = do_replace(del_count, change->buf, ins_count); free(change->buf); change->buf = buf; change->ins_count = ins_count; change->del_count = del_count; } else { // Convert insert to delete change->buf = do_delete(change->ins_count); change->del_count = change->ins_count; change->ins_count = 0; } } bool undo(void) { Change *change = buffer->cur_change; view_reset_preferred_x(view); if (!change->next) { return false; } if (is_change_chain_barrier(change)) { unsigned long count = 0; while (1) { change = change->next; if (is_change_chain_barrier(change)) { break; } reverse_change(change); count++; } if (count > 1) { info_msg("Undid %lu changes.", count); } } else { reverse_change(change); } buffer->cur_change = change->next; return true; } bool redo(unsigned long change_id) { Change *change = buffer->cur_change; view_reset_preferred_x(view); if (!change->prev) { // Don't complain if change_id is 0 if (change_id) { error_msg("Nothing to redo."); } return false; } if (change_id) { if (--change_id >= change->nr_prev) { error_msg ( "There are only %lu possible changes to redo.", change->nr_prev ); return false; } } else { // Default to newest change change_id = change->nr_prev - 1; if (change->nr_prev > 1) { info_msg ( "Redoing newest (%lu) of %lu possible changes.", change_id + 1, change->nr_prev ); } } change = change->prev[change_id]; if (is_change_chain_barrier(change)) { unsigned long count = 0; while (1) { change = change->prev[change->nr_prev - 1]; if (is_change_chain_barrier(change)) { break; } reverse_change(change); count++; } if (count > 1) { info_msg("Redid %lu changes.", count); } } else { reverse_change(change); } buffer->cur_change = change; return true; } void free_changes(Change *ch) { top: while (ch->nr_prev) { ch = ch->prev[ch->nr_prev - 1]; } // ch is leaf now while (ch->next) { Change *next = ch->next; free(ch->buf); free(ch); ch = next; if (--ch->nr_prev) { goto top; } // We have become leaf free(ch->prev); } } void buffer_insert_bytes(const char *buf, const size_t len) { size_t rec_len = len; view_reset_preferred_x(view); if (len == 0) { return; } if (buf[len - 1] != '\n' && block_iter_is_eof(&view->cursor)) { // Force newline at EOF do_insert("\n", 1); rec_len++; } do_insert(buf, len); record_insert(rec_len); if (buffer->views.count > 1) { fix_cursors(block_iter_get_offset(&view->cursor), len, 0); } } static bool would_delete_last_bytes(size_t count) { const Block *blk = view->cursor.blk; size_t offset = view->cursor.offset; while (1) { size_t avail = blk->size - offset; if (avail > count) { return false; } if (blk->node.next == view->cursor.head) { return true; } count -= avail; blk = BLOCK(blk->node.next); offset = 0; } } static void buffer_delete_bytes_internal(size_t len, bool move_after) { view_reset_preferred_x(view); if (len == 0) { return; } // Check if all newlines from EOF would be deleted if (would_delete_last_bytes(len)) { BlockIter bi = view->cursor; CodePoint u; if (block_iter_prev_char(&bi, &u) && u != '\n') { // No newline before cursor if (--len == 0) { begin_change(CHANGE_MERGE_NONE); return; } } } record_delete(do_delete(len), len, move_after); if (buffer->views.count > 1) { fix_cursors(block_iter_get_offset(&view->cursor), len, 0); } } void buffer_delete_bytes(size_t len) { buffer_delete_bytes_internal(len, false); } void buffer_erase_bytes(size_t len) { buffer_delete_bytes_internal(len, true); } void buffer_replace_bytes ( size_t del_count, const char *const inserted, size_t ins_count ) { view_reset_preferred_x(view); if (del_count == 0) { buffer_insert_bytes(inserted, ins_count); return; } if (ins_count == 0) { buffer_delete_bytes(del_count); return; } // Check if all newlines from EOF would be deleted if (would_delete_last_bytes(del_count)) { if (inserted[ins_count - 1] != '\n') { // Don't replace last newline if (--del_count == 0) { buffer_insert_bytes(inserted, ins_count); return; } } } char *deleted = do_replace(del_count, inserted, ins_count); record_replace(deleted, del_count, ins_count); if (buffer->views.count > 1) { fix_cursors(block_iter_get_offset(&view->cursor), del_count, ins_count); } } dte-1.9.1/src/change.h000066400000000000000000000016631354415143300144740ustar00rootroot00000000000000#ifndef CHANGE_H #define CHANGE_H #include #include typedef enum { CHANGE_MERGE_NONE, CHANGE_MERGE_INSERT, CHANGE_MERGE_DELETE, CHANGE_MERGE_ERASE, } ChangeMergeEnum; typedef struct Change { struct Change *next; struct Change **prev; unsigned long nr_prev; bool move_after; // Move after inserted text when undoing delete? size_t offset; size_t del_count; size_t ins_count; char *buf; // Deleted bytes (inserted bytes need not be saved) } Change; void begin_change(ChangeMergeEnum m); void end_change(void); void begin_change_chain(void); void end_change_chain(void); bool undo(void); bool redo(unsigned long change_id); void free_changes(Change *head); void buffer_insert_bytes(const char *buf, size_t len); void buffer_delete_bytes(size_t len); void buffer_erase_bytes(size_t len); void buffer_replace_bytes(size_t del_count, const char *ins, size_t ins_count); #endif dte-1.9.1/src/cmdline.c000066400000000000000000000146711354415143300146600ustar00rootroot00000000000000#include #include #include "cmdline.h" #include "history.h" #include "terminal/input.h" #include "util/ascii.h" #include "util/utf8.h" static void cmdline_delete(CommandLine *c) { size_t pos = c->pos; size_t len = 1; if (pos == c->buf.len) { return; } u_get_char(c->buf.buffer, c->buf.len, &pos); len = pos - c->pos; string_remove(&c->buf, c->pos, len); } static void cmdline_backspace(CommandLine *c) { if (c->pos) { u_prev_char(c->buf.buffer, &c->pos); cmdline_delete(c); } } static void cmdline_erase_word(CommandLine *c) { size_t i = c->pos; if (i == 0) { return; } // open /path/to/file^W => open /path/to/ // erase whitespace while (i && ascii_isspace(c->buf.buffer[i - 1])) { i--; } // erase non-word bytes while (i && !is_word_byte(c->buf.buffer[i - 1])) { i--; } // erase word bytes while (i && is_word_byte(c->buf.buffer[i - 1])) { i--; } string_remove(&c->buf, i, c->pos - i); c->pos = i; } static void cmdline_delete_word(CommandLine *c) { const unsigned char *buf = c->buf.buffer; const size_t len = c->buf.len; size_t i = c->pos; if (i == len) { return; } while (i < len && is_word_byte(buf[i])) { i++; } while (i < len && !is_word_byte(buf[i])) { i++; } string_remove(&c->buf, c->pos, i - c->pos); } static void cmdline_delete_bol(CommandLine *c) { string_remove(&c->buf, 0, c->pos); c->pos = 0; } static void cmdline_delete_eol(CommandLine *c) { c->buf.len = c->pos; } static void cmdline_prev_char(CommandLine *c) { if (c->pos) { u_prev_char(c->buf.buffer, &c->pos); } } static void cmdline_next_char(CommandLine *c) { if (c->pos < c->buf.len) { u_get_char(c->buf.buffer, c->buf.len, &c->pos); } } static void cmdline_next_word(CommandLine *c) { const unsigned char *buf = c->buf.buffer; const size_t len = c->buf.len; size_t i = c->pos; while (i < len && is_word_byte(buf[i])) { i++; } while (i < len && !is_word_byte(buf[i])) { i++; } c->pos = i; } static void cmdline_prev_word(CommandLine *c) { if (c->pos <= 1) { c->pos = 0; return; } const unsigned char *const buf = c->buf.buffer; size_t i = c->pos - 1; while (i > 0 && !is_word_byte(buf[i])) { i--; } while (i > 0 && is_word_byte(buf[i])) { i--; } if (i > 0) { i++; } c->pos = i; } static void cmdline_insert_bytes(CommandLine *c, const char *buf, size_t size) { string_make_space(&c->buf, c->pos, size); for (size_t i = 0; i < size; i++) { c->buf.buffer[c->pos++] = buf[i]; } } static void cmdline_insert_paste(CommandLine *c) { size_t size; char *text = term_read_paste(&size); for (size_t i = 0; i < size; i++) { if (text[i] == '\n') { text[i] = ' '; } } cmdline_insert_bytes(c, text, size); free(text); } static void set_text(CommandLine *c, const char *text) { string_clear(&c->buf); const size_t text_len = strlen(text); string_add_buf(&c->buf, text, text_len); c->pos = text_len; } void cmdline_clear(CommandLine *c) { string_clear(&c->buf); c->pos = 0; c->search_pos = -1; } void cmdline_set_text(CommandLine *c, const char *text) { set_text(c, text); c->search_pos = -1; } CommandLineResult cmdline_handle_key ( CommandLine *c, PointerArray *history, KeyCode key ) { if (key <= KEY_UNICODE_MAX) { c->pos += string_insert_ch(&c->buf, c->pos, key); return CMDLINE_KEY_HANDLED; } switch (key) { case CTRL('['): // ESC case CTRL('C'): case CTRL('G'): cmdline_clear(c); return CMDLINE_CANCEL; case CTRL('D'): cmdline_delete(c); goto reset_search_pos; case CTRL('K'): cmdline_delete_eol(c); goto reset_search_pos; case CTRL('H'): case CTRL('?'): if (c->buf.len > 0) { cmdline_backspace(c); } goto reset_search_pos; case CTRL('U'): cmdline_delete_bol(c); goto reset_search_pos; case CTRL('W'): case MOD_META | MOD_CTRL | 'H': case MOD_META | MOD_CTRL | '?': cmdline_erase_word(c); goto reset_search_pos; case MOD_CTRL | KEY_DELETE: case MOD_META | KEY_DELETE: case MOD_META | 'd': cmdline_delete_word(c); goto reset_search_pos; case CTRL('A'): c->pos = 0; goto handled; case CTRL('B'): cmdline_prev_char(c); goto handled; case CTRL('E'): c->pos = c->buf.len; goto handled; case CTRL('F'): cmdline_next_char(c); goto handled; case KEY_DELETE: cmdline_delete(c); goto reset_search_pos; case KEY_LEFT: cmdline_prev_char(c); goto handled; case KEY_RIGHT: cmdline_next_char(c); goto handled; case CTRL(KEY_LEFT): case MOD_META | 'b': cmdline_prev_word(c); goto handled; case CTRL(KEY_RIGHT): case MOD_META | 'f': cmdline_next_word(c); goto handled; case KEY_HOME: case MOD_META | KEY_LEFT: c->pos = 0; goto handled; case KEY_END: case MOD_META | KEY_RIGHT: c->pos = c->buf.len; goto handled; case KEY_UP: if (history == NULL) { return CMDLINE_UNKNOWN_KEY; } if (c->search_pos < 0) { free(c->search_text); c->search_text = string_clone_cstring(&c->buf); c->search_pos = history->count; } if (history_search_forward(history, &c->search_pos, c->search_text)) { set_text(c, history->ptrs[c->search_pos]); } goto handled; case KEY_DOWN: if (history == NULL) { return CMDLINE_UNKNOWN_KEY; } if (c->search_pos < 0) { goto handled; } if (history_search_backward(history, &c->search_pos, c->search_text)) { set_text(c, history->ptrs[c->search_pos]); } else { set_text(c, c->search_text); c->search_pos = -1; } goto handled; case KEY_PASTE: cmdline_insert_paste(c); goto reset_search_pos; default: return CMDLINE_UNKNOWN_KEY; } reset_search_pos: c->search_pos = -1; handled: return CMDLINE_KEY_HANDLED; } dte-1.9.1/src/cmdline.h000066400000000000000000000010661354415143300146570ustar00rootroot00000000000000#ifndef CMDLINE_H #define CMDLINE_H #include #include "terminal/key.h" #include "util/ptr-array.h" #include "util/string.h" typedef struct { String buf; size_t pos; ssize_t search_pos; char *search_text; } CommandLine; typedef enum { CMDLINE_UNKNOWN_KEY, CMDLINE_KEY_HANDLED, CMDLINE_CANCEL, } CommandLineResult; void cmdline_clear(CommandLine *c); void cmdline_set_text(CommandLine *c, const char *text); CommandLineResult cmdline_handle_key ( CommandLine *c, PointerArray *history, KeyCode key ); #endif dte-1.9.1/src/command-parse.c000066400000000000000000000152541354415143300157710ustar00rootroot00000000000000#include "command.h" #include "debug.h" #include "editor.h" #include "env.h" #include "error.h" #include "util/ascii.h" #include "util/ptr-array.h" #include "util/str-util.h" #include "util/string.h" #include "util/utf8.h" #include "util/xmalloc.h" static size_t parse_sq(const char *cmd, size_t len, String *buf) { size_t pos = 0; char ch = '\0'; while (pos < len) { ch = cmd[pos]; if (ch == '\'') { break; } pos++; } string_add_buf(buf, cmd, pos); if (ch == '\'') { pos++; } return pos; } static size_t unicode_escape(const char *str, size_t count, String *buf) { if (count == 0) { return 0; } CodePoint u = 0; size_t i; for (i = 0; i < count; i++) { int x = hex_decode(str[i]); if (x < 0) { break; } u = u << 4 | x; } if (u_is_unicode(u)) { string_add_ch(buf, u); } return i; } static size_t min(size_t a, size_t b) { return (a < b) ? a : b; } static size_t parse_dq(const char *cmd, size_t len, String *buf) { size_t pos = 0; while (pos < len) { unsigned char ch = cmd[pos++]; if (ch == '"') { break; } if (ch == '\\' && pos < len) { ch = cmd[pos++]; switch (ch) { case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; case '\\': case '"': break; case 'x': if (pos < len) { const int x1 = hex_decode(cmd[pos]); if (x1 >= 0 && ++pos < len) { const int x2 = hex_decode(cmd[pos]); if (x2 >= 0) { pos++; ch = x1 << 4 | x2; break; } } } continue; case 'u': pos += unicode_escape(cmd + pos, min(4, len - pos), buf); continue; case 'U': pos += unicode_escape(cmd + pos, min(8, len - pos), buf); continue; default: string_add_byte(buf, '\\'); break; } } string_add_byte(buf, ch); } return pos; } static size_t parse_var(const char *cmd, size_t len, String *buf) { if (len == 0 || !is_alpha_or_underscore(cmd[0])) { return 0; } size_t n = 1; while (n < len && is_alnum_or_underscore(cmd[n])) { n++; } char *name = xstrcut(cmd, n); char *value; if (expand_builtin_env(name, &value)) { if (value != NULL) { string_add_str(buf, value); free(value); } } else { const char *val = getenv(name); if (val != NULL) { string_add_str(buf, val); } } free(name); return n; } char *parse_command_arg(const char *cmd, size_t len, bool tilde) { String buf; size_t pos = 0; if (tilde && len >= 2 && cmd[0] == '~' && cmd[1] == '/') { const size_t home_dir_len = strlen(editor.home_dir); buf = string_new(len + home_dir_len); string_add_buf(&buf, editor.home_dir, home_dir_len); string_add_byte(&buf, '/'); pos += 2; } else { buf = string_new(len); } while (pos < len) { char ch = cmd[pos++]; switch (ch) { case '\t': case '\n': case '\r': case ' ': case ';': goto end; case '\'': pos += parse_sq(cmd + pos, len - pos, &buf); break; case '"': pos += parse_dq(cmd + pos, len - pos, &buf); break; case '$': pos += parse_var(cmd + pos, len - pos, &buf); break; case '\\': if (pos == len) { goto end; } ch = cmd[pos++]; // Fallthrough default: string_add_byte(&buf, ch); break; } } end: return string_steal_cstring(&buf); } size_t find_end(const char *cmd, const size_t startpos, CommandParseError *err) { size_t pos = startpos; while (1) { switch (cmd[pos++]) { case '\'': while (1) { if (cmd[pos] == '\'') { pos++; break; } if (cmd[pos] == '\0') { *err = CMDERR_UNCLOSED_SINGLE_QUOTE; return 0; } pos++; } break; case '"': while (1) { if (cmd[pos] == '"') { pos++; break; } if (cmd[pos] == '\0') { *err = CMDERR_UNCLOSED_DOUBLE_QUOTE; return 0; } if (cmd[pos++] == '\\') { if (cmd[pos] == '\0') { goto unexpected_eof; } pos++; } } break; case '\\': if (cmd[pos] == '\0') { goto unexpected_eof; } pos++; break; case '\0': case '\t': case '\n': case '\r': case ' ': case ';': return pos - 1; } } BUG("Unexpected break of outer loop"); unexpected_eof: *err = CMDERR_UNEXPECTED_EOF; return 0; } bool parse_commands(PointerArray *array, const char *cmd, CommandParseError *err) { size_t pos = 0; while (1) { while (ascii_isspace(cmd[pos])) { pos++; } if (cmd[pos] == '\0') { break; } if (cmd[pos] == ';') { ptr_array_add(array, NULL); pos++; continue; } size_t end = find_end(cmd, pos, err); if (*err != CMDERR_NONE) { return false; } ptr_array_add(array, parse_command_arg(cmd + pos, end - pos, true)); pos = end; } ptr_array_add(array, NULL); return true; } const char *command_parse_error_to_string(CommandParseError err) { switch (err) { case CMDERR_UNCLOSED_SINGLE_QUOTE: return "Missing '"; case CMDERR_UNCLOSED_DOUBLE_QUOTE: return "Missing \""; case CMDERR_UNEXPECTED_EOF: return "Unexpected EOF"; case CMDERR_NONE: break; } return NULL; } dte-1.9.1/src/command-run.c000066400000000000000000000072161354415143300154620ustar00rootroot00000000000000#include "alias.h" #include "change.h" #include "command.h" #include "debug.h" #include "config.h" #include "error.h" #include "parse-args.h" #include "util/str-util.h" #include "util/xmalloc.h" const Command *current_command; static bool allowed_command(const char *name) { size_t len = strlen(name); switch (len) { case 3: return !memcmp(name, "set", len); case 4: return !memcmp(name, "bind", len); case 5: return !memcmp(name, "alias", len); case 7: return !memcmp(name, "include", len); case 8: return !memcmp(name, "errorfmt", len); case 11: return !memcmp(name, "load-syntax", len); case 2: switch (name[0]) { case 'c': return name[1] == 'd'; case 'f': return name[1] == 't'; case 'h': return name[1] == 'i'; } return false; case 6: switch (name[0]) { case 'o': return !memcmp(name, "option", len); case 's': return !memcmp(name, "setenv", len); } return false; } return false; } UNITTEST { BUG_ON(!allowed_command("alias")); BUG_ON(!allowed_command("cd")); BUG_ON(!allowed_command("include")); BUG_ON(!allowed_command("set")); BUG_ON(allowed_command("alias_")); BUG_ON(allowed_command("c")); BUG_ON(allowed_command("cD")); } const Command *find_command(const Command *cmds, const char *name) { for (size_t i = 0; cmds[i].cmd; i++) { const Command *cmd = &cmds[i]; if (streq(name, cmd->name)) { return cmd; } } return NULL; } void run_command(const Command *cmds, char **av) { const Command *cmd = find_command(cmds, av[0]); if (!cmd) { PointerArray array = PTR_ARRAY_INIT; const char *alias_name = av[0]; const char *alias_value = find_alias(alias_name); CommandParseError err = 0; if (alias_value == NULL) { error_msg("No such command or alias: %s", alias_name); return; } if (!parse_commands(&array, alias_value, &err)) { const char *err_msg = command_parse_error_to_string(err); error_msg("Parsing alias %s: %s", alias_name, err_msg); ptr_array_free(&array); return; } // Remove NULL array.count--; for (size_t i = 1; av[i]; i++) { ptr_array_add(&array, xstrdup(av[i])); } ptr_array_add(&array, NULL); run_commands(cmds, &array); ptr_array_free(&array); return; } if (config_file && cmds == commands && !allowed_command(cmd->name)) { error_msg("Command %s not allowed in config file.", cmd->name); return; } // By default change can't be merged with previous one. // Any command can override this by calling begin_change() again. begin_change(CHANGE_MERGE_NONE); CommandArgs a = {.args = av + 1}; current_command = cmd; if (parse_args(cmd, &a)) { cmd->cmd(&a); } current_command = NULL; end_change(); } void run_commands(const Command *cmds, const PointerArray *array) { size_t s = 0; while (s < array->count) { size_t e = s; while (e < array->count && array->ptrs[e]) { e++; } if (e > s) { run_command(cmds, (char **)array->ptrs + s); } s = e + 1; } } void handle_command(const Command *cmds, const char *cmd) { CommandParseError err = 0; PointerArray array = PTR_ARRAY_INIT; if (!parse_commands(&array, cmd, &err)) { error_msg("%s", command_parse_error_to_string(err)); ptr_array_free(&array); return; } run_commands(cmds, &array); ptr_array_free(&array); } dte-1.9.1/src/command.c000066400000000000000000001376261354415143300146710ustar00rootroot00000000000000#include #include #include #include "alias.h" #include "bind.h" #include "change.h" #include "cmdline.h" #include "command.h" #include "config.h" #include "debug.h" #include "edit.h" #include "editor.h" #include "encoding/convert.h" #include "encoding/encoding.h" #include "error.h" #include "file-option.h" #include "filetype.h" #include "frame.h" #include "history.h" #include "load-save.h" #include "lock.h" #include "move.h" #include "msg.h" #include "parse-args.h" #include "search.h" #include "selection.h" #include "spawn.h" #include "syntax/state.h" #include "syntax/syntax.h" #include "tag.h" #include "terminal/color.h" #include "terminal/input.h" #include "terminal/terminal.h" #include "util/path.h" #include "util/str-util.h" #include "util/strtonum.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" #include "util/xsnprintf.h" #include "view.h" #include "window.h" static void do_selection(SelectionType sel) { if (sel == SELECT_NONE) { if (view->next_movement_cancels_selection) { view->next_movement_cancels_selection = false; unselect(); } return; } view->next_movement_cancels_selection = true; if (view->selection) { view->selection = sel; mark_all_lines_changed(buffer); return; } view->sel_so = block_iter_get_offset(&view->cursor); view->sel_eo = UINT_MAX; view->selection = sel; // Need to mark current line changed because cursor might // move up or down before screen is updated view_update_cursor_y(view); buffer_mark_lines_changed(view->buffer, view->cy, view->cy); } static void handle_select_chars_flag(const char *pf) { do_selection(strchr(pf, 'c') ? SELECT_CHARS : SELECT_NONE); } static void handle_select_chars_or_lines_flags(const char *pf) { SelectionType sel = SELECT_NONE; if (strchr(pf, 'l')) { sel = SELECT_LINES; } else if (strchr(pf, 'c')) { sel = SELECT_CHARS; } do_selection(sel); } // Go to compiler error saving position if file changed or cursor moved static void activate_current_message_save(void) { FileLocation *loc = file_location_create ( view->buffer->abs_filename, view->buffer->id, view->cy + 1, view->cx_char + 1 ); BlockIter save = view->cursor; activate_current_message(); if (view->cursor.blk != save.blk || view->cursor.offset != save.offset) { push_file_location(loc); } else { file_location_free(loc); } } static void cmd_alias(const CommandArgs *a) { add_alias(a->args[0], a->args[1]); } static void cmd_bind(const CommandArgs *a) { const char *key = a->args[0]; const char *cmd = a->args[1]; if (cmd) { add_binding(key, cmd); } else { remove_binding(key); } } static void cmd_bof(const CommandArgs* UNUSED_ARG(a)) { move_bof(); } static void cmd_bol(const CommandArgs *a) { handle_select_chars_flag(a->flags); if (strchr(a->flags, 's')) { move_bol_smart(); } else { move_bol(); } } static void cmd_bolsf(const CommandArgs* UNUSED_ARG(a)) { do_selection(SELECT_NONE); if (!block_iter_bol(&view->cursor)) { long top = view->vy + window_get_scroll_margin(window); if (view->cy > top) { move_up(view->cy - top); } else { block_iter_bof(&view->cursor); } } view_reset_preferred_x(view); } static void cmd_case(const CommandArgs *a) { const char *pf = a->flags; int mode = 't'; while (*pf) { switch (*pf) { case 'l': case 'u': mode = *pf; break; } pf++; } change_case(mode); } static void cmd_cd(const CommandArgs *a) { const char *dir = a->args[0]; char cwd[8192]; const char *cwdp = NULL; bool got_cwd = !!getcwd(cwd, sizeof(cwd)); if (streq(dir, "-")) { dir = getenv("OLDPWD"); if (dir == NULL || dir[0] == '\0') { error_msg("cd: OLDPWD not set"); return; } } if (chdir(dir)) { error_msg("cd: %s", strerror(errno)); return; } if (got_cwd) { setenv("OLDPWD", cwd, 1); } got_cwd = !!getcwd(cwd, sizeof(cwd)); if (got_cwd) { setenv("PWD", cwd, 1); cwdp = cwd; } for (size_t i = 0; i < buffers.count; i++) { Buffer *b = buffers.ptrs[i]; update_short_filename_cwd(b, cwdp); } // Need to update all tabbars mark_everything_changed(); } static void cmd_center_view(const CommandArgs* UNUSED_ARG(a)) { view->force_center = true; } static void cmd_clear(const CommandArgs* UNUSED_ARG(a)) { clear_lines(); } static void cmd_close(const CommandArgs *a) { const char *pf = a->flags; bool force = false; bool allow_quit = false; bool allow_wclose = false; while (*pf) { switch (*pf) { case 'f': force = true; break; case 'q': allow_quit = true; break; case 'w': allow_wclose = true; break; } pf++; } if (!view_can_close(view) && !force) { error_msg ( "The buffer is modified. " "Save or run 'close -f' to close without saving." ); return; } if (allow_quit && buffers.count == 1 && root_frame->frames.count <= 1) { editor.status = EDITOR_EXITING; return; } if (allow_wclose && window->views.count <= 1) { window_close_current(); return; } window_close_current_view(window); set_view(window->view); } static void cmd_command(const CommandArgs *a) { const char *text = a->args[0]; set_input_mode(INPUT_COMMAND); if (text) { cmdline_set_text(&editor.cmdline, text); } } static void cmd_compile(const CommandArgs *a) { const char *pf = a->flags; SpawnFlags flags = SPAWN_DEFAULT; while (*pf) { switch (*pf) { case '1': flags |= SPAWN_READ_STDOUT; break; case 'p': flags |= SPAWN_PROMPT; break; case 's': flags |= SPAWN_QUIET; break; } pf++; } const char *name = a->args[0]; Compiler *c = find_compiler(name); if (!c) { error_msg("No such error parser %s", name); return; } clear_messages(); spawn_compiler(a->args + 1, flags, c); if (message_count()) { activate_current_message_save(); } } static void cmd_copy(const CommandArgs *a) { BlockIter save = view->cursor; if (view->selection) { copy(prepare_selection(view), view->selection == SELECT_LINES); bool keep_selection = a->flags[0] == 'k'; if (!keep_selection) { unselect(); } } else { block_iter_bol(&view->cursor); BlockIter tmp = view->cursor; copy(block_iter_eat_line(&tmp), true); } view->cursor = save; } static void cmd_cut(const CommandArgs* UNUSED_ARG(a)) { const long x = view_get_preferred_x(view); if (view->selection) { cut(prepare_selection(view), view->selection == SELECT_LINES); if (view->selection == SELECT_LINES) { move_to_preferred_x(x); } unselect(); } else { BlockIter tmp; block_iter_bol(&view->cursor); tmp = view->cursor; cut(block_iter_eat_line(&tmp), true); move_to_preferred_x(x); } } static void cmd_delete(const CommandArgs* UNUSED_ARG(a)) { delete_ch(); } static void cmd_delete_eol(const CommandArgs *a) { if (view->selection) { return; } bool delete_newline_if_at_eol = a->flags[0] == 'n'; BlockIter bi = view->cursor; if (delete_newline_if_at_eol) { CodePoint ch; block_iter_get_char(&view->cursor, &ch); if (ch == '\n') { delete_ch(); return; } } buffer_delete_bytes(block_iter_eol(&bi)); } static void cmd_delete_word(const CommandArgs *a) { bool skip_non_word = a->flags[0] == 's'; BlockIter bi = view->cursor; buffer_delete_bytes(word_fwd(&bi, skip_non_word)); } static void cmd_down(const CommandArgs *a) { handle_select_chars_or_lines_flags(a->flags); move_down(1); } static void cmd_eof(const CommandArgs* UNUSED_ARG(a)) { move_eof(); } static void cmd_eol(const CommandArgs *a) { handle_select_chars_flag(a->flags); move_eol(); } static void cmd_eolsf(const CommandArgs* UNUSED_ARG(a)) { do_selection(SELECT_NONE); if (!block_iter_eol(&view->cursor)) { long bottom = view->vy + window->edit_h - 1 - window_get_scroll_margin(window); if (view->cy < bottom) { move_down(bottom - view->cy); } else { block_iter_eof(&view->cursor); } } view_reset_preferred_x(view); } static void cmd_erase(const CommandArgs* UNUSED_ARG(a)) { erase(); } static void cmd_erase_bol(const CommandArgs* UNUSED_ARG(a)) { buffer_erase_bytes(block_iter_bol(&view->cursor)); } static void cmd_erase_word(const CommandArgs *a) { bool skip_non_word = a->flags[0] == 's'; buffer_erase_bytes(word_bwd(&view->cursor, skip_non_word)); } static void cmd_errorfmt(const CommandArgs *a) { bool ignore = a->flags[0] == 'i'; add_error_fmt(a->args[0], ignore, a->args[1], a->args + 2); } static void cmd_eval(const CommandArgs *a) { FilterData data = FILTER_DATA_INIT; if (spawn_filter(a->args, &data)) { return; } exec_config(commands, data.out, data.out_len); free(data.out); } static void cmd_filter(const CommandArgs *a) { FilterData data; BlockIter save = view->cursor; if (view->selection) { data.in_len = prepare_selection(view); } else { Block *blk; data.in_len = 0; block_for_each(blk, &buffer->blocks) { data.in_len += blk->size; } move_bof(); } data.in = block_iter_get_bytes(&view->cursor, data.in_len); if (spawn_filter(a->args, &data)) { free(data.in); view->cursor = save; return; } free(data.in); buffer_replace_bytes(data.in_len, data.out, data.out_len); free(data.out); unselect(); } static void cmd_ft(const CommandArgs *a) { const char *pf = a->flags; FileDetectionType dt = FT_EXTENSION; while (*pf) { switch (*pf) { case 'b': dt = FT_BASENAME; break; case 'c': dt = FT_CONTENT; break; case 'f': dt = FT_FILENAME; break; case 'i': dt = FT_INTERPRETER; break; } pf++; } char **args = a->args; if (args[0][0] == '\0') { error_msg("Filetype can't be blank"); return; } for (size_t i = 1; args[i]; i++) { add_filetype(args[0], args[i], dt); } } static void cmd_git_open(const CommandArgs* UNUSED_ARG(a)) { set_input_mode(INPUT_GIT_OPEN); git_open_reload(); } static void cmd_hi(const CommandArgs *a) { char **args = a->args; TermColor color; if (args[0] == NULL) { exec_reset_colors_rc(); remove_extra_colors(); } else if (parse_term_color(&color, args + 1)) { color.fg = color_to_nearest(color.fg, terminal.color_type); color.bg = color_to_nearest(color.bg, terminal.color_type); set_highlight_color(args[0], &color); } // Don't call update_all_syntax_colors() needlessly. // It is called right after config has been loaded. if (editor.status != EDITOR_INITIALIZING) { update_all_syntax_colors(); mark_everything_changed(); } } static void cmd_include(const CommandArgs *a) { ConfigFlags flags = CFG_MUST_EXIST; if (a->flags[0] == 'b') { flags |= CFG_BUILTIN; } read_config(commands, a->args[0], flags); } static void cmd_insert(const CommandArgs *a) { const char *str = a->args[0]; if (strchr(a->flags, 'k')) { for (size_t i = 0; str[i]; i++) { insert_ch(str[i]); } return; } size_t del_len = 0; size_t ins_len = strlen(str); if (view->selection) { del_len = prepare_selection(view); unselect(); } buffer_replace_bytes(del_len, str, ins_len); if (strchr(a->flags, 'm')) { block_iter_skip_bytes(&view->cursor, ins_len); } } static void cmd_insert_builtin(const CommandArgs *a) { const char *name = a->args[0]; const BuiltinConfig *cfg = get_builtin_config(name); if (cfg) { buffer_insert_bytes(cfg->text.data, cfg->text.length); } else { error_msg("No built-in config with name '%s'", name); } } static void cmd_join(const CommandArgs* UNUSED_ARG(a)) { join_lines(); } static void cmd_left(const CommandArgs *a) { handle_select_chars_flag(a->flags); move_cursor_left(); } static void cmd_line(const CommandArgs *a) { const char *arg = a->args[0]; const long x = view_get_preferred_x(view); size_t line; if (!str_to_size(arg, &line) || line == 0) { error_msg("Invalid line number: %s", arg); return; } move_to_line(view, line); move_to_preferred_x(x); } static void cmd_load_syntax(const CommandArgs *a) { const char *filename = a->args[0]; const char *filetype = path_basename(filename); if (filename != filetype) { if (find_syntax(filetype)) { error_msg("Syntax for filetype %s already loaded", filetype); } else { int err; load_syntax_file(filename, true, &err); } } else { if (!find_syntax(filetype)) { load_syntax_by_filetype(filetype); } } } static void cmd_move_tab(const CommandArgs *a) { const char *str = a->args[0]; size_t j, i = ptr_array_idx(&window->views, view); if (streq(str, "left")) { j = i - 1; } else if (streq(str, "right")) { j = i + 1; } else { size_t num; if (!str_to_size(str, &num) || num == 0) { error_msg("Invalid tab position %s", str); return; } j = num - 1; if (j >= window->views.count) { j = window->views.count - 1; } } j = (window->views.count + j) % window->views.count; ptr_array_insert ( &window->views, ptr_array_remove_idx(&window->views, i), j ); window->update_tabbar = true; } static void cmd_msg(const CommandArgs *a) { const char *pf = a->flags; char dir = 0; while (*pf) { switch (*pf) { case 'n': case 'p': dir = *pf; break; } pf++; } if (dir == 'n') { activate_next_message(); } else if (dir == 'p') { activate_prev_message(); } else { activate_current_message(); } } static void cmd_new_line(const CommandArgs* UNUSED_ARG(a)) { new_line(); } static void cmd_next(const CommandArgs* UNUSED_ARG(a)) { set_view(ptr_array_next(&window->views, view)); } static void cmd_open(const CommandArgs *a) { char **args = a->args; const char *pf = a->flags; const char *requested_encoding = NULL; bool use_glob = false; while (*pf) { switch (*pf) { case 'e': requested_encoding = *args++; break; case 'g': use_glob = args[0] ? true : false; break; } pf++; } Encoding encoding = { .type = ENCODING_AUTODETECT, .name = NULL }; if (requested_encoding) { if ( lookup_encoding(requested_encoding) != UTF8 && !encoding_supported_by_iconv(requested_encoding) ) { error_msg("Unsupported encoding %s", requested_encoding); return; } encoding = encoding_from_name(requested_encoding); } char **paths = args; glob_t globbuf; if (use_glob) { int err = glob(args[0], GLOB_NOCHECK, NULL, &globbuf); while (err == 0 && *++args) { err = glob(*args, GLOB_NOCHECK | GLOB_APPEND, NULL, &globbuf); } if (globbuf.gl_pathc > 0) { paths = globbuf.gl_pathv; } } if (!paths[0]) { window_open_new_file(window); if (requested_encoding) { buffer->encoding = encoding; } } else if (!paths[1]) { // Previous view is remembered when opening single file window_open_file(window, paths[0], &encoding); } else { // It makes no sense to remember previous view when opening // multiple files window_open_files(window, paths, &encoding); } if (use_glob) { globfree(&globbuf); } } static void cmd_option(const CommandArgs *a) { char **args = a->args; size_t argc = a->nr_args; char **strs = args + 1; size_t count = argc - 1; if (argc % 2 == 0) { error_msg("Missing option value"); return; } if (!validate_local_options(strs)) { return; } if (a->flags[0] == 'r') { add_file_options ( FILE_OPTIONS_FILENAME, xstrdup(args[0]), copy_string_array(strs, count) ); return; } char *comma, *list = args[0]; do { comma = strchr(list, ','); size_t len = comma ? comma - list : strlen(list); add_file_options ( FILE_OPTIONS_FILETYPE, xstrcut(list, len), copy_string_array(strs, count) ); list = comma + 1; } while (comma); } static void cmd_paste(const CommandArgs *a) { bool at_cursor = a->flags[0] == 'c'; paste(at_cursor); } static void cmd_pgdown(const CommandArgs *a) { handle_select_chars_or_lines_flags(a->flags); long margin = window_get_scroll_margin(window); long bottom = view->vy + window->edit_h - 1 - margin; long count; if (view->cy < bottom) { count = bottom - view->cy; } else { count = window->edit_h - 1 - margin * 2; } move_down(count); } static void cmd_pgup(const CommandArgs *a) { handle_select_chars_or_lines_flags(a->flags); long margin = window_get_scroll_margin(window); long top = view->vy + margin; long count; if (view->cy > top) { count = view->cy - top; } else { count = window->edit_h - 1 - margin * 2; } move_up(count); } static void cmd_pipe_from(const CommandArgs *a) { const char *pf = a->flags; bool strip_nl = false; bool move = false; while (*pf) { switch (*pf++) { case 'm': move = true; break; case 's': strip_nl = true; break; } } FilterData data = FILTER_DATA_INIT; if (spawn_filter(a->args, &data)) { return; } size_t del_len = 0; if (view->selection) { del_len = prepare_selection(view); unselect(); } if (strip_nl && data.out_len > 0 && data.out[data.out_len - 1] == '\n') { if (--data.out_len > 0 && data.out[data.out_len - 1] == '\r') { data.out_len--; } } buffer_replace_bytes(del_len, data.out, data.out_len); free(data.out); if (move) { block_iter_skip_bytes(&view->cursor, data.out_len); } } static void cmd_pipe_to(const CommandArgs *a) { const BlockIter saved_cursor = view->cursor; const ssize_t saved_sel_so = view->sel_so; const ssize_t saved_sel_eo = view->sel_eo; size_t input_len = 0; if (view->selection) { input_len = prepare_selection(view); } else { Block *blk; block_for_each(blk, &buffer->blocks) { input_len += blk->size; } move_bof(); } char *input = block_iter_get_bytes(&view->cursor, input_len); spawn_writer(a->args, input, input_len); free(input); // Restore cursor and selection offsets, instead of calling unselect() view->cursor = saved_cursor; view->sel_so = saved_sel_so; view->sel_eo = saved_sel_eo; } static void cmd_prev(const CommandArgs* UNUSED_ARG(a)) { set_view(ptr_array_prev(&window->views, view)); } static void cmd_quit(const CommandArgs *a) { const char *pf = a->flags; bool prompt = false; while (*pf) { switch (*pf++) { case 'f': editor.status = EDITOR_EXITING; return; case 'p': prompt = true; break; } } for (size_t i = 0; i < buffers.count; i++) { Buffer *b = buffers.ptrs[i]; if (buffer_modified(b)) { // Activate modified buffer View *v = window_find_view(window, b); if (v == NULL) { // Buffer isn't open in current window. // Activate first window of the buffer. v = b->views.ptrs[0]; window = v->window; mark_everything_changed(); } set_view(v); if (prompt) { if (get_confirmation("yN", "Quit without saving changes?") == 'y') { editor.status = EDITOR_EXITING; } return; } else { error_msg ( "Save modified files or run 'quit -f' to quit" " without saving." ); return; } } } editor.status = EDITOR_EXITING; } static void cmd_redo(const CommandArgs *a) { char *arg = a->args[0]; unsigned long change_id = 0; if (arg) { if (!str_to_ulong(arg, &change_id) || change_id == 0) { error_msg("Invalid change id: %s", arg); return; } } if (redo(change_id)) { unselect(); } } static void cmd_refresh(const CommandArgs* UNUSED_ARG(a)) { mark_everything_changed(); } static void cmd_repeat(const CommandArgs *a) { char **args = a->args; unsigned int count = 0; if (!str_to_uint(args[0], &count)) { error_msg("Not a valid repeat count: %s", args[0]); return; } else if (count == 0) { return; } const Command *cmd = find_command(commands, args[1]); if (!cmd) { error_msg("No such command: %s", args[1]); return; } CommandArgs a2 = {.args = args + 2}; if (parse_args(cmd, &a2)) { while (count-- > 0) { cmd->cmd(&a2); } } } static void cmd_replace(const CommandArgs *a) { const char *pf = a->flags; unsigned int flags = 0; for (size_t i = 0; pf[i]; i++) { switch (pf[i]) { case 'b': flags |= REPLACE_BASIC; break; case 'c': flags |= REPLACE_CONFIRM; break; case 'g': flags |= REPLACE_GLOBAL; break; case 'i': flags |= REPLACE_IGNORE_CASE; break; } } reg_replace(a->args[0], a->args[1], flags); } static void cmd_right(const CommandArgs *a) { handle_select_chars_flag(a->flags); move_cursor_right(); } static void cmd_run(const CommandArgs *a) { const char *pf = a->flags; int fd[3] = {0, 1, 2}; bool prompt = false; while (*pf) { switch (*pf) { case 'p': prompt = true; break; case 's': fd[0] = -1; fd[1] = -1; fd[2] = -1; break; } pf++; } spawn(a->args, fd, prompt); } static bool stat_changed(const struct stat *const a, const struct stat *const b) { // Don't compare st_mode because we allow chmod 755 etc. return a->st_mtime != b->st_mtime || a->st_dev != b->st_dev || a->st_ino != b->st_ino; } static void cmd_save(const CommandArgs *a) { const char *pf = a->flags; char **args = a->args; char *absolute = buffer->abs_filename; Encoding encoding = buffer->encoding; const char *requested_encoding = NULL; bool force = false; bool prompt = false; LineEndingType newline = buffer->newline; mode_t old_mode = buffer->st.st_mode; struct stat st; bool new_locked = false; while (*pf) { switch (*pf) { case 'd': newline = NEWLINE_DOS; break; case 'e': requested_encoding = *args++; break; case 'f': force = true; break; case 'p': prompt = true; break; case 'u': newline = NEWLINE_UNIX; break; } pf++; } if (requested_encoding) { if ( lookup_encoding(requested_encoding) != UTF8 && !encoding_supported_by_iconv(requested_encoding) ) { error_msg("Unsupported encoding %s", requested_encoding); return; } encoding = encoding_from_name(requested_encoding); } // The encoding_from_name() call above may have allocated memory, // so use "goto error" instead of early return beyond this point, to // ensure correct de-allocation. if (args[0]) { if (args[0][0] == '\0') { error_msg("Empty filename not allowed"); goto error; } char *tmp = path_absolute(args[0]); if (!tmp) { error_msg("Failed to make absolute path: %s", strerror(errno)); goto error; } if (absolute && streq(tmp, absolute)) { free(tmp); } else { absolute = tmp; } } else { if (!absolute) { if (prompt) { set_input_mode(INPUT_COMMAND); cmdline_set_text(&editor.cmdline, "save "); // This branch is not really an error, but we still return via // the "error" label because we need to clean up memory and // that's all it's used for currently. goto error; } else { error_msg("No filename."); goto error; } } if (buffer->readonly && !force) { error_msg("Use -f to force saving read-only file."); goto error; } } if (stat(absolute, &st)) { if (errno != ENOENT) { error_msg("stat failed for %s: %s", absolute, strerror(errno)); goto error; } if (editor.options.lock_files) { if (absolute == buffer->abs_filename) { if (!buffer->locked) { if (lock_file(absolute)) { if (!force) { error_msg("Can't lock file %s", absolute); goto error; } } else { buffer->locked = true; } } } else { if (lock_file(absolute)) { if (!force) { error_msg("Can't lock file %s", absolute); goto error; } } else { new_locked = true; } } } } else { if ( absolute == buffer->abs_filename && !force && stat_changed(&buffer->st, &st) ) { error_msg ( "File has been modified by someone else." " Use -f to force overwrite." ); goto error; } if (S_ISDIR(st.st_mode)) { error_msg("Will not overwrite directory %s", absolute); goto error; } if (editor.options.lock_files) { if (absolute == buffer->abs_filename) { if (!buffer->locked) { if (lock_file(absolute)) { if (!force) { error_msg("Can't lock file %s", absolute); goto error; } } else { buffer->locked = true; } } } else { if (lock_file(absolute)) { if (!force) { error_msg("Can't lock file %s", absolute); goto error; } } else { new_locked = true; } } } if (absolute != buffer->abs_filename && !force) { error_msg("Use -f to overwrite %s.", absolute); goto error; } // Allow chmod 755 etc. buffer->st.st_mode = st.st_mode; } if (save_buffer(buffer, absolute, &encoding, newline)) { goto error; } buffer->saved_change = buffer->cur_change; buffer->readonly = false; buffer->newline = newline; if (requested_encoding) { buffer->encoding = encoding; } if (absolute != buffer->abs_filename) { if (buffer->locked) { // Filename changes, release old file lock unlock_file(buffer->abs_filename); } buffer->locked = new_locked; free(buffer->abs_filename); buffer->abs_filename = absolute; update_short_filename(buffer); // Filename change is not detected (only buffer_modified() change) mark_buffer_tabbars_changed(buffer); } if (!old_mode && streq(buffer->options.filetype, "none")) { // New file and most likely user has not changed the filetype if (buffer_detect_filetype(buffer)) { set_file_options(buffer); set_editorconfig_options(buffer); buffer_update_syntax(buffer); } } return; error: if (new_locked) { unlock_file(absolute); } if (absolute != buffer->abs_filename) { free(absolute); } } static void cmd_scroll_down(const CommandArgs* UNUSED_ARG(a)) { view->vy++; if (view->cy < view->vy) { move_down(1); } } static void cmd_scroll_pgdown(const CommandArgs* UNUSED_ARG(a)) { long max = buffer->nl - window->edit_h + 1; if (view->vy < max && max > 0) { long count = window->edit_h - 1; if (view->vy + count > max) { count = max - view->vy; } view->vy += count; move_down(count); } else if (view->cy < buffer->nl) { move_down(buffer->nl - view->cy); } } static void cmd_scroll_pgup(const CommandArgs* UNUSED_ARG(a)) { if (view->vy > 0) { long count = window->edit_h - 1; if (count > view->vy) { count = view->vy; } view->vy -= count; move_up(count); } else if (view->cy > 0) { move_up(view->cy); } } static void cmd_scroll_up(const CommandArgs* UNUSED_ARG(a)) { if (view->vy) { view->vy--; } if (view->vy + window->edit_h <= view->cy) { move_up(1); } } static void cmd_search(const CommandArgs *a) { const char *pf = a->flags; char *pattern = a->args[0]; bool history = true; char cmd = 0; bool w = false; SearchDirection dir = SEARCH_FWD; while (*pf) { switch (*pf) { case 'H': history = false; break; case 'n': case 'p': cmd = *pf; break; case 'r': dir = SEARCH_BWD; break; case 'w': w = true; if (pattern) { error_msg("Flag -w can't be used with search pattern."); return; } break; } pf++; } if (w) { char *word = view_get_word_under_cursor(view); if (word == NULL) { // Error message would not be very useful here return; } size_t len = strlen(word) + 5; pattern = xmalloc(len); xsnprintf(pattern, len, "\\<%s\\>", word); free(word); } if (pattern) { search_set_direction(dir); search_set_regexp(pattern); if (w) { search_next_word(); } else { search_next(); } if (history) { history_add(&editor.search_history, pattern, search_history_size); } if (pattern != a->args[0]) { free(pattern); } } else if (cmd == 'n') { search_next(); } else if (cmd == 'p') { search_prev(); } else { set_input_mode(INPUT_SEARCH); search_set_direction(dir); } } static void cmd_select(const CommandArgs *a) { const char *pf = a->flags; SelectionType sel = SELECT_CHARS; bool block = false; bool keep = false; while (*pf) { switch (*pf) { case 'b': block = true; break; case 'k': keep = true; break; case 'l': block = false; sel = SELECT_LINES; break; } pf++; } view->next_movement_cancels_selection = false; if (block) { select_block(); return; } if (view->selection) { if (!keep && view->selection == sel) { unselect(); return; } view->selection = sel; mark_all_lines_changed(buffer); return; } view->sel_so = block_iter_get_offset(&view->cursor); view->sel_eo = UINT_MAX; view->selection = sel; // Need to mark current line changed because cursor might // move up or down before screen is updated view_update_cursor_y(view); buffer_mark_lines_changed(view->buffer, view->cy, view->cy); } static void cmd_set(const CommandArgs *a) { const char *pf = a->flags; bool global = false; bool local = false; while (*pf) { switch (*pf) { case 'g': global = true; break; case 'l': local = true; break; } pf++; } // You can set only global values in config file if (buffer == NULL) { if (local) { error_msg("Flag -l makes no sense in config file."); return; } global = true; } char **args = a->args; size_t count = a->nr_args; if (count == 1) { set_bool_option(args[0], local, global); return; } else if (count % 2 != 0) { error_msg("One or even number of arguments expected."); return; } for (size_t i = 0; i < count; i += 2) { set_option(args[i], args[i + 1], local, global); } } static void cmd_setenv(const CommandArgs *a) { char **args = a->args; if (setenv(args[0], args[1], 1) < 0) { switch (errno) { case EINVAL: error_msg("Invalid environment variable name '%s'", args[0]); break; default: error_msg("%s", strerror(errno)); } } } static void cmd_shift(const CommandArgs *a) { const char *arg = a->args[0]; int count; if (!str_to_int(arg, &count)) { error_msg("Invalid number: %s", arg); return; } if (count == 0) { error_msg("Count must be non-zero."); return; } shift_lines(count); } static void show_alias(const char *alias_name, bool write_to_cmdline) { const char *cmd_str = find_alias(alias_name); if (cmd_str == NULL) { if (find_command(commands, alias_name)) { info_msg("%s is a built-in command, not an alias", alias_name); } else { info_msg("%s is not a known alias", alias_name); } return; } if (write_to_cmdline) { set_input_mode(INPUT_COMMAND); cmdline_set_text(&editor.cmdline, cmd_str); } else { info_msg("%s is aliased to: %s", alias_name, cmd_str); } } static void show_binding(const char *keystr, bool write_to_cmdline) { KeyCode key; if (!parse_key(&key, keystr)) { error_msg("invalid key string: %s", keystr); return; } if (u_is_unicode(key)) { info_msg("%s is not a bindable key", keystr); return; } const KeyBinding *b = lookup_binding(key); if (b == NULL) { info_msg("%s is not bound to a command", keystr); return; } if (write_to_cmdline) { set_input_mode(INPUT_COMMAND); cmdline_set_text(&editor.cmdline, b->cmd_str); } else { info_msg("%s is bound to: %s", keystr, b->cmd_str); } } static void cmd_show(const CommandArgs *a) { typedef enum { CMD_ALIAS, CMD_BIND, } CommandType; CommandType cmdtype; if (streq(a->args[0], "alias")) { cmdtype = CMD_ALIAS; } else if (streq(a->args[0], "bind")) { cmdtype = CMD_BIND; } else { error_msg("argument #1 must be: 'alias' or 'bind'"); return; } const bool cflag = a->nr_flags != 0; if (a->nr_args == 2) { const char *str = a->args[1]; switch (cmdtype) { case CMD_ALIAS: show_alias(str, cflag); break; case CMD_BIND: show_binding(str, cflag); break; } return; } if (cflag) { error_msg("\"show -c\" requires 2 arguments"); return; } char tmp[32] = "/tmp/.dte.XXXXXX"; int fd = mkstemp(tmp); if (fd < 0) { error_msg("mkstemp() failed: %s", strerror(errno)); return; } String s; switch (cmdtype) { case CMD_ALIAS: s = dump_aliases(); break; case CMD_BIND: s = dump_bindings(); break; } ssize_t rc = xwrite(fd, s.buffer, s.len); int err = errno; close(fd); string_free(&s); if (rc < 0) { error_msg("write() failed: %s", strerror(err)); unlink(tmp); return; } const char *argv[] = {editor.pager, tmp, NULL}; int child_fds[3] = {0, 1, 2}; spawn((char**)argv, child_fds, false); unlink(tmp); } static void cmd_suspend(const CommandArgs* UNUSED_ARG(a)) { suspend(); } static void cmd_tag(const CommandArgs *a) { if (a->flags[0] == 'r') { pop_file_location(); return; } clear_messages(); TagFile *tf = load_tag_file(); if (tf == NULL) { error_msg("No tag file."); return; } const char *name = a->args[0]; char *word = NULL; if (!name) { word = view_get_word_under_cursor(view); if (!word) { return; } name = word; } PointerArray tags = PTR_ARRAY_INIT; // Filename helps to find correct tags tag_file_find_tags(tf, buffer->abs_filename, name, &tags); if (tags.count == 0) { error_msg("Tag %s not found.", name); } else { for (size_t i = 0; i < tags.count; i++) { Tag *t = tags.ptrs[i]; char buf[512]; xsnprintf(buf, sizeof(buf), "Tag %s", name); Message *m = new_message(buf); m->loc = xnew0(FileLocation, 1); m->loc->filename = tag_file_get_tag_filename(tf, t); if (t->pattern) { m->loc->pattern = t->pattern; t->pattern = NULL; } else { m->loc->line = t->line; } add_message(m); } free_tags(&tags); activate_current_message_save(); } free(word); } static void cmd_toggle(const CommandArgs *a) { const char *pf = a->flags; bool global = false; bool verbose = false; while (*pf) { switch (*pf) { case 'g': global = true; break; case 'v': verbose = true; break; } pf++; } const char *option_name = a->args[0]; size_t nr_values = a->nr_args - 1; if (nr_values) { char **values = a->args + 1; toggle_option_values(option_name, global, verbose, values, nr_values); } else { toggle_option(option_name, global, verbose); } } static void cmd_undo(const CommandArgs* UNUSED_ARG(a)) { if (undo()) { unselect(); } } static void cmd_unselect(const CommandArgs* UNUSED_ARG(a)) { unselect(); } static void cmd_up(const CommandArgs *a) { handle_select_chars_or_lines_flags(a->flags); move_up(1); } static void cmd_view(const CommandArgs *a) { BUG_ON(window->views.count == 0); const char *arg = a->args[0]; size_t idx; if (streq(arg, "last")) { idx = window->views.count - 1; } else { if (!str_to_size(arg, &idx) || idx == 0) { error_msg("Invalid view index: %s", arg); return; } idx--; if (idx > window->views.count - 1) { idx = window->views.count - 1; } } set_view(window->views.ptrs[idx]); } static void cmd_wclose(const CommandArgs *a) { View *v = window_find_unclosable_view(window, view_can_close); bool force = a->flags[0] == 'f'; if (v != NULL && !force) { set_view(v); error_msg ( "Save modified files or run 'wclose -f' to close " "window without saving." ); return; } window_close_current(); } static void cmd_wflip(const CommandArgs* UNUSED_ARG(a)) { Frame *f = window->frame; if (f->parent == NULL) { return; } f->parent->vertical ^= 1; mark_everything_changed(); } static void cmd_wnext(const CommandArgs* UNUSED_ARG(a)) { window = next_window(window); set_view(window->view); mark_everything_changed(); debug_frames(); } static void cmd_word_bwd(const CommandArgs *a) { handle_select_chars_flag(a->flags); bool skip_non_word = strchr(a->flags, 's'); word_bwd(&view->cursor, skip_non_word); view_reset_preferred_x(view); } static void cmd_word_fwd(const CommandArgs *a) { handle_select_chars_flag(a->flags); bool skip_non_word = strchr(a->flags, 's'); word_fwd(&view->cursor, skip_non_word); view_reset_preferred_x(view); } static void cmd_wprev(const CommandArgs* UNUSED_ARG(a)) { window = prev_window(window); set_view(window->view); mark_everything_changed(); debug_frames(); } static void cmd_wrap_paragraph(const CommandArgs *a) { const char *arg = a->args[0]; size_t width = (size_t)buffer->options.text_width; if (arg) { if (!str_to_size(arg, &width) || width == 0 || width > 1000) { error_msg("Invalid paragraph width: %s", arg); return; } } format_paragraph(width); } static void cmd_wresize(const CommandArgs *a) { if (window->frame->parent == NULL) { // Only window return; } const char *pf = a->flags; ResizeDirection dir = RESIZE_DIRECTION_AUTO; while (*pf) { switch (*pf) { case 'h': dir = RESIZE_DIRECTION_HORIZONTAL; break; case 'v': dir = RESIZE_DIRECTION_VERTICAL; break; } pf++; } const char *arg = a->args[0]; if (arg) { int n; if (!str_to_int(arg, &n)) { error_msg("Invalid resize value: %s", arg); return; } if (arg[0] == '+' || arg[0] == '-') { add_to_frame_size(window->frame, dir, n); } else { resize_frame(window->frame, dir, n); } } else { equalize_frame_sizes(window->frame->parent); } mark_everything_changed(); debug_frames(); } static void cmd_wsplit(const CommandArgs *a) { const char *pf = a->flags; bool before = false; bool vertical = false; bool root = false; while (*pf) { switch (*pf) { case 'b': // Add new window before current window before = true; break; case 'h': // Split horizontally to get vertical layout vertical = true; break; case 'r': // Split root frame instead of current window root = true; break; } pf++; } Frame *f; if (root) { f = split_root(vertical, before); } else { f = split_frame(window, vertical, before); } View *save = view; window = f->window; view = NULL; buffer = NULL; mark_everything_changed(); if (a->args[0]) { window_open_files(window, a->args, NULL); } else { View *new = window_add_buffer(window, save->buffer); new->cursor = save->cursor; set_view(new); } if (window->views.count == 0) { // Open failed, remove new window remove_frame(window->frame); view = save; buffer = view->buffer; window = view->window; } debug_frames(); } static void cmd_wswap(const CommandArgs* UNUSED_ARG(a)) { Frame *parent = window->frame->parent; if (parent == NULL) { return; } size_t i = ptr_array_idx(&parent->frames, window->frame); size_t j = i + 1; if (j == parent->frames.count) { j = 0; } Frame *tmp = parent->frames.ptrs[i]; parent->frames.ptrs[i] = parent->frames.ptrs[j]; parent->frames.ptrs[j] = tmp; mark_everything_changed(); } // Prevent Clang whining about .max_args = -1 #if HAS_WARNING("-Wbitfield-constant-conversion") IGNORE_WARNING("-Wbitfield-constant-conversion") #endif const Command commands[] = { {"alias", "", 2, 2, cmd_alias}, {"bind", "", 1, 2, cmd_bind}, {"bof", "", 0, 0, cmd_bof}, {"bol", "cs", 0, 0, cmd_bol}, {"bolsf", "", 0, 0, cmd_bolsf}, {"case", "lu", 0, 0, cmd_case}, {"cd", "", 1, 1, cmd_cd}, {"center-view", "", 0, 0, cmd_center_view}, {"clear", "", 0, 0, cmd_clear}, {"close", "fqw", 0, 0, cmd_close}, {"command", "", 0, 1, cmd_command}, {"compile", "-1ps", 2, -1, cmd_compile}, {"copy", "k", 0, 0, cmd_copy}, {"cut", "", 0, 0, cmd_cut}, {"delete", "", 0, 0, cmd_delete}, {"delete-eol", "n", 0, 0, cmd_delete_eol}, {"delete-word", "s", 0, 0, cmd_delete_word}, {"down", "cl", 0, 0, cmd_down}, {"eof", "", 0, 0, cmd_eof}, {"eol", "c", 0, 0, cmd_eol}, {"eolsf", "", 0, 0, cmd_eolsf}, {"erase", "", 0, 0, cmd_erase}, {"erase-bol", "", 0, 0, cmd_erase_bol}, {"erase-word", "s", 0, 0, cmd_erase_word}, {"errorfmt", "i", 2, 18, cmd_errorfmt}, {"eval", "-", 1, -1, cmd_eval}, {"filter", "-", 1, -1, cmd_filter}, {"ft", "-bcfi", 2, -1, cmd_ft}, {"git-open", "", 0, 0, cmd_git_open}, {"hi", "-", 0, -1, cmd_hi}, {"include", "b", 1, 1, cmd_include}, {"insert", "km", 1, 1, cmd_insert}, {"insert-builtin", "", 1, 1, cmd_insert_builtin}, {"join", "", 0, 0, cmd_join}, {"left", "c", 0, 0, cmd_left}, {"line", "", 1, 1, cmd_line}, {"load-syntax", "", 1, 1, cmd_load_syntax}, {"move-tab", "", 1, 1, cmd_move_tab}, {"msg", "np", 0, 0, cmd_msg}, {"new-line", "", 0, 0, cmd_new_line}, {"next", "", 0, 0, cmd_next}, {"open", "e=g", 0, -1, cmd_open}, {"option", "-r", 3, -1, cmd_option}, {"paste", "c", 0, 0, cmd_paste}, {"pgdown", "cl", 0, 0, cmd_pgdown}, {"pgup", "cl", 0, 0, cmd_pgup}, {"pipe-from", "-ms", 1, -1, cmd_pipe_from}, {"pipe-to", "-", 1, -1, cmd_pipe_to}, {"prev", "", 0, 0, cmd_prev}, {"quit", "fp", 0, 0, cmd_quit}, {"redo", "", 0, 1, cmd_redo}, {"refresh", "", 0, 0, cmd_refresh}, {"repeat", "", 2, -1, cmd_repeat}, {"replace", "bcgi", 2, 2, cmd_replace}, {"right", "c", 0, 0, cmd_right}, {"run", "-ps", 1, -1, cmd_run}, {"save", "de=fpu", 0, 1, cmd_save}, {"scroll-down", "", 0, 0, cmd_scroll_down}, {"scroll-pgdown", "", 0, 0, cmd_scroll_pgdown}, {"scroll-pgup", "", 0, 0, cmd_scroll_pgup}, {"scroll-up", "", 0, 0, cmd_scroll_up}, {"search", "Hnprw", 0, 1, cmd_search}, {"select", "bkl", 0, 0, cmd_select}, {"set", "gl", 1, -1, cmd_set}, {"setenv", "", 2, 2, cmd_setenv}, {"shift", "", 1, 1, cmd_shift}, {"show", "c", 1, 2, cmd_show}, {"suspend", "", 0, 0, cmd_suspend}, {"tag", "r", 0, 1, cmd_tag}, {"toggle", "glv", 1, -1, cmd_toggle}, {"undo", "", 0, 0, cmd_undo}, {"unselect", "", 0, 0, cmd_unselect}, {"up", "cl", 0, 0, cmd_up}, {"view", "", 1, 1, cmd_view}, {"wclose", "f", 0, 0, cmd_wclose}, {"wflip", "", 0, 0, cmd_wflip}, {"wnext", "", 0, 0, cmd_wnext}, {"word-bwd", "cs", 0, 0, cmd_word_bwd}, {"word-fwd", "cs", 0, 0, cmd_word_fwd}, {"wprev", "", 0, 0, cmd_wprev}, {"wrap-paragraph", "", 0, 1, cmd_wrap_paragraph}, {"wresize", "hv", 0, 1, cmd_wresize}, {"wsplit", "bhr", 0, -1, cmd_wsplit}, {"wswap", "", 0, 0, cmd_wswap}, {"", "", 0, 0, NULL} }; UNIGNORE_WARNINGS dte-1.9.1/src/command.h000066400000000000000000000023631354415143300146630ustar00rootroot00000000000000#ifndef COMMAND_H #define COMMAND_H #include #include #include #include "util/ptr-array.h" typedef struct { char flags[8]; char **args; size_t nr_flags; size_t nr_args; } CommandArgs; typedef struct { const char name[15]; const char flags[7]; uint16_t min_args : 4; uint16_t max_args : 12; void (*cmd)(const CommandArgs *args); } Command; typedef enum { CMDERR_NONE, CMDERR_UNCLOSED_SINGLE_QUOTE, CMDERR_UNCLOSED_DOUBLE_QUOTE, CMDERR_UNEXPECTED_EOF, } CommandParseError; #define CMD_ARG_MAX 4095 // (1 << 12) - 1 // command-parse.c char *parse_command_arg(const char *cmd, size_t len, bool tilde); size_t find_end(const char *cmd, size_t pos, CommandParseError *err); bool parse_commands(PointerArray *array, const char *cmd, CommandParseError *err); const char *command_parse_error_to_string(CommandParseError err); // command-run.c extern const Command *current_command; const Command *find_command(const Command *cmds, const char *name); void run_commands(const Command *cmds, const PointerArray *array); void run_command(const Command *cmds, char **argv); void handle_command(const Command *cmds, const char *cmd); // command.c extern const Command commands[]; #endif dte-1.9.1/src/compiler.c000066400000000000000000000035521354415143300150530ustar00rootroot00000000000000#include "compiler.h" #include "error.h" #include "regexp.h" #include "util/str-util.h" #include "util/xmalloc.h" static PointerArray compilers = PTR_ARRAY_INIT; static Compiler *add_compiler(const char *name) { Compiler *c = find_compiler(name); if (c) { return c; } c = xnew0(Compiler, 1); c->name = xstrdup(name); ptr_array_add(&compilers, c); return c; } Compiler *find_compiler(const char *name) { for (size_t i = 0; i < compilers.count; i++) { Compiler *c = compilers.ptrs[i]; if (streq(c->name, name)) { return c; } } return NULL; } void add_error_fmt ( const char *compiler, bool ignore, const char *format, char **desc ) { static const char names[][8] = {"file", "line", "column", "message"}; int idx[ARRAY_COUNT(names)] = {-1, -1, -1, 0}; ErrorFormat *f; for (size_t i = 0, j = 0; desc[i]; i++) { for (j = 0; j < ARRAY_COUNT(names); j++) { if (streq(desc[i], names[j])) { idx[j] = ((int)i) + 1; break; } } if (streq(desc[i], "_")) { continue; } if (j == ARRAY_COUNT(names)) { error_msg("Unknown substring name %s.", desc[i]); return; } } f = xnew0(ErrorFormat, 1); f->ignore = ignore; f->msg_idx = idx[3]; f->file_idx = idx[0]; f->line_idx = idx[1]; f->column_idx = idx[2]; if (!regexp_compile(&f->re, format, 0)) { free(f); return; } for (size_t i = 0; i < ARRAY_COUNT(idx); i++) { // NOTE: -1 is larger than 0UL if (idx[i] > (int)f->re.re_nsub) { error_msg("Invalid substring count."); regfree(&f->re); free(f); return; } } ptr_array_add(&add_compiler(compiler)->error_formats, f); } dte-1.9.1/src/compiler.h000066400000000000000000000007601354415143300150560ustar00rootroot00000000000000#ifndef COMPILER_H #define COMPILER_H #include #include #include #include "util/ptr-array.h" typedef struct { bool ignore; int8_t msg_idx; int8_t file_idx; int8_t line_idx; int8_t column_idx; regex_t re; } ErrorFormat; typedef struct { char *name; PointerArray error_formats; } Compiler; void add_error_fmt(const char *compiler, bool ignore, const char *format, char **desc); Compiler *find_compiler(const char *name); #endif dte-1.9.1/src/completion.c000066400000000000000000000332261354415143300154130ustar00rootroot00000000000000#include #include #include "completion.h" #include "alias.h" #include "cmdline.h" #include "command.h" #include "debug.h" #include "config.h" #include "editor.h" #include "env.h" #include "options.h" #include "syntax/color.h" #include "tag.h" #include "util/ascii.h" #include "util/path.h" #include "util/ptr-array.h" #include "util/str-util.h" #include "util/string.h" #include "util/xmalloc.h" static struct { // Part of string that is to be replaced char *escaped; char *parsed; char *head; char *tail; PointerArray completions; size_t idx; // Should we add space after completed string if we have only one match? bool add_space; bool tilde_expanded; } completion; static int strptrcmp(const void *v1, const void *v2) { const char *const *s1 = v1; const char *const *s2 = v2; return strcmp(*s1, *s2); } static void sort_completions(void) { ptr_array_sort(&completion.completions, strptrcmp); } void add_completion(char *str) { ptr_array_add(&completion.completions, str); } static void collect_commands(const char *prefix) { for (size_t i = 0; commands[i].cmd; i++) { const Command *c = &commands[i]; if (str_has_prefix(c->name, prefix)) { add_completion(xstrdup(c->name)); } } collect_aliases(prefix); } static void do_collect_files ( const char *dirname, const char *dirprefix, const char *fileprefix, bool directories_only ) { char path[8192]; size_t plen = strlen(dirname); const size_t flen = strlen(fileprefix); BUG_ON(plen == 0U); if (plen >= sizeof(path) - 2) { return; } DIR *const dir = opendir(dirname); if (!dir) { return; } memcpy(path, dirname, plen); if (path[plen - 1] != '/') { path[plen++] = '/'; } const struct dirent *de; while ((de = readdir(dir))) { const char *name = de->d_name; if (flen) { if (strncmp(name, fileprefix, flen) != 0) { continue; } } else { if (name[0] == '.') { continue; } } size_t len = strlen(name); if (plen + len + 2 > sizeof(path)) { continue; } memcpy(path + plen, name, len + 1); struct stat st; if (lstat(path, &st)) { continue; } bool is_dir = S_ISDIR(st.st_mode); if (S_ISLNK(st.st_mode)) { if (!stat(path, &st)) { is_dir = S_ISDIR(st.st_mode); } } if (!is_dir && directories_only) { continue; } String buf = STRING_INIT; if (dirprefix[0]) { string_add_str(&buf, dirprefix); if (!str_has_suffix(dirprefix, "/")) { string_add_byte(&buf, '/'); } } string_add_str(&buf, name); if (is_dir) { string_add_byte(&buf, '/'); } add_completion(string_steal_cstring(&buf)); } closedir(dir); } static void collect_files(bool directories_only) { char *str = parse_command_arg ( completion.escaped, strlen(completion.escaped), false ); if (!streq(completion.parsed, str)) { // ~ was expanded const char *fileprefix = path_basename(str); completion.tilde_expanded = true; if (fileprefix == str) { // str doesn't contain slashes // complete ~ to ~/ or ~user to ~user/ size_t len = strlen(str); char *s = xmalloc(len + 2); memcpy(s, str, len); s[len] = '/'; s[len + 1] = '\0'; add_completion(s); } else { char *dir = path_dirname(completion.parsed); char *dirprefix = path_dirname(str); do_collect_files(dir, dirprefix, fileprefix, directories_only); free(dirprefix); free(dir); } } else { const char *fileprefix = path_basename(completion.parsed); if (fileprefix == completion.parsed) { // completion.parsed doesn't contain slashes do_collect_files(".", "", fileprefix, directories_only); } else { char *dir = path_dirname(completion.parsed); do_collect_files(dir, dir, fileprefix, directories_only); free(dir); } } free(str); if (completion.completions.count == 1) { // Add space if completed string is not a directory const char *s = completion.completions.ptrs[0]; size_t len = strlen(s); if (len > 0) { completion.add_space = s[len - 1] != '/'; } } } static void collect_env(const char *prefix) { extern char **environ; for (size_t i = 0; environ[i]; i++) { const char *e = environ[i]; if (str_has_prefix(e, prefix)) { const char *end = strchr(e, '='); if (end) { add_completion(xstrcut(e, end - e)); } } } collect_builtin_env(prefix); } static void collect_colors_and_attributes(const char *prefix) { static const char names[][16] = { "keep", "default", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "gray", "darkgray", "lightred", "lightgreen", "lightyellow", "lightblue", "lightmagenta", "lightcyan", "white", "underline", "reverse", "blink", "dim", "bold", "invisible", "italic", "strikethrough" }; for (size_t i = 0; i < ARRAY_COUNT(names); i++) { if (str_has_prefix(names[i], prefix)) { add_completion(xstrdup(names[i])); } } } static void collect_completions(char **args, size_t argc) { if (!argc) { collect_commands(completion.parsed); return; } const Command *cmd = find_command(commands, args[0]); if (!cmd) { return; } const StringView cmd_name = string_view_from_cstring(cmd->name); if ( string_view_equal_literal(&cmd_name, "open") || string_view_equal_literal(&cmd_name, "wsplit") || string_view_equal_literal(&cmd_name, "save") || string_view_equal_literal(&cmd_name, "compile") || string_view_equal_literal(&cmd_name, "run") || string_view_equal_literal(&cmd_name, "pipe-from") || string_view_equal_literal(&cmd_name, "pipe-to") ) { collect_files(false); return; } if (string_view_equal_literal(&cmd_name, "cd")) { collect_files(true); return; } if (string_view_equal_literal(&cmd_name, "include")) { switch (argc) { case 1: collect_files(false); break; case 2: if (streq(args[1], "-b")) { collect_builtin_configs(completion.parsed, false); } break; } return; } if (string_view_equal_literal(&cmd_name, "insert-builtin")) { if (argc == 1) { collect_builtin_configs(completion.parsed, true); } return; } if (string_view_equal_literal(&cmd_name, "hi")) { switch (argc) { case 1: collect_hl_colors(completion.parsed); break; default: collect_colors_and_attributes(completion.parsed); break; } return; } if (string_view_equal_literal(&cmd_name, "set")) { if (argc % 2) { collect_options(completion.parsed); } else { collect_option_values(args[argc - 1], completion.parsed); } return; } if (string_view_equal_literal(&cmd_name, "toggle") && argc == 1) { collect_toggleable_options(completion.parsed); return; } if (string_view_equal_literal(&cmd_name, "tag") && argc == 1) { TagFile *tf = load_tag_file(); if (tf != NULL) { collect_tags(tf, completion.parsed); } return; } if (string_view_equal_literal(&cmd_name, "show")) { switch (argc) { case 1: if (str_has_prefix("alias", completion.parsed)) { add_completion(xstrdup("alias")); } if (str_has_prefix("bind", completion.parsed)) { add_completion(xstrdup("bind")); } break; case 2: if (streq(args[1], "alias")) { collect_aliases(completion.parsed); } break; case 3: if ( (streq(args[1], "alias") && streq(args[2], "-c")) || (streq(args[1], "-c") && streq(args[2], "alias")) ) { collect_aliases(completion.parsed); } break; } return; } } static void init_completion(void) { const char *cmd = string_borrow_cstring(&editor.cmdline.buf); PointerArray array = PTR_ARRAY_INIT; ssize_t semicolon = -1; ssize_t completion_pos = -1; size_t pos = 0; while (1) { while (ascii_isspace(cmd[pos])) { pos++; } if (pos >= editor.cmdline.pos) { completion_pos = editor.cmdline.pos; break; } if (!cmd[pos]) { break; } if (cmd[pos] == ';') { semicolon = array.count; ptr_array_add(&array, NULL); pos++; continue; } CommandParseError err = 0; size_t end = find_end(cmd, pos, &err); if (err || end >= editor.cmdline.pos) { completion_pos = pos; break; } if (semicolon + 1 == array.count) { char *name = xstrslice(cmd, pos, end); const char *value = find_alias(name); if (value) { size_t save = array.count; if (!parse_commands(&array, value, &err)) { for (size_t i = save; i < array.count; i++) { free(array.ptrs[i]); array.ptrs[i] = NULL; } array.count = save; ptr_array_add(&array, parse_command_arg(name, end - pos, true)); } else { // Remove NULL array.count--; } } else { ptr_array_add(&array, parse_command_arg(name, end - pos, true)); } free(name); } else { ptr_array_add(&array, parse_command_arg(cmd + pos, end - pos, true)); } pos = end; } const char *str = cmd + completion_pos; size_t len = editor.cmdline.pos - completion_pos; if (len && str[0] == '$') { bool var = true; if (len >= 2) { if (is_alpha_or_underscore(str[1])) { for (size_t i = 2; i < len; i++) { if (is_alnum_or_underscore(str[i])) { continue; } var = false; break; } } else { var = false; } } if (var) { char *name = xstrslice(str, 1, len); completion_pos++; completion.escaped = NULL; completion.parsed = NULL; completion.head = xstrcut(cmd, completion_pos); completion.tail = xstrdup(cmd + editor.cmdline.pos); collect_env(name); sort_completions(); free(name); ptr_array_free(&array); return; } } completion.escaped = xstrcut(str, len); completion.parsed = parse_command_arg(completion.escaped, len, true); completion.head = xstrcut(cmd, completion_pos); completion.tail = xstrdup(cmd + editor.cmdline.pos); completion.add_space = true; collect_completions ( (char **)array.ptrs + 1 + semicolon, array.count - semicolon - 1 ); sort_completions(); ptr_array_free(&array); } static char *escape(const char *str) { String buf = STRING_INIT; if (!str[0]) { return xmemdup_literal("\"\""); } if (str[0] == '~' && !completion.tilde_expanded) { string_add_byte(&buf, '\\'); } for (size_t i = 0; str[i]; i++) { const char ch = str[i]; switch (ch) { case ' ': case '"': case '$': case '\'': case ';': case '\\': string_add_byte(&buf, '\\'); // Fallthrough default: string_add_byte(&buf, ch); break; } } return string_steal_cstring(&buf); } void complete_command(void) { if (!completion.head) { init_completion(); } if (!completion.completions.count) { return; } char *middle = escape(completion.completions.ptrs[completion.idx]); size_t middle_len = strlen(middle); size_t head_len = strlen(completion.head); size_t tail_len = strlen(completion.tail); char *str = xmalloc(head_len + middle_len + tail_len + 2); memcpy(str, completion.head, head_len); memcpy(str + head_len, middle, middle_len); if (completion.completions.count == 1 && completion.add_space) { str[head_len + middle_len] = ' '; middle_len++; } memcpy(str + head_len + middle_len, completion.tail, tail_len + 1); cmdline_set_text(&editor.cmdline, str); editor.cmdline.pos = head_len + middle_len; free(middle); free(str); completion.idx = (completion.idx + 1) % completion.completions.count; if (completion.completions.count == 1) { reset_completion(); } } void reset_completion(void) { free(completion.escaped); free(completion.parsed); free(completion.head); free(completion.tail); ptr_array_free(&completion.completions); MEMZERO(&completion); } dte-1.9.1/src/completion.h000066400000000000000000000002151354415143300154100ustar00rootroot00000000000000#ifndef COMPLETION_H #define COMPLETION_H void complete_command(void); void reset_completion(void); void add_completion(char *str); #endif dte-1.9.1/src/config.c000066400000000000000000000104011354415143300144750ustar00rootroot00000000000000#include #include #include "config.h" #include "completion.h" #include "debug.h" #include "error.h" #include "terminal/terminal.h" #include "util/ascii.h" #include "util/readfile.h" #include "util/str-util.h" #include "util/string.h" #include "util/xmalloc.h" #include "../build/builtin-config.h" const char *config_file; int config_line; static bool is_command(const char *str, size_t len) { for (size_t i = 0; i < len; i++) { if (str[i] == '#') { return false; } if (!ascii_isspace(str[i])) { return true; } } return false; } // Odd number of backslashes at end of line? static bool has_line_continuation(const char *str, size_t len) { ssize_t pos = len - 1; while (pos >= 0 && str[pos] == '\\') { pos--; } return (len - 1 - pos) % 2; } void exec_config(const Command *cmds, const char *buf, size_t size) { const char *ptr = buf; String line = STRING_INIT; while (ptr < buf + size) { size_t n = buf + size - ptr; char *end = memchr(ptr, '\n', n); if (end) { n = end - ptr; } if (line.len || is_command(ptr, n)) { if (has_line_continuation(ptr, n)) { string_add_buf(&line, ptr, n - 1); } else { string_add_buf(&line, ptr, n); handle_command(cmds, string_borrow_cstring(&line)); string_clear(&line); } } config_line++; ptr += n + 1; } if (line.len) { handle_command(cmds, string_borrow_cstring(&line)); } string_free(&line); } void list_builtin_configs(void) { for (size_t i = 0; i < ARRAY_COUNT(builtin_configs); i++) { fputs(builtin_configs[i].name, stdout); fputc('\n', stdout); } } void collect_builtin_configs(const char *const prefix, bool syntaxes) { for (size_t i = 0; i < ARRAY_COUNT(builtin_configs); i++) { const BuiltinConfig *cfg = &builtin_configs[i]; if (syntaxes == false && str_has_prefix(cfg->name, "syntax/")) { return; } else if (str_has_prefix(cfg->name, prefix)) { add_completion(xstrdup(cfg->name)); } } } const BuiltinConfig *get_builtin_config(const char *const name) { for (size_t i = 0; i < ARRAY_COUNT(builtin_configs); i++) { if (streq(name, builtin_configs[i].name)) { return &builtin_configs[i]; } } return NULL; } int do_read_config(const Command *cmds, const char *filename, ConfigFlags flags) { const bool must_exist = flags & CFG_MUST_EXIST; const bool builtin = flags & CFG_BUILTIN; if (builtin) { const BuiltinConfig *cfg = get_builtin_config(filename); if (cfg) { config_file = filename; config_line = 1; exec_config(cmds, cfg->text.data, cfg->text.length); return 0; } else if (must_exist) { error_msg ( "Error reading '%s': no built-in config exists for that path", filename ); return 1; } else { return 0; } } char *buf; ssize_t size = read_file(filename, &buf); int err = errno; if (size < 0) { if (err != ENOENT || must_exist) { error_msg("Error reading %s: %s", filename, strerror(err)); } return err; } config_file = filename; config_line = 1; exec_config(cmds, buf, size); free(buf); return 0; } int read_config(const Command *cmds, const char *filename, ConfigFlags flags) { // Recursive const char *saved_config_file = config_file; int saved_config_line = config_line; int ret = do_read_config(cmds, filename, flags); config_file = saved_config_file; config_line = saved_config_line; return ret; } void exec_reset_colors_rc(void) { bool colors = terminal.color_type >= TERM_8_COLOR; const char *cfg = colors ? "color/reset" : "color/reset-basic"; read_config(commands, cfg, CFG_MUST_EXIST | CFG_BUILTIN); } UNITTEST { // Built-in configs can be customized, but these 3 are required: BUG_ON(!get_builtin_config("rc")); BUG_ON(!get_builtin_config("color/reset")); BUG_ON(!get_builtin_config("color/reset-basic")); } dte-1.9.1/src/config.h000066400000000000000000000014211354415143300145040ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H #include #include "command.h" #include "util/string-view.h" typedef enum { CFG_NOFLAGS = 0, CFG_MUST_EXIST = 1 << 0, CFG_BUILTIN = 1 << 1 } ConfigFlags; typedef struct { const char *const name; const StringView text; } BuiltinConfig; extern const char *config_file; extern int config_line; void list_builtin_configs(void); void collect_builtin_configs(const char *prefix, bool syntaxes); const BuiltinConfig *get_builtin_config(const char *name) PURE; void exec_config(const Command *cmds, const char *buf, size_t size); int do_read_config(const Command *cmds, const char *filename, ConfigFlags f); int read_config(const Command *cmds, const char *filename, ConfigFlags f); void exec_reset_colors_rc(void); #endif dte-1.9.1/src/ctags.c000066400000000000000000000076311354415143300143440ustar00rootroot00000000000000#include #include #include "ctags.h" #include "util/str-util.h" #include "util/strtonum.h" #include "util/xmalloc.h" static size_t parse_excmd(Tag *t, const char *buf, size_t size) { const char ch = *buf; if (ch == '/' || ch == '?') { // The search pattern is not a real regular expression. // Need to escape special characters. char *pattern = xmalloc(size * 2); for (size_t i = 1, j = 0; i < size; i++) { if (buf[i] == '\\' && i + 1 < size) { i++; if (buf[i] == '\\') { pattern[j++] = '\\'; } pattern[j++] = buf[i]; continue; } if (buf[i] == ch) { if (i + 2 < size && buf[i + 1] == ';' && buf[i + 2] == '"') { i += 2; } pattern[j] = '\0'; t->pattern = pattern; return i + 1; } switch (buf[i]) { case '*': case '[': case ']': pattern[j++] = '\\'; break; } pattern[j++] = buf[i]; } free(pattern); return 0; } unsigned long line; size_t i = buf_parse_ulong(buf, size, &line); if (i == 0) { return 0; } if (i + 1 < size && buf[i] == ';' && buf[i + 1] == '"') { i += 2; } t->line = line; return i; } static bool parse_line(Tag *t, const char *buf, size_t size) { MEMZERO(t); const char *end = memchr(buf, '\t', size); if (!end) { goto error; } size_t len = end - buf; t->name = xstrcut(buf, len); size_t si = len + 1; if (si >= size) { goto error; } end = memchr(buf + si, '\t', size - si); len = end - buf - si; t->filename = xstrslice(buf, si, si + len); si += len + 1; if (si >= size) { goto error; } // excmd can contain tabs len = parse_excmd(t, buf + si, size - si); if (!len) { goto error; } si += len; if (si == size) { return true; } /* * Extension fields (key:[value]): * * file: visibility limited to this file * struct:NAME tag is member of struct NAME * union:NAME tag is member of union NAME * typeref:struct:NAME::MEMBER_TYPE MEMBER_TYPE is type of the tag */ if (buf[si] != '\t') { goto error; } si++; while (si < size) { size_t ei = si; while (ei < size && buf[ei] != '\t') { ei++; } len = ei - si; if (len == 1) { t->kind = buf[si]; } else if (len == 5 && !memcmp(buf + si, "file:", 5)) { t->local = true; } // FIXME: struct/union/typeref si = ei + 1; } return true; error: free_tag(t); return false; } bool next_tag ( const TagFile *tf, size_t *posp, const char *prefix, bool exact, Tag *t ) { size_t prefix_len = strlen(prefix); size_t pos = *posp; while (pos < tf->size) { size_t len = tf->size - pos; char *line = tf->buf + pos; char *end = memchr(line, '\n', len); if (end) { len = end - line; } pos += len + 1; if (!len || line[0] == '!') { continue; } if (len <= prefix_len || memcmp(line, prefix, prefix_len) != 0) { continue; } if (exact && line[prefix_len] != '\t') { continue; } if (!parse_line(t, line, len)) { continue; } *posp = pos; return true; } return false; } // NOTE: t itself is not freed void free_tag(Tag *t) { free(t->name); free(t->filename); free(t->pattern); free(t->member); free(t->typeref); } dte-1.9.1/src/ctags.h000066400000000000000000000007541354415143300143500ustar00rootroot00000000000000#ifndef CTAGS_H #define CTAGS_H #include #include typedef struct { char *filename; char *buf; size_t size; time_t mtime; } TagFile; typedef struct { char *name; char *filename; char *pattern; char *member; char *typeref; unsigned long line; char kind; bool local; } Tag; bool next_tag ( const TagFile *tf, size_t *posp, const char *prefix, bool exact, Tag *t ); void free_tag(Tag *t); #endif dte-1.9.1/src/debug.c000066400000000000000000000035541354415143300143310ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "debug.h" #include "editor.h" #include "terminal/terminal.h" #include "util/xreadwrite.h" #include "util/xsnprintf.h" void term_cleanup(void) { if (editor.status == EDITOR_INITIALIZING) { return; } if (!editor.child_controls_terminal) { editor.ui_end(); } if (terminal.control_codes.deinit.length) { xwrite ( STDOUT_FILENO, terminal.control_codes.deinit.data, terminal.control_codes.deinit.length ); } } NORETURN void fatal_error(const char *msg, int err) { term_cleanup(); errno = err; perror(msg); abort(); } #if DEBUG >= 1 NORETURN void bug(const char *file, int line, const char *func, const char *fmt, ...) { term_cleanup(); fprintf(stderr, "\n%s:%d: **BUG** in %s() function: '", file, line, func); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputs("'\n", stderr); fflush(stderr); abort(); } #endif #ifdef DEBUG_PRINT void debug_print(const char *function, const char *fmt, ...) { static int fd = -1; if (fd < 0) { char *filename = editor_file("debug.log"); fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666); free(filename); BUG_ON(fd < 0); // Don't leak file descriptor to parent processes int r = fcntl(fd, F_SETFD, FD_CLOEXEC); BUG_ON(r == -1); } char buf[4096]; size_t write_max = ARRAY_COUNT(buf); const size_t len1 = xsnprintf(buf, write_max, "%s: ", function); write_max -= len1; va_list ap; va_start(ap, fmt); const size_t len2 = xvsnprintf(buf + len1, write_max, fmt, ap); va_end(ap); xwrite(fd, buf, len1 + len2); } #endif dte-1.9.1/src/debug.h000066400000000000000000000013451354415143300143320ustar00rootroot00000000000000#ifndef DEBUG_H #define DEBUG_H #include "util/macros.h" #define BUG_ON(a) do { \ if (unlikely(a)) { \ BUG("%s", #a); \ } \ } while (0) #if DEBUG >= 1 #define BUG(...) bug(__FILE__, __LINE__, __func__, __VA_ARGS__) NORETURN COLD PRINTF(4) void bug(const char *file, int line, const char *func, const char *fmt, ...); #else #define BUG(...) UNREACHABLE() #endif #ifdef DEBUG_PRINT #define d_print(...) debug_print(__func__, __VA_ARGS__) PRINTF(2) void debug_print(const char *function, const char *fmt, ...); #else PRINTF(1) static inline void d_print(const char* UNUSED_ARG(fmt), ...) {} #endif void term_cleanup(void); NORETURN COLD NONNULL_ARGS void fatal_error(const char *msg, int err); #endif dte-1.9.1/src/edit.c000066400000000000000000000534411354415143300141700ustar00rootroot00000000000000#include #include "edit.h" #include "buffer.h" #include "change.h" #include "indent.h" #include "move.h" #include "regexp.h" #include "selection.h" #include "util/string.h" #include "util/utf8.h" #include "util/xmalloc.h" #include "view.h" typedef struct { String buf; char *indent; size_t indent_len; size_t indent_width; size_t cur_width; size_t text_width; } ParagraphFormatter; static char *copy_buf; static size_t copy_len; static bool copy_is_lines; static const char spattern[] = "\\{[ \t]*(//.*|/\\*.*\\*/[ \t]*)?$"; static const char epattern[] = "^[ \t]*\\}"; /* * Stupid { ... } block selector. * * Because braces can be inside strings or comments and writing real * parser for many programming languages does not make sense the rules * for selecting a block are made very simple. Line that matches \{\s*$ * starts a block and line that matches ^\s*\} ends it. */ void select_block(void) { BlockIter sbi, ebi, bi = view->cursor; LineRef lr; int level = 0; // If current line does not match \{\s*$ but matches ^\s*\} then // cursor is likely at end of the block you want to select. fetch_this_line(&bi, &lr); if ( !regexp_match_nosub(spattern, lr.line, lr.size) && regexp_match_nosub(epattern, lr.line, lr.size) ) { block_iter_prev_line(&bi); } while (1) { fetch_this_line(&bi, &lr); if (regexp_match_nosub(spattern, lr.line, lr.size)) { if (level++ == 0) { sbi = bi; block_iter_next_line(&bi); break; } } if (regexp_match_nosub(epattern, lr.line, lr.size)) { level--; } if (!block_iter_prev_line(&bi)) { return; } } while (1) { fetch_this_line(&bi, &lr); if (regexp_match_nosub(epattern, lr.line, lr.size)) { if (--level == 0) { ebi = bi; break; } } if (regexp_match_nosub(spattern, lr.line, lr.size)) { level++; } if (!block_iter_next_line(&bi)) { return; } } view->cursor = sbi; view->sel_so = block_iter_get_offset(&ebi); view->sel_eo = UINT_MAX; view->selection = SELECT_LINES; mark_all_lines_changed(buffer); } static int get_indent_of_matching_brace(void) { BlockIter bi = view->cursor; LineRef lr; int level = 0; while (block_iter_prev_line(&bi)) { fetch_this_line(&bi, &lr); if (regexp_match_nosub(spattern, lr.line, lr.size)) { if (level++ == 0) { IndentInfo info; get_indent_info(lr.line, lr.size, &info); return info.width; } } if (regexp_match_nosub(epattern, lr.line, lr.size)) { level--; } } return -1; } void unselect(void) { if (view->selection) { view->selection = SELECT_NONE; mark_all_lines_changed(buffer); } } static void record_copy(char *buf, size_t len, bool is_lines) { if (copy_buf) { free(copy_buf); } copy_buf = buf; copy_len = len; copy_is_lines = is_lines; } void cut(size_t len, bool is_lines) { if (len) { char *buf = block_iter_get_bytes(&view->cursor, len); record_copy(buf, len, is_lines); buffer_delete_bytes(len); } } void copy(size_t len, bool is_lines) { if (len) { char *buf = block_iter_get_bytes(&view->cursor, len); record_copy(buf, len, is_lines); } } void insert_text(const char *text, size_t size) { size_t del_count = 0; if (view->selection) { del_count = prepare_selection(view); unselect(); } buffer_replace_bytes(del_count, text, size); block_iter_skip_bytes(&view->cursor, size); } void paste(bool at_cursor) { if (!copy_buf) { return; } size_t del_count = 0; if (view->selection) { del_count = prepare_selection(view); unselect(); } if (copy_is_lines && !at_cursor) { const long x = view_get_preferred_x(view); if (!del_count) { block_iter_eat_line(&view->cursor); } buffer_replace_bytes(del_count, copy_buf, copy_len); // Try to keep cursor column move_to_preferred_x(x); // New preferred_x view_reset_preferred_x(view); } else { buffer_replace_bytes(del_count, copy_buf, copy_len); } } void delete_ch(void) { size_t size = 0; if (view->selection) { size = prepare_selection(view); unselect(); } else { begin_change(CHANGE_MERGE_DELETE); if (buffer->options.emulate_tab) { size = get_indent_level_bytes_right(); } if (size == 0) { BlockIter bi = view->cursor; size = block_iter_next_column(&bi); } } buffer_delete_bytes(size); } void erase(void) { size_t size = 0; if (view->selection) { size = prepare_selection(view); unselect(); } else { begin_change(CHANGE_MERGE_ERASE); if (buffer->options.emulate_tab) { size = get_indent_level_bytes_left(); block_iter_back_bytes(&view->cursor, size); } if (size == 0) { CodePoint u; size = block_iter_prev_char(&view->cursor, &u); } } buffer_erase_bytes(size); } // Go to beginning of whitespace (tabs and spaces) under cursor and // return number of whitespace bytes after cursor after moving cursor static size_t goto_beginning_of_whitespace(void) { BlockIter bi = view->cursor; size_t count = 0; CodePoint u; // Count spaces and tabs at or after cursor while (block_iter_next_char(&bi, &u)) { if (u != '\t' && u != ' ') { break; } count++; } // Count spaces and tabs before cursor while (block_iter_prev_char(&view->cursor, &u)) { if (u != '\t' && u != ' ') { block_iter_next_char(&view->cursor, &u); break; } count++; } return count; } static bool ws_only(const LineRef *lr) { for (size_t i = 0, n = lr->size; i < n; i++) { char ch = lr->line[i]; if (ch != ' ' && ch != '\t') { return false; } } return true; } // Non-empty line can be used to determine size of indentation for the next line static bool find_non_empty_line_bwd(BlockIter *bi) { block_iter_bol(bi); do { LineRef lr; fill_line_ref(bi, &lr); if (!ws_only(&lr)) { return true; } } while (block_iter_prev_line(bi)); return false; } static void insert_nl(void) { size_t del_count = 0; size_t ins_count = 1; char *ins = NULL; // Prepare deleted text (selection or whitespace around cursor) if (view->selection) { del_count = prepare_selection(view); unselect(); } else { // Trim whitespace around cursor del_count = goto_beginning_of_whitespace(); } // Prepare inserted indentation if (buffer->options.auto_indent) { // Current line will be split at cursor position BlockIter bi = view->cursor; size_t len = block_iter_bol(&bi); LineRef lr; fill_line_ref(&bi, &lr); lr.size = len; if (ws_only(&lr)) { // This line is (or will become) white space only. // Find previous non whitespace only line. if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { fill_line_ref(&bi, &lr); ins = get_indent_for_next_line(lr.line, lr.size); } } else { ins = get_indent_for_next_line(lr.line, lr.size); } } begin_change(CHANGE_MERGE_NONE); if (ins) { // Add newline before indent ins_count = strlen(ins); memmove(ins + 1, ins, ins_count); ins[0] = '\n'; ins_count++; buffer_replace_bytes(del_count, ins, ins_count); free(ins); } else { buffer_replace_bytes(del_count, "\n", ins_count); } end_change(); // Move after inserted text block_iter_skip_bytes(&view->cursor, ins_count); } void insert_ch(CodePoint ch) { size_t del_count = 0; size_t ins_count = 0; if (ch == '\n') { insert_nl(); return; } char *ins = xmalloc(8); if (view->selection) { // Prepare deleted text (selection) del_count = prepare_selection(view); unselect(); } else if ( ch == '}' && buffer->options.auto_indent && buffer->options.brace_indent ) { BlockIter bi = view->cursor; LineRef curlr; block_iter_bol(&bi); fill_line_ref(&bi, &curlr); if (ws_only(&curlr)) { int width = get_indent_of_matching_brace(); if (width >= 0) { // Replace current (ws only) line with some indent + '}' block_iter_bol(&view->cursor); del_count = curlr.size; if (width) { free(ins); ins = make_indent(width); ins_count = strlen(ins); // '}' will be replace the terminating NUL } } } } // Prepare inserted text if (ch == '\t' && buffer->options.expand_tab) { ins_count = buffer->options.indent_width; memset(ins, ' ', ins_count); } else { u_set_char_raw(ins, &ins_count, ch); } // Record change if (del_count) { begin_change(CHANGE_MERGE_NONE); } else { begin_change(CHANGE_MERGE_INSERT); } buffer_replace_bytes(del_count, ins, ins_count); end_change(); // Move after inserted text block_iter_skip_bytes(&view->cursor, ins_count); free(ins); } static void join_selection(void) { size_t count = prepare_selection(view); size_t len = 0, join = 0; BlockIter bi; CodePoint ch = 0; unselect(); bi = view->cursor; begin_change_chain(); while (count > 0) { if (!len) { view->cursor = bi; } count -= block_iter_next_char(&bi, &ch); if (ch == '\t' || ch == ' ') { len++; } else if (ch == '\n') { len++; join++; } else { if (join) { buffer_replace_bytes(len, " ", 1); // Skip the space we inserted and the char we read last block_iter_next_char(&view->cursor, &ch); block_iter_next_char(&view->cursor, &ch); bi = view->cursor; } len = 0; join = 0; } } // Don't replace last \n that is at end of the selection if (join && ch == '\n') { join--; len--; } if (join) { if (ch == '\n') { // Don't add space to end of line buffer_delete_bytes(len); } else { buffer_replace_bytes(len, " ", 1); } } end_change_chain(); } void join_lines(void) { BlockIter bi = view->cursor; if (view->selection) { join_selection(); return; } if (!block_iter_next_line(&bi)) { return; } if (block_iter_is_eof(&bi)) { return; } BlockIter next = bi; CodePoint u; size_t count = 1; block_iter_prev_char(&bi, &u); while (block_iter_prev_char(&bi, &u)) { if (u != '\t' && u != ' ') { block_iter_next_char(&bi, &u); break; } count++; } while (block_iter_next_char(&next, &u)) { if (u != '\t' && u != ' ') { break; } count++; } view->cursor = bi; if (u == '\n') { buffer_delete_bytes(count); } else { buffer_replace_bytes(count, " ", 1); } } static void shift_right(size_t nr_lines, size_t count) { size_t indent_size; char *indent; indent = alloc_indent(count, &indent_size); size_t i = 0; while (1) { IndentInfo info; LineRef lr; fetch_this_line(&view->cursor, &lr); get_indent_info(lr.line, lr.size, &info); if (info.wsonly) { if (info.bytes) { // Remove indentation buffer_delete_bytes(info.bytes); } } else if (info.sane) { // Insert whitespace buffer_insert_bytes(indent, indent_size); } else { // Replace whole indentation with sane one size_t size; char *buf = alloc_indent(info.level + count, &size); buffer_replace_bytes(info.bytes, buf, size); free(buf); } if (++i == nr_lines) { break; } block_iter_eat_line(&view->cursor); } free(indent); } static void shift_left(size_t nr_lines, size_t count) { size_t i = 0; while (1) { IndentInfo info; LineRef lr; fetch_this_line(&view->cursor, &lr); get_indent_info(lr.line, lr.size, &info); if (info.wsonly) { if (info.bytes) { // Remove indentation buffer_delete_bytes(info.bytes); } } else if (info.level && info.sane) { size_t n = count; if (n > info.level) { n = info.level; } if (use_spaces_for_indent()) { n *= buffer->options.indent_width; } buffer_delete_bytes(n); } else if (info.bytes) { // Replace whole indentation with sane one if (info.level > count) { size_t size; char *buf = alloc_indent(info.level - count, &size); buffer_replace_bytes(info.bytes, buf, size); free(buf); } else { buffer_delete_bytes(info.bytes); } } if (++i == nr_lines) { break; } block_iter_eat_line(&view->cursor); } } static void do_shift_lines(int count, size_t nr_lines) { begin_change_chain(); block_iter_bol(&view->cursor); if (count > 0) { shift_right(nr_lines, count); } else { shift_left(nr_lines, -count); } end_change_chain(); } void shift_lines(int count) { long x = view_get_preferred_x(view) + buffer->options.indent_width * count; if (x < 0) { x = 0; } if (view->selection) { SelectionInfo info; view->selection = SELECT_LINES; init_selection(view, &info); view->cursor = info.si; size_t nr_lines = get_nr_selected_lines(&info); do_shift_lines(count, nr_lines); if (info.swapped) { // Cursor should be at beginning of selection block_iter_bol(&view->cursor); view->sel_so = block_iter_get_offset(&view->cursor); while (--nr_lines) { block_iter_prev_line(&view->cursor); } } else { BlockIter save = view->cursor; while (--nr_lines) { block_iter_prev_line(&view->cursor); } view->sel_so = block_iter_get_offset(&view->cursor); view->cursor = save; } } else { do_shift_lines(count, 1); } move_to_preferred_x(x); } void clear_lines(void) { size_t del_count = 0, ins_count = 0; char *indent = NULL; if (buffer->options.auto_indent) { BlockIter bi = view->cursor; if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { LineRef lr; fill_line_ref(&bi, &lr); indent = get_indent_for_next_line(lr.line, lr.size); } } if (view->selection) { view->selection = SELECT_LINES; del_count = prepare_selection(view); unselect(); // Don't delete last newline if (del_count) { del_count--; } } else { block_iter_eol(&view->cursor); del_count = block_iter_bol(&view->cursor); } if (!indent && !del_count) { return; } if (indent) { ins_count = strlen(indent); } buffer_replace_bytes(del_count, indent, ins_count); free(indent); block_iter_skip_bytes(&view->cursor, ins_count); } void new_line(void) { size_t ins_count = 1; char *ins = NULL; block_iter_eol(&view->cursor); if (buffer->options.auto_indent) { BlockIter bi = view->cursor; if (find_non_empty_line_bwd(&bi)) { LineRef lr; fill_line_ref(&bi, &lr); ins = get_indent_for_next_line(lr.line, lr.size); } } if (ins) { ins_count = strlen(ins); memmove(ins + 1, ins, ins_count); ins[0] = '\n'; ins_count++; buffer_insert_bytes(ins, ins_count); free(ins); } else { buffer_insert_bytes("\n", 1); } block_iter_skip_bytes(&view->cursor, ins_count); } static void add_word(ParagraphFormatter *pf, const char *word, size_t len) { size_t i = 0; size_t word_width = 0; while (i < len) { word_width += u_char_width(u_get_char(word, len, &i)); } if (pf->cur_width && pf->cur_width + 1 + word_width > pf->text_width) { string_add_byte(&pf->buf, '\n'); pf->cur_width = 0; } if (pf->cur_width == 0) { if (pf->indent_len) { string_add_buf(&pf->buf, pf->indent, pf->indent_len); } pf->cur_width = pf->indent_width; } else { string_add_byte(&pf->buf, ' '); pf->cur_width++; } string_add_buf(&pf->buf, word, len); pf->cur_width += word_width; } static bool is_paragraph_separator(const char *line, size_t size) { return regexp_match_nosub("^[ \t]*(/\\*|\\*/)?[ \t]*$", line, size); } static size_t get_indent_width(const char *line, size_t size) { IndentInfo info; get_indent_info(line, size, &info); return info.width; } static bool in_paragraph(const char *line, size_t size, size_t indent_width) { if (get_indent_width(line, size) != indent_width) { return false; } return !is_paragraph_separator(line, size); } static size_t paragraph_size(void) { BlockIter bi = view->cursor; LineRef lr; block_iter_bol(&bi); fill_line_ref(&bi, &lr); if (is_paragraph_separator(lr.line, lr.size)) { // Not in paragraph return 0; } size_t indent_width = get_indent_width(lr.line, lr.size); // Go to beginning of paragraph while (block_iter_prev_line(&bi)) { fill_line_ref(&bi, &lr); if (!in_paragraph(lr.line, lr.size, indent_width)) { block_iter_eat_line(&bi); break; } } view->cursor = bi; // Get size of paragraph size_t size = 0; do { size_t bytes = block_iter_eat_line(&bi); if (!bytes) { break; } size += bytes; fill_line_ref(&bi, &lr); } while (in_paragraph(lr.line, lr.size, indent_width)); return size; } void format_paragraph(size_t text_width) { size_t len; if (view->selection) { view->selection = SELECT_LINES; len = prepare_selection(view); } else { len = paragraph_size(); } if (!len) { return; } char *sel = block_iter_get_bytes(&view->cursor, len); size_t indent_width = get_indent_width(sel, len); char *indent = make_indent(indent_width); ParagraphFormatter pf = { .buf = STRING_INIT, .indent = indent, .indent_len = indent ? strlen(indent) : 0, .indent_width = indent_width, .cur_width = 0, .text_width = text_width }; size_t i = 0; while (1) { size_t start, tmp; while (i < len) { tmp = i; if (!u_is_breakable_whitespace(u_get_char(sel, len, &tmp))) { break; } i = tmp; } if (i == len) { break; } start = i; while (i < len) { tmp = i; if (u_is_breakable_whitespace(u_get_char(sel, len, &tmp))) { break; } i = tmp; } add_word(&pf, sel + start, i - start); } if (pf.buf.len) { string_add_byte(&pf.buf, '\n'); } buffer_replace_bytes(len, pf.buf.buffer, pf.buf.len); if (pf.buf.len) { block_iter_skip_bytes(&view->cursor, pf.buf.len - 1); } string_free(&pf.buf); free(pf.indent); free(sel); unselect(); } void change_case(int mode) { bool was_selecting = false; bool move = true; size_t text_len, i; char *src; String dst = STRING_INIT; if (view->selection) { SelectionInfo info; init_selection(view, &info); view->cursor = info.si; text_len = info.eo - info.so; unselect(); was_selecting = true; move = !info.swapped; } else { CodePoint u; if (!block_iter_get_char(&view->cursor, &u)) { return; } text_len = u_char_size(u); } src = block_iter_get_bytes(&view->cursor, text_len); i = 0; while (i < text_len) { CodePoint u = u_get_char(src, text_len, &i); switch (mode) { case 't': if (iswupper(u)) { u = towlower(u); } else { u = towupper(u); } break; case 'l': u = towlower(u); break; case 'u': u = towupper(u); break; } string_add_ch(&dst, u); } buffer_replace_bytes(text_len, dst.buffer, dst.len); free(src); if (move && dst.len > 0) { if (was_selecting) { // Move cursor back to where it was size_t idx = dst.len; u_prev_char(dst.buffer, &idx); block_iter_skip_bytes(&view->cursor, idx); } else { block_iter_skip_bytes(&view->cursor, dst.len); } } string_free(&dst); } dte-1.9.1/src/edit.h000066400000000000000000000010361354415143300141660ustar00rootroot00000000000000#ifndef EDIT_H #define EDIT_H #include #include #include "util/unicode.h" void select_block(void); void unselect(void); void cut(size_t len, bool is_lines); void copy(size_t len, bool is_lines); void insert_text(const char *text, size_t size); void paste(bool at_cursor); void delete_ch(void); void erase(void); void insert_ch(CodePoint ch); void join_lines(void); void shift_lines(int count); void clear_lines(void); void new_line(void); void format_paragraph(size_t text_width); void change_case(int mode); #endif dte-1.9.1/src/editor.c000066400000000000000000000263551354415143300145350ustar00rootroot00000000000000#include #include #include #include #include #include "editor.h" #include "buffer.h" #include "command.h" #include "config.h" #include "debug.h" #include "error.h" #include "mode.h" #include "screen.h" #include "search.h" #include "terminal/input.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/ascii.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "view.h" #include "window.h" #include "../build/version.h" static volatile sig_atomic_t terminal_resized; static void resize(void); static void ui_end(void); EditorState editor = { .status = EDITOR_INITIALIZING, .input_mode = INPUT_NORMAL, .child_controls_terminal = false, .everything_changed = false, .search_history = PTR_ARRAY_INIT, .command_history = PTR_ARRAY_INIT, .version = version, .cmdline_x = 0, .resize = resize, .ui_end = ui_end, .cmdline = { .buf = STRING_INIT, .pos = 0, .search_pos = -1, .search_text = NULL }, .options = { .auto_indent = true, .detect_indent = 0, .editorconfig = false, .emulate_tab = false, .expand_tab = false, .file_history = true, .indent_width = 8, .syntax = true, .tab_width = 8, .text_width = 72, .ws_error = WSE_SPECIAL, // Global-only options .case_sensitive_search = CSS_TRUE, .display_invisible = false, .display_special = false, .esc_timeout = 100, .lock_files = true, .newline = NEWLINE_UNIX, .scroll_margin = 0, .set_window_title = false, .show_line_numbers = false, .statusline_left = NULL, .statusline_right = NULL, .tab_bar = TAB_BAR_HORIZONTAL, .tab_bar_max_components = 0, .tab_bar_width = 25, .filesize_limit = 250, }, .mode_ops = { [INPUT_NORMAL] = &normal_mode_ops, [INPUT_COMMAND] = &command_mode_ops, [INPUT_SEARCH] = &search_mode_ops, [INPUT_GIT_OPEN] = &git_open_ops } }; void init_editor_state(void) { const char *const pager = getenv("PAGER"); const char *const home = getenv("HOME"); const char *const dte_home = getenv("DTE_HOME"); editor.pager = xstrdup(pager ? pager : "less"); editor.home_dir = xstrdup(home ? home : ""); if (dte_home) { editor.user_config_dir = xstrdup(dte_home); } else { editor.user_config_dir = xasprintf("%s/.dte", editor.home_dir); } setlocale(LC_CTYPE, ""); editor.charset = encoding_from_name(nl_langinfo(CODESET)); editor.term_utf8 = (editor.charset.type == UTF8); editor.options.statusline_left = " %f%s%m%r%s%M"; editor.options.statusline_right = " %y,%X %u %E %n %t %p "; } static void sanity_check(void) { #if DEBUG >= 1 View *v = window->view; Block *blk; block_for_each(blk, &v->buffer->blocks) { if (blk == v->cursor.blk) { BUG_ON(v->cursor.offset > v->cursor.blk->size); return; } } BUG("cursor not seen"); #endif } void any_key(void) { fputs("Press any key to continue\r\n", stderr); KeyCode key; while (!term_read_key(&key)) { ; } if (key == KEY_PASTE) { term_discard_paste(); } } static void update_window_full(Window *w) { View *v = w->view; view_update_cursor_x(v); view_update_cursor_y(v); view_update(v); print_tabbar(w); if (editor.options.show_line_numbers) { update_line_numbers(w, true); } update_range(v, v->vy, v->vy + w->edit_h); update_status_line(w); } static void restore_cursor(void) { View *v = window->view; switch (editor.input_mode) { case INPUT_NORMAL: terminal.move_cursor ( window->edit_x + v->cx_display - v->vx, window->edit_y + v->cy - v->vy ); break; case INPUT_COMMAND: case INPUT_SEARCH: terminal.move_cursor(editor.cmdline_x, terminal.height - 1); break; case INPUT_GIT_OPEN: break; } } static void start_update(void) { term_hide_cursor(); } static void clear_update_tabbar(Window *w) { w->update_tabbar = false; } static void end_update(void) { restore_cursor(); term_show_cursor(); term_output_flush(); window->view->buffer->changed_line_min = INT_MAX; window->view->buffer->changed_line_max = -1; for_each_window(clear_update_tabbar); } static void update_all_windows(void) { update_window_sizes(); for_each_window(update_window_full); update_separators(); } static void update_window(Window *w) { if (w->update_tabbar) { print_tabbar(w); } View *v = w->view; if (editor.options.show_line_numbers) { // Force updating lines numbers if all lines changed update_line_numbers(w, v->buffer->changed_line_max == INT_MAX); } long y1 = v->buffer->changed_line_min; long y2 = v->buffer->changed_line_max; if (y1 < v->vy) { y1 = v->vy; } if (y2 > v->vy + w->edit_h - 1) { y2 = v->vy + w->edit_h - 1; } update_range(v, y1, y2 + 1); update_status_line(w); } // Update all visible views containing this buffer static void update_buffer_windows(const Buffer *const b) { for (size_t i = 0, n = b->views.count; i < n; i++) { View *v = b->views.ptrs[i]; if (v->window->view == v) { // Visible view if (v != window->view) { // Restore cursor v->cursor.blk = BLOCK(v->buffer->blocks.next); block_iter_goto_offset(&v->cursor, v->saved_cursor_offset); // These have already been updated for current view view_update_cursor_x(v); view_update_cursor_y(v); view_update(v); } update_window(v->window); } } } void normal_update(void) { start_update(); update_term_title(window->view->buffer); update_all_windows(); update_command_line(); end_update(); } void handle_sigwinch(int UNUSED_ARG(signum)) { terminal_resized = true; } static void resize(void) { terminal_resized = false; update_screen_size(); // Turn keypad on (makes cursor keys work) terminal.put_control_code(terminal.control_codes.keypad_on); // Use alternate buffer if possible terminal.put_control_code(terminal.control_codes.cup_mode_on); editor.mode_ops[editor.input_mode]->update(); } static void ui_end(void) { terminal.put_control_code(terminal.control_codes.reset_colors); terminal.put_control_code(terminal.control_codes.reset_attrs); terminal.clear_screen(); terminal.move_cursor(0, terminal.height - 1); term_show_cursor(); terminal.put_control_code(terminal.control_codes.cup_mode_off); terminal.put_control_code(terminal.control_codes.keypad_off); term_output_flush(); terminal.cooked(); } void suspend(void) { if (getpid() == getsid(0)) { // Session leader can't suspend return; } if ( !editor.child_controls_terminal && editor.status != EDITOR_INITIALIZING ) { ui_end(); } kill(0, SIGSTOP); } char *editor_file(const char *name) { return xasprintf("%s/%s", editor.user_config_dir, name); } char get_confirmation(const char *choices, const char *format, ...) { char buf[4096]; va_list ap; va_start(ap, format); vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); size_t pos = strlen(buf); buf[pos++] = ' '; buf[pos++] = '['; unsigned char def = 0; for (size_t i = 0, n = strlen(choices); i < n; i++) { if (ascii_isupper(choices[i])) { def = ascii_tolower(choices[i]); } buf[pos++] = choices[i]; buf[pos++] = '/'; } buf[pos - 1] = ']'; buf[pos] = '\0'; // update_windows() assumes these have been called for the current view View *v = window->view; view_update_cursor_x(v); view_update_cursor_y(v); view_update(v); // Set changed_line_min and changed_line_max before calling update_range() mark_all_lines_changed(v->buffer); start_update(); update_term_title(v->buffer); update_buffer_windows(v->buffer); show_message(buf, false); end_update(); KeyCode key; while (1) { if (term_read_key(&key)) { if (key == KEY_PASTE) { term_discard_paste(); continue; } if (key == CTRL('C') || key == CTRL('G') || key == CTRL('[')) { key = 0; break; } if (key == KEY_ENTER && def) { key = def; break; } if (key > 127) { continue; } key = ascii_tolower(key); if (strchr(choices, key)) { break; } if (key == def) { break; } } else if (terminal_resized) { resize(); } } return key; } typedef struct { bool is_modified; unsigned int id; long cy; long vx; long vy; } ScreenState; static void update_screen(const ScreenState *const s) { if (editor.everything_changed) { editor.mode_ops[editor.input_mode]->update(); editor.everything_changed = false; return; } View *v = window->view; view_update_cursor_x(v); view_update_cursor_y(v); view_update(v); Buffer *b = v->buffer; if (s->id == b->id) { if (s->vx != v->vx || s->vy != v->vy) { mark_all_lines_changed(b); } else { // Because of trailing whitespace highlighting and // highlighting current line in different color // the lines cy (old cursor y) and v->cy need // to be updated. // // Always update at least current line. buffer_mark_lines_changed(b, s->cy, v->cy); } if (s->is_modified != buffer_modified(b)) { mark_buffer_tabbars_changed(b); } } else { window->update_tabbar = true; mark_all_lines_changed(b); } start_update(); if (window->update_tabbar) { update_term_title(b); } update_buffer_windows(b); update_command_line(); end_update(); } void main_loop(void) { while (editor.status == EDITOR_RUNNING) { if (terminal_resized) { resize(); } KeyCode key; if (!term_read_key(&key)) { continue; } clear_error(); if (editor.input_mode == INPUT_GIT_OPEN) { editor.mode_ops[editor.input_mode]->keypress(key); editor.mode_ops[editor.input_mode]->update(); } else { const View *v = window->view; const ScreenState s = { .is_modified = buffer_modified(v->buffer), .id = v->buffer->id, .cy = v->cy, .vx = v->vx, .vy = v->vy }; editor.mode_ops[editor.input_mode]->keypress(key); sanity_check(); if (editor.input_mode == INPUT_GIT_OPEN) { editor.mode_ops[INPUT_GIT_OPEN]->update(); } else { update_screen(&s); } } } } dte-1.9.1/src/editor.h000066400000000000000000000026541354415143300145360ustar00rootroot00000000000000#ifndef EDITOR_H #define EDITOR_H #include #include "cmdline.h" #include "mode.h" #include "options.h" #include "encoding/encoding.h" #include "terminal/color.h" #include "util/macros.h" #include "util/ptr-array.h" typedef enum { EDITOR_INITIALIZING, EDITOR_RUNNING, EDITOR_EXITING, } EditorStatus; typedef enum { INPUT_NORMAL, INPUT_COMMAND, INPUT_SEARCH, INPUT_GIT_OPEN, } InputMode; typedef struct { EditorStatus status; const EditorModeOps *mode_ops[4]; InputMode input_mode; CommandLine cmdline; GlobalOptions options; const char *home_dir; const char *user_config_dir; Encoding charset; const char *pager; bool child_controls_terminal; bool everything_changed; bool term_utf8; size_t cmdline_x; PointerArray search_history; PointerArray command_history; const char *const version; void (*resize)(void); void (*ui_end)(void); } EditorState; extern EditorState editor; static inline void mark_everything_changed(void) { editor.everything_changed = true; } static inline void set_input_mode(InputMode mode) { editor.input_mode = mode; } void init_editor_state(void); char *editor_file(const char *name) XSTRDUP; char get_confirmation(const char *choices, const char *format, ...) PRINTF(2); void any_key(void); void normal_update(void); void handle_sigwinch(int signum); void suspend(void); void main_loop(void); #endif dte-1.9.1/src/editorconfig/000077500000000000000000000000001354415143300155445ustar00rootroot00000000000000dte-1.9.1/src/editorconfig/editorconfig.c000066400000000000000000000144051354415143300203700ustar00rootroot00000000000000#include #include #include #include #include "editorconfig.h" #include "ini.h" #include "match.h" #include "../debug.h" #include "../util/ascii.h" #include "../util/path.h" #include "../util/string.h" #include "../util/string-view.h" #include "../util/strtonum.h" typedef struct { const char *const pathname; StringView config_file_dir; EditorConfigOptions options; bool match; } UserData; typedef enum { EC_INDENT_STYLE, EC_INDENT_SIZE, EC_TAB_WIDTH, EC_MAX_LINE_LENGTH, EC_UNKNOWN_PROPERTY, } PropertyType; #define CMP(s, val) if (!memcmp(name->data, s, STRLEN(s))) return val; break static PropertyType lookup_property(const StringView *name) { switch (name->length) { case 9: CMP("tab_width", EC_TAB_WIDTH); case 11: CMP("indent_size", EC_INDENT_SIZE); case 12: CMP("indent_style", EC_INDENT_STYLE); case 15: CMP("max_line_length", EC_MAX_LINE_LENGTH); } return EC_UNKNOWN_PROPERTY; } static void editorconfig_option_set ( EditorConfigOptions *options, const StringView *name, const StringView *val ) { unsigned int n = 0; switch (lookup_property(name)) { case EC_INDENT_STYLE: if (string_view_equal_literal_icase(val, "space")) { options->indent_style = INDENT_STYLE_SPACE; } else if (string_view_equal_literal_icase(val, "tab")) { options->indent_style = INDENT_STYLE_TAB; } else { options->indent_style = INDENT_STYLE_UNSPECIFIED; } break; case EC_INDENT_SIZE: if (string_view_equal_literal_icase(val, "tab")) { options->indent_size_is_tab = true; options->indent_size = 0; } else { buf_parse_uint(val->data, val->length, &n); // If buf_parse_uint() failed, n is zero, which is deliberately // used to "reset" the option due to an invalid value options->indent_size = n; options->indent_size_is_tab = false; } break; case EC_TAB_WIDTH: buf_parse_uint(val->data, val->length, &n); options->tab_width = n; break; case EC_MAX_LINE_LENGTH: buf_parse_uint(val->data, val->length, &n); options->max_line_length = n; break; case EC_UNKNOWN_PROPERTY: break; } } static int ini_handler(const IniData *data, void *ud) { UserData *userdata = ud; if (data->section.length == 0) { if ( string_view_equal_literal_icase(&data->name, "root") && string_view_equal_literal_icase(&data->value, "true") ) { // root=true, clear all previous values userdata->options = editorconfig_options_init(); } return 1; } if (data->name_idx == 0) { // If name_idx is zero, it indicates that the name/value pair is // the first in the section and therefore requires a new pattern // to be built and tested for a match const StringView ecdir = userdata->config_file_dir; String pattern = string_new(ecdir.length + data->section.length + 16); // Escape editorconfig special chars in path for (size_t i = 0, n = ecdir.length; i < n; i++) { const char ch = ecdir.data[i]; switch (ch) { case '*': case ',': case '-': case '?': case '[': case '\\': case ']': case '{': case '}': string_add_byte(&pattern, '\\'); // Fallthrough default: string_add_byte(&pattern, ch); } } if (!string_view_memchr(&data->section, '/')) { // No slash in pattern, append "**/" string_add_literal(&pattern, "**/"); } else if (data->section.data[0] != '/') { // Pattern contains at least one slash but not at the start, add one string_add_byte(&pattern, '/'); } string_add_string_view(&pattern, &data->section); userdata->match = ec_pattern_match ( pattern.buffer, pattern.len, userdata->pathname ); string_free(&pattern); } else { // Otherwise, the section is the same as was passed in the last // callback invocation and the value of userdata->match can // just be reused } if (userdata->match) { editorconfig_option_set(&userdata->options, &data->name, &data->value); } return 1; } int get_editorconfig_options(const char *pathname, EditorConfigOptions *opts) { BUG_ON(!path_is_absolute(pathname)); UserData data = { .pathname = pathname, .config_file_dir = STRING_VIEW_INIT, .match = false }; static const char ecfilename[16] = "/.editorconfig"; char buf[8192]; memcpy(buf, ecfilename, sizeof ecfilename); const char *ptr = pathname + 1; size_t dir_len = 1; // Iterate up directory tree, looking for ".editorconfig" at each level while (1) { data.config_file_dir = string_view(buf, dir_len); int err_num = ini_parse(buf, ini_handler, &data); if (err_num > 0) { return err_num; } const char *const slash = strchr(ptr, '/'); if (slash == NULL) { break; } dir_len = slash - pathname; memcpy(buf, pathname, dir_len); memcpy(buf + dir_len, ecfilename, sizeof ecfilename); ptr = slash + 1; } // Set indent_size to "tab" if indent_size is not specified and // indent_style is set to "tab". if ( data.options.indent_size == 0 && data.options.indent_style == INDENT_STYLE_TAB ) { data.options.indent_size_is_tab = true; } // Set indent_size to tab_width if indent_size is "tab" and // tab_width is specified. if (data.options.indent_size_is_tab && data.options.tab_width > 0) { data.options.indent_size = data.options.tab_width; } // Set tab_width to indent_size if indent_size is specified as // something other than "tab" and tab_width is unspecified if ( data.options.indent_size != 0 && data.options.tab_width == 0 && !data.options.indent_size_is_tab ) { data.options.tab_width = data.options.indent_size; } *opts = data.options; return 0; } dte-1.9.1/src/editorconfig/editorconfig.h000066400000000000000000000014411354415143300203710ustar00rootroot00000000000000#ifndef EDITORCONFIG_H #define EDITORCONFIG_H #include #include "../util/macros.h" typedef enum { INDENT_STYLE_UNSPECIFIED, INDENT_STYLE_TAB, INDENT_STYLE_SPACE, } EditorConfigIndentStyle; typedef struct { unsigned int indent_size; unsigned int tab_width; unsigned int max_line_length; EditorConfigIndentStyle indent_style; bool indent_size_is_tab; } EditorConfigOptions; static inline EditorConfigOptions editorconfig_options_init(void) { return (EditorConfigOptions) { .indent_size = 0, .tab_width = 0, .max_line_length = 0, .indent_style = INDENT_STYLE_UNSPECIFIED, .indent_size_is_tab = false }; } NONNULL_ARG(1) int get_editorconfig_options(const char *pathname, EditorConfigOptions *opts); #endif dte-1.9.1/src/editorconfig/ini.c000066400000000000000000000057031354415143300164740ustar00rootroot00000000000000#include #include #include "ini.h" #include "../debug.h" #include "../util/ascii.h" #include "../util/readfile.h" static char *trim_left(char *str) { while (ascii_isspace(*str)) { str++; } return str; } static void strip_trailing_comments_and_whitespace(StringView *line) { const char *str = line->data; size_t len = line->length; // Remove inline comments char prev_char = '\0'; for (size_t i = len; i > 0; i--) { if (ascii_isspace(str[i]) && (prev_char == '#' || prev_char == ';')) { len = i; } prev_char = str[i]; } // Trim trailing whitespace const char *ptr = str + len - 1; while (ptr > str && ascii_isspace(*ptr--)) { len--; } line->length = len; } UNITTEST { StringView tmp = STRING_VIEW(" \t key = val # inline comment "); string_view_trim_left(&tmp); strip_trailing_comments_and_whitespace(&tmp); BUG_ON(!string_view_equal_literal(&tmp, "key = val")); } int ini_parse(const char *filename, IniCallback callback, void *userdata) { char *buf; const ssize_t ssize = read_file(filename, &buf); if (ssize < 0) { return -1; } const size_t size = ssize; size_t pos = 0; if (size >= 3 && memcmp(buf, "\xEF\xBB\xBF", 3) == 0) { // Skip past UTF-8 BOM pos += 3; } StringView section = STRING_VIEW_INIT; unsigned int nameidx = 0; while (pos < size) { StringView line = buf_slice_next_line(buf, &pos, size); string_view_trim_left(&line); if (line.length < 2) { continue; } switch (line.data[0]) { case ';': case '#': continue; case '[': strip_trailing_comments_and_whitespace(&line); if (line.length > 1 && line.data[line.length - 1] == ']') { section = string_view(line.data + 1, line.length - 2); nameidx = 0; } continue; } strip_trailing_comments_and_whitespace(&line); char *delim = string_view_memchr(&line, '='); if (delim) { const size_t before_delim_len = delim - line.data; size_t name_len = before_delim_len; while (name_len > 0 && ascii_isblank(line.data[name_len - 1])) { name_len--; } if (name_len == 0) { continue; } char *after_delim = delim + 1; char *value = trim_left(after_delim); size_t diff = value - after_delim; size_t value_len = line.length - before_delim_len - 1 - diff; const IniData data = { .section = section, .name = string_view(line.data, name_len), .value = string_view(value, value_len), .name_idx = nameidx++, }; callback(&data, userdata); } } free(buf); return 0; } dte-1.9.1/src/editorconfig/ini.h000066400000000000000000000005501354415143300164740ustar00rootroot00000000000000#ifndef EDITORCONFIG_INI_H #define EDITORCONFIG_INI_H #include "../util/string-view.h" typedef struct { StringView section; StringView name; StringView value; unsigned int name_idx; } IniData; typedef int (*IniCallback)(const IniData *data, void *userdata); int ini_parse(const char *filename, IniCallback handler, void *userdata); #endif dte-1.9.1/src/editorconfig/match.c000066400000000000000000000140541354415143300170100ustar00rootroot00000000000000#include #include #include #include #include "match.h" #include "../debug.h" #include "../util/str-util.h" #include "../util/string.h" static size_t get_last_paired_brace_index(const char *str, size_t len) { size_t last_paired_index = 0; size_t open_braces = 0; for (size_t i = 0; i < len; i++) { const char ch = str[i]; switch (ch) { case '\\': i++; break; case '{': open_braces++; if (open_braces >= 32) { // If nesting goes too deep, just return 0 and let // ec_pattern_match() escape all braces return 0; } break; case '}': if (open_braces != 0) { last_paired_index = i; open_braces--; } break; } } if (open_braces == 0) { return last_paired_index; } else { // If there are unclosed braces, just return 0 return 0; } } static size_t handle_bracket_expression(const char *pat, size_t len, String *buf) { BUG_ON(len == 0); if (len == 1) { string_add_literal(buf, "\\["); return 0; } // Skip past opening bracket pat++; bool closed = false; size_t i = 0; while (i < len) { const char ch = pat[i++]; if (ch == ']') { closed = true; break; } } if (!closed) { string_add_literal(buf, "\\["); return 0; } // TODO: interpret characters according to editorconfig instead // of just copying the bracket expression to be interpretted as // regex string_add_byte(buf, '['); if (pat[0] == '!') { string_add_byte(buf, '^'); string_add_buf(buf, pat + 1, i - 1); } else { string_add_buf(buf, pat, i); } return i; } // Skips past empty alternates in brace groups and returns the number // of bytes (commas) skipped static size_t skip_empty_alternates(const char *str, size_t len) { size_t i = 1; while (i < len && str[i] == ',') { i++; } return i - 1; } bool ec_pattern_match(const char *pattern, size_t pattern_len, const char *path) { String buf = STRING_INIT; size_t brace_level = 0; size_t last_paired_brace_index = get_last_paired_brace_index(pattern, pattern_len); bool brace_group_has_empty_alternate[32]; MEMZERO(&brace_group_has_empty_alternate); for (size_t i = 0; i < pattern_len; i++) { char ch = pattern[i]; switch (ch) { case '\\': if (i + 1 < pattern_len) { ch = pattern[++i]; if (is_regex_special_char(ch)) { string_add_byte(&buf, '\\'); } string_add_byte(&buf, ch); } else { string_add_literal(&buf, "\\\\"); } break; case '?': string_add_literal(&buf, "[^/]"); break; case '*': if (i + 1 < pattern_len && pattern[i + 1] == '*') { string_add_literal(&buf, ".*"); i++; } else { string_add_literal(&buf, "[^/]*"); } break; case '[': // The entire bracket expression is handled in a separate // loop because the POSIX regex escaping rules are different // in that context. i += handle_bracket_expression(pattern + i, pattern_len - i, &buf); break; case '{': { if (i >= last_paired_brace_index) { string_add_literal(&buf, "\\{"); break; } brace_level++; size_t skip = skip_empty_alternates(pattern + i, pattern_len - i); if (skip > 0) { i += skip; brace_group_has_empty_alternate[brace_level] = true; } if (i + 1 < pattern_len && pattern[i + 1] == '}') { // If brace group contains only empty alternates, emit nothing brace_group_has_empty_alternate[brace_level] = false; i++; brace_level--; } else { string_add_byte(&buf, '('); } break; } case '}': if (i > last_paired_brace_index || brace_level == 0) { goto add_byte; } string_add_byte(&buf, ')'); if (brace_group_has_empty_alternate[brace_level]) { string_add_byte(&buf, '?'); } brace_group_has_empty_alternate[brace_level] = false; brace_level--; break; case ',': { if (i >= last_paired_brace_index || brace_level == 0) { goto add_byte; } size_t skip = skip_empty_alternates(pattern + i, pattern_len - i); if (skip > 0) { i += skip; brace_group_has_empty_alternate[brace_level] = true; } if (i + 1 < pattern_len && pattern[i + 1] == '}') { brace_group_has_empty_alternate[brace_level] = true; } else { string_add_byte(&buf, '|'); } break; } case '/': if (i + 3 < pattern_len && memcmp(pattern + i, "/**/", 4) == 0) { string_add_literal(&buf, "(/|/.*/)"); i += 3; break; } goto add_byte; case '.': case '(': case ')': case '|': case '+': string_add_byte(&buf, '\\'); // Fallthrough default: add_byte: string_add_byte(&buf, ch); } } string_add_byte(&buf, '$'); char *regex_pattern = string_steal_cstring(&buf); regex_t re; bool compiled = !regcomp(&re, regex_pattern, REG_EXTENDED | REG_NOSUB); free(regex_pattern); if (!compiled) { return false; } int res = regexec(&re, path, 0, NULL, 0); regfree(&re); return res == 0; } dte-1.9.1/src/editorconfig/match.h000066400000000000000000000003201354415143300170040ustar00rootroot00000000000000#ifndef EDITORCONFIG_MATCH_H #define EDITORCONFIG_MATCH_H #include #include "../util/macros.h" NONNULL_ARGS bool ec_pattern_match(const char *pattern, size_t pat_len, const char *path); #endif dte-1.9.1/src/encoding/000077500000000000000000000000001354415143300146565ustar00rootroot00000000000000dte-1.9.1/src/encoding/bom.c000066400000000000000000000017151354415143300156030ustar00rootroot00000000000000#include #include "bom.h" #include "../debug.h" #include "../util/macros.h" static const ByteOrderMark boms[NR_ENCODING_TYPES] = { [UTF8] = {{0xef, 0xbb, 0xbf}, 3}, [UTF16BE] = {{0xfe, 0xff}, 2}, [UTF16LE] = {{0xff, 0xfe}, 2}, [UTF32BE] = {{0x00, 0x00, 0xfe, 0xff}, 4}, [UTF32LE] = {{0xff, 0xfe, 0x00, 0x00}, 4}, }; EncodingType detect_encoding_from_bom(const unsigned char *buf, size_t size) { // Iterate array backwards to ensure UTF32LE is checked before UTF16LE for (int i = NR_ENCODING_TYPES - 1; i >= 0; i--) { const unsigned int bom_len = boms[i].len; if (bom_len > 0 && size >= bom_len && !memcmp(buf, boms[i].bytes, bom_len)) { return (EncodingType) i; } } return UNKNOWN_ENCODING; } const ByteOrderMark *get_bom_for_encoding(EncodingType encoding) { BUG_ON(encoding >= NR_ENCODING_TYPES); const ByteOrderMark *bom = &boms[encoding]; return bom->len ? bom : NULL; } dte-1.9.1/src/encoding/bom.h000066400000000000000000000005151354415143300156050ustar00rootroot00000000000000#ifndef ENCODING_BOM_H #define ENCODING_BOM_H #include #include "encoding.h" typedef struct { const unsigned char bytes[4]; unsigned int len; } ByteOrderMark; const ByteOrderMark *get_bom_for_encoding(EncodingType encoding); EncodingType detect_encoding_from_bom(const unsigned char *buf, size_t size); #endif dte-1.9.1/src/encoding/convert.c000066400000000000000000000201211354415143300164760ustar00rootroot00000000000000#include #include "convert.h" #include "../debug.h" #include "../util/macros.h" #ifndef ICONV_DISABLE #include #include #include #include #include "encoding.h" #include "../util/ascii.h" #include "../util/str-util.h" #include "../util/utf8.h" #include "../util/xmalloc.h" static unsigned char replacement[2] = "\xc2\xbf"; // U+00BF struct cconv { iconv_t cd; char *obuf; size_t osize; size_t opos; size_t consumed; size_t errors; // Temporary input buffer char tbuf[16]; size_t tcount; // Replacement character 0xBF (inverted question mark) char rbuf[4]; size_t rcount; // Input character size in bytes. zero for UTF-8. size_t char_size; }; static struct cconv *create(iconv_t cd) { struct cconv *c = xnew0(struct cconv, 1); c->cd = cd; c->osize = 8192; c->obuf = xmalloc(c->osize); return c; } static size_t encoding_char_size(const char *encoding) { if (str_has_prefix(encoding, "UTF-16")) { return 2; } if (str_has_prefix(encoding, "UTF-32")) { return 4; } return 1; } static size_t iconv_wrapper ( iconv_t cd, char **restrict inbuf, size_t *restrict inbytesleft, char **restrict outbuf, size_t *restrict outbytesleft ) { // POSIX defines the second parameter of iconv(3) as "char **restrict" // but NetBSD declares it as "const char **restrict". #ifdef __NetBSD__ #if HAS_WARNING("-Wincompatible-pointer-types-discards-qualifiers") IGNORE_WARNING("-Wincompatible-pointer-types-discards-qualifiers") #else IGNORE_WARNING("-Wincompatible-pointer-types") #endif #endif return iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft); #ifdef __NetBSD__ UNIGNORE_WARNINGS #endif } static void encode_replacement(struct cconv *c) { char *ib = replacement; char *ob = c->rbuf; size_t ic = sizeof(replacement); size_t oc = sizeof(c->rbuf); size_t rc = iconv_wrapper(c->cd, &ib, &ic, &ob, &oc); if (rc == (size_t)-1) { c->rbuf[0] = '\xbf'; c->rcount = 1; } else { c->rcount = ob - c->rbuf; } } static void resize_obuf(struct cconv *c) { c->osize *= 2; xrenew(c->obuf, c->osize); } static void add_replacement(struct cconv *c) { if (c->osize - c->opos < 4) { resize_obuf(c); } memcpy(c->obuf + c->opos, c->rbuf, c->rcount); c->opos += c->rcount; } static size_t handle_invalid(struct cconv *c, const char *buf, size_t count) { d_print("%zu %zu\n", c->char_size, count); add_replacement(c); if (c->char_size == 0) { // Converting from UTF-8 size_t idx = 0; CodePoint u = u_get_char(buf, count, &idx); d_print("U+%04" PRIX32 "\n", u); return idx; } if (c->char_size > count) { // wtf return 1; } return c->char_size; } static int xiconv(struct cconv *c, char **ib, size_t *ic) { while (1) { char *ob = c->obuf + c->opos; size_t oc = c->osize - c->opos; size_t rc = iconv_wrapper(c->cd, ib, ic, &ob, &oc); c->opos = ob - c->obuf; if (rc == (size_t)-1) { switch (errno) { case EILSEQ: c->errors++; // Reset iconv(c->cd, NULL, NULL, NULL, NULL); return errno; case EINVAL: return errno; case E2BIG: resize_obuf(c); continue; default: BUG("iconv: %s", strerror(errno)); } } else { c->errors += rc; } return 0; } } static size_t convert_incomplete(struct cconv *c, const char *input, size_t len) { size_t ic, ipos = 0; char *ib; while (c->tcount < sizeof(c->tbuf) && ipos < len) { size_t skip; c->tbuf[c->tcount++] = input[ipos++]; ib = c->tbuf; ic = c->tcount; int rc = xiconv(c, &ib, &ic); if (ic > 0) { memmove(c->tbuf, ib, ic); } c->tcount = ic; switch (rc) { case EINVAL: // Incomplete character at end of input buffer. // Try again with more input data. continue; case EILSEQ: // Invalid multibyte sequence skip = handle_invalid(c, c->tbuf, c->tcount); c->tcount -= skip; if (c->tcount > 0) { d_print("tcount=%zu, skip=%zu\n", c->tcount, skip); memmove(c->tbuf, c->tbuf + skip, c->tcount); continue; } return ipos; } break; } d_print("%zu %zu\n", ipos, c->tcount); return ipos; } void cconv_process(struct cconv *c, const char *input, size_t len) { if (c->consumed > 0) { size_t fill = c->opos - c->consumed; memmove(c->obuf, c->obuf + c->consumed, fill); c->opos = fill; c->consumed = 0; } if (c->tcount > 0) { size_t ipos = convert_incomplete(c, input, len); input += ipos; len -= ipos; } char *ib = (char *)input; size_t ic = len; while (ic > 0) { size_t skip; switch (xiconv(c, &ib, &ic)) { case EINVAL: // Incomplete character at end of input buffer. if (ic < sizeof(c->tbuf)) { memcpy(c->tbuf, ib, ic); c->tcount = ic; } else { // FIXME } ic = 0; break; case EILSEQ: // Invalid multibyte sequence. skip = handle_invalid(c, ib, ic); ic -= skip; ib += skip; break; } } } struct cconv *cconv_to_utf8(const char *encoding) { iconv_t cd = iconv_open("UTF-8", encoding); if (cd == (iconv_t)-1) { return NULL; } struct cconv *c = create(cd); memcpy(c->rbuf, replacement, sizeof(replacement)); c->rcount = sizeof(replacement); c->char_size = encoding_char_size(encoding); return c; } struct cconv *cconv_from_utf8(const char *encoding) { iconv_t cd = iconv_open(encoding, "UTF-8"); if (cd == (iconv_t)-1) { return NULL; } struct cconv *c = create(cd); encode_replacement(c); return c; } void cconv_flush(struct cconv *c) { if (c->tcount > 0) { // Replace incomplete character at end of input buffer. d_print("incomplete character at EOF\n"); add_replacement(c); c->tcount = 0; } } size_t cconv_nr_errors(const struct cconv *c) { return c->errors; } char *cconv_consume_line(struct cconv *c, size_t *len) { char *line = c->obuf + c->consumed; char *nl = memchr(line, '\n', c->opos - c->consumed); if (nl == NULL) { *len = 0; return NULL; } size_t n = nl - line + 1; c->consumed += n; *len = n; return line; } char *cconv_consume_all(struct cconv *c, size_t *len) { char *buf = c->obuf + c->consumed; *len = c->opos - c->consumed; c->consumed = c->opos; return buf; } void cconv_free(struct cconv *c) { iconv_close(c->cd); free(c->obuf); free(c); } bool encoding_supported_by_iconv(const char *encoding) { iconv_t cd = iconv_open("UTF-8", encoding); if (cd == (iconv_t) -1) { return false; } iconv_close(cd); return true; } #else // Not using iconv -- replace conversion routines with stubs DISABLE_WARNING("-Wunused-parameter") bool encoding_supported_by_iconv(const char *encoding) { return false; } struct cconv *cconv_to_utf8(const char *encoding) { errno = EILSEQ; return NULL; } struct cconv *cconv_from_utf8(const char *encoding) { errno = EILSEQ; return NULL; } #define FAIL() BUG("unsupported"); fatal_error(__func__, ENOTSUP) void cconv_process(struct cconv *c, const char *input, size_t len) {FAIL();} void cconv_flush(struct cconv *c) {FAIL();} size_t cconv_nr_errors(const struct cconv *c) {FAIL();} char *cconv_consume_line(struct cconv *c, size_t *len) {FAIL();} char *cconv_consume_all(struct cconv *c, size_t *len) {FAIL();} void cconv_free(struct cconv *c) {FAIL();} #endif dte-1.9.1/src/encoding/convert.h000066400000000000000000000011001354415143300164770ustar00rootroot00000000000000#ifndef ENCODING_CONVERT_H #define ENCODING_CONVERT_H #include #include struct cconv; struct cconv *cconv_to_utf8(const char *encoding); struct cconv *cconv_from_utf8(const char *encoding); void cconv_process(struct cconv *c, const char *input, size_t len); void cconv_flush(struct cconv *c); size_t cconv_nr_errors(const struct cconv *c); char *cconv_consume_line(struct cconv *c, size_t *len); char *cconv_consume_all(struct cconv *c, size_t *len); void cconv_free(struct cconv *c); bool encoding_supported_by_iconv(const char *encoding); #endif dte-1.9.1/src/encoding/decoder.c000066400000000000000000000107541354415143300164360ustar00rootroot00000000000000#include #include "decoder.h" #include "convert.h" #include "../editor.h" #include "../util/hashset.h" #include "../util/utf8.h" #include "../util/xmalloc.h" static bool fill(FileDecoder *dec) { size_t icount = dec->isize - dec->ipos; // Smaller than cconv.obuf to make realloc less likely size_t max = 7 * 1024; if (icount > max) { icount = max; } if (dec->ipos == dec->isize) { return false; } cconv_process(dec->cconv, dec->ibuf + dec->ipos, icount); dec->ipos += icount; if (dec->ipos == dec->isize) { // Must be flushed after all input has been fed cconv_flush(dec->cconv); } return true; } static bool decode_and_read_line(FileDecoder *dec, char **linep, size_t *lenp) { char *line; size_t len; while (1) { line = cconv_consume_line(dec->cconv, &len); if (line) { break; } if (!fill(dec)) { break; } } if (line) { // Newline not wanted len--; } else { line = cconv_consume_all(dec->cconv, &len); if (len == 0) { return false; } } *linep = line; *lenp = len; return true; } static bool read_utf8_line(FileDecoder *dec, char **linep, size_t *lenp) { char *line = (char *)dec->ibuf + dec->ipos; const char *nl = memchr(line, '\n', dec->isize - dec->ipos); size_t len; if (nl) { len = nl - line; dec->ipos += len + 1; } else { len = dec->isize - dec->ipos; if (len == 0) { return false; } dec->ipos += len; } *linep = line; *lenp = len; return true; } static int set_encoding(FileDecoder *dec, const char *encoding) { if (strcmp(encoding, "UTF-8") == 0) { dec->read_line = read_utf8_line; } else { dec->cconv = cconv_to_utf8(encoding); if (dec->cconv == NULL) { return -1; } dec->read_line = decode_and_read_line; } dec->encoding = str_intern(encoding); return 0; } static bool detect(FileDecoder *dec, const unsigned char *line, size_t len) { for (size_t i = 0; i < len; i++) { if (line[i] >= 0x80) { size_t idx = i; CodePoint u = u_get_nonascii(line, len, &idx); const char *encoding; if (u_is_unicode(u)) { encoding = "UTF-8"; } else if (editor.term_utf8) { if (dec->isize <= (32 * 1024 * 1024)) { // If locale is UTF-8 but file doesn't contain valid // UTF-8 and is also fairly small, just assume latin1 encoding = "ISO-8859-1"; } else { // Large files are likely binary; just decode as // UTF-8 to avoid costly charset conversion encoding = "UTF-8"; } } else { // Assume encoding is same as locale encoding = editor.charset.name; } if (set_encoding(dec, encoding)) { // FIXME: error message? set_encoding(dec, "UTF-8"); } return true; } } // ASCII return false; } static bool detect_and_read_line(FileDecoder *dec, char **linep, size_t *lenp) { char *line = (char *)dec->ibuf + dec->ipos; const char *nl = memchr(line, '\n', dec->isize - dec->ipos); size_t len; if (nl) { len = nl - line; } else { len = dec->isize - dec->ipos; if (len == 0) { return false; } } if (detect(dec, line, len)) { // Encoding detected return dec->read_line(dec, linep, lenp); } // Only ASCII so far dec->ipos += len; if (nl) { dec->ipos++; } *linep = line; *lenp = len; return true; } FileDecoder *new_file_decoder ( const char *encoding, const unsigned char *buf, size_t size ) { FileDecoder *dec = xnew0(FileDecoder, 1); dec->ibuf = buf; dec->isize = size; dec->read_line = detect_and_read_line; if (encoding) { if (set_encoding(dec, encoding)) { free_file_decoder(dec); return NULL; } } return dec; } void free_file_decoder(FileDecoder *dec) { if (dec->cconv != NULL) { cconv_free(dec->cconv); } free(dec); } bool file_decoder_read_line(FileDecoder *dec, char **linep, size_t *lenp) { return dec->read_line(dec, linep, lenp); } dte-1.9.1/src/encoding/decoder.h000066400000000000000000000010371354415143300164350ustar00rootroot00000000000000#ifndef ENCODING_DECODER_H #define ENCODING_DECODER_H #include #include typedef struct FileDecoder { const char *encoding; const unsigned char *ibuf; ssize_t ipos, isize; struct cconv *cconv; bool (*read_line)(struct FileDecoder *dec, char **linep, size_t *lenp); } FileDecoder; FileDecoder *new_file_decoder(const char *encoding, const unsigned char *buf, size_t size); void free_file_decoder(FileDecoder *dec); bool file_decoder_read_line(FileDecoder *dec, char **line, size_t *len); #endif dte-1.9.1/src/encoding/encoder.c000066400000000000000000000031201354415143300164350ustar00rootroot00000000000000#include #include #include "encoder.h" #include "convert.h" #include "../util/utf8.h" #include "../util/xmalloc.h" #include "../util/xreadwrite.h" FileEncoder *new_file_encoder(const Encoding *encoding, LineEndingType nls, int fd) { FileEncoder *enc = xnew0(FileEncoder, 1); enc->nls = nls; enc->fd = fd; if (encoding->type != UTF8) { enc->cconv = cconv_from_utf8(encoding->name); if (enc->cconv == NULL) { free(enc); return NULL; } } return enc; } void free_file_encoder(FileEncoder *enc) { if (enc->cconv != NULL) { cconv_free(enc->cconv); } free(enc->nbuf); free(enc); } static size_t unix_to_dos ( FileEncoder *enc, const unsigned char *buf, size_t size ) { if (enc->nsize < size * 2) { enc->nsize = size * 2; xrenew(enc->nbuf, enc->nsize); } size_t d = 0; for (size_t s = 0; s < size; s++) { unsigned char ch = buf[s]; if (ch == '\n') { enc->nbuf[d++] = '\r'; } enc->nbuf[d++] = ch; } return d; } // NOTE: buf must contain whole characters! ssize_t file_encoder_write ( FileEncoder *enc, const unsigned char *buf, size_t size ) { if (enc->nls == NEWLINE_DOS) { size = unix_to_dos(enc, buf, size); buf = enc->nbuf; } if (enc->cconv == NULL) { return xwrite(enc->fd, buf, size); } cconv_process(enc->cconv, buf, size); cconv_flush(enc->cconv); buf = cconv_consume_all(enc->cconv, &size); return xwrite(enc->fd, buf, size); } dte-1.9.1/src/encoding/encoder.h000066400000000000000000000011211354415143300164410ustar00rootroot00000000000000#ifndef ENCODING_ENCODER_H #define ENCODING_ENCODER_H #include #include "../util/macros.h" #include "../encoding/encoding.h" typedef enum { NEWLINE_UNIX, NEWLINE_DOS, } LineEndingType; typedef struct { struct cconv *cconv; unsigned char *nbuf; size_t nsize; LineEndingType nls; int fd; } FileEncoder; FileEncoder *new_file_encoder(const Encoding *encoding, LineEndingType nls, int fd) NONNULL_ARGS; void free_file_encoder(FileEncoder *enc); ssize_t file_encoder_write(FileEncoder *enc, const unsigned char *buf, size_t size) NONNULL_ARGS; #endif dte-1.9.1/src/encoding/encoding.c000066400000000000000000000041321354415143300166100ustar00rootroot00000000000000#include #include "encoding.h" #include "../util/ascii.h" #include "../util/hashset.h" #include "../util/xmalloc.h" static const char encoding_names[][16] = { [UTF8] = "UTF-8", [UTF16] = "UTF-16", [UTF16BE] = "UTF-16BE", [UTF16LE] = "UTF-16LE", [UTF32] = "UTF-32", [UTF32BE] = "UTF-32BE", [UTF32LE] = "UTF-32LE", }; static_assert(ARRAY_COUNT(encoding_names) == NR_ENCODING_TYPES - 1); static const struct { const char alias[8]; EncodingType encoding; } encoding_aliases[] = { {"UTF8", UTF8}, {"UTF16", UTF16}, {"UTF16BE", UTF16BE}, {"UTF16LE", UTF16LE}, {"UTF32", UTF32}, {"UTF32BE", UTF32BE}, {"UTF32LE", UTF32LE}, {"UCS2", UTF16}, {"UCS-2", UTF16}, {"UCS-2BE", UTF16BE}, {"UCS-2LE", UTF16LE}, {"UCS4", UTF32}, {"UCS-4", UTF32}, {"UCS-4BE", UTF32BE}, {"UCS-4LE", UTF32LE}, }; EncodingType lookup_encoding(const char *name) { for (size_t i = 0; i < ARRAY_COUNT(encoding_names); i++) { if (ascii_streq_icase(name, encoding_names[i])) { return (EncodingType) i; } } for (size_t i = 0; i < ARRAY_COUNT(encoding_aliases); i++) { if (ascii_streq_icase(name, encoding_aliases[i].alias)) { return encoding_aliases[i].encoding; } } return UNKNOWN_ENCODING; } static const char *encoding_type_to_string(EncodingType type) { if (type < NR_ENCODING_TYPES && type != UNKNOWN_ENCODING) { return str_intern(encoding_names[type]); } return NULL; } Encoding encoding_from_name(const char *name) { const EncodingType type = lookup_encoding(name); const char *normalized_name; if (type == UNKNOWN_ENCODING) { char *upper = xstrdup_toupper(name); normalized_name = str_intern(upper); free(upper); } else { normalized_name = encoding_type_to_string(type); } return (Encoding) { .type = type, .name = normalized_name }; } Encoding encoding_from_type(EncodingType type) { return (Encoding) { .type = type, .name = encoding_type_to_string(type) }; } dte-1.9.1/src/encoding/encoding.h000066400000000000000000000015201354415143300166130ustar00rootroot00000000000000#ifndef ENCODING_ENCODING_H #define ENCODING_ENCODING_H #include "../util/macros.h" typedef enum { UTF8, UTF16, UTF16BE, UTF16LE, UTF32, UTF32BE, UTF32LE, UNKNOWN_ENCODING, NR_ENCODING_TYPES, // This value is used by the "open" command to instruct other // routines that no specific encoding was requested and that // it should be detected instead. It is always replaced by // some other value by the time a file is successfully opened. ENCODING_AUTODETECT } EncodingType; typedef struct { EncodingType type; // An interned encoding name compatible with iconv_open(3) const char *name; } Encoding; Encoding encoding_from_type(EncodingType type); Encoding encoding_from_name(const char *name) NONNULL_ARGS; EncodingType lookup_encoding(const char *name) NONNULL_ARGS; #endif dte-1.9.1/src/env.c000066400000000000000000000042611354415143300140270ustar00rootroot00000000000000#include "env.h" #include "completion.h" #include "debug.h" #include "editor.h" #include "error.h" #include "selection.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "window.h" typedef struct { const char *name; char *(*expand)(void); } BuiltinEnv; static char *expand_dte_home(void) { return xstrdup(editor.user_config_dir); } static char *expand_file(void) { if (editor.status != EDITOR_RUNNING) { return NULL; } const char *filename = buffer->abs_filename; return filename ? xstrdup(filename) : NULL; } static char *expand_filetype(void) { if (editor.status != EDITOR_RUNNING) { return NULL; } return xstrdup(buffer->options.filetype); } static char *expand_lineno(void) { if (editor.status != EDITOR_RUNNING) { return NULL; } return xasprintf("%ld", view->cy + 1); } static char *expand_word(void) { if (editor.status != EDITOR_RUNNING) { return NULL; } size_t size; char *str = view_get_selection(view, &size); if (str != NULL) { xrenew(str, size + 1); str[size] = '\0'; } else { str = view_get_word_under_cursor(view); if (str == NULL) { str = NULL; } } return str; } static char *expand_pkgdatadir(void) { error_msg("The $PKGDATADIR variable was removed in dte v1.4"); return NULL; } static const BuiltinEnv builtin[] = { {"PKGDATADIR", expand_pkgdatadir}, {"DTE_HOME", expand_dte_home}, {"FILE", expand_file}, {"FILETYPE", expand_filetype}, {"LINENO", expand_lineno}, {"WORD", expand_word}, }; void collect_builtin_env(const char *prefix) { for (size_t i = 0; i < ARRAY_COUNT(builtin); i++) { const char *name = builtin[i].name; if (str_has_prefix(name, prefix)) { add_completion(xstrdup(name)); } } } // Returns NULL only if name isn't in builtin array bool expand_builtin_env(const char *name, char **value) { for (size_t i = 0; i < ARRAY_COUNT(builtin); i++) { const BuiltinEnv *be = &builtin[i]; if (streq(be->name, name)) { *value = be->expand(); return true; } } return false; } dte-1.9.1/src/env.h000066400000000000000000000002421354415143300140270ustar00rootroot00000000000000#ifndef ENV_H #define ENV_H #include void collect_builtin_env(const char *prefix); bool expand_builtin_env(const char *name, char **value); #endif dte-1.9.1/src/error.c000066400000000000000000000032321354415143300143650ustar00rootroot00000000000000#include #include "error.h" #include "config.h" #include "editor.h" static char error_buf[256]; static unsigned int nr_errors; static bool msg_is_error; static bool supress_errors; void clear_error(void) { error_buf[0] = '\0'; } void error_msg(const char *format, ...) { if (supress_errors) { return; } int pos = 0; if (config_file) { if (current_command) { pos = snprintf ( error_buf, sizeof(error_buf), "%s:%d: %s: ", config_file, config_line, current_command->name ); } else { pos = snprintf ( error_buf, sizeof(error_buf), "%s:%d: ", config_file, config_line ); } } if (pos >= 0 && pos < (sizeof(error_buf) - 3)) { va_list ap; va_start(ap, format); vsnprintf(error_buf + pos, sizeof(error_buf) - pos, format, ap); va_end(ap); } msg_is_error = true; nr_errors++; if (editor.status != EDITOR_RUNNING) { fputs(error_buf, stderr); fputc('\n', stderr); } } void info_msg(const char *format, ...) { va_list ap; va_start(ap, format); vsnprintf(error_buf, sizeof(error_buf), format, ap); va_end(ap); msg_is_error = false; } const char *get_msg(bool *is_error) { *is_error = msg_is_error; return error_buf; } unsigned int get_nr_errors(void) { return nr_errors; } void suppress_error_msg(void) { supress_errors = true; } void unsuppress_error_msg(void) { supress_errors = false; } dte-1.9.1/src/error.h000066400000000000000000000005511354415143300143730ustar00rootroot00000000000000#ifndef ERROR_H #define ERROR_H #include #include "util/macros.h" void error_msg(const char *format, ...) PRINTF(1); void info_msg(const char *format, ...) PRINTF(1); void clear_error(void); const char *get_msg(bool *is_error) NONNULL_ARGS; unsigned int get_nr_errors(void); void suppress_error_msg(void); void unsuppress_error_msg(void); #endif dte-1.9.1/src/file-history.c000066400000000000000000000076071354415143300156640ustar00rootroot00000000000000#include #include #include #include #include #include "file-history.h" #include "error.h" #include "util/path.h" #include "util/ptr-array.h" #include "util/readfile.h" #include "util/strtonum.h" #include "util/wbuf.h" #include "util/xmalloc.h" #include "util/xsnprintf.h" typedef struct { unsigned long row, col; size_t filename_len; char filename[]; } HistoryEntry; static PointerArray history = PTR_ARRAY_INIT; #define max_history_size 500 static bool entry_match(const HistoryEntry *e, const char *filename, size_t len) { return len == e->filename_len && memcmp(filename, e->filename, len) == 0; } static ssize_t lookup_entry_index(const char *filename, size_t filename_len) { for (size_t i = 0, n = history.count; i < n; i++) { const HistoryEntry *e = history.ptrs[i]; if (entry_match(e, filename, filename_len)) { return i; } } return -1; } void add_file_history(unsigned long row, unsigned long col, const char *filename) { const size_t filename_len = strlen(filename); const ssize_t idx = lookup_entry_index(filename, filename_len); if (idx >= 0) { HistoryEntry *e = history.ptrs[idx]; ptr_array_remove_idx(&history, (size_t)idx); if (row > 1 || col > 1) { e->row = row; e->col = col; // Re-insert at end of array ptr_array_add(&history, e); } else { free(e); } return; } if (row <= 1 && col <= 1) { return; } while (history.count >= max_history_size) { free(ptr_array_remove_idx(&history, 0)); } HistoryEntry *e = xmalloc(sizeof(HistoryEntry) + filename_len); e->row = row; e->col = col; e->filename_len = filename_len; memcpy(e->filename, filename, filename_len); ptr_array_add(&history, e); } static bool parse_ulong(const char **strp, unsigned long *valp) { const char *str = *strp; const size_t len = strlen(str); const size_t ndigits = buf_parse_ulong(str, len, valp); if (ndigits > 0) { *strp = str + ndigits; return true; } return false; } void load_file_history(const char *filename) { char *buf; const ssize_t ssize = read_file(filename, &buf); if (ssize < 0) { if (errno != ENOENT) { error_msg("Error reading %s: %s", filename, strerror(errno)); } return; } const size_t size = ssize; size_t pos = 0; while (pos < size) { const char *line = buf_next_line(buf, &pos, size); unsigned long row, col; if (!parse_ulong(&line, &row) || row == 0 || *line++ != ' ') { continue; } if (!parse_ulong(&line, &col) || col == 0 || *line++ != ' ') { continue; } const char *path = line; if (!path_is_absolute(path)) { continue; } add_file_history(row, col, path); } free(buf); } void save_file_history(const char *filename) { WriteBuffer buf = WBUF_INIT; buf.fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (buf.fd < 0) { error_msg("Error creating %s: %s", filename, strerror(errno)); return; } for (size_t i = 0, n = history.count; i < n; i++) { const HistoryEntry *e = history.ptrs[i]; wbuf_need_space(&buf, 64); buf.fill += xsnprintf(buf.buf + buf.fill, 64, "%lu %lu ", e->row, e->col); wbuf_write(&buf, e->filename, e->filename_len); wbuf_write_ch(&buf, '\n'); } wbuf_flush(&buf); close(buf.fd); } bool find_file_in_history(const char *filename, unsigned long *row, unsigned long *col) { const ssize_t idx = lookup_entry_index(filename, strlen(filename)); if (idx >= 0) { const HistoryEntry *e = history.ptrs[idx]; *row = e->row; *col = e->col; return true; } return false; } dte-1.9.1/src/file-history.h000066400000000000000000000005251354415143300156610ustar00rootroot00000000000000#ifndef FILE_HISTORY_H #define FILE_HISTORY_H #include void add_file_history(unsigned long row, unsigned long col, const char *filename); void load_file_history(const char *filename); void save_file_history(const char *filename); bool find_file_in_history(const char *filename, unsigned long *row, unsigned long *col); #endif dte-1.9.1/src/file-option.c000066400000000000000000000060301354415143300154600ustar00rootroot00000000000000#include #include "file-option.h" #include "editorconfig/editorconfig.h" #include "options.h" #include "regexp.h" #include "spawn.h" #include "util/ptr-array.h" #include "util/str-util.h" #include "util/string-view.h" #include "util/strtonum.h" #include "util/xmalloc.h" typedef struct { FileOptionType type; char *type_or_pattern; char **strs; } FileOption; static PointerArray file_options = PTR_ARRAY_INIT; static void set_options(char **args) { for (size_t i = 0; args[i]; i += 2) { set_option(args[i], args[i + 1], true, false); } } void set_editorconfig_options(Buffer *b) { if (!b->options.editorconfig) { return; } const char *path = b->abs_filename; char cwd[8192]; if (path == NULL) { // For buffers with no associated filename, use a dummy path of // "$PWD/_", to obtain generic settings for the working directory // or the user's default settings. if (getcwd(cwd, sizeof(cwd) - 2) == NULL) { return; } size_t n = strlen(cwd); cwd[n++] = '/'; cwd[n++] = '_'; cwd[n] = '\0'; path = cwd; } EditorConfigOptions opts; if (get_editorconfig_options(path, &opts) != 0) { return; } switch (opts.indent_style) { case INDENT_STYLE_SPACE: b->options.expand_tab = true; b->options.emulate_tab = true; b->options.detect_indent = 0; break; case INDENT_STYLE_TAB: b->options.expand_tab = false; b->options.emulate_tab = false; b->options.detect_indent = 0; break; case INDENT_STYLE_UNSPECIFIED: break; } const unsigned int indent_size = opts.indent_size; if (indent_size > 0 && indent_size <= 8) { b->options.indent_width = indent_size; b->options.detect_indent = 0; } const unsigned int tab_width = opts.tab_width; if (tab_width > 0 && tab_width <= 8) { b->options.tab_width = tab_width; } const unsigned int max_line_length = opts.max_line_length; if (max_line_length > 0 && max_line_length <= 1000) { b->options.text_width = max_line_length; } } void set_file_options(Buffer *b) { for (size_t i = 0; i < file_options.count; i++) { const FileOption *opt = file_options.ptrs[i]; if (opt->type == FILE_OPTIONS_FILETYPE) { if (streq(opt->type_or_pattern, b->options.filetype)) { set_options(opt->strs); } continue; } const char *f = b->abs_filename; if (f && regexp_match_nosub(opt->type_or_pattern, f, strlen(f))) { set_options(opt->strs); } } } void add_file_options(FileOptionType type, char *to, char **strs) { if (type == FILE_OPTIONS_FILENAME && !regexp_is_valid(to, REG_NEWLINE)) { free(to); free_string_array(strs); return; } FileOption *opt = xnew(FileOption, 1); opt->type = type; opt->type_or_pattern = to; opt->strs = strs; ptr_array_add(&file_options, opt); } dte-1.9.1/src/file-option.h000066400000000000000000000004621354415143300154700ustar00rootroot00000000000000#ifndef FILE_OPTION_H #define FILE_OPTION_H #include "buffer.h" typedef enum { FILE_OPTIONS_FILENAME, FILE_OPTIONS_FILETYPE, } FileOptionType; void set_file_options(Buffer *b); void add_file_options(FileOptionType type, char *to, char **strs); void set_editorconfig_options(Buffer *b); #endif dte-1.9.1/src/filetype.c000066400000000000000000000166221354415143300150640ustar00rootroot00000000000000#include #include "filetype.h" #include "debug.h" #include "error.h" #include "regexp.h" #include "util/ascii.h" #include "util/macros.h" #include "util/path.h" #include "util/ptr-array.h" #include "util/str-util.h" #include "util/string-view.h" #include "util/xmalloc.h" static int ft_compare(const void *key, const void *elem) { const StringView *sv = key; const char *ext = elem; // Cast to first member of struct int res = memcmp(sv->data, ext, sv->length); if (res == 0 && ext[sv->length] != '\0') { return -1; } return res; } // Built-in filetypes #include "filetype/names.c" #include "filetype/basenames.c" #include "filetype/extensions.c" #include "filetype/interpreters.c" #include "filetype/ignored-exts.c" #include "filetype/signatures.c" // Filetypes dynamically added via the `ft` command. // Not grouped by name to make it possible to order them freely. typedef struct { FileDetectionType type; uint8_t name_len; uint8_t str_len; char data[]; // Contains name followed by str (both null-terminated) } UserFileTypeEntry; static PointerArray filetypes = PTR_ARRAY_INIT; static const char *ft_get_name(const UserFileTypeEntry *ft) { return ft->data; } static const char *ft_get_str(const UserFileTypeEntry *ft) { return ft->data + ft->name_len + 1; } void add_filetype(const char *name, const char *str, FileDetectionType type) { const size_t name_len = strlen(name); const size_t str_len = strlen(str); if (unlikely(name_len >= 256 || str_len >= 256)) { error_msg("ft argument exceeds maximum length (255 bytes)"); return; } regex_t re; switch (type) { case FT_CONTENT: case FT_FILENAME: if (!regexp_compile(&re, str, REG_NEWLINE | REG_NOSUB)) { return; } regfree(&re); break; default: break; } const size_t data_len = name_len + str_len + 2; UserFileTypeEntry *ft = xmalloc(sizeof(UserFileTypeEntry) + data_len); ft->type = type; ft->name_len = (uint8_t) name_len; ft->str_len = (uint8_t) str_len; memcpy(ft->data, name, name_len + 1); memcpy(ft->data + name_len + 1, str, str_len + 1); ptr_array_add(&filetypes, ft); } static StringView get_ext(const StringView filename) { StringView ext = STRING_VIEW_INIT; if (filename.length < 3) { return ext; } ext.data = string_view_memrchr(&filename, '.'); if (ext.data == NULL) { return ext; } ext.data++; ext.length = filename.length - (ext.data - filename.data); if (ext.length && ext.data[ext.length - 1] == '~') { ext.length--; } if (ext.length == 0) { return ext; } if (is_ignored_extension(ext.data, ext.length)) { int idx = -2; while (ext.data + idx >= filename.data) { if (ext.data[idx] == '.') { int len = -idx - 2; if (len) { ext.data -= len + 1; ext.length = len; } break; } idx--; } } return ext; } // Parse hashbang and return interpreter name, without version number. // For example, if line is "#!/usr/bin/env python2", "python" is returned. static StringView get_interpreter(const StringView line) { StringView sv = STRING_VIEW_INIT; if (line.length < 4 || line.data[0] != '#' || line.data[1] != '!') { return sv; } static const char pat[] = "^#! */.*(/env +|/)([a-zA-Z0-9_-]+)[0-9.]*( |$)"; static regex_t re; static bool compiled; if (!compiled) { compiled = regexp_compile(&re, pat, REG_NEWLINE); BUG_ON(!compiled); BUG_ON(re.re_nsub < 2); } regmatch_t m[3]; if (!regexp_exec(&re, line.data, line.length, ARRAY_COUNT(m), m, 0)) { return sv; } regoff_t start = m[2].rm_so; regoff_t end = m[2].rm_eo; BUG_ON(start < 0 || end < 0); sv = string_view(line.data + start, end - start); return sv; } static bool ft_str_match(const UserFileTypeEntry *ft, const StringView sv) { const char *str = ft_get_str(ft); const size_t len = (size_t)ft->str_len; return sv.length > 0 && string_view_equal_strn(&sv, str, len); } static bool ft_regex_match(const UserFileTypeEntry *ft, const StringView sv) { const char *str = ft_get_str(ft); return sv.length > 0 && regexp_match_nosub(str, sv.data, sv.length); } HOT const char *find_ft(const char *filename, StringView line) { const char *b = filename ? path_basename(filename) : NULL; const StringView base = string_view_from_cstring(b); const StringView ext = get_ext(base); const StringView path = string_view_from_cstring(filename); const StringView interpreter = get_interpreter(line); // Search user `ft` entries for (size_t i = 0; i < filetypes.count; i++) { const UserFileTypeEntry *ft = filetypes.ptrs[i]; switch (ft->type) { case FT_EXTENSION: if (!ft_str_match(ft, ext)) { continue; } break; case FT_BASENAME: if (!ft_str_match(ft, base)) { continue; } break; case FT_FILENAME: if (!ft_regex_match(ft, path)) { continue; } break; case FT_CONTENT: if (!ft_regex_match(ft, line)) { continue; } break; case FT_INTERPRETER: if (!ft_str_match(ft, interpreter)) { continue; } break; } return ft_get_name(ft); } // Search built-in lookup tables if (interpreter.length) { FileTypeEnum ft = filetype_from_interpreter(interpreter); if (ft) { return builtin_filetype_names[ft]; } } if (base.length) { FileTypeEnum ft = filetype_from_basename(base); if (ft) { return builtin_filetype_names[ft]; } } if (line.length) { FileTypeEnum ft = filetype_from_signature(line); if (ft) { return builtin_filetype_names[ft]; } } if (ext.length) { FileTypeEnum ft = filetype_from_extension(ext); if (ft) { return builtin_filetype_names[ft]; } } if (string_view_has_literal_prefix(&path, "/etc/default/")) { return builtin_filetype_names[SHELL]; } else if (string_view_has_literal_prefix(&path, "/etc/nginx/")) { return builtin_filetype_names[NGINX]; } const StringView conf = STRING_VIEW("conf"); if (string_view_equal(&ext, &conf)) { if (string_view_has_literal_prefix(&path, "/etc/systemd/")) { return builtin_filetype_names[INI]; } else if ( string_view_has_literal_prefix(&path, "/etc/") || string_view_has_literal_prefix(&path, "/usr/share/") || string_view_has_literal_prefix(&path, "/usr/local/share/") ) { return builtin_filetype_names[CONFIG]; } } return NULL; } bool is_ft(const char *name) { for (size_t i = 0; i < filetypes.count; i++) { const UserFileTypeEntry *ft = filetypes.ptrs[i]; if (streq(ft_get_name(ft), name)) { return true; } } for (size_t i = 0; i < ARRAY_COUNT(builtin_filetype_names); i++) { if (streq(builtin_filetype_names[i], name)) { return true; } } return false; } dte-1.9.1/src/filetype.h000066400000000000000000000006341354415143300150650ustar00rootroot00000000000000#ifndef FILETYPE_H #define FILETYPE_H #include #include #include "util/string-view.h" typedef enum { FT_EXTENSION, FT_FILENAME, FT_CONTENT, FT_INTERPRETER, FT_BASENAME, } FileDetectionType; void add_filetype(const char *name, const char *str, FileDetectionType type); bool is_ft(const char *name); const char *find_ft(const char *filename, StringView line); #endif dte-1.9.1/src/filetype/000077500000000000000000000000001354415143300147115ustar00rootroot00000000000000dte-1.9.1/src/filetype/basenames.c000066400000000000000000000055211354415143300170160ustar00rootroot00000000000000typedef struct { const char key[16]; const FileTypeEnum filetype; } FileBasenameMap; static const FileBasenameMap basenames[] = { {"APKBUILD", SHELL}, {"BSDmakefile", MAKE}, {"BUILD.bazel", PYTHON}, {"CMakeLists.txt", CMAKE}, {"COMMIT_EDITMSG", GITCOMMIT}, {"Capfile", RUBY}, {"Cargo.lock", TOML}, {"Dockerfile", DOCKER}, {"Doxyfile", CONFIG}, {"GNUmakefile", MAKE}, {"Gemfile", RUBY}, {"Gemfile.lock", RUBY}, {"Kbuild", MAKE}, {"Makefile", MAKE}, {"Makefile.am", MAKE}, {"Makefile.in", MAKE}, {"PKGBUILD", SHELL}, {"Project.ede", EMACSLISP}, {"Rakefile", RUBY}, {"Vagrantfile", RUBY}, {"build.gradle", GRADLE}, {"config.ld", LUA}, {"configure.ac", M4}, {"fstab", CONFIG}, {"git-rebase-todo", GITREBASE}, {"hosts", CONFIG}, {"ip6tables.rules", CONFIG}, {"iptables.rules", CONFIG}, {"krb5.conf", INI}, {"makefile", MAKE}, {"menu.lst", CONFIG}, {"meson.build", MESON}, {"mimeapps.list", INI}, {"mkinitcpio.conf", SHELL}, {"nginx.conf", NGINX}, {"pacman.conf", INI}, {"robots.txt", ROBOTSTXT}, {"rockspec.in", LUA}, {"terminalrc", INI}, {"texmf.cnf", TEXMFCNF}, {"yum.conf", INI}, }; // These are matched with or without a leading dot static const FileBasenameMap dotfiles[] = { {"Xresources", XRESOURCES}, {"bash_logout", SHELL}, {"bash_profile", SHELL}, {"bashrc", SHELL}, {"clang-format", YAML}, {"clang-tidy", YAML}, {"cshrc", SHELL}, {"drirc", XML}, {"editorconfig", INI}, {"emacs", EMACSLISP}, {"gemrc", YAML}, {"gitattributes", CONFIG}, {"gitconfig", INI}, {"gitmodules", INI}, {"gnus", EMACSLISP}, {"indent.pro", INDENT}, {"inputrc", CONFIG}, {"jshintrc", JSON}, {"lcovrc", CONFIG}, {"luacheckrc", LUA}, {"luacov", LUA}, {"profile", SHELL}, {"xinitrc", SHELL}, {"xprofile", SHELL}, {"xserverrc", SHELL}, {"zlogin", SHELL}, {"zlogout", SHELL}, {"zprofile", SHELL}, {"zshenv", SHELL}, {"zshrc", SHELL}, }; static FileTypeEnum filetype_from_basename(StringView sv) { switch (sv.length) { case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: break; case 17: return memcmp(sv.data, "meson_options.txt", 17) ? NONE : MESON; default: return NONE; } const FileBasenameMap *e = bsearch ( &sv, basenames, ARRAY_COUNT(basenames), sizeof(basenames[0]), ft_compare ); if (e) { return e->filetype; } if (sv.data[0] == '.') { sv.data++; sv.length--; } e = bsearch ( &sv, dotfiles, ARRAY_COUNT(dotfiles), sizeof(dotfiles[0]), ft_compare ); return e ? e->filetype : NONE; } dte-1.9.1/src/filetype/extensions.c000066400000000000000000000106421354415143300172570ustar00rootroot00000000000000static const struct FileExtensionMap { const char ext[12]; const FileTypeEnum filetype; } extensions[] = { {"ada", ADA}, {"adb", ADA}, {"ads", ADA}, {"asd", LISP}, {"asm", ASSEMBLY}, {"auk", AWK}, {"automount", INI}, {"awk", AWK}, {"bash", SHELL}, {"bat", BATCHFILE}, {"bbl", TEX}, {"bib", BIBTEX}, {"btm", BATCHFILE}, {"c++", CPLUSPLUS}, {"cc", CPLUSPLUS}, {"cl", LISP}, {"clj", CLOJURE}, {"cls", TEX}, {"cmake", CMAKE}, {"cmd", BATCHFILE}, {"coffee", COFFEESCRIPT}, {"cpp", CPLUSPLUS}, {"cr", RUBY}, {"cs", CSHARP}, {"cson", COFFEESCRIPT}, {"css", CSS}, {"csv", CSV}, {"cxx", CPLUSPLUS}, {"dart", DART}, {"desktop", INI}, {"di", D}, {"diff", DIFF}, {"doap", XML}, {"docbook", XML}, {"docker", DOCKER}, {"dot", DOT}, {"doxy", CONFIG}, {"dterc", DTERC}, {"dtx", TEX}, {"ebuild", SHELL}, {"el", LISP}, {"emacs", EMACSLISP}, {"eml", MAIL}, {"eps", POSTSCRIPT}, {"flatpakref", INI}, {"flatpakrepo", INI}, {"frag", GLSL}, {"gawk", AWK}, {"gemspec", RUBY}, {"geojson", JSON}, {"glsl", GLSL}, {"glslf", GLSL}, {"glslv", GLSL}, {"gnuplot", GNUPLOT}, {"go", GO}, {"gp", GNUPLOT}, {"gperf", GPERF}, {"gpi", GNUPLOT}, {"groovy", GROOVY}, {"gsed", SED}, {"gv", DOT}, {"hh", CPLUSPLUS}, {"hpp", CPLUSPLUS}, {"hs", HASKELL}, {"htm", HTML}, {"html", HTML}, {"hxx", CPLUSPLUS}, {"ini", INI}, {"ins", TEX}, {"java", JAVA}, {"js", JAVASCRIPT}, {"json", JSON}, {"ksh", SHELL}, {"lsp", LISP}, {"ltx", TEX}, {"lua", LUA}, {"m4", M4}, {"mak", MAKE}, {"make", MAKE}, {"markdown", MARKDOWN}, {"mawk", AWK}, {"md", MARKDOWN}, {"mk", MAKE}, {"mkd", MARKDOWN}, {"mkdn", MARKDOWN}, {"moon", MOONSCRIPT}, {"mount", INI}, {"nawk", AWK}, {"nginx", NGINX}, {"nginxconf", NGINX}, {"nim", NIM}, {"ninja", NINJA}, {"nix", NIX}, {"page", XML}, {"patch", DIFF}, {"path", INI}, {"pc", PKGCONFIG}, {"perl", PERL}, {"php", PHP}, {"pl", PERL}, {"pls", INI}, {"plt", GNUPLOT}, {"pm", PERL}, {"po", GETTEXT}, {"pot", GETTEXT}, {"pp", RUBY}, {"proto", PROTOBUF}, {"ps", POSTSCRIPT}, {"py", PYTHON}, {"py3", PYTHON}, {"rake", RUBY}, {"rb", RUBY}, {"rdf", XML}, {"rkt", RACKET}, {"rktd", RACKET}, {"rktl", RACKET}, {"rockspec", LUA}, {"rs", RUST}, {"rst", RESTRUCTUREDTEXT}, {"scala", SCALA}, {"scm", SCHEME}, {"scss", SCSS}, {"sed", SED}, {"service", INI}, {"sh", SHELL}, {"sld", SCHEME}, {"slice", INI}, {"sls", SCHEME}, {"socket", INI}, {"sql", SQL}, {"ss", SCHEME}, {"sty", TEX}, {"svg", XML}, {"target", INI}, {"tcl", TCL}, {"tex", TEX}, {"texi", TEXINFO}, {"texinfo", TEXINFO}, {"timer", INI}, {"toml", TOML}, {"topojson", JSON}, {"ts", TYPESCRIPT}, {"tsx", TYPESCRIPT}, {"ui", XML}, {"vala", VALA}, {"vapi", VALA}, {"vcard", VCARD}, {"vcf", VCARD}, {"ver", VERILOG}, {"vert", GLSL}, {"vh", VHDL}, {"vhd", VHDL}, {"vhdl", VHDL}, {"vim", VIML}, {"wsgi", PYTHON}, {"xhtml", HTML}, {"xml", XML}, {"xsd", XML}, {"xsl", XML}, {"xslt", XML}, {"yaml", YAML}, {"yml", YAML}, {"zig", ZIG}, {"zsh", SHELL}, }; static FileTypeEnum filetype_from_extension(const StringView sv) { switch (sv.length) { case 1: switch (sv.data[0]) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return ROFF; case 'c': case 'h': return C; case 'C': case 'H': return CPLUSPLUS; case 'S': case 's': return ASSEMBLY; case 'd': return D; case 'l': return LEX; case 'm': return OBJECTIVEC; case 'v': return VERILOG; case 'y': return YACC; } return NONE; case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: break; default: return NONE; } const struct FileExtensionMap *e = bsearch ( &sv, extensions, ARRAY_COUNT(extensions), sizeof(extensions[0]), ft_compare ); return e ? e->filetype : NONE; } dte-1.9.1/src/filetype/ignored-exts.c000066400000000000000000000016401354415143300174660ustar00rootroot00000000000000static bool is_ignored_extension(const char *s, size_t len) { switch (len) { case 3: switch (s[0]) { case 'b': return !memcmp(s, "bak", len); case 'n': return !memcmp(s, "new", len); case 'o': return !memcmp(s, "old", len); } break; case 4: return !memcmp(s, "orig", len); case 6: switch (s[0]) { case 'p': return !memcmp(s, "pacnew", len); case 'r': return !memcmp(s, "rpmnew", len); } break; case 7: switch (s[0]) { case 'r': return !memcmp(s, "rpmsave", len); case 'p': switch (s[3]) { case 'o': return !memcmp(s, "pacorig", len); case 's': return !memcmp(s, "pacsave", len); } break; } break; case 8: return !memcmp(s, "dpkg-old", len); case 9: return !memcmp(s, "dpkg-dist", len); } return false; } dte-1.9.1/src/filetype/interpreters.c000066400000000000000000000032461354415143300176100ustar00rootroot00000000000000static const struct FileInterpreterMap { const char key[8]; const FileTypeEnum filetype; } interpreters[] = { {"ash", SHELL}, {"awk", AWK}, {"bash", SHELL}, {"bigloo", SCHEME}, {"ccl", LISP}, {"chicken", SCHEME}, {"clisp", LISP}, {"coffee", COFFEESCRIPT}, {"crystal", RUBY}, {"dash", SHELL}, {"ecl", LISP}, {"gawk", AWK}, {"gmake", MAKE}, {"gnuplot", GNUPLOT}, {"groovy", GROOVY}, {"gsed", SED}, {"guile", SCHEME}, {"jruby", RUBY}, {"ksh", SHELL}, {"lisp", LISP}, {"lua", LUA}, {"luajit", LUA}, {"macruby", RUBY}, {"make", MAKE}, {"mawk", AWK}, {"mksh", SHELL}, {"moon", MOONSCRIPT}, {"nawk", AWK}, {"node", JAVASCRIPT}, {"pdksh", SHELL}, {"perl", PERL}, {"php", PHP}, {"python", PYTHON}, {"r6rs", SCHEME}, {"racket", SCHEME}, {"rake", RUBY}, {"ruby", RUBY}, {"sbcl", LISP}, {"sed", SED}, {"sh", SHELL}, {"tcc", C}, {"tclsh", TCL}, {"wish", TCL}, {"zsh", SHELL}, }; static FileTypeEnum filetype_from_interpreter(const StringView sv) { switch (sv.length) { case 2: case 3: case 4: case 5: case 6: case 7: break; case 10: switch (sv.data[0]) { case 'o': return memcmp(sv.data, "openrc-run", 10) ? NONE : SHELL; case 'r': return memcmp(sv.data, "runhaskell", 10) ? NONE : HASKELL; } // Fallthrough default: return NONE; } const struct FileInterpreterMap *e = bsearch ( &sv, interpreters, ARRAY_COUNT(interpreters), sizeof(interpreters[0]), ft_compare ); return e ? e->filetype : NONE; } dte-1.9.1/src/filetype/names.c000066400000000000000000000076271354415143300161740ustar00rootroot00000000000000typedef enum { NONE = 0, ADA, ASSEMBLY, AWK, BATCHFILE, BIBTEX, C, CPLUSPLUS, CLOJURE, CMAKE, COFFEESCRIPT, CONFIG, CSHARP, CSS, CSV, D, DART, DIFF, DOCKER, DOT, DTERC, EMACSLISP, GETTEXT, GITCOMMIT, GITREBASE, GLSL, GNUPLOT, GO, GPERF, GRADLE, GROOVY, HASKELL, HTML, INDENT, INI, JAVA, JAVASCRIPT, JSON, LEX, LISP, LUA, M4, MAIL, MAKE, MARKDOWN, MESON, MOONSCRIPT, NGINX, NIM, NINJA, NIX, OBJECTIVEC, PERL, PHP, PKGCONFIG, POSTSCRIPT, PROTOBUF, PYTHON, RACKET, ROBOTSTXT, ROFF, RESTRUCTUREDTEXT, RUBY, RUST, SCALA, SCHEME, SCSS, SED, SHELL, SQL, TCL, TEX, TEXINFO, TEXMFCNF, TOML, TYPESCRIPT, VALA, VCARD, VERILOG, VHDL, VIML, XML, XRESOURCES, YACC, YAML, ZIG, NR_BUILTIN_FILETYPES } FileTypeEnum; static const char builtin_filetype_names[NR_BUILTIN_FILETYPES][16] = { [NONE] = "none", [ADA] = "ada", [ASSEMBLY] = "asm", [AWK] = "awk", [BATCHFILE] = "batch", [BIBTEX] = "bibtex", [C] = "c", [CPLUSPLUS] = "c", [CLOJURE] = "clojure", [CMAKE] = "cmake", [COFFEESCRIPT] = "coffeescript", [CONFIG] = "config", [CSHARP] = "csharp", [CSS] = "css", [CSV] = "csv", [DART] = "dart", [D] = "d", [DIFF] = "diff", [DOCKER] = "docker", [DOT] = "dot", [DTERC] = "dte", [EMACSLISP] = "elisp", [GETTEXT] = "gettext", [GITCOMMIT] = "gitcommit", [GITREBASE] = "gitrebase", [GLSL] = "glsl", [GNUPLOT] = "gnuplot", [GO] = "go", [GPERF] = "gperf", [GRADLE] = "gradle", [GROOVY] = "groovy", [HASKELL] = "haskell", [HTML] = "html", [INDENT] = "indent", [INI] = "ini", [JAVA] = "java", [JAVASCRIPT] = "javascript", [JSON] = "json", [LEX] = "lex", [LISP] = "lisp", [LUA] = "lua", [M4] = "m4", [MAIL] = "mail", [MAKE] = "make", [MARKDOWN] = "markdown", [MESON] = "meson", [MOONSCRIPT] = "moonscript", [NGINX] = "nginx", [NIM] = "nim", [NINJA] = "ninja", [NIX] = "nix", [OBJECTIVEC] = "objc", [PERL] = "perl", [PHP] = "php", [PKGCONFIG] = "pkg-config", [POSTSCRIPT] = "postscript", [PROTOBUF] = "protobuf", [PYTHON] = "python", [RACKET] = "racket", [RESTRUCTUREDTEXT] = "rst", [ROBOTSTXT] = "robotstxt", [ROFF] = "roff", [RUBY] = "ruby", [RUST] = "rust", [SCALA] = "scala", [SCHEME] = "scheme", [SCSS] = "scss", [SED] = "sed", [SHELL] = "sh", [SQL] = "sql", [TCL] = "tcl", [TEXINFO] = "texinfo", [TEXMFCNF] = "texmfcnf", [TEX] = "tex", [TOML] = "toml", [TYPESCRIPT] = "typescript", [VALA] = "vala", [VCARD] = "vcard", [VERILOG] = "verilog", [VHDL] = "vhdl", [VIML] = "viml", [XML] = "xml", [XRESOURCES] = "xresources", [YACC] = "yacc", [YAML] = "yaml", [ZIG] = "zig", }; UNITTEST { BUG_ON(strcmp(builtin_filetype_names[0], "none") != 0); BUG_ON(strcmp(builtin_filetype_names[1], "ada") != 0); for (size_t i = 2; i < ARRAY_COUNT(builtin_filetype_names); i++) { const char *const name = builtin_filetype_names[i]; if (name[0] == '\0') { BUG("missing value at builtin_filetype_names[%zu]", i); } // Ensure fixed-size char arrays are null-terminated BUG_ON(memchr(name, '\0', sizeof(builtin_filetype_names[0])) == NULL); // Ensure FileTypeEnum values are sorted according to their name // string (to allow name -> value lookups via binary search). const char *const prev = builtin_filetype_names[i - 1]; if (memcmp(name, prev, 16) < 0) { BUG("Filetype names not in sorted order: %s, %s", prev, name); } } } dte-1.9.1/src/filetype/signatures.c000066400000000000000000000021451354415143300172430ustar00rootroot00000000000000typedef struct { const unsigned char NONSTRING bytes[11]; uint8_t length; FileTypeEnum filetype; } FileSignatureMap; #define SIG(str, ft) { \ .bytes = str, \ .length = STRLEN(str), \ .filetype = ft \ } static const FileSignatureMap signatures[] = { SIG("bytes, sig->length)) { return sig->filetype; } } return NONE; } dte-1.9.1/src/frame.c000066400000000000000000000263421354415143300143350ustar00rootroot00000000000000#include "frame.h" #include "debug.h" #include "util/xmalloc.h" #include "window.h" Frame *root_frame; static int get_min_w(const Frame *f) { if (f->window) { return 8; } if (f->vertical) { int max = 0; for (size_t i = 0, n = f->frames.count; i < n; i++) { int w = get_min_w(f->frames.ptrs[i]); if (w > max) { max = w; } } return max; } else { int w = f->frames.count - 1; // Separators for (size_t i = 0, n = f->frames.count; i < n; i++) { w += get_min_w(f->frames.ptrs[i]); } return w; } } static int get_min_h(const Frame *f) { if (f->window) { return 3; } if (!f->vertical) { int max = 0; for (size_t i = 0, n = f->frames.count; i < n; i++) { int h = get_min_h(f->frames.ptrs[i]); if (h > max) { max = h; } } return max; } else { int h = 0; // No separators for (size_t i = 0, n = f->frames.count; i < n; i++) { h += get_min_h(f->frames.ptrs[i]); } return h; } } static int get_min(const Frame *f) { if (f->parent->vertical) { return get_min_h(f); } return get_min_w(f); } static int get_size(const Frame *f) { if (f->parent->vertical) { return f->h; } return f->w; } static int get_container_size(const Frame *f) { if (f->vertical) { return f->h; } return f->w; } static void set_size(Frame *f, int size) { if (f->parent->vertical) { set_frame_size(f, f->parent->w, size); } else { set_frame_size(f, size, f->parent->h); } } static void divide_equally(const Frame *f) { size_t count = f->frames.count; BUG_ON(count == 0); int *size = xnew0(int, count); int *min = xnew(int, count); for (size_t i = 0; i < count; i++) { min[i] = get_min(f->frames.ptrs[i]); } int q, r, used; int s = get_container_size(f); size_t n = count; // Consume q and r as equally as possible do { used = 0; q = s / n; r = s % n; for (size_t i = 0; i < count; i++) { if (size[i] == 0 && min[i] > q) { size[i] = min[i]; used += min[i]; n--; } } s -= used; } while (used && n > 0); for (size_t i = 0; i < count; i++) { Frame *c = f->frames.ptrs[i]; if (size[i] == 0) { size[i] = q + (r-- > 0); } set_size(c, size[i]); } free(size); free(min); } static void fix_size(const Frame *f) { size_t count = f->frames.count; int *size = xnew0(int, count); int *min = xnew(int, count); int total = 0; for (size_t i = 0; i < count; i++) { const Frame *c = f->frames.ptrs[i]; min[i] = get_min(c); size[i] = get_size(c); if (size[i] < min[i]) { size[i] = min[i]; } total += size[i]; } int s = get_container_size(f); if (total > s) { int n = total - s; for (ssize_t i = count - 1; n > 0 && i >= 0; i--) { int new_size = size[i] - n; if (new_size < min[i]) { new_size = min[i]; } n -= size[i] - new_size; size[i] = new_size; } } else { size[count - 1] += s - total; } for (size_t i = 0; i < count; i++) { set_size(f->frames.ptrs[i], size[i]); } free(size); free(min); } static void add_to_sibling_size(Frame *f, int count) { const Frame *parent = f->parent; size_t idx = ptr_array_idx(&parent->frames, f); if (idx == parent->frames.count - 1) { f = parent->frames.ptrs[idx - 1]; } else { f = parent->frames.ptrs[idx + 1]; } set_size(f, get_size(f) + count); } static int sub(Frame *f, int count) { int min = get_min(f); int old = get_size(f); int new = old - count; if (new < min) { new = min; } if (new != old) { set_size(f, new); } return count - (old - new); } static void subtract_from_sibling_size(const Frame *f, int count) { const Frame *parent = f->parent; size_t idx = ptr_array_idx(&parent->frames, f); for (size_t i = idx + 1, n = parent->frames.count; i < n; i++) { count = sub(parent->frames.ptrs[i], count); if (count == 0) { return; } } for (size_t i = idx; i > 0; i--) { count = sub(parent->frames.ptrs[i - 1], count); if (count == 0) { return; } } } static void resize_to(Frame *f, int size) { const Frame *parent = f->parent; int total = parent->vertical ? parent->h : parent->w; int count = parent->frames.count; int min = get_min(f); int max = total - (count - 1) * min; int change; if (max < min) { max = min; } if (size < min) { size = min; } if (size > max) { size = max; } change = size - get_size(f); if (change == 0) { return; } set_size(f, size); if (change < 0) { add_to_sibling_size(f, -change); } else { subtract_from_sibling_size(f, change); } } static bool rightmost_frame(const Frame *f) { const Frame *parent = f->parent; if (parent == NULL) { return true; } if (!parent->vertical) { if (f != parent->frames.ptrs[parent->frames.count - 1]) { return false; } } return rightmost_frame(parent); } static Frame *new_frame(void) { Frame *f = xnew0(Frame, 1); f->equal_size = true; return f; } static Frame *add_frame(Frame *parent, Window *w, size_t idx) { Frame *f = new_frame(); f->parent = parent; f->window = w; w->frame = f; if (parent != NULL) { BUG_ON(idx > parent->frames.count); ptr_array_insert(&parent->frames, f, idx); parent->window = NULL; } return f; } Frame *new_root_frame(Window *w) { return add_frame(NULL, w, 0); } static Frame *find_resizable(Frame *f, ResizeDirection dir) { if (dir == RESIZE_DIRECTION_AUTO) { return f; } while (f->parent) { if (dir == RESIZE_DIRECTION_VERTICAL && f->parent->vertical) { return f; } if (dir == RESIZE_DIRECTION_HORIZONTAL && !f->parent->vertical) { return f; } f = f->parent; } return NULL; } void set_frame_size(Frame *f, int w, int h) { int min_w = get_min_w(f); int min_h = get_min_h(f); if (w < min_w) { w = min_w; } if (h < min_h) { h = min_h; } f->w = w; f->h = h; if (f->window) { if (!rightmost_frame(f)) { w--; // Separator } set_window_size(f->window, w, h); return; } if (f->equal_size) { divide_equally(f); } else { fix_size(f); } } void equalize_frame_sizes(Frame *parent) { parent->equal_size = true; divide_equally(parent); update_window_coordinates(); } void add_to_frame_size(Frame *f, ResizeDirection dir, int amount) { f = find_resizable(f, dir); if (f == NULL) { return; } f->parent->equal_size = false; if (f->parent->vertical) { resize_to(f, f->h + amount); } else { resize_to(f, f->w + amount); } update_window_coordinates(); } void resize_frame(Frame *f, ResizeDirection dir, int size) { f = find_resizable(f, dir); if (f == NULL) { return; } f->parent->equal_size = false; resize_to(f, size); update_window_coordinates(); } static void update_frame_coordinates(const Frame *f, int x, int y) { if (f->window) { set_window_coordinates(f->window, x, y); return; } for (size_t i = 0, n = f->frames.count; i < n; i++) { const Frame *c = f->frames.ptrs[i]; update_frame_coordinates(c, x, y); if (f->vertical) { y += c->h; } else { x += c->w; } } } void update_window_coordinates(void) { update_frame_coordinates(root_frame, 0, 0); } Frame *split_frame(Window *w, bool vertical, bool before) { Frame *f = w->frame; Frame *parent = f->parent; if (parent == NULL || parent->vertical != vertical) { // Reparent w f->vertical = vertical; add_frame(f, w, 0); parent = f; } size_t idx = ptr_array_idx(&parent->frames, w->frame); if (!before) { idx++; } Window *neww = new_window(); f = add_frame(parent, neww, idx); parent->equal_size = true; // Recalculate set_frame_size(parent, parent->w, parent->h); update_window_coordinates(); return f; } // Doesn't really split root but adds new frame between root and its contents Frame *split_root(bool vertical, bool before) { Frame *new_root, *f; new_root = new_frame(); new_root->vertical = vertical; f = new_frame(); f->parent = new_root; f->window = new_window(); f->window->frame = f; if (before) { ptr_array_add(&new_root->frames, f); ptr_array_add(&new_root->frames, root_frame); } else { ptr_array_add(&new_root->frames, root_frame); ptr_array_add(&new_root->frames, f); } root_frame->parent = new_root; set_frame_size(new_root, root_frame->w, root_frame->h); root_frame = new_root; update_window_coordinates(); return f; } // NOTE: does not remove f from f->parent->frames static void free_frame(Frame *f) { f->parent = NULL; ptr_array_free_cb(&f->frames, FREE_FUNC(free_frame)); if (f->window != NULL) { window_free(f->window); f->window = NULL; } free(f); } void remove_frame(Frame *f) { Frame *parent = f->parent; if (parent == NULL) { free_frame(f); return; } ptr_array_remove(&parent->frames, f); free_frame(f); if (parent->frames.count == 1) { // Replace parent with the only child frame Frame *gp = parent->parent; Frame *c = parent->frames.ptrs[0]; c->parent = gp; c->w = parent->w; c->h = parent->h; if (gp) { size_t idx = ptr_array_idx(&gp->frames, parent); gp->frames.ptrs[idx] = c; } else { root_frame = c; } free(parent->frames.ptrs); free(parent); parent = c; } // Recalculate set_frame_size(parent, parent->w, parent->h); update_window_coordinates(); } #ifdef DEBUG_FRAMES static void debug_frame(const Frame *f, int level) { d_print ( "%*s%dx%d %d %d %zu\n", level * 4, "", f->w, f->h, f->vertical, f->equal_size, f->frames.count ); if (f->window) { d_print ( "%*swindow %d,%d %dx%d\n", (level + 1) * 4, "", f->window->x, f->window->y, f->window->w, f->window->h ); } BUG_ON(f->window && f->frames.count); if (f->window) { BUG_ON(f != f->window->frame); } for (size_t i = 0, n = f->frames.count; i < n; i++) { const Frame *c = f->frames.ptrs[i]; BUG_ON(c->parent != f); debug_frame(c, level + 1); } } void debug_frames(void) { debug_frame(root_frame, 0); } #endif dte-1.9.1/src/frame.h000066400000000000000000000020231354415143300143300ustar00rootroot00000000000000#ifndef FRAME_H #define FRAME_H #include #include "util/ptr-array.h" typedef struct Frame { struct Frame *parent; // Every frame contains either one window or multiple subframes PointerArray frames; struct Window *window; // Width and height int w, h; bool vertical; bool equal_size; } Frame; typedef enum { RESIZE_DIRECTION_AUTO, RESIZE_DIRECTION_HORIZONTAL, RESIZE_DIRECTION_VERTICAL, } ResizeDirection; extern Frame *root_frame; Frame *new_root_frame(struct Window *w); void set_frame_size(Frame *f, int w, int h); void equalize_frame_sizes(Frame *parent); void add_to_frame_size(Frame *f, ResizeDirection dir, int amount); void resize_frame(Frame *f, ResizeDirection dir, int size); void update_window_coordinates(void); Frame *split_frame(struct Window *w, bool vertical, bool before); Frame *split_root(bool vertical, bool before); void remove_frame(Frame *f); #ifdef DEBUG_FRAMES void debug_frames(void); #else static inline void debug_frames(void) {} #endif #endif dte-1.9.1/src/history.c000066400000000000000000000045041354415143300147400ustar00rootroot00000000000000#include #include #include #include "history.h" #include "error.h" #include "util/ptr-array.h" #include "util/readfile.h" #include "util/str-util.h" #include "util/wbuf.h" #include "util/xmalloc.h" // Add item to end of array void history_add(PointerArray *history, const char *text, size_t max_entries) { if (text[0] == '\0') { return; } // Don't add identical entries for (size_t i = 0, n = history->count; i < n; i++) { if (streq(history->ptrs[i], text)) { // Move identical entry to end ptr_array_add(history, ptr_array_remove_idx(history, i)); return; } } if (history->count == max_entries) { free(ptr_array_remove_idx(history, 0)); } ptr_array_add(history, xstrdup(text)); } bool history_search_forward ( const PointerArray *history, ssize_t *pos, const char *text ) { ssize_t i = *pos; while (--i >= 0) { if (str_has_prefix(history->ptrs[i], text)) { *pos = i; return true; } } return false; } bool history_search_backward ( const PointerArray *history, ssize_t *pos, const char *text ) { ssize_t i = *pos; while (++i < history->count) { if (str_has_prefix(history->ptrs[i], text)) { *pos = i; return true; } } return false; } void history_load(PointerArray *history, const char *filename, size_t max_entries) { char *buf; const ssize_t ssize = read_file(filename, &buf); if (ssize < 0) { if (errno != ENOENT) { error_msg("Error reading %s: %s", filename, strerror(errno)); } return; } const size_t size = ssize; size_t pos = 0; while (pos < size) { history_add(history, buf_next_line(buf, &pos, size), max_entries); } free(buf); } void history_save(const PointerArray *history, const char *filename) { WriteBuffer buf = WBUF_INIT; buf.fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (buf.fd < 0) { error_msg("Error creating %s: %s", filename, strerror(errno)); return; } for (size_t i = 0, n = history->count; i < n; i++) { wbuf_write_str(&buf, history->ptrs[i]); wbuf_write_ch(&buf, '\n'); } wbuf_flush(&buf); close(buf.fd); } dte-1.9.1/src/history.h000066400000000000000000000011261354415143300147420ustar00rootroot00000000000000#ifndef HISTORY_H #define HISTORY_H #include #include #include "util/ptr-array.h" #define search_history_size 100 #define command_history_size 500 void history_add(PointerArray *history, const char *text, size_t max_entries); bool history_search_forward(const PointerArray *history, ssize_t *pos, const char *text); bool history_search_backward(const PointerArray *history, ssize_t *pos, const char *text); void history_load(PointerArray *history, const char *filename, size_t max_entries); void history_save(const PointerArray *history, const char *filename); #endif dte-1.9.1/src/indent.c000066400000000000000000000110551354415143300145170ustar00rootroot00000000000000#include #include "indent.h" #include "buffer.h" #include "regexp.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "view.h" char *make_indent(size_t width) { if (width == 0) { return NULL; } char *str; if (use_spaces_for_indent()) { str = xmalloc(width + 1); memset(str, ' ', width); str[width] = '\0'; } else { size_t tw = buffer->options.tab_width; size_t nt = width / tw; size_t ns = width % tw; str = xmalloc(nt + ns + 1); memset(str, '\t', nt); memset(str + nt, ' ', ns); str[nt + ns] = '\0'; } return str; } static bool indent_inc(const char *line, size_t len) { const char *re1 = "\\{[\t ]*(//.*|/\\*.*\\*/[\t ]*)?$"; const char *re2 = "\\}[\t ]*(//.*|/\\*.*\\*/[\t ]*)?$"; if (buffer->options.brace_indent) { if (regexp_match_nosub(re1, line, len)) { return true; } if (regexp_match_nosub(re2, line, len)) { return false; } } re1 = buffer->options.indent_regex; return re1 && *re1 && regexp_match_nosub(re1, line, len); } char *get_indent_for_next_line(const char *line, size_t len) { IndentInfo info; get_indent_info(line, len, &info); if (indent_inc(line, len)) { size_t w = buffer->options.indent_width; info.width = (info.width + w) / w * w; } return make_indent(info.width); } void get_indent_info(const char *buf, size_t len, IndentInfo *info) { size_t spaces = 0; size_t tabs = 0; size_t pos = 0; MEMZERO(info); info->sane = true; while (pos < len) { if (buf[pos] == ' ') { info->width++; spaces++; } else if (buf[pos] == '\t') { size_t tw = buffer->options.tab_width; info->width = (info->width + tw) / tw * tw; tabs++; } else { break; } info->bytes++; pos++; if (info->width % buffer->options.indent_width == 0 && info->sane) { info->sane = use_spaces_for_indent() ? !tabs : !spaces; } } info->level = info->width / buffer->options.indent_width; info->wsonly = pos == len; } bool use_spaces_for_indent(void) { return buffer->options.expand_tab == true || buffer->options.indent_width != buffer->options.tab_width; } static ssize_t get_current_indent_bytes(const char *buf, size_t cursor_offset) { size_t tw = buffer->options.tab_width; size_t ibytes = 0; size_t iwidth = 0; for (size_t i = 0; i < cursor_offset; i++) { if (iwidth % buffer->options.indent_width == 0) { ibytes = 0; iwidth = 0; } switch (buf[i]) { case '\t': iwidth = (iwidth + tw) / tw * tw; break; case ' ': iwidth++; break; default: // Cursor not at indentation return -1; } ibytes++; } if (iwidth % buffer->options.indent_width) { // Cursor at middle of indentation level return -1; } return (ssize_t)ibytes; } size_t get_indent_level_bytes_left(void) { LineRef lr; size_t cursor_offset = fetch_this_line(&view->cursor, &lr); if (!cursor_offset) { return 0; } ssize_t ibytes = get_current_indent_bytes(lr.line, cursor_offset); return (ibytes < 0) ? 0 : (size_t)ibytes; } size_t get_indent_level_bytes_right(void) { LineRef lr; size_t cursor_offset = fetch_this_line(&view->cursor, &lr); ssize_t ibytes = get_current_indent_bytes(lr.line, cursor_offset); if (ibytes < 0) { return 0; } size_t tw = buffer->options.tab_width; size_t iwidth = 0; for (size_t i = cursor_offset, n = lr.size; i < n; i++) { switch (lr.line[i]) { case '\t': iwidth = (iwidth + tw) / tw * tw; break; case ' ': iwidth++; break; default: // No full indentation level at cursor position return 0; } if (iwidth % buffer->options.indent_width == 0) { return i - cursor_offset + 1; } } return 0; } char *alloc_indent(size_t count, size_t *sizep) { char *indent; size_t size; if (use_spaces_for_indent()) { size = buffer->options.indent_width * count; indent = xmalloc(size); memset(indent, ' ', size); } else { size = count; indent = xmalloc(size); memset(indent, '\t', size); } *sizep = size; return indent; } dte-1.9.1/src/indent.h000066400000000000000000000015451354415143300145270ustar00rootroot00000000000000#ifndef INDENT_H #define INDENT_H #include #include typedef struct { // Size in bytes size_t bytes; // Width in chars size_t width; // Number of whole indentation levels (depends on the indent-width option) size_t level; // Only spaces or tabs depending on expand-tab, indent-width and tab-width. // Note that "sane" line can contain spaces after tabs for alignment. bool sane; // The line is empty or contains only white space bool wsonly; } IndentInfo; char *make_indent(size_t width); char *get_indent_for_next_line(const char *line, size_t len); void get_indent_info(const char *buf, size_t len, IndentInfo *info); bool use_spaces_for_indent(void); size_t get_indent_level_bytes_left(void); size_t get_indent_level_bytes_right(void); char *alloc_indent(size_t count, size_t *sizep); #endif dte-1.9.1/src/load-save.c000066400000000000000000000250771354415143300151220ustar00rootroot00000000000000#include #include #include #include #include #include #include "load-save.h" #include "block.h" #include "debug.h" #include "editor.h" #include "encoding/bom.h" #include "encoding/convert.h" #include "encoding/decoder.h" #include "encoding/encoder.h" #include "error.h" #include "util/macros.h" #include "util/path.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" #include "util/xsnprintf.h" static void add_block(Buffer *b, Block *blk) { b->nl += blk->nl; list_add_before(&blk->node, &b->blocks); } static Block *add_utf8_line ( Buffer *b, Block *blk, const unsigned char *line, size_t len ) { size_t size = len + 1; if (blk) { size_t avail = blk->alloc - blk->size; if (size <= avail) { goto copy; } add_block(b, blk); } if (size < 8192) { size = 8192; } blk = block_new(size); copy: memcpy(blk->data + blk->size, line, len); blk->size += len; blk->data[blk->size++] = '\n'; blk->nl++; return blk; } static int decode_and_add_blocks ( Buffer *b, const unsigned char *buf, size_t size ) { EncodingType bom_type = detect_encoding_from_bom(buf, size); switch (b->encoding.type) { case ENCODING_AUTODETECT: if (bom_type != UNKNOWN_ENCODING) { BUG_ON(b->encoding.name != NULL); Encoding e = encoding_from_type(bom_type); if (encoding_supported_by_iconv(e.name)) { b->encoding = e; } else { b->encoding = encoding_from_type(UTF8); } } break; case UTF16: switch (bom_type) { case UTF16LE: case UTF16BE: b->encoding = encoding_from_type(bom_type); break; default: // "open -e UTF-16" but incompatible or no BOM. // Do what the user wants. Big-endian is default. b->encoding = encoding_from_type(UTF16BE); } break; case UTF32: switch (bom_type) { case UTF32LE: case UTF32BE: b->encoding = encoding_from_type(bom_type); break; default: // "open -e UTF-32" but incompatible or no BOM. // Do what the user wants. Big-endian is default. b->encoding = encoding_from_type(UTF32BE); } break; default: break; } // Skip BOM only if it matches the specified file encoding. if (bom_type != UNKNOWN_ENCODING && bom_type == b->encoding.type) { const size_t bom_len = get_bom_for_encoding(bom_type)->len; buf += bom_len; size -= bom_len; } FileDecoder *dec = new_file_decoder(b->encoding.name, buf, size); if (dec == NULL) { return -1; } char *line; size_t len; if (file_decoder_read_line(dec, &line, &len)) { if (len && line[len - 1] == '\r') { b->newline = NEWLINE_DOS; len--; } Block *blk = add_utf8_line(b, NULL, line, len); while (file_decoder_read_line(dec, &line, &len)) { if (b->newline == NEWLINE_DOS && len && line[len - 1] == '\r') { len--; } blk = add_utf8_line(b, blk, line, len); } if (blk) { add_block(b, blk); } } if (b->encoding.type == ENCODING_AUTODETECT) { if (dec->encoding) { b->encoding = encoding_from_name(dec->encoding); } else { b->encoding = editor.charset; } } free_file_decoder(dec); return 0; } static void fixup_blocks(Buffer *b) { if (list_empty(&b->blocks)) { Block *blk = block_new(1); list_add_before(&blk->node, &b->blocks); } else { // Incomplete lines are not allowed because they are // special cases and cause lots of trouble. Block *blk = BLOCK(b->blocks.prev); if (blk->size && blk->data[blk->size - 1] != '\n') { if (blk->size == blk->alloc) { blk->alloc = ROUND_UP(blk->size + 1, 64); xrenew(blk->data, blk->alloc); } blk->data[blk->size++] = '\n'; blk->nl++; b->nl++; } } } int read_blocks(Buffer *b, int fd) { size_t size = b->st.st_size; size_t map_size = 64 * 1024; unsigned char *buf = NULL; bool mapped = false; // st_size is zero for some files in /proc. // Can't mmap files in /proc and /sys. if (size >= map_size) { // NOTE: size must be greater than 0 buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (buf == MAP_FAILED) { buf = NULL; } else { mapped = true; } } if (!mapped) { size_t alloc = map_size; size_t pos = 0; buf = xmalloc(alloc); while (1) { ssize_t rc = xread(fd, buf + pos, alloc - pos); if (rc < 0) { free(buf); return -1; } if (rc == 0) { break; } pos += rc; if (pos == alloc) { alloc *= 2; xrenew(buf, alloc); } } size = pos; } int rc = decode_and_add_blocks(b, buf, size); if (mapped) { munmap(buf, size); } else { free(buf); } if (rc) { return rc; } fixup_blocks(b); return rc; } int load_buffer(Buffer *b, bool must_exist, const char *filename) { int fd = open(filename, O_RDONLY); if (fd < 0) { if (errno != ENOENT) { error_msg("Error opening %s: %s", filename, strerror(errno)); return -1; } if (must_exist) { error_msg("File %s does not exist.", filename); return -1; } fixup_blocks(b); } else { if (fstat(fd, &b->st) != 0) { error_msg("fstat failed on %s: %s", filename, strerror(errno)); close(fd); return -1; } if (!S_ISREG(b->st.st_mode)) { error_msg("Not a regular file %s", filename); close(fd); return -1; } if (b->st.st_size / 1024 / 1024 > editor.options.filesize_limit) { error_msg ( "File size exceeds 'filesize-limit' option (%uMiB): %s", editor.options.filesize_limit, filename ); close(fd); return -1; } if (read_blocks(b, fd)) { error_msg("Error reading %s: %s", filename, strerror(errno)); close(fd); return -1; } close(fd); } if (b->encoding.type == ENCODING_AUTODETECT) { b->encoding = editor.charset; } return 0; } static mode_t get_umask(void) { // Wonderful get-and-set API mode_t old = umask(0); umask(old); return old; } static int write_buffer(Buffer *b, FileEncoder *enc, EncodingType bom_type) { size_t size = 0; if (bom_type != UTF8) { const ByteOrderMark *bom = get_bom_for_encoding(bom_type); if (bom) { size = bom->len; if (xwrite(enc->fd, bom->bytes, size) < 0) { error_msg("Write error: %s", strerror(errno)); return -1; } } } Block *blk; block_for_each(blk, &b->blocks) { ssize_t rc = file_encoder_write(enc, blk->data, blk->size); if (rc < 0) { error_msg("Write error: %s", strerror(errno)); return -1; } size += rc; } if (enc->cconv != NULL && cconv_nr_errors(enc->cconv)) { // Any real error hides this message error_msg ( "Warning: %zu nonreversible character conversions. File saved.", cconv_nr_errors(enc->cconv) ); } // Need to truncate if writing to existing file if (ftruncate(enc->fd, size)) { error_msg("Truncate failed: %s", strerror(errno)); return -1; } return 0; } int save_buffer ( Buffer *b, const char *filename, const Encoding *encoding, LineEndingType newline ) { FileEncoder *enc; int fd = -1; char tmp[8192]; tmp[0] = '\0'; // Don't use temporary file when saving file in /tmp because // crontab command doesn't like the file to be replaced. if (!str_has_prefix(filename, "/tmp/")) { // Try to use temporary file first (safer) const char *base = path_basename(filename); const StringView dir = path_slice_dirname(filename); const int dlen = (int)dir.length; xsnprintf(tmp, sizeof tmp, "%.*s/.tmp.%s.XXXXXX", dlen, dir.data, base); fd = mkstemp(tmp); if (fd < 0) { // No write permission to the directory? tmp[0] = '\0'; } else if (b->st.st_mode) { // Preserve ownership and mode of the original file if possible. UNUSED int u1 = fchown(fd, b->st.st_uid, b->st.st_gid); UNUSED int u2 = fchmod(fd, b->st.st_mode); } else { // New file fchmod(fd, 0666 & ~get_umask()); } } if (fd < 0) { // Overwrite the original file (if exists) directly. // Ownership is preserved automatically if the file exists. mode_t mode = b->st.st_mode; if (mode == 0) { // New file. mode = 0666 & ~get_umask(); } fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, mode); if (fd < 0) { error_msg("Error opening file: %s", strerror(errno)); return -1; } } enc = new_file_encoder(encoding, newline, fd); if (enc == NULL) { // This should never happen because encoding is validated early error_msg("iconv_open: %s", strerror(errno)); close(fd); goto error; } if (write_buffer(b, enc, encoding->type)) { close(fd); goto error; } if (close(fd)) { error_msg("Close failed: %s", strerror(errno)); goto error; } if (*tmp && rename(tmp, filename)) { error_msg("Rename failed: %s", strerror(errno)); goto error; } free_file_encoder(enc); stat(filename, &b->st); return 0; error: if (enc != NULL) { free_file_encoder(enc); } if (*tmp) { unlink(tmp); } else { // Not using temporary file therefore mtime may have changed. // Update stat to avoid "File has been modified by someone else" // error later when saving the file again. stat(filename, &b->st); } return -1; } dte-1.9.1/src/load-save.h000066400000000000000000000004211354415143300151110ustar00rootroot00000000000000#ifndef LOAD_SAVE_H #define LOAD_SAVE_H #include "buffer.h" int load_buffer(Buffer *b, bool must_exist, const char *filename); int save_buffer(Buffer *b, const char *filename, const Encoding *encoding, LineEndingType newline); int read_blocks(Buffer *b, int fd); #endif dte-1.9.1/src/lock.c000066400000000000000000000111731354415143300141670ustar00rootroot00000000000000#include #include #include #include #include #include #include "lock.h" #include "buffer.h" #include "editor.h" #include "error.h" #include "util/ascii.h" #include "util/readfile.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" #include "util/xsnprintf.h" static char *file_locks; static char *file_locks_lock; static bool process_exists(pid_t pid) { return !kill(pid, 0); } static pid_t rewrite_lock_file(char *buf, ssize_t *sizep, const char *filename) { size_t filename_len = strlen(filename); pid_t my_pid = getpid(); ssize_t size = *sizep; ssize_t pos = 0; pid_t other_pid = 0; while (pos < size) { ssize_t bol = pos; bool remove_line = false; pid_t pid = 0; while (pos < size && ascii_isdigit(buf[pos])) { pid *= 10; pid += buf[pos++] - '0'; } while (pos < size && (buf[pos] == ' ' || buf[pos] == '\t')) { pos++; } char *nl = memchr(buf + pos, '\n', size - pos); ssize_t next_bol = nl - buf + 1; bool same = filename_len == next_bol - 1 - pos && !memcmp(buf + pos, filename, filename_len) ; if (pid == my_pid) { if (same) { // lock = 1 => pid conflict. lock must be stale // lock = 0 => normal unlock case remove_line = true; } } else if (process_exists(pid)) { if (same) { other_pid = pid; } } else { // Release lock from dead process remove_line = true; } if (remove_line) { memmove(buf + bol, buf + next_bol, size - next_bol); size -= next_bol - bol; pos = bol; } else { pos = next_bol; } } *sizep = size; return other_pid; } static int lock_or_unlock(const char *filename, bool lock) { if (!file_locks) { file_locks = editor_file("file-locks"); file_locks_lock = editor_file("file-locks.lock"); } if (streq(filename, file_locks) || streq(filename, file_locks_lock)) { return 0; } int tries = 0; int wfd; while (1) { wfd = open(file_locks_lock, O_WRONLY | O_CREAT | O_EXCL, 0666); if (wfd >= 0) { break; } if (errno != EEXIST) { error_msg ( "Error creating %s: %s", file_locks_lock, strerror(errno) ); return -1; } if (++tries == 3) { if (unlink(file_locks_lock)) { error_msg ( "Error removing stale lock file %s: %s", file_locks_lock, strerror(errno) ); return -1; } error_msg("Stale lock file %s removed.", file_locks_lock); } else { const struct timespec req = { .tv_sec = 0, .tv_nsec = 100 * 1000000, }; nanosleep(&req, NULL); } } char *buf = NULL; ssize_t size = read_file(file_locks, &buf); if (size < 0) { if (errno != ENOENT) { error_msg("Error reading %s: %s", file_locks, strerror(errno)); goto error; } size = 0; } if (size > 0 && buf[size - 1] != '\n') { buf[size++] = '\n'; } pid_t pid = rewrite_lock_file(buf, &size, filename); if (lock) { if (pid == 0) { const size_t n = strlen(filename) + 32; xrenew(buf, size + n); xsnprintf(buf + size, n, "%d %s\n", getpid(), filename); size += strlen(buf + size); } else { error_msg("File is locked (%s) by process %d", file_locks, pid); } } if (xwrite(wfd, buf, size) < 0) { error_msg("Error writing %s: %s", file_locks_lock, strerror(errno)); goto error; } if (close(wfd)) { error_msg("Error closing %s: %s", file_locks_lock, strerror(errno)); goto error; } if (rename(file_locks_lock, file_locks)) { error_msg ( "Renaming %s to %s: %s", file_locks_lock, file_locks, strerror(errno) ); goto error; } free(buf); return pid == 0 ? 0 : -1; error: unlink(file_locks_lock); free(buf); close(wfd); return -1; } int lock_file(const char *filename) { return lock_or_unlock(filename, true); } void unlock_file(const char *filename) { lock_or_unlock(filename, false); } dte-1.9.1/src/lock.h000066400000000000000000000001641354415143300141720ustar00rootroot00000000000000#ifndef LOCK_H #define LOCK_H int lock_file(const char *filename); void unlock_file(const char *filename); #endif dte-1.9.1/src/main.c000066400000000000000000000270341354415143300141660ustar00rootroot00000000000000#include #include #include #include #include #include #include "alias.h" #include "config.h" #include "debug.h" #include "editor.h" #include "error.h" #include "file-history.h" #include "frame.h" #include "history.h" #include "load-save.h" #include "move.h" #include "screen.h" #include "search.h" #include "syntax/state.h" #include "syntax/syntax.h" #include "terminal/color.h" #include "terminal/input.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/str-util.h" #include "util/strtonum.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" #include "view.h" #include "window.h" static void handle_sigtstp(int UNUSED_ARG(signum)) { suspend(); } static void handle_sigcont(int UNUSED_ARG(signum)) { if ( !editor.child_controls_terminal && editor.status != EDITOR_INITIALIZING ) { terminal.raw(); editor.resize(); } } static void handle_fatal_signal(int signum) { term_cleanup(); struct sigaction sa; MEMZERO(&sa); sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_DFL; sigaction(signum, &sa, NULL); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, signum); sigprocmask(SIG_UNBLOCK, &mask, NULL); raise(signum); } static void do_sigaction(int sig, const struct sigaction *action) { if (sigaction(sig, action, NULL) != 0) { BUG("Failed to add handler for signal %d: %s", sig, strerror(errno)); } } static void set_signal_handlers(void) { // SIGABRT is not included here, since we can always call // term_cleanup() explicitly, before calling abort(). static const int fatal_signals[] = { SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGSYS, SIGTRAP, SIGXCPU, SIGXFSZ, SIGALRM, SIGPROF, SIGVTALRM, SIGHUP, SIGTERM, }; static const int ignored_signals[] = { SIGINT, SIGQUIT, SIGPIPE, SIGUSR1, SIGUSR2, }; static const struct { int signum; void (*handler)(int); } handled_signals[] = { {SIGTSTP, handle_sigtstp}, {SIGCONT, handle_sigcont}, #ifdef SIGWINCH {SIGWINCH, handle_sigwinch}, #endif }; struct sigaction action; MEMZERO(&action); sigfillset(&action.sa_mask); action.sa_handler = handle_fatal_signal; for (size_t i = 0; i < ARRAY_COUNT(fatal_signals); i++) { do_sigaction(fatal_signals[i], &action); } action.sa_handler = SIG_IGN; for (size_t i = 0; i < ARRAY_COUNT(ignored_signals); i++) { do_sigaction(ignored_signals[i], &action); } sigemptyset(&action.sa_mask); for (size_t i = 0; i < ARRAY_COUNT(handled_signals); i++) { action.sa_handler = handled_signals[i].handler; do_sigaction(handled_signals[i].signum, &action); } } static int dump_builtin_config(const char *const name) { const BuiltinConfig *cfg = get_builtin_config(name); if (cfg) { xwrite(STDOUT_FILENO, cfg->text.data, cfg->text.length); return 0; } else { fprintf(stderr, "Error: no built-in config with name '%s'\n", name); return 1; } } static void showkey_loop(void) { terminal.raw(); terminal.put_control_code(terminal.control_codes.init); terminal.put_control_code(terminal.control_codes.keypad_on); term_add_literal("Press any key combination, or use Ctrl+D to exit\r\n"); term_output_flush(); bool loop = true; while (loop) { KeyCode key; if (!term_read_key(&key)) { term_add_literal(" UNKNOWN -\r\n"); term_output_flush(); continue; } switch (key) { case KEY_PASTE: term_discard_paste(); continue; case CTRL('D'): loop = false; break; } const char *str = key_to_string(key); term_sprintf(" %-12s 0x%-12" PRIX32 "\r\n", str, key); term_output_flush(); } terminal.put_control_code(terminal.control_codes.keypad_off); terminal.put_control_code(terminal.control_codes.deinit); term_output_flush(); terminal.cooked(); } static const char usage[] = "Usage: %s [OPTIONS] [[+LINE] FILE]...\n\n" "Options:\n" " -c COMMAND Run COMMAND after editor starts\n" " -t CTAG Jump to source location of CTAG\n" " -r RCFILE Read user config from RCFILE instead of ~/.dte/rc\n" " -s FILE Validate dte-syntax commands in FILE and exit\n" " -b NAME Print built-in config matching NAME and exit\n" " -B Print list of built-in config names and exit\n" " -H Don't load or save history files\n" " -R Don't read user config file\n" " -K Start editor in \"showkey\" mode\n" " -h Display help summary and exit\n" " -V Display version number and exit\n" "\n"; int main(int argc, char *argv[]) { static const char optstring[] = "hBHKRVb:c:t:r:s:"; const char *tag = NULL; const char *rc = NULL; const char *command = NULL; const char *lint_syntax = NULL; bool read_rc = true; bool use_showkey = false; bool load_and_save_history = true; int ch; init_editor_state(); while ((ch = getopt(argc, argv, optstring)) != -1) { switch (ch) { case 'c': command = optarg; break; case 't': tag = optarg; break; case 'r': rc = optarg; break; case 's': lint_syntax = optarg; goto loop_break; case 'R': read_rc = false; break; case 'b': return dump_builtin_config(optarg); case 'B': list_builtin_configs(); return 0; case 'H': load_and_save_history = false; break; case 'K': use_showkey = true; goto loop_break; case 'V': printf("dte %s\n", editor.version); puts("(C) 2017-2019 Craig Barnes"); puts("(C) 2010-2015 Timo Hirvonen"); return 0; case 'h': printf(usage, argv[0]); return 0; case '?': default: return 1; } } loop_break: if (lint_syntax) { int err; const Syntax *s = load_syntax_file(lint_syntax, CFG_MUST_EXIST, &err); if (s) { const size_t n = s->states.count; const char *p = (n > 1) ? "s" : ""; printf("OK: loaded syntax '%s' with %zu state%s\n", s->name, n, p); } else if (err == EINVAL) { error_msg("%s: no default syntax found", lint_syntax); } return get_nr_errors() ? 1 : 0; } if (!isatty(STDOUT_FILENO)) { fputs("stdout doesn't refer to a terminal\n", stderr); return 1; } Buffer *stdin_buffer = NULL; if (!isatty(STDIN_FILENO)) { Buffer *b = buffer_new(&editor.charset); if (read_blocks(b, STDIN_FILENO) == 0) { b->display_filename = xmemdup_literal("(stdin)"); stdin_buffer = b; } else { free_buffer(b); error_msg("Unable to read redirected stdin"); } if (!freopen("/dev/tty", "r", stdin)) { fputs("Cannot reopen input tty\n", stderr); return 1; } } term_init(); if (use_showkey) { showkey_loop(); return 0; } // Create this early. Needed if lock-files is true. const char *const editor_dir = editor.user_config_dir; if (mkdir(editor_dir, 0755) != 0 && errno != EEXIST) { error_msg("Error creating %s: %s", editor_dir, strerror(errno)); } terminal.save_title(); exec_reset_colors_rc(); read_config(commands, "rc", CFG_MUST_EXIST | CFG_BUILTIN); fill_builtin_colors(); // NOTE: syntax_changed() uses window. Should possibly create // window after reading rc. window = new_window(); root_frame = new_root_frame(window); if (read_rc) { if (rc) { read_config(commands, rc, CFG_MUST_EXIST); } else { char *filename = editor_file("rc"); read_config(commands, filename, CFG_NOFLAGS); free(filename); } } update_all_syntax_colors(); sort_aliases(); set_signal_handlers(); char *file_history_filename = NULL; char *command_history_filename = NULL; char *search_history_filename = NULL; if (load_and_save_history) { file_history_filename = editor_file("file-history"); command_history_filename = editor_file("command-history"); search_history_filename = editor_file("search-history"); load_file_history(file_history_filename); history_load ( &editor.command_history, command_history_filename, command_history_size ); history_load ( &editor.search_history, search_history_filename, search_history_size ); if (editor.search_history.count) { search_set_regexp ( editor.search_history.ptrs[editor.search_history.count - 1] ); } } // Initialize terminal but don't update screen yet. Also display // "Press any key to continue" prompt if there were any errors // during reading configuration files. terminal.raw(); if (get_nr_errors()) { any_key(); clear_error(); } editor.status = EDITOR_RUNNING; for (int i = optind, lineno = 0; i < argc; i++) { if (argv[i][0] == '+' && lineno <= 0) { const char *const lineno_string = &argv[i][1]; if (!str_to_int(lineno_string, &lineno) || lineno <= 0) { error_msg("Invalid line number: '%s'", lineno_string); } } else { View *v = window_open_buffer(window, argv[i], false, NULL); if (lineno > 0) { set_view(v); move_to_line(v, lineno); lineno = 0; } } } if (stdin_buffer) { window_add_buffer(window, stdin_buffer); } const View *empty_buffer = NULL; if (window->views.count == 0) { empty_buffer = window_open_empty_buffer(window); } set_view(window->views.ptrs[0]); if (command || tag) { editor.resize(); } if (command) { handle_command(commands, command); } if (tag) { const char *tag_command[] = {"tag", tag, NULL}; run_command(commands, (char**)tag_command); } if ( // If window_open_empty_buffer() was called above empty_buffer // ...and no commands were executed via the "-c" flag && !command // ...and a file was opened via the "-t" flag && tag && window->views.count > 1 ) { // Close the empty buffer, leaving just the buffer opened via "-t" remove_view(window->views.ptrs[0]); } terminal.put_control_code(terminal.control_codes.init); editor.resize(); main_loop(); terminal.restore_title(); editor.ui_end(); terminal.put_control_code(terminal.control_codes.deinit); term_output_flush(); // Unlock files and add files to file history remove_frame(root_frame); if (load_and_save_history) { history_save(&editor.command_history, command_history_filename); free(command_history_filename); history_save(&editor.search_history, search_history_filename); free(search_history_filename); save_file_history(file_history_filename); free(file_history_filename); } return 0; } dte-1.9.1/src/mode-command.c000066400000000000000000000026551354415143300156040ustar00rootroot00000000000000#include "cmdline.h" #include "command.h" #include "completion.h" #include "editor.h" #include "error.h" #include "history.h" #include "mode.h" static void command_mode_handle_enter(void) { reset_completion(); set_input_mode(INPUT_NORMAL); const char *str = string_borrow_cstring(&editor.cmdline.buf); PointerArray array = PTR_ARRAY_INIT; CommandParseError err = 0; bool ok = parse_commands(&array, str, &err); // This is done before run_commands() because "command [text]" // can modify the contents of the command-line history_add(&editor.command_history, str, command_history_size); cmdline_clear(&editor.cmdline); if (ok) { run_commands(commands, &array); } else { error_msg("Parsing command: %s", command_parse_error_to_string(err)); } ptr_array_free(&array); } static void command_mode_keypress(KeyCode key) { switch (key) { case KEY_ENTER: command_mode_handle_enter(); return; case '\t': complete_command(); return; } switch (cmdline_handle_key(&editor.cmdline, &editor.command_history, key)) { case CMDLINE_KEY_HANDLED: reset_completion(); return; case CMDLINE_CANCEL: set_input_mode(INPUT_NORMAL); return; case CMDLINE_UNKNOWN_KEY: return; } } const EditorModeOps command_mode_ops = { .keypress = command_mode_keypress, .update = normal_update, }; dte-1.9.1/src/mode-git-open.c000066400000000000000000000162451354415143300157100ustar00rootroot00000000000000#include "cmdline.h" #include "editor.h" #include "error.h" #include "mode.h" #include "screen.h" #include "spawn.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/ascii.h" #include "util/ptr-array.h" #include "util/utf8.h" #include "util/unicode.h" #include "util/xmalloc.h" #include "window.h" static struct { PointerArray files; char *all_files; size_t size; size_t selected; size_t scroll; } git_open; static void git_open_clear(void) { free(git_open.all_files); git_open.all_files = NULL; git_open.size = 0; git_open.files.count = 0; git_open.selected = 0; git_open.scroll = 0; } static char *cdup(void) { static const char *const cmd[] = {"git", "rev-parse", "--show-cdup", NULL}; FilterData data = FILTER_DATA_INIT; if (spawn_filter((char **)cmd, &data)) { return NULL; } const size_t len = data.out_len; if (len > 1 && data.out[len - 1] == '\n') { data.out[len - 1] = '\0'; return data.out; } free(data.out); return NULL; } static void git_open_load(void) { static const char *cmd[] = {"git", "ls-files", "-z", NULL, NULL}; FilterData data = FILTER_DATA_INIT; int status = 0; char *dir = cdup(); cmd[3] = dir; if ((status = spawn_filter((char **)cmd, &data)) == 0) { git_open.all_files = data.out; git_open.size = data.out_len; } else { set_input_mode(INPUT_NORMAL); error_msg("git-open: 'git ls-files' command returned %d", status); } free(dir); } static bool contains_upper(const char *str) { size_t i = 0; while (str[i]) { if (u_is_upper(u_str_get_char(str, &i))) { return true; } } return false; } static void split(PointerArray *words, const char *str) { size_t i = 0; while (str[i]) { while (ascii_isspace(str[i])) { i++; } if (!str[i]) { break; } const size_t s = i++; while (str[i] && !ascii_isspace(str[i])) { i++; } ptr_array_add(words, xstrslice(str, s, i)); } } static bool words_match(const char *name, const PointerArray *words) { for (size_t i = 0, n = words->count; i < n; i++) { if (!strstr(name, words->ptrs[i])) { return false; } } return true; } static bool words_match_icase(const char *name, const PointerArray *words) { for (size_t i = 0, n = words->count; i < n; i++) { if (u_str_index(name, words->ptrs[i]) < 0) { return false; } } return true; } static const char *selected_file(void) { if (git_open.files.count == 0) { return NULL; } return git_open.files.ptrs[git_open.selected]; } static void git_open_filter(void) { const char *str = string_borrow_cstring(&editor.cmdline.buf); char *ptr = git_open.all_files; char *end = git_open.all_files + git_open.size; bool (*match)(const char*, const PointerArray*) = words_match_icase; PointerArray words = PTR_ARRAY_INIT; // NOTE: words_match_icase() requires str to be lowercase if (contains_upper(str)) { match = words_match; } split(&words, str); git_open.files.count = 0; while (ptr < end) { char *zero = memchr(ptr, 0, end - ptr); if (zero == NULL) { break; } if (match(ptr, &words)) { ptr_array_add(&git_open.files, ptr); } ptr = zero + 1; } ptr_array_free(&words); git_open.selected = 0; git_open.scroll = 0; } static void up(size_t count) { if (count >= git_open.selected) { git_open.selected = 0; } else { git_open.selected -= count; } } static void down(size_t count) { if (git_open.files.count > 1) { git_open.selected += count; if (git_open.selected >= git_open.files.count) { git_open.selected = git_open.files.count - 1; } } } static void open_selected(void) { const char *sel = selected_file(); if (sel != NULL) { window_open_file(window, sel, NULL); } } void git_open_reload(void) { git_open_clear(); git_open_load(); git_open_filter(); } static size_t terminal_page_height(void) { if (terminal.height >= 6) { return terminal.height - 2; } else { return 1; } } static void git_open_keypress(KeyCode key) { switch (key) { case KEY_ENTER: open_selected(); cmdline_clear(&editor.cmdline); set_input_mode(INPUT_NORMAL); break; case CTRL('O'): open_selected(); down(1); break; case MOD_META | 'e': if (git_open.files.count > 1) { git_open.selected = git_open.files.count - 1; } break; case MOD_META | 't': git_open.selected = 0; break; case KEY_UP: up(1); break; case KEY_DOWN: down(1); break; case KEY_PAGE_UP: up(terminal_page_height()); break; case KEY_PAGE_DOWN: down(terminal_page_height()); break; case '\t': if (git_open.selected + 1 >= git_open.files.count) { git_open.selected = 0; } else { down(1); } break; default: switch (cmdline_handle_key(&editor.cmdline, NULL, key)) { case CMDLINE_UNKNOWN_KEY: break; case CMDLINE_KEY_HANDLED: git_open_filter(); break; case CMDLINE_CANCEL: set_input_mode(INPUT_NORMAL); break; } } mark_everything_changed(); } static void git_open_update_screen(void) { int x = 0; int y = 0; int w = terminal.width; int h = terminal.height - 1; int max_y = git_open.scroll + h - 1; int i = 0; if (h >= git_open.files.count) { git_open.scroll = 0; } if (git_open.scroll > git_open.selected) { git_open.scroll = git_open.selected; } if (git_open.selected > max_y) { git_open.scroll += git_open.selected - max_y; } term_output_reset(x, w, 0); terminal.move_cursor(0, 0); editor.cmdline_x = print_command('/'); term_clear_eol(); y++; for (; i < h; i++) { int file_idx = git_open.scroll + i; char *file; TermColor color; if (file_idx >= git_open.files.count) { break; } file = git_open.files.ptrs[file_idx]; obuf.x = 0; terminal.move_cursor(x, y + i); color = *builtin_colors[BC_DEFAULT]; if (file_idx == git_open.selected) { mask_color(&color, builtin_colors[BC_SELECTION]); } terminal.set_color(&color); term_add_str(file); term_clear_eol(); } set_builtin_color(BC_DEFAULT); for (; i < h; i++) { obuf.x = 0; terminal.move_cursor(x, y + i); term_clear_eol(); } } static void git_open_update(void) { term_hide_cursor(); update_term_title(window->view->buffer); git_open_update_screen(); terminal.move_cursor(editor.cmdline_x, 0); term_show_cursor(); term_output_flush(); } const EditorModeOps git_open_ops = { .keypress = git_open_keypress, .update = git_open_update, }; dte-1.9.1/src/mode-normal.c000066400000000000000000000022011354415143300154410ustar00rootroot00000000000000#include "bind.h" #include "change.h" #include "edit.h" #include "editor.h" #include "mode.h" #include "terminal/input.h" #include "util/unicode.h" #include "view.h" #include "window.h" static void insert_paste(void) { size_t size; char *text = term_read_paste(&size); // Because this is not a command (see run_command()) you have to // call begin_change() to avoid merging this change into previous begin_change(CHANGE_MERGE_NONE); insert_text(text, size); end_change(); free(text); } static void normal_mode_keypress(KeyCode key) { switch (key) { case '\t': if (view->selection == SELECT_LINES) { shift_lines(1); return; } break; case MOD_SHIFT | '\t': if (view->selection == SELECT_LINES) { shift_lines(-1); return; } break; case KEY_PASTE: insert_paste(); return; } if (u_is_unicode(key)) { insert_ch(key); } else { handle_binding(key); } } const EditorModeOps normal_mode_ops = { .keypress = normal_mode_keypress, .update = normal_update, }; dte-1.9.1/src/mode-search.c000066400000000000000000000023051354415143300154230ustar00rootroot00000000000000#include "cmdline.h" #include "editor.h" #include "history.h" #include "mode.h" #include "search.h" static void search_mode_keypress(KeyCode key) { switch (key) { case KEY_ENTER: if (editor.cmdline.buf.len > 0) { const char *str = string_borrow_cstring(&editor.cmdline.buf); search_set_regexp(str); search_next(); history_add(&editor.search_history, str, search_history_size); } else { search_next(); } cmdline_clear(&editor.cmdline); set_input_mode(INPUT_NORMAL); return; case MOD_META | 'c': editor.options.case_sensitive_search = (editor.options.case_sensitive_search + 1) % 3; return; case MOD_META | 'r': search_set_direction(current_search_direction() ^ 1); return; case '\t': return; } switch (cmdline_handle_key(&editor.cmdline, &editor.search_history, key)) { case CMDLINE_CANCEL: set_input_mode(INPUT_NORMAL); return; case CMDLINE_UNKNOWN_KEY: case CMDLINE_KEY_HANDLED: return; } } const EditorModeOps search_mode_ops = { .keypress = search_mode_keypress, .update = normal_update, }; dte-1.9.1/src/mode.h000066400000000000000000000005551354415143300141720ustar00rootroot00000000000000#ifndef MODE_H #define MODE_H #include "terminal/key.h" typedef struct { void (*keypress)(KeyCode key); void (*update)(void); } EditorModeOps; extern const EditorModeOps normal_mode_ops; extern const EditorModeOps command_mode_ops; extern const EditorModeOps search_mode_ops; extern const EditorModeOps git_open_ops; void git_open_reload(void); #endif dte-1.9.1/src/move.c000066400000000000000000000145321354415143300142070ustar00rootroot00000000000000#include "move.h" #include "buffer.h" #include "indent.h" #include "util/ascii.h" #include "util/utf8.h" typedef enum { CT_SPACE, CT_NEWLINE, CT_WORD, CT_OTHER, } CharTypeEnum; void move_to_preferred_x(long preferred_x) { LineRef lr; view->preferred_x = preferred_x; block_iter_bol(&view->cursor); fill_line_ref(&view->cursor, &lr); if (buffer->options.emulate_tab && view->preferred_x < lr.size) { const size_t iw = buffer->options.indent_width; const size_t ilevel = view->preferred_x / iw; for (size_t i = 0; i < lr.size && lr.line[i] == ' '; i++) { if (i + 1 == (ilevel + 1) * iw) { // Force cursor to beginning of the indentation level view->cursor.offset += ilevel * iw; return; } } } const unsigned int tw = buffer->options.tab_width; unsigned long x = 0; size_t i = 0; while (x < view->preferred_x && i < lr.size) { CodePoint u = lr.line[i++]; if (u < 0x80) { if (!ascii_iscntrl(u)) { x++; } else if (u == '\t') { x = (x + tw) / tw * tw; } else if (u == '\n') { break; } else { x += 2; } } else { const size_t next = i; i--; u = u_get_nonascii(lr.line, lr.size, &i); x += u_char_width(u); if (x > view->preferred_x) { i = next; break; } } } if (x > view->preferred_x) { i--; } view->cursor.offset += i; // If cursor stopped on a zero-width char, move to the next spacing char. // TODO: Incorporate this cursor fixup into the logic above. CodePoint u; if (block_iter_get_char(&view->cursor, &u) && u_is_zero_width(u)) { block_iter_next_column(&view->cursor); } } void move_cursor_left(void) { if (buffer->options.emulate_tab) { size_t size = get_indent_level_bytes_left(); if (size) { block_iter_back_bytes(&view->cursor, size); view_reset_preferred_x(view); return; } } block_iter_prev_column(&view->cursor); view_reset_preferred_x(view); } void move_cursor_right(void) { if (buffer->options.emulate_tab) { size_t size = get_indent_level_bytes_right(); if (size) { block_iter_skip_bytes(&view->cursor, size); view_reset_preferred_x(view); return; } } block_iter_next_column(&view->cursor); view_reset_preferred_x(view); } void move_bol(void) { block_iter_bol(&view->cursor); view_reset_preferred_x(view); } void move_bol_smart(void) { LineRef lr; const size_t cursor_offset = fetch_this_line(&view->cursor, &lr); size_t indent_bytes = 0; while (ascii_isblank(*lr.line++)) { indent_bytes++; } size_t move_bytes; if (cursor_offset > indent_bytes) { move_bytes = cursor_offset - indent_bytes; } else { move_bytes = cursor_offset; } block_iter_back_bytes(&view->cursor, move_bytes); view_reset_preferred_x(view); } void move_eol(void) { block_iter_eol(&view->cursor); view_reset_preferred_x(view); } void move_up(long count) { const long x = view_get_preferred_x(view); while (count > 0) { if (!block_iter_prev_line(&view->cursor)) { break; } count--; } move_to_preferred_x(x); } void move_down(long count) { const long x = view_get_preferred_x(view); while (count > 0) { if (!block_iter_eat_line(&view->cursor)) { break; } count--; } move_to_preferred_x(x); } void move_bof(void) { block_iter_bof(&view->cursor); view_reset_preferred_x(view); } void move_eof(void) { block_iter_eof(&view->cursor); view_reset_preferred_x(view); } void move_to_line(View *v, size_t line) { block_iter_goto_line(&v->cursor, line - 1); v->center_on_scroll = true; } void move_to_column(View *v, size_t column) { block_iter_bol(&v->cursor); while (column-- > 1) { CodePoint u; if (!block_iter_next_char(&v->cursor, &u)) { break; } if (u == '\n') { block_iter_prev_char(&v->cursor, &u); break; } } view_reset_preferred_x(v); } static CharTypeEnum get_char_type(CodePoint u) { if (u == '\n') { return CT_NEWLINE; } if (u_is_breakable_whitespace(u)) { return CT_SPACE; } if (u_is_word_char(u)) { return CT_WORD; } return CT_OTHER; } static bool get_current_char_type(BlockIter *bi, CharTypeEnum *type) { CodePoint u; if (!block_iter_get_char(bi, &u)) { return false; } *type = get_char_type(u); return true; } static size_t skip_fwd_char_type(BlockIter *bi, CharTypeEnum type) { size_t count = 0; CodePoint u; while (block_iter_next_char(bi, &u)) { if (get_char_type(u) != type) { block_iter_prev_char(bi, &u); break; } count += u_char_size(u); } return count; } static size_t skip_bwd_char_type(BlockIter *bi, CharTypeEnum type) { size_t count = 0; CodePoint u; while (block_iter_prev_char(bi, &u)) { if (get_char_type(u) != type) { block_iter_next_char(bi, &u); break; } count += u_char_size(u); } return count; } size_t word_fwd(BlockIter *bi, bool skip_non_word) { size_t count = 0; CharTypeEnum type; while (1) { count += skip_fwd_char_type(bi, CT_SPACE); if (!get_current_char_type(bi, &type)) { return count; } if ( count && (!skip_non_word || (type == CT_WORD || type == CT_NEWLINE)) ) { return count; } count += skip_fwd_char_type(bi, type); } } size_t word_bwd(BlockIter *bi, bool skip_non_word) { size_t count = 0; CharTypeEnum type; CodePoint u; do { count += skip_bwd_char_type(bi, CT_SPACE); if (!block_iter_prev_char(bi, &u)) { return count; } type = get_char_type(u); count += u_char_size(u); count += skip_bwd_char_type(bi, type); } while (skip_non_word && type != CT_WORD && type != CT_NEWLINE); return count; } dte-1.9.1/src/move.h000066400000000000000000000010621354415143300142060ustar00rootroot00000000000000#ifndef MOVE_H #define MOVE_H #include #include "block-iter.h" #include "view.h" void move_to_preferred_x(long preferred_x); void move_cursor_left(void); void move_cursor_right(void); void move_bol(void); void move_bol_smart(void); void move_eol(void); void move_up(long count); void move_down(long count); void move_bof(void); void move_eof(void); void move_to_line(View *v, size_t line); void move_to_column(View *v, size_t column); size_t word_fwd(BlockIter *bi, bool skip_non_word); size_t word_bwd(BlockIter *bi, bool skip_non_word); #endif dte-1.9.1/src/msg.c000066400000000000000000000110731354415143300140240ustar00rootroot00000000000000#include "msg.h" #include "buffer.h" #include "error.h" #include "move.h" #include "search.h" #include "util/ptr-array.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "window.h" static PointerArray file_locations = PTR_ARRAY_INIT; static PointerArray msgs = PTR_ARRAY_INIT; static size_t msg_pos; FileLocation *file_location_create ( const char *filename, unsigned long buffer_id, unsigned long line, unsigned long column ) { FileLocation *loc = xnew0(FileLocation, 1); loc->filename = filename ? xstrdup(filename) : NULL; loc->buffer_id = buffer_id; loc->line = line; loc->column = column; return loc; } void file_location_free(FileLocation *loc) { free(loc->filename); free(loc->pattern); free(loc); } static bool file_location_equals(const FileLocation *a, const FileLocation *b) { if (!xstreq(a->filename, b->filename)) { return false; } if (a->buffer_id != b->buffer_id) { return false; } if (!xstreq(a->pattern, b->pattern)) { return false; } if (a->line != b->line) { return false; } if (a->column != b->column) { return false; } return true; } static bool file_location_go(const FileLocation *loc) { Window *w = window; View *v = window_open_buffer(w, loc->filename, true, NULL); bool ok = true; if (!v) { // Failed to open file. Error message should be visible. return false; } if (w->view != v) { set_view(v); // Force centering view to the cursor because file changed v->force_center = true; } if (loc->pattern != NULL) { bool err = false; search_tag(loc->pattern, &err); ok = !err; } else if (loc->line > 0) { move_to_line(v, loc->line); if (loc->column > 0) { move_to_column(v, loc->column); } } return ok; } static bool file_location_return(const FileLocation *loc) { Window *w = window; Buffer *b = find_buffer_by_id(loc->buffer_id); View *v; if (b != NULL) { v = window_get_view(w, b); } else { if (loc->filename == NULL) { // Can't restore closed buffer that had no filename. // Try again. return false; } v = window_open_buffer(w, loc->filename, true, NULL); } if (v == NULL) { // Open failed. Don't try again. return true; } set_view(v); move_to_line(v, loc->line); move_to_column(v, loc->column); return true; } void push_file_location(FileLocation *loc) { ptr_array_add(&file_locations, loc); } void pop_file_location(void) { bool go = true; while (file_locations.count > 0 && go) { FileLocation *loc = file_locations.ptrs[--file_locations.count]; go = !file_location_return(loc); file_location_free(loc); } } static void free_message(Message *m) { free(m->msg); if (m->loc != NULL) { file_location_free(m->loc); } free(m); } static bool message_equals(const Message *a, const Message *b) { if (!streq(a->msg, b->msg)) { return false; } if (a->loc == NULL) { return b->loc == NULL; } if (b->loc == NULL) { return false; } return file_location_equals(a->loc, b->loc); } static bool is_duplicate(const Message *m) { for (size_t i = 0; i < msgs.count; i++) { if (message_equals(m, msgs.ptrs[i])) { return true; } } return false; } Message *new_message(const char *msg) { Message *m = xnew0(Message, 1); m->msg = xstrdup(msg); return m; } void add_message(Message *m) { if (is_duplicate(m)) { free_message(m); } else { ptr_array_add(&msgs, m); } } void activate_current_message(void) { if (msg_pos == msgs.count) { return; } const Message *m = msgs.ptrs[msg_pos]; if (m->loc != NULL && m->loc->filename != NULL) { if (!file_location_go(m->loc)) { // Error message is visible return; } } if (msgs.count == 1) { info_msg("%s", m->msg); } else { info_msg("[%zu/%zu] %s", msg_pos + 1, msgs.count, m->msg); } } void activate_next_message(void) { if (msg_pos + 1 < msgs.count) { msg_pos++; } activate_current_message(); } void activate_prev_message(void) { if (msg_pos > 0) { msg_pos--; } activate_current_message(); } void clear_messages(void) { ptr_array_free_cb(&msgs, FREE_FUNC(free_message)); msg_pos = 0; } size_t message_count(void) { return msgs.count; } dte-1.9.1/src/msg.h000066400000000000000000000020001354415143300140170ustar00rootroot00000000000000#ifndef MSG_H #define MSG_H #include #include "util/macros.h" typedef struct { // Needed after buffer is closed char *filename; // Needed if buffer doesn't have filename. // Pointer would have to be set to NULL after closing buffer. unsigned long buffer_id; // If pattern is set then line and column are 0 and vice versa char *pattern; // Regex from tag file unsigned long line, column; } FileLocation; typedef struct { char *msg; FileLocation *loc; } Message; FileLocation *file_location_create ( const char *filename, unsigned long buffer_id, unsigned long line, unsigned long column ); void file_location_free(FileLocation *loc); void push_file_location(FileLocation *loc); void pop_file_location(void); Message *new_message(const char *msg); void add_message(Message *m); void activate_current_message(void); void activate_next_message(void); void activate_prev_message(void); void clear_messages(void); size_t message_count(void) PURE; #endif dte-1.9.1/src/options.c000066400000000000000000000460661354415143300147430ustar00rootroot00000000000000#include "options.h" #include "completion.h" #include "debug.h" #include "editor.h" #include "error.h" #include "file-option.h" #include "filetype.h" #include "regexp.h" #include "screen.h" #include "terminal/terminal.h" #include "util/hashset.h" #include "util/str-util.h" #include "util/string-view.h" #include "util/strtonum.h" #include "util/xmalloc.h" #include "util/xsnprintf.h" #include "view.h" #include "window.h" typedef enum { OPT_STR, OPT_UINT, OPT_ENUM, OPT_FLAG, } OptionType; typedef struct { const struct OptionOps *ops; const char *name; size_t offset; bool local; bool global; union { struct { // Optional bool (*validate)(const char *value); } str_opt; struct { unsigned int min; unsigned int max; } uint_opt; struct { const char **values; } enum_opt; struct { const char **values; } flag_opt; } u; // Optional void (*on_change)(void); } OptionDesc; typedef union { // OPT_STR const char *str_val; // OPT_UINT, OPT_ENUM, OPT_FLAG unsigned int uint_val; } OptionValue; typedef struct OptionOps { OptionValue (*get)(const OptionDesc *desc, void *ptr); void (*set)(const OptionDesc *desc, void *ptr, OptionValue value); bool (*parse)(const OptionDesc *desc, const char *str, OptionValue *value); const char *(*string)(const OptionDesc *desc, OptionValue value); bool (*equals)(const OptionDesc *desc, void *ptr, OptionValue value); } OptionOps; #define STR_OPT(_name, OLG, _validate, _on_change) { \ .ops = &option_ops[OPT_STR], \ .name = _name, \ OLG \ .u = { .str_opt = { \ .validate = _validate, \ } }, \ .on_change = _on_change, \ } #define UINT_OPT(_name, OLG, _min, _max, _on_change) { \ .ops = &option_ops[OPT_UINT], \ .name = _name, \ OLG \ .u = { .uint_opt = { \ .min = _min, \ .max = _max, \ } }, \ .on_change = _on_change, \ } #define ENUM_OPT(_name, OLG, _values, _on_change) { \ .ops = &option_ops[OPT_ENUM], \ .name = _name, \ OLG \ .u = { .enum_opt = { \ .values = _values, \ } }, \ .on_change = _on_change, \ } #define FLAG_OPT(_name, OLG, _values, _on_change) { \ .ops = &option_ops[OPT_FLAG], \ .name = _name, \ OLG \ .u = { .flag_opt = { \ .values = _values, \ } }, \ .on_change = _on_change, \ } // Can't reuse ENUM_OPT() because of weird macro expansion rules #define BOOL_OPT(_name, OLG, _on_change) { \ .ops = &option_ops[OPT_ENUM], \ .name = _name, \ OLG \ .u = { .enum_opt = { \ .values = bool_enum, \ } }, \ .on_change = _on_change, \ } #define OLG(_offset, _local, _global) \ .offset = _offset, \ .local = _local, \ .global = _global, \ #define L(member) OLG(offsetof(LocalOptions, member), true, false) #define G(member) OLG(offsetof(GlobalOptions, member), false, true) #define C(member) OLG(offsetof(CommonOptions, member), true, true) static void filetype_changed(void) { Buffer *b = window->view->buffer; set_file_options(b); buffer_update_syntax(b); } static void set_window_title_changed(void) { if (editor.options.set_window_title) { if (editor.status == EDITOR_RUNNING) { update_term_title(window->view->buffer); } } else { terminal.restore_title(); terminal.save_title(); } } static void syntax_changed(void) { if (window->view != NULL) { buffer_update_syntax(window->view->buffer); } } static bool validate_statusline_format(const char *value) { static const char chars[] = "fmryYxXpEMnstu%"; size_t i = 0; while (value[i]) { char ch = value[i++]; if (ch == '%') { ch = value[i++]; if (!ch) { error_msg("Format character expected after '%%'."); return false; } if (!strchr(chars, ch)) { error_msg("Invalid format character '%c'.", ch); return false; } } } return true; } static bool validate_filetype(const char *value) { if (!is_ft(value)) { error_msg("No such file type %s", value); return false; } return true; } static bool validate_regex(const char *value) { return value[0] == '\0' || regexp_is_valid(value, REG_NEWLINE); } static OptionValue str_get(const OptionDesc* UNUSED_ARG(desc), void *ptr) { OptionValue v; v.str_val = *(char**)ptr; return v; } static void str_set ( const OptionDesc* UNUSED_ARG(desc), void *ptr, OptionValue value ) { const char **strp = ptr; *strp = str_intern(value.str_val); } static bool str_parse ( const OptionDesc *desc, const char *str, OptionValue *value ) { if (desc->u.str_opt.validate && !desc->u.str_opt.validate(str)) { value->str_val = NULL; return false; } value->str_val = str; return true; } static const char *str_string(const OptionDesc* UNUSED_ARG(desc), OptionValue value) { const char *s = value.str_val; return s ? s : ""; } static bool str_equals ( const OptionDesc* UNUSED_ARG(desc), void *ptr, OptionValue value ) { return xstreq(*(char**)ptr, value.str_val); } static OptionValue uint_get(const OptionDesc* UNUSED_ARG(desc), void *ptr) { OptionValue v; v.uint_val = *(unsigned int*)ptr; return v; } static void uint_set ( const OptionDesc* UNUSED_ARG(desc), void *ptr, OptionValue value ) { *(unsigned int*)ptr = value.uint_val; } static bool uint_parse ( const OptionDesc *desc, const char *str, OptionValue *value ) { unsigned int val; if (!str_to_uint(str, &val)) { error_msg("Integer value for %s expected.", desc->name); return false; } if (val < desc->u.uint_opt.min || val > desc->u.uint_opt.max) { error_msg ( "Value for %s must be in %u-%u range.", desc->name, desc->u.uint_opt.min, desc->u.uint_opt.max ); return false; } value->uint_val = val; return true; } static const char *uint_string(const OptionDesc* UNUSED_ARG(desc), OptionValue value) { static char buf[64]; xsnprintf(buf, sizeof buf, "%u", value.uint_val); return buf; } static bool uint_equals(const OptionDesc* UNUSED_ARG(desc), void *ptr, OptionValue value) { return *(unsigned int*)ptr == value.uint_val; } static bool enum_parse ( const OptionDesc *desc, const char *str, OptionValue *value ) { unsigned int i; for (i = 0; desc->u.enum_opt.values[i]; i++) { if (streq(desc->u.enum_opt.values[i], str)) { value->uint_val = i; return true; } } unsigned int val; if (!str_to_uint(str, &val) || val >= i) { error_msg("Invalid value for %s.", desc->name); return false; } value->uint_val = val; return true; } static const char *enum_string(const OptionDesc *desc, OptionValue value) { return desc->u.enum_opt.values[value.uint_val]; } static bool flag_parse ( const OptionDesc *desc, const char *str, OptionValue *value ) { // "0" is allowed for compatibility and is the same as "" if (str[0] == '0' && str[1] == '\0') { value->uint_val = 0; return true; } const char **values = desc->u.flag_opt.values; const char *ptr = str; unsigned int flags = 0; while (*ptr) { const char *end = strchr(ptr, ','); size_t len; if (end) { len = end - ptr; end++; } else { len = strlen(ptr); end = ptr + len; } const StringView flag = string_view(ptr, len); ptr = end; size_t i; for (i = 0; values[i]; i++) { if (string_view_equal_cstr(&flag, values[i])) { flags |= 1u << i; break; } } if (!values[i]) { error_msg ( "Invalid flag '%.*s' for %s.", (int)flag.length, flag.data, desc->name ); return false; } } value->uint_val = flags; return true; } static const char *flag_string(const OptionDesc *desc, OptionValue value) { static char buf[256]; unsigned int flags = value.uint_val; if (!flags) { buf[0] = '0'; buf[1] = '\0'; return buf; } char *ptr = buf; const char **values = desc->u.flag_opt.values; for (size_t i = 0; values[i]; i++) { if (flags & (1 << i)) { size_t len = strlen(values[i]); memcpy(ptr, values[i], len); ptr += len; *ptr++ = ','; } } ptr[-1] = '\0'; return buf; } static const OptionOps option_ops[] = { [OPT_STR] = {str_get, str_set, str_parse, str_string, str_equals}, [OPT_UINT] = {uint_get, uint_set, uint_parse, uint_string, uint_equals}, [OPT_ENUM] = {uint_get, uint_set, enum_parse, enum_string, uint_equals}, [OPT_FLAG] = {uint_get, uint_set, flag_parse, flag_string, uint_equals}, }; static const char *bool_enum[] = {"false", "true", NULL}; static const char *newline_enum[] = {"unix", "dos", NULL}; static const char *case_sensitive_search_enum[] = { "false", "true", "auto", NULL }; static const char *detect_indent_values[] = { "1", "2", "3", "4", "5", "6", "7", "8", NULL }; static const char *tab_bar_enum[] = { "hidden", "horizontal", "vertical", "auto", NULL }; static const char *ws_error_values[] = { "trailing", "space-indent", "space-align", "tab-indent", "tab-after-indent", "special", "auto-indent", NULL }; static const OptionDesc option_desc[] = { BOOL_OPT("auto-indent", C(auto_indent), NULL), BOOL_OPT("brace-indent", L(brace_indent), NULL), ENUM_OPT("case-sensitive-search", G(case_sensitive_search), case_sensitive_search_enum, NULL), FLAG_OPT("detect-indent", C(detect_indent), detect_indent_values, NULL), BOOL_OPT("display-invisible", G(display_invisible), NULL), BOOL_OPT("display-special", G(display_special), NULL), BOOL_OPT("editorconfig", C(editorconfig), NULL), BOOL_OPT("emulate-tab", C(emulate_tab), NULL), UINT_OPT("esc-timeout", G(esc_timeout), 0, 2000, NULL), BOOL_OPT("expand-tab", C(expand_tab), NULL), BOOL_OPT("file-history", C(file_history), NULL), UINT_OPT("filesize-limit", G(filesize_limit), 0, 16000, NULL), STR_OPT("filetype", L(filetype), validate_filetype, filetype_changed), UINT_OPT("indent-width", C(indent_width), 1, 8, NULL), STR_OPT("indent-regex", L(indent_regex), validate_regex, NULL), BOOL_OPT("lock-files", G(lock_files), NULL), ENUM_OPT("newline", G(newline), newline_enum, NULL), UINT_OPT("scroll-margin", G(scroll_margin), 0, 100, NULL), BOOL_OPT("set-window-title", G(set_window_title), set_window_title_changed), BOOL_OPT("show-line-numbers", G(show_line_numbers), NULL), STR_OPT("statusline-left", G(statusline_left), validate_statusline_format, NULL), STR_OPT("statusline-right", G(statusline_right), validate_statusline_format, NULL), BOOL_OPT("syntax", C(syntax), syntax_changed), ENUM_OPT("tab-bar", G(tab_bar), tab_bar_enum, NULL), UINT_OPT("tab-bar-max-components", G(tab_bar_max_components), 0, 10, NULL), UINT_OPT("tab-bar-width", G(tab_bar_width), TAB_BAR_MIN_WIDTH, 100, NULL), UINT_OPT("tab-width", C(tab_width), 1, 8, NULL), UINT_OPT("text-width", C(text_width), 1, 1000, NULL), FLAG_OPT("ws-error", C(ws_error), ws_error_values, NULL), }; static char *local_ptr(const OptionDesc *desc, const LocalOptions *opt) { return (char*)opt + desc->offset; } static char *global_ptr(const OptionDesc *desc) { return (char*)&editor.options + desc->offset; } static bool desc_is(const OptionDesc *desc, OptionType type) { return desc->ops == &option_ops[type]; } static void desc_set(const OptionDesc *desc, void *ptr, OptionValue value) { desc->ops->set(desc, ptr, value); if (desc->on_change) { desc->on_change(); } else { mark_everything_changed(); } } static const OptionDesc *find_option(const char *name) { for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { const OptionDesc *desc = &option_desc[i]; if (streq(name, desc->name)) { return desc; } } return NULL; } static const OptionDesc *must_find_option(const char *name) { const OptionDesc *desc = find_option(name); if (desc == NULL) { error_msg("No such option %s", name); } return desc; } static const OptionDesc *must_find_global_option(const char *name) { const OptionDesc *desc = must_find_option(name); if (desc && !desc->global) { error_msg("Option %s is not global", name); return NULL; } return desc; } static void do_set_option ( const OptionDesc *desc, const char *value, bool local, bool global ) { if (local && !desc->local) { error_msg("Option %s is not local", desc->name); return; } if (global && !desc->global) { error_msg("Option %s is not global", desc->name); return; } OptionValue val; if (!desc->ops->parse(desc, value, &val)) { return; } if (!local && !global) { // Set both by default if (desc->local) { local = true; } if (desc->global) { global = true; } } if (local) { desc_set(desc, local_ptr(desc, &buffer->options), val); } if (global) { desc_set(desc, global_ptr(desc), val); } } void set_option(const char *name, const char *value, bool local, bool global) { const OptionDesc *desc = must_find_option(name); if (!desc) { return; } do_set_option(desc, value, local, global); } void set_bool_option(const char *name, bool local, bool global) { const OptionDesc *desc = must_find_option(name); if (!desc) { return; } if (!desc_is(desc, OPT_ENUM) || desc->u.enum_opt.values != bool_enum) { error_msg("Option %s is not boolean.", desc->name); return; } do_set_option(desc, "true", local, global); } static const OptionDesc *find_toggle_option(const char *name, bool *global) { if (*global) { return must_find_global_option(name); } // Toggle local value by default if option has both values const OptionDesc *desc = must_find_option(name); if (desc && !desc->local) { *global = true; } return desc; } static unsigned int toggle(unsigned int value, const char **values) { if (!values[++value]) { value = 0; } return value; } void toggle_option(const char *name, bool global, bool verbose) { const OptionDesc *desc = find_toggle_option(name, &global); if (!desc) { return; } if (!desc_is(desc, OPT_ENUM)) { error_msg("Option %s is not toggleable.", name); return; } char *ptr; if (global) { ptr = global_ptr(desc); } else { ptr = local_ptr(desc, &buffer->options); } OptionValue value; value.uint_val = toggle(*(unsigned int *)ptr, desc->u.enum_opt.values); desc_set(desc, ptr, value); if (verbose) { const char *str = desc->ops->string(desc, value); info_msg("%s = %s", desc->name, str); } } void toggle_option_values ( const char *name, bool global, bool verbose, char **values, size_t count ) { const OptionDesc *desc = find_toggle_option(name, &global); if (!desc) { return; } BUG_ON(count == 0); size_t current = 0; bool error = false; char *ptr = global ? global_ptr(desc) : local_ptr(desc, &buffer->options); OptionValue *parsed_values = xnew(OptionValue, count); for (size_t i = 0; i < count; i++) { if (desc->ops->parse(desc, values[i], &parsed_values[i])) { if (desc->ops->equals(desc, ptr, parsed_values[i])) { current = i + 1; } } else { error = true; } } if (!error) { size_t i = current % count; desc_set(desc, ptr, parsed_values[i]); if (verbose) { const char *str = desc->ops->string(desc, parsed_values[i]); info_msg("%s = %s", desc->name, str); } } free(parsed_values); } bool validate_local_options(char **strs) { bool valid = true; for (size_t i = 0; strs[i]; i += 2) { const char *name = strs[i]; const char *value = strs[i + 1]; const OptionDesc *desc = must_find_option(name); if (desc == NULL) { valid = false; } else if (!desc->local) { error_msg("Option %s is not local", name); valid = false; } else if (streq(name, "filetype")) { error_msg("filetype cannot be set via option command"); valid = false; } else { OptionValue val; if (!desc->ops->parse(desc, value, &val)) { valid = false; } } } return valid; } void collect_options(const char *prefix) { for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { const OptionDesc *desc = &option_desc[i]; if (str_has_prefix(desc->name, prefix)) { add_completion(xstrdup(desc->name)); } } } void collect_toggleable_options(const char *prefix) { for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { const OptionDesc *desc = &option_desc[i]; if (desc_is(desc, OPT_ENUM) && str_has_prefix(desc->name, prefix)) { add_completion(xstrdup(desc->name)); } } } void collect_option_values(const char *name, const char *prefix) { const OptionDesc *desc = find_option(name); if (!desc) { return; } if (!*prefix) { // Complete value char *ptr; if (desc->local) { ptr = local_ptr(desc, &buffer->options); } else { ptr = global_ptr(desc); } OptionValue value = desc->ops->get(desc, ptr); add_completion(xstrdup(desc->ops->string(desc, value))); } else if (desc_is(desc, OPT_ENUM)) { // Complete possible values for (size_t i = 0; desc->u.enum_opt.values[i]; i++) { if (str_has_prefix(desc->u.enum_opt.values[i], prefix)) { add_completion(xstrdup(desc->u.enum_opt.values[i])); } } } else if (desc_is(desc, OPT_FLAG)) { // Complete possible values const char *comma = strrchr(prefix, ','); size_t prefix_len = 0; if (comma) { prefix_len = ++comma - prefix; } for (size_t i = 0; desc->u.flag_opt.values[i]; i++) { const char *str = desc->u.flag_opt.values[i]; if (str_has_prefix(str, prefix + prefix_len)) { size_t str_len = strlen(str); char *completion = xmalloc(prefix_len + str_len + 1); memcpy(completion, prefix, prefix_len); memcpy(completion + prefix_len, str, str_len + 1); add_completion(completion); } } } } dte-1.9.1/src/options.h000066400000000000000000000051651354415143300147430ustar00rootroot00000000000000#ifndef OPTIONS_H #define OPTIONS_H #include #include "encoding/encoder.h" enum { // Trailing whitespace WSE_TRAILING = 1 << 0, // Spaces in indentation. // Does not include less than tab-width spaces at end of indentation. WSE_SPACE_INDENT = 1 << 1, // Less than tab-width spaces at end of indentation WSE_SPACE_ALIGN = 1 << 2, // Tab in indentation WSE_TAB_INDENT = 1 << 3, // Tab anywhere but in indentation WSE_TAB_AFTER_INDENT = 1 << 4, // Special whitespace characters WSE_SPECIAL = 1 << 5, // expand-tab = false: WSE_SPACE_INDENT // expand-tab = true: WSE_TAB_AFTER_INDENT | WSE_TAB_INDENT WSE_AUTO_INDENT = 1 << 6, }; typedef enum { CSS_FALSE, CSS_TRUE, CSS_AUTO, } SearchCaseSensitivity; typedef enum { TAB_BAR_HIDDEN, TAB_BAR_HORIZONTAL, TAB_BAR_VERTICAL, TAB_BAR_AUTO, } TabBarMode; #define COMMON_OPTIONS \ unsigned int auto_indent; \ unsigned int detect_indent; \ unsigned int editorconfig; \ unsigned int emulate_tab; \ unsigned int expand_tab; \ unsigned int file_history; \ unsigned int indent_width; \ unsigned int syntax; \ unsigned int tab_width; \ unsigned int text_width; \ unsigned int ws_error typedef struct { COMMON_OPTIONS; } CommonOptions; typedef struct { COMMON_OPTIONS; // Only local unsigned int brace_indent; const char *filetype; const char *indent_regex; } LocalOptions; typedef struct { COMMON_OPTIONS; // Only global unsigned int display_invisible; unsigned int display_special; unsigned int esc_timeout; unsigned int filesize_limit; unsigned int lock_files; unsigned int scroll_margin; unsigned int set_window_title; unsigned int show_line_numbers; unsigned int tab_bar_max_components; unsigned int tab_bar_width; LineEndingType newline; // Default value for new files SearchCaseSensitivity case_sensitive_search; TabBarMode tab_bar; const char *statusline_left; const char *statusline_right; } GlobalOptions; #undef COMMON_OPTIONS #define TAB_BAR_MIN_WIDTH 12 void set_option(const char *name, const char *value, bool local, bool global); void set_bool_option(const char *name, bool local, bool global); void toggle_option(const char *name, bool global, bool verbose); void toggle_option_values(const char *name, bool global, bool verbose, char **values, size_t count); bool validate_local_options(char **strs); void collect_options(const char *prefix); void collect_toggleable_options(const char *prefix); void collect_option_values(const char *name, const char *prefix); #endif dte-1.9.1/src/parse-args.c000066400000000000000000000062231354415143300153030ustar00rootroot00000000000000#include #include "parse-args.h" #include "debug.h" #include "error.h" #include "util/str-util.h" /* * Flags and first "--" are removed. * Flag arguments are moved to beginning. * Other arguments come right after flag arguments. * * a->args field should be set before calling. * If parsing succeeds, the other field are set and true is returned. */ bool parse_args(const Command *cmd, CommandArgs *a) { char **args = a->args; BUG_ON(!args); size_t argc = 0; while (args[argc]) { argc++; } const char *flag_desc = cmd->flags; size_t nr_flags = 0; size_t nr_flag_args = 0; bool flags_after_arg = true; if (*flag_desc == '-') { flag_desc++; flags_after_arg = false; } size_t i = 0; while (args[i]) { char *arg = args[i]; if (streq(arg, "--")) { // Move the NULL too memmove(args + i, args + i + 1, (argc - i) * sizeof(*args)); free(arg); argc--; break; } if (arg[0] != '-' || !arg[1]) { if (!flags_after_arg) { break; } i++; continue; } for (size_t j = 1; arg[j]; j++) { char flag = arg[j]; char *flag_arg; char *flagp = strchr(flag_desc, flag); if (!flagp || flag == '=') { error_msg("Invalid option -%c", flag); return false; } a->flags[nr_flags++] = flag; if (nr_flags == ARRAY_COUNT(a->flags)) { error_msg("Too many options given."); return false; } if (flagp[1] != '=') { continue; } if (j > 1 || arg[j + 1]) { error_msg ( "Flag -%c must be given separately because it" " requires an argument.", flag ); return false; } flag_arg = args[i + 1]; if (!flag_arg) { error_msg("Option -%c requires on argument.", flag); return false; } // Move flag argument before any other arguments if (i != nr_flag_args) { // farg1 arg1 arg2 -f farg2 arg3 // farg1 farg2 arg1 arg2 arg3 size_t count = i - nr_flag_args; memmove ( args + nr_flag_args + 1, args + nr_flag_args, count * sizeof(*args) ); } args[nr_flag_args++] = flag_arg; i++; } memmove(args + i, args + i + 1, (argc - i) * sizeof(*args)); free(arg); argc--; } // Don't count arguments to flags as arguments to command argc -= nr_flag_args; if (argc < cmd->min_args) { error_msg("Not enough arguments"); return false; } if (argc > cmd->max_args) { error_msg("Too many arguments"); return false; } a->flags[nr_flags] = '\0'; a->nr_args = argc; a->nr_flags = nr_flags; return true; } dte-1.9.1/src/parse-args.h000066400000000000000000000002711354415143300153050ustar00rootroot00000000000000#ifndef PARSE_ARGS_H #define PARSE_ARGS_H #include #include "command.h" #include "util/macros.h" bool parse_args(const Command *cmd, CommandArgs *a) NONNULL_ARGS; #endif dte-1.9.1/src/regexp.c000066400000000000000000000040711354415143300145300ustar00rootroot00000000000000#include #include "regexp.h" #include "debug.h" #include "error.h" #include "util/xmalloc.h" bool regexp_match_nosub(const char *pattern, const char *buf, size_t size) { regex_t re; bool compiled = regexp_compile(&re, pattern, REG_NEWLINE | REG_NOSUB); BUG_ON(!compiled); regmatch_t m; bool ret = regexp_exec(&re, buf, size, 1, &m, 0); regfree(&re); return ret; } bool regexp_match ( const char *pattern, const char *buf, size_t size, PointerArray *m ) { regex_t re; bool compiled = regexp_compile(&re, pattern, REG_NEWLINE); BUG_ON(!compiled); bool ret = regexp_exec_sub(&re, buf, size, m, 0); regfree(&re); return ret; } bool regexp_compile_internal(regex_t *re, const char *pattern, int flags) { int err = regcomp(re, pattern, flags); if (err) { char msg[1024]; regerror(err, re, msg, sizeof(msg)); error_msg("%s: %s", msg, pattern); return false; } return true; } bool regexp_exec ( const regex_t *re, const char *buf, size_t size, size_t nr_m, regmatch_t *m, int flags ) { BUG_ON(!nr_m); // Clang's address sanitizer seemingly doesn't take REG_STARTEND into // account when checking for buffer overflow. #if defined(REG_STARTEND) && !defined(CLANG_ASAN_ENABLED) m[0].rm_so = 0; m[0].rm_eo = size; return !regexec(re, buf, nr_m, m, flags | REG_STARTEND); #else // Buffer must be null-terminated if REG_STARTEND isn't supported char *tmp = xstrcut(buf, size); int ret = !regexec(re, tmp, nr_m, m, flags); free(tmp); return ret; #endif } bool regexp_exec_sub ( const regex_t *re, const char *buf, size_t size, PointerArray *matches, int flags ) { regmatch_t m[16]; bool ret = regexp_exec(re, buf, size, ARRAY_COUNT(m), m, flags); if (!ret) { return false; } for (size_t i = 0; i < ARRAY_COUNT(m); i++) { if (m[i].rm_so == -1) { break; } ptr_array_add(matches, xstrslice(buf, m[i].rm_so, m[i].rm_eo)); } return true; } dte-1.9.1/src/regexp.h000066400000000000000000000020741354415143300145360ustar00rootroot00000000000000#ifndef UTIL_REGEXP_H #define UTIL_REGEXP_H #include #include #include "util/ptr-array.h" bool regexp_match_nosub(const char *pattern, const char *buf, size_t size); bool regexp_match(const char *pattern, const char *buf, size_t size, PointerArray *m); bool regexp_compile_internal(regex_t *re, const char *pattern, int flags); bool regexp_exec(const regex_t *re, const char *buf, size_t size, size_t nr_m, regmatch_t *m, int flags); bool regexp_exec_sub(const regex_t *re, const char *buf, size_t size, PointerArray *matches, int flags); static inline bool regexp_compile(regex_t *re, const char *pattern, int flags) { return regexp_compile_internal(re, pattern, flags | REG_EXTENDED); } static inline bool regexp_is_valid(const char *pattern, int flags) { regex_t re; if (!regexp_compile(&re, pattern, flags | REG_NOSUB)) { return false; } regfree(&re); return true; } static inline bool regexp_compile_basic(regex_t *re, const char *pattern, int flags) { return regexp_compile_internal(re, pattern, flags); } #endif dte-1.9.1/src/screen-cmdline.c000066400000000000000000000044521354415143300161310ustar00rootroot00000000000000#include "screen.h" #include "debug.h" #include "editor.h" #include "error.h" #include "search.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/utf8.h" static void print_message(const char *msg, bool is_error) { enum builtin_color c = BC_COMMANDLINE; if (msg[0]) { c = is_error ? BC_ERRORMSG : BC_INFOMSG; } set_builtin_color(c); size_t i = 0; while (msg[i]) { CodePoint u = u_get_char(msg, i + 4, &i); if (!term_put_char(u)) { break; } } } void show_message(const char *msg, bool is_error) { term_output_reset(0, terminal.width, 0); terminal.move_cursor(0, terminal.height - 1); print_message(msg, is_error); term_clear_eol(); } size_t print_command(char prefix) { CodePoint u; // Width of characters up to and including cursor position size_t w = 1; // ":" (prefix) size_t i = 0; while (i <= editor.cmdline.pos && i < editor.cmdline.buf.len) { u = u_get_char(editor.cmdline.buf.buffer, editor.cmdline.buf.len, &i); w += u_char_width(u); } if (editor.cmdline.pos == editor.cmdline.buf.len) { w++; } if (w > terminal.width) { obuf.scroll_x = w - terminal.width; } set_builtin_color(BC_COMMANDLINE); i = 0; term_put_char(prefix); size_t x = obuf.x - obuf.scroll_x; while (i < editor.cmdline.buf.len) { BUG_ON(obuf.x > obuf.scroll_x + obuf.width); u = u_get_char(editor.cmdline.buf.buffer, editor.cmdline.buf.len, &i); if (!term_put_char(u)) { break; } if (i <= editor.cmdline.pos) { x = obuf.x - obuf.scroll_x; } } return x; } void update_command_line(void) { char prefix = ':'; term_output_reset(0, terminal.width, 0); terminal.move_cursor(0, terminal.height - 1); switch (editor.input_mode) { case INPUT_NORMAL: { bool msg_is_error; const char *msg = get_msg(&msg_is_error); print_message(msg, msg_is_error); break; } case INPUT_SEARCH: prefix = current_search_direction() == SEARCH_FWD ? '/' : '?'; // fallthrough case INPUT_COMMAND: editor.cmdline_x = print_command(prefix); break; case INPUT_GIT_OPEN: break; } term_clear_eol(); } dte-1.9.1/src/screen-status.c000066400000000000000000000143551354415143300160440ustar00rootroot00000000000000#include "screen.h" #include "editor.h" #include "selection.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/utf8.h" #include "util/xsnprintf.h" typedef struct { char *buf; size_t size; size_t pos; bool separator; const Window *win; const char *misc_status; } Formatter; static void add_ch(Formatter *f, char ch) { f->buf[f->pos++] = ch; } static void add_separator(Formatter *f) { if (f->separator && f->pos < f->size) { add_ch(f, ' '); } f->separator = false; } static void add_status_str(Formatter *f, const char *str) { if (!*str) { return; } add_separator(f); size_t idx = 0; while (f->pos < f->size && str[idx]) { u_set_char(f->buf, &f->pos, u_str_get_char(str, &idx)); } } PRINTF(2) static void add_status_format(Formatter *f, const char *format, ...) { char buf[1024]; va_list ap; va_start(ap, format); xvsnprintf(buf, sizeof(buf), format, ap); va_end(ap); add_status_str(f, buf); } static void add_status_pos(Formatter *f) { size_t lines = f->win->view->buffer->nl; int h = f->win->edit_h; long pos = f->win->view->vy; if (lines <= h) { if (pos) { add_status_str(f, "Bot"); } else { add_status_str(f, "All"); } } else if (pos == 0) { add_status_str(f, "Top"); } else if (pos + h - 1 >= lines) { add_status_str(f, "Bot"); } else { const long d = lines - (h - 1); add_status_format(f, "%2ld%%", (pos * 100 + d / 2) / d); } } static void sf_format(Formatter *f, char *buf, size_t size, const char *format) { View *v = f->win->view; f->buf = buf; f->size = size - 5; // Max length of char and terminating NUL f->pos = 0; f->separator = false; CodePoint u; bool got_char = block_iter_get_char(&v->cursor, &u) > 0; while (f->pos < f->size && *format) { char ch = *format++; if (ch != '%') { add_separator(f); add_ch(f, ch); continue; } ch = *format++; switch (ch) { case 'f': add_status_str(f, buffer_filename(v->buffer)); break; case 'm': if (buffer_modified(v->buffer)) { add_status_str(f, "*"); } break; case 'r': if (v->buffer->readonly) { add_status_str(f, "RO"); } break; case 'y': add_status_format(f, "%ld", v->cy + 1); break; case 'Y': add_status_format(f, "%zu", v->buffer->nl); break; case 'x': add_status_format(f, "%ld", v->cx_display + 1); break; case 'X': add_status_format(f, "%ld", v->cx_char + 1); if (v->cx_display != v->cx_char) { add_status_format(f, "-%ld", v->cx_display + 1); } break; case 'p': add_status_pos(f); break; case 'E': add_status_str(f, v->buffer->encoding.name); break; case 'M': { if (f->misc_status != NULL) { add_status_str(f, f->misc_status); } break; } case 'n': switch (v->buffer->newline) { case NEWLINE_UNIX: add_status_str(f, "LF"); break; case NEWLINE_DOS: add_status_str(f, "CRLF"); break; } break; case 's': f->separator = true; break; case 't': add_status_str(f, v->buffer->options.filetype); break; case 'u': if (got_char) { if (u_is_unicode(u)) { add_status_format(f, "U+%04X", u); } else { add_status_str(f, "Invalid"); } } break; case '%': add_separator(f); add_ch(f, ch); break; case '\0': f->buf[f->pos] = '\0'; return; } } f->buf[f->pos] = '\0'; } static const char *format_misc_status(const Window *win) { if (editor.input_mode == INPUT_SEARCH) { switch (editor.options.case_sensitive_search) { case CSS_FALSE: return "[case-sensitive = false]"; case CSS_TRUE: return "[case-sensitive = true]"; case CSS_AUTO: return "[case-sensitive = auto]"; } return NULL; } if (win->view->selection == SELECT_NONE) { return NULL; } static char buf[32]; SelectionInfo si; init_selection(win->view, &si); switch (win->view->selection) { case SELECT_CHARS: xsnprintf(buf, sizeof(buf), "[%zu chars]", get_nr_selected_chars(&si)); return buf; case SELECT_LINES: xsnprintf(buf, sizeof(buf), "[%zu lines]", get_nr_selected_lines(&si)); return buf; case SELECT_NONE: break; } return NULL; } void update_status_line(const Window *win) { Formatter f = { .win = win, .misc_status = format_misc_status(win) }; char lbuf[256]; char rbuf[256]; sf_format(&f, lbuf, sizeof(lbuf), editor.options.statusline_left); sf_format(&f, rbuf, sizeof(rbuf), editor.options.statusline_right); term_output_reset(win->x, win->w, 0); terminal.move_cursor(win->x, win->y + win->h - 1); set_builtin_color(BC_STATUSLINE); size_t lw = u_str_width(lbuf); size_t rw = u_str_width(rbuf); if (lw + rw <= win->w) { // Both fit term_add_str(lbuf); term_set_bytes(' ', win->w - lw - rw); term_add_str(rbuf); } else if (lw <= win->w && rw <= win->w) { // Both would fit separately, draw overlapping term_add_str(lbuf); obuf.x = win->w - rw; terminal.move_cursor(win->x + win->w - rw, win->y + win->h - 1); term_add_str(rbuf); } else if (lw <= win->w) { // Left fits term_add_str(lbuf); term_clear_eol(); } else if (rw <= win->w) { // Right fits term_set_bytes(' ', win->w - rw); term_add_str(rbuf); } else { term_clear_eol(); } } dte-1.9.1/src/screen-tabbar.c000066400000000000000000000175361354415143300157600ustar00rootroot00000000000000#include "screen.h" #include "debug.h" #include "editor.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/strtonum.h" #include "util/utf8.h" #include "util/xsnprintf.h" static int tab_title_width(int number, const char *filename) { return 3 + number_width(number) + u_str_width(filename); } static void update_tab_title_width(View *v, int tab_number) { int w = tab_title_width(tab_number, buffer_filename(v->buffer)); v->tt_width = w; v->tt_truncated_width = w; } static void update_first_tab_idx(Window *win) { size_t max_first_idx = win->views.count; for (size_t w = 0; max_first_idx > 0; max_first_idx--) { const View *v = win->views.ptrs[max_first_idx - 1]; w += v->tt_truncated_width; if (w > win->w) { break; } } size_t min_first_idx = win->views.count; for (size_t w = 0; min_first_idx > 0; min_first_idx--) { const View *v = win->views.ptrs[min_first_idx - 1]; if (w || v == win->view) { w += v->tt_truncated_width; } if (w > win->w) { break; } } if (win->first_tab_idx < min_first_idx) { win->first_tab_idx = min_first_idx; } if (win->first_tab_idx > max_first_idx) { win->first_tab_idx = max_first_idx; } } static void calculate_tabbar(Window *win) { int total_w = 0; for (size_t i = 0, n = win->views.count; i < n; i++) { View *v = win->views.ptrs[i]; if (v == win->view) { // Make sure current tab is visible if (win->first_tab_idx > i) { win->first_tab_idx = i; } } update_tab_title_width(v, i + 1); total_w += v->tt_width; } if (total_w <= win->w) { // All tabs fit without truncating win->first_tab_idx = 0; return; } // Truncate all wide tabs total_w = 0; int truncated_count = 0; for (size_t i = 0, n = win->views.count; i < n; i++) { View *v = win->views.ptrs[i]; int truncated_w = 20; if (v->tt_width > truncated_w) { v->tt_truncated_width = truncated_w; total_w += truncated_w; truncated_count++; } else { total_w += v->tt_width; } } if (total_w > win->w) { // Not all tabs fit even after truncating wide tabs update_first_tab_idx(win); return; } // All tabs fit after truncating wide tabs int extra = win->w - total_w; // Divide extra space between truncated tabs while (extra > 0) { BUG_ON(truncated_count == 0); int extra_avg = extra / truncated_count; int extra_mod = extra % truncated_count; for (size_t i = 0, n = win->views.count; i < n; i++) { View *v = win->views.ptrs[i]; int add = v->tt_width - v->tt_truncated_width; if (add == 0) { continue; } int avail = extra_avg; if (extra_mod) { // This is needed for equal divide if (extra_avg == 0) { avail++; extra_mod--; } } if (add > avail) { add = avail; } else { truncated_count--; } v->tt_truncated_width += add; extra -= add; } } win->first_tab_idx = 0; } static void print_horizontal_tab_title(const View *v, size_t idx) { int skip = v->tt_width - v->tt_truncated_width; const char *filename = buffer_filename(v->buffer); if (skip > 0) { filename += u_skip_chars(filename, &skip); } char buf[16]; xsnprintf ( buf, sizeof(buf), "%c%zu%c", obuf.x == 0 && idx > 0 ? '<' : ' ', idx + 1, buffer_modified(v->buffer) ? '+' : ':' ); if (v == v->window->view) { set_builtin_color(BC_ACTIVETAB); } else { set_builtin_color(BC_INACTIVETAB); } term_add_str(buf); term_add_str(filename); if (obuf.x == obuf.width - 1 && idx < v->window->views.count - 1) { term_put_char('>'); } else { term_put_char(' '); } } static void print_horizontal_tabbar(Window *win) { term_output_reset(win->x, win->w, 0); terminal.move_cursor(win->x, win->y); calculate_tabbar(win); size_t i; for (i = win->first_tab_idx; i < win->views.count; i++) { const View *v = win->views.ptrs[i]; if (obuf.x + v->tt_truncated_width > win->w) { break; } print_horizontal_tab_title(v, i); } set_builtin_color(BC_TABBAR); if (i != win->views.count) { while (obuf.x < obuf.width - 1) { term_put_char(' '); } if (obuf.x == obuf.width - 1) { term_put_char('>'); } } else { term_clear_eol(); } } static void print_vertical_tab_title(const View *v, int idx, int width) { const char *orig_filename = buffer_filename(v->buffer); const char *filename = orig_filename; unsigned int max = editor.options.tab_bar_max_components; char buf[16]; xsnprintf ( buf, sizeof(buf), "%2d%s", idx + 1, buffer_modified(v->buffer) ? "+" : " " ); if (max) { int count = 1; for (size_t i = 0; filename[i]; i++) { if (filename[i] == '/') { count++; } } // Ignore first slash because it does not separate components if (filename[0] == '/') { count--; } if (count > max) { // Skip possible first slash size_t i; for (i = 1; ; i++) { if (filename[i] == '/' && --count == max) { i++; break; } } filename += i; } } else { int skip = strlen(buf) + u_str_width(filename) - width + 1; if (skip > 0) { filename += u_skip_chars(filename, &skip); } } if (filename != orig_filename) { // filename was shortened. Add "<<" symbol. size_t i = strlen(buf); u_set_char(buf, &i, 0xab); buf[i] = '\0'; } if (v == v->window->view) { set_builtin_color(BC_ACTIVETAB); } else { set_builtin_color(BC_INACTIVETAB); } term_add_str(buf); term_add_str(filename); term_clear_eol(); } static void print_vertical_tabbar(Window *win) { int width = vertical_tabbar_width(win); int h = win->edit_h; size_t cur_idx = 0; for (size_t i = 0; i < win->views.count; i++) { if (win->view == win->views.ptrs[i]) { cur_idx = i; break; } } if (win->views.count <= h) { // All tabs fit win->first_tab_idx = 0; } else { size_t max_y = win->first_tab_idx + h - 1; if (win->first_tab_idx > cur_idx) { win->first_tab_idx = cur_idx; } if (cur_idx > max_y) { win->first_tab_idx += cur_idx - max_y; } } term_output_reset(win->x, width, 0); int n = h; if (n + win->first_tab_idx > win->views.count) { n = win->views.count - win->first_tab_idx; } size_t i; for (i = 0; i < n; i++) { size_t idx = win->first_tab_idx + i; obuf.x = 0; terminal.move_cursor(win->x, win->y + i); print_vertical_tab_title(win->views.ptrs[idx], idx, width); } set_builtin_color(BC_TABBAR); for (; i < h; i++) { obuf.x = 0; terminal.move_cursor(win->x, win->y + i); term_clear_eol(); } } void print_tabbar(Window *win) { switch (tabbar_visibility(win)) { case TAB_BAR_HORIZONTAL: print_horizontal_tabbar(win); break; case TAB_BAR_VERTICAL: print_vertical_tabbar(win); break; default: break; } } dte-1.9.1/src/screen-view.c000066400000000000000000000266761354415143300155040ustar00rootroot00000000000000#include "screen.h" #include "debug.h" #include "editor.h" #include "selection.h" #include "syntax/highlight.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/ascii.h" #include "util/utf8.h" typedef struct { const View *view; size_t line_nr; size_t offset; ssize_t sel_so; ssize_t sel_eo; const unsigned char *line; size_t size; size_t pos; size_t indent_size; size_t trailing_ws_offset; HlColor **colors; } LineInfo; static bool is_default_bg_color(int32_t color) { return color == builtin_colors[BC_DEFAULT]->bg || color < 0; } // Like mask_color() but can change bg color only if it has not been changed yet static void mask_color2(TermColor *color, const TermColor *over) { if (over->fg != -2) { color->fg = over->fg; } if (over->bg != -2 && is_default_bg_color(color->bg)) { color->bg = over->bg; } if (!(over->attr & ATTR_KEEP)) { color->attr = over->attr; } } static void mask_selection_and_current_line ( const LineInfo *info, TermColor *color ) { if (info->offset >= info->sel_so && info->offset < info->sel_eo) { mask_color(color, builtin_colors[BC_SELECTION]); } else if (info->line_nr == info->view->cy) { mask_color2(color, builtin_colors[BC_CURRENTLINE]); } } static bool is_non_text(CodePoint u) { if (u < 0x20) { return u != '\t' || editor.options.display_special; } if (u == 0x7f) { return true; } return u_is_unprintable(u); } static int get_ws_error_option(const Buffer *b) { int flags = b->options.ws_error; if (flags & WSE_AUTO_INDENT) { if (b->options.expand_tab) { flags |= WSE_TAB_AFTER_INDENT | WSE_TAB_INDENT; } else { flags |= WSE_SPACE_INDENT; } } return flags; } static bool whitespace_error(const LineInfo *info, CodePoint u, size_t i) { const View *v = info->view; int flags = get_ws_error_option(v->buffer); if (i >= info->trailing_ws_offset && flags & WSE_TRAILING) { // Trailing whitespace if (info->line_nr != v->cy || v->cx < info->trailing_ws_offset) { return true; } // Cursor is on this line and on the whitespace or at eol. It would // be annoying if the line you are editing displays trailing // whitespace as an error. } if (u == '\t') { if (i < info->indent_size) { // In indentation if (flags & WSE_TAB_INDENT) { return true; } } else { if (flags & WSE_TAB_AFTER_INDENT) { return true; } } } else if (i < info->indent_size) { // Space in indentation const char *line = info->line; int count = 0, pos = i; while (pos > 0 && line[pos - 1] == ' ') { pos--; } while (pos < info->size && line[pos] == ' ') { pos++; count++; } if (count >= v->buffer->options.tab_width) { // Spaces used instead of tab if (flags & WSE_SPACE_INDENT) { return true; } } else if (pos < info->size && line[pos] == '\t') { // Space before tab if (flags & WSE_SPACE_INDENT) { return true; } } else { // Less than tab width spaces at end of indentation if (flags & WSE_SPACE_ALIGN) { return true; } } } return false; } static CodePoint screen_next_char(LineInfo *info) { size_t count, pos = info->pos; CodePoint u = info->line[pos]; TermColor color; bool ws_error = false; if (u < 0x80) { info->pos++; count = 1; if (u == '\t' || u == ' ') { ws_error = whitespace_error(info, u, pos); } } else { u = u_get_nonascii(info->line, info->size, &info->pos); count = info->pos - pos; if ( u_is_special_whitespace(u) // Highly annoying no-break space etc. && (info->view->buffer->options.ws_error & WSE_SPECIAL) ) { ws_error = true; } } if (info->colors && info->colors[pos]) { color = info->colors[pos]->color; } else { color = *builtin_colors[BC_DEFAULT]; } if (is_non_text(u)) { mask_color(&color, builtin_colors[BC_NONTEXT]); } if (ws_error) { mask_color(&color, builtin_colors[BC_WSERROR]); } mask_selection_and_current_line(info, &color); set_color(&color); info->offset += count; return u; } static void screen_skip_char(LineInfo *info) { CodePoint u = info->line[info->pos++]; info->offset++; if (u < 0x80) { if (!ascii_iscntrl(u)) { obuf.x++; } else if (u == '\t' && obuf.tab != TAB_CONTROL) { obuf.x += (obuf.x + obuf.tab_width) / obuf.tab_width * obuf.tab_width - obuf.x; } else { // Control obuf.x += 2; } } else { size_t pos = info->pos; info->pos--; u = u_get_nonascii(info->line, info->size, &info->pos); obuf.x += u_char_width(u); info->offset += info->pos - pos; } } static bool is_notice(const char *word, size_t len) { switch (len) { case 3: return !memcmp(word, "XXX", 3); case 4: return !memcmp(word, "TODO", 4); case 5: return !memcmp(word, "FIXME", 5); } return false; } // Highlight certain words inside comments static void hl_words(const LineInfo *info) { HlColor *cc = find_color("comment"); HlColor *nc = find_color("notice"); if (info->colors == NULL || cc == NULL || nc == NULL) { return; } size_t i = info->pos; if (i >= info->size) { return; } // Go to beginning of partially visible word inside comment while (i > 0 && info->colors[i] == cc && is_word_byte(info->line[i])) { i--; } // This should be more than enough. I'm too lazy to iterate characters // instead of bytes and calculate text width. const size_t max = info->pos + terminal.width * 4 + 8; size_t si; while (i < info->size) { if (info->colors[i] != cc || !is_word_byte(info->line[i])) { if (i > max) { break; } i++; } else { // Beginning of a word inside comment si = i++; while ( i < info->size && info->colors[i] == cc && is_word_byte(info->line[i]) ) { i++; } if (is_notice(info->line + si, i - si)) { for (size_t j = si; j < i; j++) { info->colors[j] = nc; } } } } } static void line_info_init ( LineInfo *info, const View *v, const BlockIter *bi, size_t line_nr ) { memset(info, 0, sizeof(*info)); info->view = v; info->line_nr = line_nr; info->offset = block_iter_get_offset(bi); if (!v->selection) { info->sel_so = -1; info->sel_eo = -1; } else if (v->sel_eo != UINT_MAX) { // Already calculated info->sel_so = v->sel_so; info->sel_eo = v->sel_eo; BUG_ON(info->sel_so > info->sel_eo); } else { SelectionInfo sel; init_selection(v, &sel); info->sel_so = sel.so; info->sel_eo = sel.eo; } } static void line_info_set_line ( LineInfo *info, const LineRef *lr, HlColor **colors ) { BUG_ON(lr->size == 0); BUG_ON(lr->line[lr->size - 1] != '\n'); info->line = lr->line; info->size = lr->size - 1; info->pos = 0; info->colors = colors; { size_t i, n; for (i = 0, n = info->size; i < n; i++) { char ch = info->line[i]; if (ch != '\t' && ch != ' ') { break; } } info->indent_size = i; } info->trailing_ws_offset = INT_MAX; for (ssize_t i = info->size - 1; i >= 0; i--) { char ch = info->line[i]; if (ch != '\t' && ch != ' ') { break; } info->trailing_ws_offset = i; } } static void print_line(LineInfo *info) { // Screen might be scrolled horizontally. Skip most invisible // characters using screen_skip_char(), which is much faster than // buf_skip(screen_next_char(info)). // // There can be a wide character (tab, control code etc.) that is // partially visible and can't be skipped using screen_skip_char(). while (obuf.x + 8 < obuf.scroll_x && info->pos < info->size) { screen_skip_char(info); } hl_words(info); while (info->pos < info->size) { BUG_ON(obuf.x > obuf.scroll_x + obuf.width); CodePoint u = screen_next_char(info); if (!term_put_char(u)) { // +1 for newline info->offset += info->size - info->pos + 1; return; } } TermColor color; if (editor.options.display_special && obuf.x >= obuf.scroll_x) { // Syntax highlighter highlights \n but use default color anyway color = *builtin_colors[BC_DEFAULT]; mask_color(&color, builtin_colors[BC_NONTEXT]); mask_selection_and_current_line(info, &color); set_color(&color); term_put_char('$'); } color = *builtin_colors[BC_DEFAULT]; mask_selection_and_current_line(info, &color); set_color(&color); info->offset++; term_clear_eol(); } void update_range(const View *v, long y1, long y2) { const int edit_x = v->window->edit_x; const int edit_y = v->window->edit_y; const int edit_w = v->window->edit_w; const int edit_h = v->window->edit_h; term_output_reset(edit_x, edit_w, v->vx); obuf.tab_width = v->buffer->options.tab_width; obuf.tab = editor.options.display_special ? TAB_SPECIAL : TAB_NORMAL; BlockIter bi = v->cursor; for (long i = 0, n = v->cy - y1; i < n; i++) { block_iter_prev_line(&bi); } for (long i = 0, n = y1 - v->cy; i < n; i++) { block_iter_eat_line(&bi); } block_iter_bol(&bi); LineInfo info; line_info_init(&info, v, &bi, y1); y1 -= v->vy; y2 -= v->vy; bool got_line = !block_iter_is_eof(&bi); hl_fill_start_states(v->buffer, info.line_nr); long i; for (i = y1; got_line && i < y2; i++) { obuf.x = 0; terminal.move_cursor(edit_x, edit_y + i); LineRef lr; fill_line_nl_ref(&bi, &lr); bool next_changed; HlColor **colors = hl_line(v->buffer, &lr, info.line_nr, &next_changed); line_info_set_line(&info, &lr, colors); print_line(&info); got_line = !!block_iter_next_line(&bi); info.line_nr++; if (next_changed && i + 1 == y2 && y2 < edit_h) { // More lines need to be updated not because their // contents have changed but because their highlight // state has. y2++; } } if (i < y2 && info.line_nr == v->cy) { // Dummy empty line is shown only if cursor is on it TermColor color = *builtin_colors[BC_DEFAULT]; obuf.x = 0; mask_color2(&color, builtin_colors[BC_CURRENTLINE]); set_color(&color); terminal.move_cursor(edit_x, edit_y + i++); term_clear_eol(); } if (i < y2) { set_builtin_color(BC_NOLINE); } for (; i < y2; i++) { obuf.x = 0; terminal.move_cursor(edit_x, edit_y + i); term_put_char('~'); term_clear_eol(); } } dte-1.9.1/src/screen.c000066400000000000000000000062061354415143300145170ustar00rootroot00000000000000#include #include "screen.h" #include "cmdline.h" #include "editor.h" #include "frame.h" #include "search.h" #include "terminal/input.h" #include "terminal/no-op.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/path.h" #include "util/utf8.h" #include "util/xsnprintf.h" #include "view.h" void set_color(const TermColor *color) { TermColor tmp = *color; // NOTE: -2 (keep) is treated as -1 (default) if (tmp.fg < 0) { tmp.fg = builtin_colors[BC_DEFAULT]->fg; } if (tmp.bg < 0) { tmp.bg = builtin_colors[BC_DEFAULT]->bg; } terminal.set_color(&tmp); } void set_builtin_color(enum builtin_color c) { set_color(builtin_colors[c]); } void update_term_title(const Buffer *b) { if (!editor.options.set_window_title || terminal.set_title == no_op_s) { return; } // FIXME: title must not contain control characters char title[1024]; snprintf ( title, sizeof title, "%s %c dte", buffer_filename(b), buffer_modified(b) ? '+' : '-' ); terminal.set_title(title); } void mask_color(TermColor *color, const TermColor *over) { if (over->fg != -2) { color->fg = over->fg; } if (over->bg != -2) { color->bg = over->bg; } if (!(over->attr & ATTR_KEEP)) { color->attr = over->attr; } } static void print_separator(Window *win) { if (win->x + win->w == terminal.width) { return; } for (int y = 0; y < win->h; y++) { terminal.move_cursor(win->x + win->w, win->y + y); term_add_byte('|'); } } void update_separators(void) { set_builtin_color(BC_STATUSLINE); for_each_window(print_separator); } void update_line_numbers(Window *win, bool force) { const View *v = win->view; size_t lines = v->buffer->nl; int x = win->x + vertical_tabbar_width(win); calculate_line_numbers(win); long first = v->vy + 1; long last = v->vy + win->edit_h; if (last > lines) { last = lines; } if ( !force && win->line_numbers.first == first && win->line_numbers.last == last ) { return; } win->line_numbers.first = first; win->line_numbers.last = last; term_output_reset(win->x, win->w, 0); set_builtin_color(BC_LINENUMBER); for (int i = 0; i < win->edit_h; i++) { long line = v->vy + i + 1; int w = win->line_numbers.width - 1; char buf[32]; if (line > lines) { xsnprintf(buf, sizeof(buf), "%*s ", w, ""); } else { xsnprintf(buf, sizeof(buf), "%*ld ", w, line); } terminal.move_cursor(x, win->edit_y + i); term_add_bytes(buf, win->line_numbers.width); } } void update_window_sizes(void) { set_frame_size(root_frame, terminal.width, terminal.height - 1); update_window_coordinates(); } void update_screen_size(void) { if (term_get_size(&terminal.width, &terminal.height)) { if (terminal.width < 3) { terminal.width = 3; } if (terminal.height < 3) { terminal.height = 3; } update_window_sizes(); } } dte-1.9.1/src/screen.h000066400000000000000000000015031354415143300145170ustar00rootroot00000000000000#ifndef SCREEN_H #define SCREEN_H #include #include #include "buffer.h" #include "terminal/color.h" #include "view.h" #include "window.h" // screen.c void update_term_title(const Buffer *b); void update_separators(void); void update_window_sizes(void); void update_line_numbers(Window *win, bool force); void update_screen_size(void); void set_color(const TermColor *color); void set_builtin_color(enum builtin_color c); void mask_color(TermColor *color, const TermColor *over); // screen-cmdline.c void update_command_line(void); size_t print_command(char prefix); void show_message(const char *msg, bool is_error); // screen-tabbar.c void print_tabbar(Window *w); // screen-status.c void update_status_line(const Window *win); // screen-view.c void update_range(const View *v, long y1, long y2); #endif dte-1.9.1/src/search.c000066400000000000000000000304411354415143300145030ustar00rootroot00000000000000#include "search.h" #include "buffer.h" #include "change.h" #include "edit.h" #include "editor.h" #include "error.h" #include "regexp.h" #include "selection.h" #include "util/ascii.h" #include "util/string.h" #include "util/xmalloc.h" #include "view.h" #define MAX_SUBSTRINGS 32 static bool do_search_fwd(regex_t *regex, BlockIter *bi, bool skip) { int flags = block_iter_is_bol(bi) ? 0 : REG_NOTBOL; do { regmatch_t match; LineRef lr; if (block_iter_is_eof(bi)) { return false; } fill_line_ref(bi, &lr); // NOTE: If this is the first iteration then lr.line contains // partial line (text starting from the cursor position) and // if match.rm_so is 0 then match is at beginning of the text // which is same as the cursor position. if (regexp_exec(regex, lr.line, lr.size, 1, &match, flags)) { if (skip && match.rm_so == 0) { // Ignore match at current cursor position regoff_t count = match.rm_eo; if (count == 0) { // It is safe to skip one byte because every line // has one extra byte (newline) that is not in lr.line count = 1; } block_iter_skip_bytes(bi, (size_t)count); return do_search_fwd(regex, bi, false); } block_iter_skip_bytes(bi, match.rm_so); view->cursor = *bi; view->center_on_scroll = true; view_reset_preferred_x(view); return true; } skip = false; // Not at cursor position anymore flags = 0; } while (block_iter_next_line(bi)); return false; } static bool do_search_bwd(regex_t *regex, BlockIter *bi, ssize_t cx, bool skip) { if (block_iter_is_eof(bi)) { goto next; } do { regmatch_t match; LineRef lr; int flags = 0; regoff_t offset = -1; regoff_t pos = 0; fill_line_ref(bi, &lr); while ( pos <= lr.size && regexp_exec(regex, lr.line + pos, lr.size - pos, 1, &match, flags) ) { flags = REG_NOTBOL; if (cx >= 0) { if (pos + match.rm_so >= cx) { // Ignore match at or after cursor break; } if (skip && pos + match.rm_eo > cx) { // Search -rw should not find word under cursor break; } } // This might be what we want (last match before cursor) offset = pos + match.rm_so; pos += match.rm_eo; if (match.rm_so == match.rm_eo) { // Zero length match break; } } if (offset >= 0) { block_iter_skip_bytes(bi, offset); view->cursor = *bi; view->center_on_scroll = true; view_reset_preferred_x(view); return true; } next: cx = -1; } while (block_iter_prev_line(bi)); return false; } bool search_tag(const char *pattern, bool *err) { BlockIter bi = BLOCK_ITER_INIT(&buffer->blocks); regex_t regex; bool found = false; if (!regexp_compile_basic(®ex, pattern, REG_NEWLINE)) { *err = true; } else if (do_search_fwd(®ex, &bi, false)) { view->center_on_scroll = true; found = true; } else { // Don't center view to cursor unnecessarily view->force_center = false; error_msg("Tag not found."); *err = true; } regfree(®ex); return found; } static struct { regex_t regex; char *pattern; SearchDirection direction; // If zero then regex hasn't been compiled int re_flags; } current_search; void search_set_direction(SearchDirection dir) { current_search.direction = dir; } SearchDirection current_search_direction(void) { return current_search.direction; } static void free_regex(void) { if (current_search.re_flags) { regfree(¤t_search.regex); current_search.re_flags = 0; } } static bool has_upper(const char *str) { for (size_t i = 0; str[i]; i++) { if (ascii_isupper(str[i])) { return true; } } return false; } static bool update_regex(void) { int re_flags = REG_NEWLINE; switch (editor.options.case_sensitive_search) { case CSS_TRUE: break; case CSS_FALSE: re_flags |= REG_ICASE; break; case CSS_AUTO: if (!has_upper(current_search.pattern)) { re_flags |= REG_ICASE; } break; } if (re_flags == current_search.re_flags) { return true; } free_regex(); current_search.re_flags = re_flags; if (regexp_compile ( ¤t_search.regex, current_search.pattern, current_search.re_flags )) { return true; } free_regex(); return false; } void search_set_regexp(const char *pattern) { free_regex(); free(current_search.pattern); current_search.pattern = xstrdup(pattern); } static void do_search_next(bool skip) { BlockIter bi = view->cursor; if (!current_search.pattern) { error_msg("No previous search pattern."); return; } if (!update_regex()) { return; } if (current_search.direction == SEARCH_FWD) { if (do_search_fwd(¤t_search.regex, &bi, true)) { return; } block_iter_bof(&bi); if (do_search_fwd(¤t_search.regex, &bi, false)) { info_msg("Continuing at top."); } else { info_msg("Pattern '%s' not found.", current_search.pattern); } } else { size_t cursor_x = block_iter_bol(&bi); if (do_search_bwd(¤t_search.regex, &bi, cursor_x, skip)) { return; } block_iter_eof(&bi); if (do_search_bwd(¤t_search.regex, &bi, -1, false)) { info_msg("Continuing at bottom."); } else { info_msg("Pattern '%s' not found.", current_search.pattern); } } } void search_prev(void) { current_search.direction ^= 1; search_next(); current_search.direction ^= 1; } void search_next(void) { do_search_next(false); } void search_next_word(void) { do_search_next(true); } static void build_replacement ( String *buf, const char *line, const char *format, regmatch_t *m ) { size_t i = 0; while (format[i]) { int ch = format[i++]; if (ch == '\\') { if (format[i] >= '1' && format[i] <= '9') { int n = format[i++] - '0'; int len = m[n].rm_eo - m[n].rm_so; if (len > 0) { string_add_buf(buf, line + m[n].rm_so, len); } } else if (format[i] != '\0') { string_add_byte(buf, format[i++]); } } else if (ch == '&') { int len = m[0].rm_eo - m[0].rm_so; if (len > 0) { string_add_buf(buf, line + m[0].rm_so, len); } } else { string_add_byte(buf, ch); } } } /* * s/abc/x * * string to match against * ------------------------------------------- * "foo abc bar abc baz" "foo abc bar abc baz" * "foo x bar abc baz" " bar abc baz" */ static unsigned int replace_on_line ( LineRef *lr, regex_t *re, const char *format, BlockIter *bi, ReplaceFlags *flagsp ) { unsigned char *buf = (unsigned char *)lr->line; ReplaceFlags flags = *flagsp; regmatch_t m[MAX_SUBSTRINGS]; size_t pos = 0; int eflags = 0; unsigned int nr = 0; while (regexp_exec ( re, buf + pos, lr->size - pos, MAX_SUBSTRINGS, m, eflags )) { regoff_t match_len = m[0].rm_eo - m[0].rm_so; bool skip = false; // Move cursor to beginning of the text to replace block_iter_skip_bytes(bi, m[0].rm_so); view->cursor = *bi; if (flags & REPLACE_CONFIRM) { switch (get_confirmation("Ynaq", "Replace?")) { case 'y': break; case 'n': skip = true; break; case 'a': flags &= ~REPLACE_CONFIRM; *flagsp = flags; // Record rest of the changes as one chain begin_change_chain(); break; case 'q': case 0: *flagsp = flags | REPLACE_CANCEL; goto out; } } if (skip) { // Move cursor after the matched text block_iter_skip_bytes(&view->cursor, match_len); } else { String b = STRING_INIT; build_replacement(&b, buf + pos, format, m); // lineref is invalidated by modification if (buf == lr->line && lr->size != 0) { buf = xmemdup(buf, lr->size); } buffer_replace_bytes(match_len, b.buffer, b.len); nr++; // Update selection length if (view->selection) { view->sel_eo += b.len; view->sel_eo -= match_len; } // Move cursor after the replaced text block_iter_skip_bytes(&view->cursor, b.len); string_free(&b); } *bi = view->cursor; if (!match_len) { break; } if (!(flags & REPLACE_GLOBAL)) { break; } pos += m[0].rm_so + match_len; // Don't match beginning of line again eflags = REG_NOTBOL; } out: if (buf != lr->line) { free(buf); } return nr; } void reg_replace(const char *pattern, const char *format, ReplaceFlags flags) { BlockIter bi = BLOCK_ITER_INIT(&buffer->blocks); size_t nr_bytes; bool swapped = false; int re_flags = REG_NEWLINE; unsigned int nr_substitutions = 0; size_t nr_lines = 0; regex_t re; if (pattern[0] == '\0') { error_msg("Search pattern must contain at least 1 character"); return; } if (flags & REPLACE_IGNORE_CASE) { re_flags |= REG_ICASE; } if (flags & REPLACE_BASIC) { if (!regexp_compile_basic(&re, pattern, re_flags)) { return; } } else { if (!regexp_compile(&re, pattern, re_flags)) { return; } } if (view->selection) { SelectionInfo info; init_selection(view, &info); view->cursor = info.si; view->sel_so = info.so; view->sel_eo = info.eo; swapped = info.swapped; bi = view->cursor; nr_bytes = info.eo - info.so; } else { BlockIter eof = bi; block_iter_eof(&eof); nr_bytes = block_iter_get_offset(&eof); } // Record multiple changes as one chain only when replacing all if (!(flags & REPLACE_CONFIRM)) { begin_change_chain(); } while (1) { // Number of bytes to process size_t count; LineRef lr; unsigned int nr; fill_line_ref(&bi, &lr); count = lr.size; if (lr.size > nr_bytes) { // End of selection is not full line lr.size = nr_bytes; } nr = replace_on_line(&lr, &re, format, &bi, &flags); if (nr) { nr_substitutions += nr; nr_lines++; } if (flags & REPLACE_CANCEL) { break; } if (count + 1 >= nr_bytes) { break; } nr_bytes -= count + 1; block_iter_next_line(&bi); } if (!(flags & REPLACE_CONFIRM)) { end_change_chain(); } regfree(&re); if (nr_substitutions) { info_msg("%u substitutions on %zu lines.", nr_substitutions, nr_lines); } else if (!(flags & REPLACE_CANCEL)) { info_msg("Pattern '%s' not found.", pattern); } if (view->selection) { // Undo what init_selection() did if (view->sel_eo) { view->sel_eo--; } if (swapped) { ssize_t tmp = view->sel_so; view->sel_so = view->sel_eo; view->sel_eo = tmp; } block_iter_goto_offset(&view->cursor, view->sel_eo); view->sel_eo = UINT_MAX; } } dte-1.9.1/src/search.h000066400000000000000000000013011354415143300145010ustar00rootroot00000000000000#ifndef SEARCH_H #define SEARCH_H #include #include "util/macros.h" typedef enum { SEARCH_FWD, SEARCH_BWD, } SearchDirection; typedef enum { REPLACE_CONFIRM = 1 << 0, REPLACE_GLOBAL = 1 << 1, REPLACE_IGNORE_CASE = 1 << 2, REPLACE_BASIC = 1 << 3, REPLACE_CANCEL = 1 << 4, } ReplaceFlags; bool search_tag(const char *pattern, bool *err); void search_set_direction(SearchDirection dir); SearchDirection current_search_direction(void) PURE; void search_set_regexp(const char *pattern); void search_prev(void); void search_next(void); void search_next_word(void); NONNULL_ARGS void reg_replace(const char *pattern, const char *format, ReplaceFlags flags); #endif dte-1.9.1/src/selection.c000066400000000000000000000037601354415143300152270ustar00rootroot00000000000000#include "selection.h" #include "buffer.h" void init_selection(const View *v, SelectionInfo *info) { BlockIter ei; CodePoint u; info->so = v->sel_so; info->eo = block_iter_get_offset(&v->cursor); info->si = v->cursor; block_iter_goto_offset(&info->si, info->so); info->swapped = false; if (info->so > info->eo) { size_t o = info->so; info->so = info->eo; info->eo = o; info->si = v->cursor; info->swapped = true; } ei = info->si; block_iter_skip_bytes(&ei, info->eo - info->so); if (block_iter_is_eof(&ei)) { if (info->so == info->eo) { return; } info->eo -= block_iter_prev_char(&ei, &u); } if (v->selection == SELECT_LINES) { info->so -= block_iter_bol(&info->si); info->eo += block_iter_eat_line(&ei); } else { // Character under cursor belongs to the selection info->eo += block_iter_next_column(&ei); } } size_t prepare_selection(View *v) { SelectionInfo info; init_selection(v, &info); v->cursor = info.si; return info.eo - info.so; } char *view_get_selection(View *v, size_t *size) { char *buf = NULL; *size = 0; if (v->selection) { BlockIter save = v->cursor; *size = prepare_selection(v); buf = block_iter_get_bytes(&v->cursor, *size); v->cursor = save; } return buf; } size_t get_nr_selected_lines(const SelectionInfo *info) { BlockIter bi = info->si; size_t pos = info->so; CodePoint u = 0; size_t nr_lines = 1; while (pos < info->eo) { if (u == '\n') { nr_lines++; } pos += block_iter_next_char(&bi, &u); } return nr_lines; } size_t get_nr_selected_chars(const SelectionInfo *info) { BlockIter bi = info->si; size_t pos = info->so; CodePoint u; size_t nr_chars = 0; while (pos < info->eo) { nr_chars++; pos += block_iter_next_char(&bi, &u); } return nr_chars; } dte-1.9.1/src/selection.h000066400000000000000000000006501354415143300152270ustar00rootroot00000000000000#ifndef SELECTION_H #define SELECTION_H #include "view.h" typedef struct { BlockIter si; size_t so; size_t eo; bool swapped; } SelectionInfo; void init_selection(const View *v, SelectionInfo *info); size_t prepare_selection(View *v); char *view_get_selection(View *v, size_t *size); size_t get_nr_selected_lines(const SelectionInfo *info); size_t get_nr_selected_chars(const SelectionInfo *info); #endif dte-1.9.1/src/spawn.c000066400000000000000000000206031354415143300143650ustar00rootroot00000000000000#include #include #include #include #include #include #include "spawn.h" #include "editor.h" #include "error.h" #include "msg.h" #include "regexp.h" #include "terminal/terminal.h" #include "util/exec.h" #include "util/string.h" #include "util/strtonum.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" static void handle_error_msg(const Compiler *c, char *str) { size_t i, len; for (i = 0; str[i]; i++) { if (str[i] == '\n') { str[i] = '\0'; break; } if (str[i] == '\t') { str[i] = ' '; } } len = i; if (len == 0) { return; } for (i = 0; i < c->error_formats.count; i++) { const ErrorFormat *p = c->error_formats.ptrs[i]; PointerArray m = PTR_ARRAY_INIT; if (!regexp_exec_sub(&p->re, str, len, &m, 0)) { continue; } if (!p->ignore) { Message *msg = new_message(m.ptrs[p->msg_idx]); msg->loc = xnew0(FileLocation, 1); if (p->file_idx >= 0) { msg->loc->filename = xstrdup(m.ptrs[p->file_idx]); if (p->line_idx >= 0) { str_to_ulong(m.ptrs[p->line_idx], &msg->loc->line); } if (p->column_idx >= 0) { str_to_ulong(m.ptrs[p->column_idx], &msg->loc->column); } } add_message(msg); } ptr_array_free(&m); return; } add_message(new_message(str)); } static void read_errors(const Compiler *c, int fd, bool quiet) { FILE *f = fdopen(fd, "r"); char line[4096]; if (!f) { // Should not happen return; } while (fgets(line, sizeof(line), f)) { if (!quiet) { fputs(line, stderr); } handle_error_msg(c, line); } fclose(f); } static void filter(int rfd, int wfd, FilterData *fdata) { size_t wlen = 0; String buf = STRING_INIT; if (!fdata->in_len) { close(wfd); wfd = -1; } while (1) { fd_set rfds, wfds; fd_set *wfdsp = NULL; int fd_high = rfd; FD_ZERO(&rfds); FD_SET(rfd, &rfds); if (wfd >= 0) { FD_ZERO(&wfds); FD_SET(wfd, &wfds); wfdsp = &wfds; } if (wfd > fd_high) { fd_high = wfd; } if (select(fd_high + 1, &rfds, wfdsp, NULL, NULL) < 0) { if (errno == EINTR) { continue; } error_msg("select: %s", strerror(errno)); break; } if (FD_ISSET(rfd, &rfds)) { char data[8192]; ssize_t rc = read(rfd, data, sizeof(data)); if (rc < 0) { error_msg("read: %s", strerror(errno)); break; } if (!rc) { if (wlen < fdata->in_len) { error_msg("Command did not read all data."); } break; } string_add_buf(&buf, data, (size_t) rc); } if (wfdsp && FD_ISSET(wfd, &wfds)) { ssize_t rc = write(wfd, fdata->in + wlen, fdata->in_len - wlen); if (rc < 0) { error_msg("write: %s", strerror(errno)); break; } wlen += (size_t) rc; if (wlen == fdata->in_len) { if (close(wfd)) { error_msg("close: %s", strerror(errno)); break; } wfd = -1; } } } fdata->out = string_steal(&buf, &fdata->out_len); } static int open_dev_null(int flags) { int fd = open("/dev/null", flags); if (fd < 0) { error_msg("Error opening /dev/null: %s", strerror(errno)); } else { close_on_exec(fd); } return fd; } static int handle_child_error(pid_t pid) { int ret = wait_child(pid); if (ret < 0) { error_msg("waitpid: %s", strerror(errno)); } else if (ret >= 256) { error_msg("Child received signal %d", ret >> 8); } else if (ret) { error_msg("Child returned %d", ret); } return ret; } int spawn_filter(char **argv, FilterData *data) { int p0[2] = {-1, -1}; int p1[2] = {-1, -1}; int dev_null = -1; data->out = NULL; data->out_len = 0; if (pipe_close_on_exec(p0) || pipe_close_on_exec(p1)) { error_msg("pipe: %s", strerror(errno)); goto error; } dev_null = open_dev_null(O_WRONLY); if (dev_null < 0) { goto error; } int fd[3] = {p0[0], p1[1], dev_null}; const pid_t pid = fork_exec(argv, fd); if (pid < 0) { error_msg("Error: %s", strerror(errno)); goto error; } close(dev_null); close(p0[0]); close(p1[1]); filter(p1[0], p0[1], data); close(p1[0]); close(p0[1]); if (handle_child_error(pid)) { return -1; } return 0; error: close(p0[0]); close(p0[1]); close(p1[0]); close(p1[1]); close(dev_null); return -1; } int spawn_writer(char **argv, const char *text, size_t length) { if (length == 0) { return 0; } int p[2] = {-1, -1}; int dev_null = -1; if (pipe_close_on_exec(p)) { error_msg("pipe: %s", strerror(errno)); goto error; } dev_null = open_dev_null(O_WRONLY); if (dev_null < 0) { goto error; } int fd[3] = {p[0], dev_null, dev_null}; const pid_t pid = fork_exec(argv, fd); if (pid < 0) { error_msg("Error: %s", strerror(errno)); goto error; } close(dev_null); close(p[0]); if (xwrite(p[1], text, length) < 0) { error_msg("write: %s", strerror(errno)); close(p[1]); return -1; } close(p[1]); return handle_child_error(pid) ? -1 : 0; error: close(p[0]); close(p[1]); close(dev_null); return -1; } void spawn_compiler(char **args, SpawnFlags flags, const Compiler *c) { const bool read_stdout = !!(flags & SPAWN_READ_STDOUT); const bool quiet = !!(flags & SPAWN_QUIET); bool prompt = !!(flags & SPAWN_PROMPT); int p[2], fd[3]; fd[0] = open_dev_null(O_RDONLY); if (fd[0] < 0) { return; } const int dev_null = open_dev_null(O_WRONLY); if (dev_null < 0) { close(fd[0]); return; } if (pipe_close_on_exec(p)) { error_msg("pipe: %s", strerror(errno)); close(dev_null); close(fd[0]); return; } if (read_stdout) { fd[1] = p[1]; fd[2] = quiet ? dev_null : 2; } else { fd[1] = quiet ? dev_null : 1; fd[2] = p[1]; } if (!quiet) { editor.child_controls_terminal = true; editor.ui_end(); } const pid_t pid = fork_exec(args, fd); if (pid < 0) { error_msg("Error: %s", strerror(errno)); close(p[1]); prompt = false; } else { // Must close write end of the pipe before read_errors() or // the read end never gets EOF! close(p[1]); read_errors(c, p[0], quiet); handle_child_error(pid); } if (!quiet) { terminal.raw(); if (prompt) { any_key(); } editor.resize(); editor.child_controls_terminal = false; } close(p[0]); close(dev_null); close(fd[0]); } void spawn(char **args, int fd[3], bool prompt) { const int dev_null = open_dev_null(O_WRONLY); if (dev_null < 0) { return; } unsigned int redir_count = 0; if (fd[0] < 0) { fd[0] = open_dev_null(O_RDONLY); if (fd[0] < 0) { close(dev_null); return; } redir_count++; } if (fd[1] < 0) { fd[1] = dev_null; redir_count++; } if (fd[2] < 0) { fd[2] = dev_null; redir_count++; } const bool quiet = (redir_count == 3); if (!quiet) { editor.child_controls_terminal = true; editor.ui_end(); } const pid_t pid = fork_exec(args, fd); if (pid < 0) { error_msg("Error: %s", strerror(errno)); prompt = false; } else { handle_child_error(pid); } if (!quiet) { terminal.raw(); if (prompt) { any_key(); } editor.resize(); editor.child_controls_terminal = false; } if (dev_null >= 0) { close(dev_null); } } dte-1.9.1/src/spawn.h000066400000000000000000000013721354415143300143740ustar00rootroot00000000000000#ifndef SPAWN_H #define SPAWN_H #include "compiler.h" typedef enum { SPAWN_DEFAULT = 0, SPAWN_READ_STDOUT = 1 << 0, // Read errors from stdout instead of stderr SPAWN_QUIET = 1 << 2, // Redirect streams to /dev/null SPAWN_PROMPT = 1 << 4 // Show "press any key to continue" prompt } SpawnFlags; typedef struct { char *in; char *out; size_t in_len; size_t out_len; } FilterData; #define FILTER_DATA_INIT { \ .in = NULL, \ .out = NULL, \ .in_len = 0, \ .out_len = 0 \ } int spawn_filter(char **argv, FilterData *data); int spawn_writer(char **argv, const char *text, size_t length); void spawn_compiler(char **args, SpawnFlags flags, const Compiler *c); void spawn(char **args, int fd[3], bool prompt); #endif dte-1.9.1/src/syntax/000077500000000000000000000000001354415143300144165ustar00rootroot00000000000000dte-1.9.1/src/syntax/bitset.c000066400000000000000000000011111354415143300160460ustar00rootroot00000000000000#include "bitset.h" static void bitset_add(uint8_t *set, unsigned char ch) { unsigned int byte = ch / 8; unsigned int bit = ch & 7; set[byte] |= 1u << bit; } void bitset_add_pattern(uint8_t *set, const unsigned char *pattern) { for (size_t i = 0; pattern[i]; i++) { unsigned int ch = pattern[i]; bitset_add(set, ch); if (pattern[i + 1] == '-' && pattern[i + 2]) { // Add char range for (ch = ch + 1; ch <= pattern[i + 2]; ch++) { bitset_add(set, ch); } i += 2; } } } dte-1.9.1/src/syntax/bitset.h000066400000000000000000000013451354415143300160640ustar00rootroot00000000000000#ifndef SYNTAX_BITSET_H #define SYNTAX_BITSET_H #include #include #include // This is a container type for storing a *set* of chars (bytes). // It uses an array of 256 bits (32 bytes) for lookups, with each // bit index used to determine whether or not the byte with that // value is in the set. typedef uint8_t BitSet[256 / 8]; static inline bool bitset_contains(const uint8_t *set, unsigned char ch) { unsigned int byte = ch / 8; unsigned int bit = ch & 7; return set[byte] & 1u << bit; } static inline void bitset_invert(uint8_t *set) { for (size_t i = 0; i < 32; i++) { set[i] = ~set[i]; } } void bitset_add_pattern(uint8_t *set, const unsigned char *pattern); #endif dte-1.9.1/src/syntax/color.c000066400000000000000000000057741354415143300157150ustar00rootroot00000000000000#include #include "color.h" #include "../debug.h" #include "../completion.h" #include "../util/macros.h" #include "../util/ptr-array.h" #include "../util/str-util.h" #include "../util/xmalloc.h" TermColor *builtin_colors[NR_BC]; static PointerArray hl_colors = PTR_ARRAY_INIT; static const char builtin_color_names[NR_BC][16] = { [BC_DEFAULT] = "default", [BC_NONTEXT] = "nontext", [BC_NOLINE] = "noline", [BC_WSERROR] = "wserror", [BC_SELECTION] = "selection", [BC_CURRENTLINE] = "currentline", [BC_LINENUMBER] = "linenumber", [BC_STATUSLINE] = "statusline", [BC_COMMANDLINE] = "commandline", [BC_ERRORMSG] = "errormsg", [BC_INFOMSG] = "infomsg", [BC_TABBAR] = "tabbar", [BC_ACTIVETAB] = "activetab", [BC_INACTIVETAB] = "inactivetab", }; UNITTEST { for (size_t i = 0; i < ARRAY_COUNT(builtin_color_names); i++) { const char *const name = builtin_color_names[i]; if (name[0] == '\0') { BUG("missing string at builtin_color_names[%zu]", i); } if (memchr(name, '\0', sizeof(builtin_color_names[0])) == NULL) { BUG("builtin_color_names[%zu] missing null-terminator", i); } } } void fill_builtin_colors(void) { for (size_t i = 0; i < NR_BC; i++) { builtin_colors[i] = &find_color(builtin_color_names[i])->color; } } HlColor *set_highlight_color(const char *name, const TermColor *color) { for (size_t i = 0, n = hl_colors.count; i < n; i++) { HlColor *c = hl_colors.ptrs[i]; if (streq(name, c->name)) { c->color = *color; return c; } } HlColor *c = xnew(HlColor, 1); c->name = xstrdup(name); c->color = *color; ptr_array_add(&hl_colors, c); return c; } static HlColor *find_real_color(const char *name) { for (size_t i = 0; i < hl_colors.count; i++) { HlColor *c = hl_colors.ptrs[i]; if (streq(c->name, name)) { return c; } } return NULL; } HlColor *find_color(const char *name) { HlColor *color = find_real_color(name); if (color) { return color; } const char *dot = strchr(name, '.'); if (dot) { return find_real_color(dot + 1); } return NULL; } // NOTE: you have to call update_all_syntax_colors() after this void remove_extra_colors(void) { BUG_ON(hl_colors.count < NR_BC); for (size_t i = NR_BC; i < hl_colors.count; i++) { HlColor *c = hl_colors.ptrs[i]; // Make possible use after free error easy to see c->color.fg = COLOR_RED; c->color.bg = COLOR_YELLOW; c->color.attr = ATTR_BOLD; free(c->name); c->name = NULL; free(c); hl_colors.ptrs[i] = NULL; } hl_colors.count = NR_BC; } void collect_hl_colors(const char *prefix) { for (size_t i = 0, n = hl_colors.count; i < n; i++) { const HlColor *c = hl_colors.ptrs[i]; if (str_has_prefix(c->name, prefix)) { add_completion(xstrdup(c->name)); } } } dte-1.9.1/src/syntax/color.h000066400000000000000000000012661354415143300157120ustar00rootroot00000000000000#ifndef SYNTAX_COLOR_H #define SYNTAX_COLOR_H #include "../terminal/color.h" enum builtin_color { BC_DEFAULT, BC_NONTEXT, BC_NOLINE, BC_WSERROR, BC_SELECTION, BC_CURRENTLINE, BC_LINENUMBER, BC_STATUSLINE, BC_COMMANDLINE, BC_ERRORMSG, BC_INFOMSG, BC_TABBAR, BC_ACTIVETAB, BC_INACTIVETAB, NR_BC }; typedef struct { char *name; TermColor color; } HlColor; extern TermColor *builtin_colors[NR_BC]; void fill_builtin_colors(void); HlColor *set_highlight_color(const char *name, const TermColor *color); HlColor *find_color(const char *name); void remove_extra_colors(void); void collect_hl_colors(const char *prefix); #endif dte-1.9.1/src/syntax/highlight.c000066400000000000000000000315551354415143300165420ustar00rootroot00000000000000#include #include #include "highlight.h" #include "syntax.h" #include "../debug.h" #include "../util/ascii.h" #include "../util/xmalloc.h" static bool state_is_valid(const State *st) { return ((uintptr_t)st & 1) == 0; } static void mark_state_invalid(void **ptrs, size_t idx) { const State *st = ptrs[idx]; ptrs[idx] = (State *)((uintptr_t)st | 1); } static bool states_equal(void **ptrs, size_t idx, const State *b) { const State *a = (State *)((uintptr_t)ptrs[idx] & ~(uintptr_t)1); return a == b; } static bool is_buffered(const Condition *cond, const char *str, size_t len) { if (len != (size_t)cond->u.cond_bufis.len) { return false; } if (cond->u.cond_bufis.icase) { return mem_equal_icase(cond->u.cond_bufis.str, str, len); } return !memcmp(cond->u.cond_bufis.str, str, len); } static State *handle_heredoc ( Syntax *syn, State *state, const char *delim, size_t len ) { for (size_t i = 0, n = state->heredoc.states.count; i < n; i++) { HeredocState *s = state->heredoc.states.ptrs[i]; if (s->len == len && !memcmp(s->delim, delim, len)) { return s->state; } } SyntaxMerge m = { .subsyn = state->heredoc.subsyntax, .return_state = state->a.destination, .delim = delim, .delim_len = len }; HeredocState *s = xnew0(HeredocState, 1); s->state = merge_syntax(syn, &m); s->delim = xmemdup(delim, len); s->len = len; ptr_array_add(&state->heredoc.states, s); return s->state; } // Line should be terminated with \n unless it's the last line static HlColor **highlight_line ( Syntax *syn, State *state, const LineRef *lr, State **ret ) { static HlColor **colors; static size_t alloc; const char *const line = lr->line; const size_t len = lr->size; size_t i = 0; ssize_t sidx = -1; if (len > alloc) { alloc = ROUND_UP(len, 128); xrenew(colors, alloc); } while (1) { const Condition *cond; const Action *a; unsigned char ch; top: if (i == len) { break; } ch = line[i]; for (size_t ci = 0, n = state->conds.count; ci < n; ci++) { cond = state->conds.ptrs[ci]; a = &cond->a; switch (cond->type) { case COND_CHAR_BUFFER: if (!bitset_contains(cond->u.cond_char.bitset, ch)) { break; } if (sidx < 0) { sidx = i; } colors[i++] = a->emit_color; state = a->destination; goto top; case COND_BUFIS: if (sidx >= 0 && is_buffered(cond, line + sidx, i - sidx)) { for (size_t idx = sidx; idx < i; idx++) { colors[idx] = a->emit_color; } sidx = -1; state = a->destination; goto top; } break; case COND_CHAR: if (!bitset_contains(cond->u.cond_char.bitset, ch)) { break; } colors[i++] = a->emit_color; sidx = -1; state = a->destination; goto top; case COND_CHAR1: if (cond->u.cond_single_char.ch != ch) { break; } colors[i++] = a->emit_color; sidx = -1; state = a->destination; goto top; case COND_INLIST: if ( sidx >= 0 && hashset_get ( &cond->u.cond_inlist.list->strings, line + sidx, i - sidx ) ) { for (size_t idx = sidx; idx < i; idx++) { colors[idx] = a->emit_color; } sidx = -1; state = a->destination; goto top; } break; case COND_RECOLOR: { ssize_t idx = i - cond->u.cond_recolor.len; if (idx < 0) { idx = 0; } while (idx < i) { colors[idx++] = a->emit_color; } } break; case COND_RECOLOR_BUFFER: if (sidx >= 0) { while (sidx < i) { colors[sidx++] = a->emit_color; } sidx = -1; } break; case COND_STR: { size_t slen = cond->u.cond_str.len; size_t end = i + slen; if ( len >= end && !memcmp(cond->u.cond_str.str, line + i, slen) ) { while (i < end) { colors[i++] = a->emit_color; } sidx = -1; state = a->destination; goto top; } } break; case COND_STR_ICASE: { size_t slen = cond->u.cond_str.len; size_t end = i + slen; if ( len >= end && mem_equal_icase(cond->u.cond_str.str, line + i, slen) ) { while (i < end) { colors[i++] = a->emit_color; } sidx = -1; state = a->destination; goto top; } } break; case COND_STR2: // Optimized COND_STR (length 2, case sensitive) if ( ch == cond->u.cond_str.str[0] && len - i > 1 && line[i + 1] == cond->u.cond_str.str[1] ) { colors[i++] = a->emit_color; colors[i++] = a->emit_color; sidx = -1; state = a->destination; goto top; } break; case COND_HEREDOCEND: { const char *str = cond->u.cond_heredocend.str; size_t slen = cond->u.cond_heredocend.len; size_t end = i + slen; if (len >= end && (slen == 0 || !memcmp(str, line + i, slen))) { while (i < end) { colors[i++] = a->emit_color; } sidx = -1; state = a->destination; goto top; } } break; } } switch (state->type) { case STATE_EAT: colors[i++] = state->a.emit_color; // fallthrough case STATE_NOEAT: sidx = -1; // fallthrough case STATE_NOEAT_BUFFER: a = &state->a; state = a->destination; break; case STATE_HEREDOCBEGIN: if (sidx < 0) { sidx = i; } state = handle_heredoc(syn, state, line + sidx, i - sidx); break; case STATE_INVALID: default: BUG("Invalid default action type should never make it here"); } } if (ret) { *ret = state; } return colors; } static void resize_line_states(PointerArray *s, size_t count) { if (s->alloc < count) { s->alloc = ROUND_UP(count, 64); xrenew(s->ptrs, s->alloc); } } static void move_line_states ( PointerArray *s, size_t to, size_t from, size_t count ) { memmove(s->ptrs + to, s->ptrs + from, count * sizeof(*s->ptrs)); } static void block_iter_move_down(BlockIter *bi, size_t count) { while (count--) { block_iter_eat_line(bi); } } static ssize_t fill_hole(Buffer *b, BlockIter *bi, ssize_t sidx, ssize_t eidx) { void **ptrs = b->line_start_states.ptrs; ssize_t idx = sidx; while (idx < eidx) { LineRef lr; State *st; fill_line_nl_ref(bi, &lr); block_iter_eat_line(bi); highlight_line(b->syn, ptrs[idx++], &lr, &st); if (ptrs[idx] == st) { // Was not invalidated and didn't change break; } if (states_equal(ptrs, idx, st)) { // Was invalidated and didn't change ptrs[idx] = st; } else { // Invalidated or not but changed anyway ptrs[idx] = st; if (idx == eidx) { mark_state_invalid(ptrs, idx + 1); } } } return idx - sidx; } void hl_fill_start_states(Buffer *b, size_t line_nr) { BlockIter bi = BLOCK_ITER_INIT(&b->blocks); PointerArray *s = &b->line_start_states; ssize_t current_line = 0; ssize_t idx = 0; if (b->syn == NULL) { return; } // NOTE: "+ 2" so that you don't have to worry about overflow in fill_hole() resize_line_states(s, line_nr + 2); State **states = (State **)s->ptrs; // Update invalid ssize_t last = line_nr; if (last >= s->count) { last = s->count - 1; } while (1) { while (idx <= last && state_is_valid(states[idx])) { idx++; } if (idx > last) { break; } // Go to line before first hole idx--; block_iter_move_down(&bi, idx - current_line); current_line = idx; // NOTE: might not fill entire hole, which is ok ssize_t count = fill_hole(b, &bi, idx, last); idx += count; current_line += count; } // Add new block_iter_move_down(&bi, s->count - 1 - current_line); while (s->count - 1 < line_nr) { LineRef lr; fill_line_nl_ref(&bi, &lr); highlight_line ( b->syn, states[s->count - 1], &lr, &states[s->count] ); s->count++; block_iter_eat_line(&bi); } } HlColor **hl_line ( Buffer *b, const LineRef *lr, size_t line_nr, bool *next_changed ) { *next_changed = false; if (b->syn == NULL) { return NULL; } PointerArray *s = &b->line_start_states; BUG_ON(line_nr >= s->count); State *next; HlColor **colors = highlight_line(b->syn, s->ptrs[line_nr++], lr, &next); if (line_nr == s->count) { resize_line_states(s, s->count + 1); s->ptrs[s->count++] = next; *next_changed = true; } else if (s->ptrs[line_nr] == next) { // Was not invalidated and didn't change } else if (states_equal(s->ptrs, line_nr, next)) { // Was invalidated and didn't change s->ptrs[line_nr] = next; // *next_changed = 1; } else { // Invalidated or not but changed anyway s->ptrs[line_nr] = next; *next_changed = true; if (line_nr + 1 < s->count) { mark_state_invalid(s->ptrs, line_nr + 1); } } return colors; } // Called after text has been inserted to re-highlight changed lines void hl_insert(Buffer *b, size_t first, size_t lines) { PointerArray *s = &b->line_start_states; size_t last = first + lines; if (first >= s->count) { // Nothing to re-highlight return; } if (last + 1 >= s->count) { // Last already highlighted lines changed. // There's nothing to gain, throw them away. s->count = first + 1; return; } // Add room for new line states if (lines) { size_t to = last + 1; size_t from = first + 1; resize_line_states(s, s->count + lines); move_line_states(s, to, from, s->count - from); s->count += lines; } // Invalidate start states of new and changed lines for (size_t i = first + 1; i <= last + 1; i++) { mark_state_invalid(s->ptrs, i); } } // Called after text has been deleted to re-highlight changed lines void hl_delete(Buffer *b, size_t first, size_t deleted_nl) { PointerArray *s = &b->line_start_states; size_t last = first + deleted_nl; if (s->count == 1) { return; } if (first >= s->count) { // Nothing to highlight return; } if (last + 1 >= s->count) { // Last already highlighted lines changed. // There's nothing to gain, throw them away. s->count = first + 1; return; } // There are already highlighted lines after changed lines. // Try to save the work. // Remove deleted lines (states) if (deleted_nl) { size_t to = first + 1; size_t from = last + 1; move_line_states(s, to, from, s->count - from); s->count -= deleted_nl; } // Invalidate line start state after the changed line mark_state_invalid(s->ptrs, first + 1); } dte-1.9.1/src/syntax/highlight.h000066400000000000000000000006301354415143300165350ustar00rootroot00000000000000#ifndef SYNTAX_HIGHLIGHT_H #define SYNTAX_HIGHLIGHT_H #include #include #include "../buffer.h" #include "../terminal/color.h" HlColor **hl_line(Buffer *b, const LineRef *lr, size_t line_nr, bool *next_changed); void hl_fill_start_states(Buffer *b, size_t line_nr); void hl_insert(Buffer *b, size_t first, size_t lines); void hl_delete(Buffer *b, size_t first, size_t lines); #endif dte-1.9.1/src/syntax/state.c000066400000000000000000000320471354415143300157100ustar00rootroot00000000000000#include #include "state.h" #include "syntax.h" #include "../command.h" #include "../config.h" #include "../editor.h" #include "../error.h" #include "../parse-args.h" #include "../terminal/color.h" #include "../util/path.h" #include "../util/str-util.h" #include "../util/strtonum.h" #include "../util/xmalloc.h" #include "../util/xsnprintf.h" static Syntax *current_syntax; static State *current_state; static unsigned int saved_nr_errors; // Used to check if nr_errors changed static bool no_syntax(void) { if (current_syntax) { return false; } error_msg("No syntax started"); return true; } static bool no_state(void) { if (no_syntax()) { return true; } if (current_state) { return false; } error_msg("No state started"); return true; } static void close_state(void) { if (current_state && current_state->type == STATE_INVALID) { // Command prefix in error message makes no sense const Command *save = current_command; current_command = NULL; error_msg("No default action in state %s", current_state->name); current_command = save; } current_state = NULL; } static State *find_or_add_state(const char *name) { State *st = find_state(current_syntax, name); if (st) { return st; } st = xnew0(State, 1); st->name = xstrdup(name); st->defined = false; st->type = STATE_INVALID; ptr_array_add(¤t_syntax->states, st); return st; } static State *reference_state(const char *name) { if (streq(name, "this")) { return current_state; } return find_or_add_state(name); } static bool not_subsyntax(void) { if (is_subsyntax(current_syntax)) { return false; } error_msg("Destination state END allowed only in a subsyntax."); return true; } static bool subsyntax_call(const char *name, const char *ret, State **dest) { SyntaxMerge m = { .subsyn = find_any_syntax(name), .return_state = NULL, .delim = NULL, .delim_len = 0, }; bool ok = true; if (!m.subsyn) { error_msg("No such syntax %s", name); ok = false; } else if (!is_subsyntax(m.subsyn)) { error_msg("Syntax %s is not subsyntax", name); ok = false; } if (streq(ret, "END")) { if (not_subsyntax()) { ok = false; } } else if (ok) { m.return_state = reference_state(ret); } if (ok) { *dest = merge_syntax(current_syntax, &m); } return ok; } static bool destination_state(const char *name, State **dest) { const char *const sep = strchr(name, ':'); if (sep) { // subsyntax:returnstate char *sub = xstrcut(name, sep - name); bool success = subsyntax_call(sub, sep + 1, dest); free(sub); return success; } if (streq(name, "END")) { if (not_subsyntax()) { return false; } *dest = NULL; return true; } *dest = reference_state(name); return true; } static Condition *add_condition ( ConditionType type, const char *dest, const char *emit ) { if (no_state()) { return NULL; } State *d = NULL; if (dest && !destination_state(dest, &d)) { return NULL; } Condition *c = xnew0(Condition, 1); c->a.destination = d; c->a.emit_name = emit ? xstrdup(emit) : NULL; c->type = type; ptr_array_add(¤t_state->conds, c); return c; } static void cmd_bufis(const CommandArgs *a) { char **args = a->args; const bool icase = a->flags[0] == 'i'; const char *str = args[0]; const size_t len = strlen(str); Condition *c; if (len > ARRAY_COUNT(c->u.cond_bufis.str)) { error_msg ( "Maximum length of string is %zu bytes", ARRAY_COUNT(c->u.cond_bufis.str) ); return; } c = add_condition(COND_BUFIS, args[1], args[2]); if (c) { memcpy(c->u.cond_bufis.str, str, len); c->u.cond_bufis.len = len; c->u.cond_bufis.icase = icase; } } static void cmd_char(const CommandArgs *a) { const char *pf = a->flags; bool n_flag = false; bool b_flag = false; while (*pf) { switch (*pf) { case 'b': b_flag = true; break; case 'n': n_flag = true; break; } pf++; } char **args = a->args; ConditionType type; if (b_flag) { type = COND_CHAR_BUFFER; } else if (!n_flag && args[0][0] != '\0' && args[0][1] == '\0') { type = COND_CHAR1; } else { type = COND_CHAR; } Condition *c = add_condition(type, args[1], args[2]); if (!c) { return; } if (type == COND_CHAR1) { c->u.cond_single_char.ch = (unsigned char)args[0][0]; } else { bitset_add_pattern(c->u.cond_char.bitset, args[0]); if (n_flag) { bitset_invert(c->u.cond_char.bitset); } } } static void cmd_default(const CommandArgs *a) { close_state(); if (no_syntax()) { return; } ptr_array_add ( ¤t_syntax->default_colors, copy_string_array(a->args, a->nr_args) ); } static void cmd_eat(const CommandArgs *a) { if (no_state()) { return; } if (!destination_state(a->args[0], ¤t_state->a.destination)) { return; } current_state->type = STATE_EAT; current_state->a.emit_name = a->args[1] ? xstrdup(a->args[1]) : NULL; current_state = NULL; } static void cmd_heredocbegin(const CommandArgs *a) { if (no_state()) { return; } const char *sub = a->args[0]; Syntax *subsyn = find_any_syntax(sub); if (!subsyn) { error_msg("No such syntax %s", sub); return; } if (!is_subsyntax(subsyn)) { error_msg("Syntax %s is not subsyntax", sub); return; } // a.destination is used as the return state if (!destination_state(a->args[1], ¤t_state->a.destination)) { return; } current_state->a.emit_name = NULL; current_state->type = STATE_HEREDOCBEGIN; current_state->heredoc.subsyntax = subsyn; current_state = NULL; // Normally merge() marks subsyntax used but in case of heredocs merge() // is not called when syntax file is loaded. subsyn->used = true; } static void cmd_heredocend(const CommandArgs *a) { add_condition(COND_HEREDOCEND, a->args[0], a->args[1]); current_syntax->heredoc = true; } static void cmd_list(const CommandArgs *a) { close_state(); if (no_syntax()) { return; } char **args = a->args; const char *name = args[0]; StringList *list = find_string_list(current_syntax, name); if (list == NULL) { list = xnew0(StringList, 1); list->name = xstrdup(name); ptr_array_add(¤t_syntax->string_lists, list); } else if (list->defined) { error_msg("List %s already exists.", name); return; } list->defined = true; bool icase = a->flags[0] == 'i'; size_t nstrings = a->nr_args - 1; hashset_init(&list->strings, nstrings, icase); hashset_add_many(&list->strings, args + 1, nstrings); } static void cmd_inlist(const CommandArgs *a) { char **args = a->args; const char *name = args[0]; const char *emit = args[2] ? args[2] : name; StringList *list = find_string_list(current_syntax, name); Condition *c = add_condition(COND_INLIST, args[1], emit); if (c == NULL) { return; } if (list == NULL) { // Add undefined list list = xnew0(StringList, 1); list->name = xstrdup(name); ptr_array_add(¤t_syntax->string_lists, list); } list->used = true; c->u.cond_inlist.list = list; } static void cmd_noeat(const CommandArgs *a) { if (no_state()) { return; } const char *arg = a->args[0]; if (streq(arg, current_state->name)) { error_msg ( "Using noeat to to jump to parent state causes" " infinite loop" ); return; } State *dest; if (!destination_state(arg, &dest)) { return; } if (dest == current_state) { // The noeat command doesn't consume anything, so jumping to // the same state will always produce an infinite loop. current_state->type = STATE_INVALID; return; } current_state->a.destination = dest; current_state->a.emit_name = NULL; current_state->type = a->flags[0] == 'b' ? STATE_NOEAT_BUFFER : STATE_NOEAT; current_state = NULL; } static void cmd_recolor(const CommandArgs *a) { // If length is not specified then buffered bytes will be recolored ConditionType type = COND_RECOLOR_BUFFER; size_t len = 0; const char *len_str = a->args[1]; if (len_str) { type = COND_RECOLOR; if (!str_to_size(len_str, &len)) { error_msg("invalid number: %s", len_str); return; } if (len == 0) { error_msg("number of bytes must be larger than 0"); return; } if (len > 2500) { error_msg("number of bytes cannot be larger than 2500"); return; } } Condition *c = add_condition(type, NULL, a->args[0]); if (c && type == COND_RECOLOR) { c->u.cond_recolor.len = len; } } static void cmd_state(const CommandArgs *a) { close_state(); if (no_syntax()) { return; } const char *name = a->args[0]; if (streq(name, "END") || streq(name, "this")) { error_msg("%s is reserved state name", name); return; } State *s = find_or_add_state(name); if (s->defined) { error_msg("State %s already exists.", name); return; } s->defined = true; s->emit_name = xstrdup(a->args[1] ? a->args[1] : a->args[0]); current_state = s; } static void cmd_str(const CommandArgs *a) { bool icase = a->flags[0] == 'i'; ConditionType type = icase ? COND_STR_ICASE : COND_STR; const char *str = a->args[0]; Condition *c; size_t len = strlen(str); if (len > ARRAY_COUNT(c->u.cond_str.str)) { error_msg ( "Maximum length of string is %zu bytes", ARRAY_COUNT(c->u.cond_str.str) ); return; } // Strings of length 2 are very common if (!icase && len == 2) { type = COND_STR2; } c = add_condition(type, a->args[1], a->args[2]); if (c) { memcpy(c->u.cond_str.str, str, len); c->u.cond_str.len = len; } } static void finish_syntax(void) { close_state(); finalize_syntax(current_syntax, saved_nr_errors); current_syntax = NULL; } static void cmd_syntax(const CommandArgs *a) { if (current_syntax) { finish_syntax(); } current_syntax = xnew0(Syntax, 1); current_syntax->name = xstrdup(a->args[0]); current_state = NULL; saved_nr_errors = get_nr_errors(); } static void cmd_include(const CommandArgs *a); // Prevent Clang whining about .max_args = -1 #if HAS_WARNING("-Wbitfield-constant-conversion") IGNORE_WARNING("-Wbitfield-constant-conversion") #endif static const Command syntax_commands[] = { {"bufis", "i", 2, 3, cmd_bufis}, {"char", "bn", 2, 3, cmd_char}, {"default", "", 2, -1, cmd_default}, {"eat", "", 1, 2, cmd_eat}, {"heredocbegin", "", 2, 2, cmd_heredocbegin}, {"heredocend", "", 1, 2, cmd_heredocend}, {"include", "b", 1, 1, cmd_include}, {"inlist", "", 2, 3, cmd_inlist}, {"list", "i", 2, -1, cmd_list}, {"noeat", "b", 1, 1, cmd_noeat}, {"recolor", "", 1, 2, cmd_recolor}, {"state", "", 1, 2, cmd_state}, {"str", "i", 2, 3, cmd_str}, {"syntax", "", 1, 1, cmd_syntax}, {"", "", 0, 0, NULL} }; UNIGNORE_WARNINGS static void cmd_include(const CommandArgs *a) { ConfigFlags flags = CFG_MUST_EXIST; if (a->flags[0] == 'b') { flags |= CFG_BUILTIN; } read_config(syntax_commands, a->args[0], flags); } Syntax *load_syntax_file(const char *filename, ConfigFlags flags, int *err) { const char *saved_config_file = config_file; int saved_config_line = config_line; *err = do_read_config(syntax_commands, filename, flags); if (*err) { config_file = saved_config_file; config_line = saved_config_line; return NULL; } if (current_syntax) { finish_syntax(); find_unused_subsyntaxes(); } config_file = saved_config_file; config_line = saved_config_line; Syntax *syn = find_syntax(path_basename(filename)); if (syn && editor.status != EDITOR_INITIALIZING) { update_syntax_colors(syn); } if (syn == NULL) { *err = EINVAL; } return syn; } Syntax *load_syntax_by_filetype(const char *filetype) { const char *cfgdir = editor.user_config_dir; char filename[4096]; int err; xsnprintf(filename, sizeof filename, "%s/syntax/%s", cfgdir, filetype); Syntax *syn = load_syntax_file(filename, CFG_NOFLAGS, &err); if (syn || err != ENOENT) { return syn; } xsnprintf(filename, sizeof filename, "syntax/%s", filetype); return load_syntax_file(filename, CFG_BUILTIN, &err); } dte-1.9.1/src/syntax/state.h000066400000000000000000000003701354415143300157070ustar00rootroot00000000000000#ifndef SYNTAX_STATE_H #define SYNTAX_STATE_H #include #include "syntax.h" #include "../config.h" Syntax *load_syntax_file(const char *filename, ConfigFlags f, int *err); Syntax *load_syntax_by_filetype(const char *filetype); #endif dte-1.9.1/src/syntax/syntax.c000066400000000000000000000211521354415143300161110ustar00rootroot00000000000000#include #include "syntax.h" #include "state.h" #include "../error.h" #include "../util/ascii.h" #include "../util/str-util.h" #include "../util/xmalloc.h" static PointerArray syntaxes = PTR_ARRAY_INIT; StringList *find_string_list(const Syntax *syn, const char *name) { for (size_t i = 0, n = syn->string_lists.count; i < n; i++) { StringList *list = syn->string_lists.ptrs[i]; if (streq(list->name, name)) { return list; } } return NULL; } State *find_state(const Syntax *syn, const char *name) { for (size_t i = 0, n = syn->states.count; i < n; i++) { State *s = syn->states.ptrs[i]; if (streq(s->name, name)) { return s; } } return NULL; } static bool has_destination(ConditionType type) { switch (type) { case COND_RECOLOR: case COND_RECOLOR_BUFFER: return false; default: return true; } } Syntax *find_any_syntax(const char *name) { for (size_t i = 0; i < syntaxes.count; i++) { Syntax *syn = syntaxes.ptrs[i]; if (streq(syn->name, name)) { return syn; } } return NULL; } static const char *fix_name(const char *name, const char *prefix) { static char buf[64]; snprintf(buf, sizeof(buf), "%s%s", prefix, name); return buf; } static void fix_action(Syntax *syn, Action *a, const char *prefix) { if (a->destination) { const char *name = fix_name(a->destination->name, prefix); a->destination = find_state(syn, name); } if (a->emit_name) { a->emit_name = xstrdup(a->emit_name); } } static void fix_conditions ( Syntax *syn, State *s, SyntaxMerge *m, const char *prefix ) { for (size_t i = 0, n = s->conds.count; i < n; i++) { Condition *c = s->conds.ptrs[i]; fix_action(syn, &c->a, prefix); if (c->a.destination == NULL && has_destination(c->type)) { c->a.destination = m->return_state; } if (m->delim && c->type == COND_HEREDOCEND) { c->u.cond_heredocend.str = xmemdup(m->delim, m->delim_len); c->u.cond_heredocend.len = m->delim_len; } } fix_action(syn, &s->a, prefix); if (s->a.destination == NULL) { s->a.destination = m->return_state; } } static const char *get_prefix(void) { static int counter; static char prefix[32]; snprintf(prefix, sizeof(prefix), "%d-", counter++); return prefix; } static void update_state_colors(Syntax *syn, State *s); State *merge_syntax(Syntax *syn, SyntaxMerge *m) { // NOTE: string_lists is owned by Syntax so there's no need to // copy it. Freeing Condition does not free any string lists. const char *prefix = get_prefix(); PointerArray *states = &syn->states; size_t old_count = states->count; states->count += m->subsyn->states.count; if (states->count > states->alloc) { states->alloc = states->count; xrenew(states->ptrs, states->alloc); } memcpy ( states->ptrs + old_count, m->subsyn->states.ptrs, sizeof(*states->ptrs) * m->subsyn->states.count ); for (size_t i = old_count; i < states->count; i++) { State *s = xmemdup(states->ptrs[i], sizeof(State)); states->ptrs[i] = s; s->name = xstrdup(fix_name(s->name, prefix)); s->emit_name = xstrdup(s->emit_name); if (s->conds.count > 0) { s->conds.ptrs = xmemdup ( s->conds.ptrs, sizeof(void *) * s->conds.alloc ); for (size_t j = 0; j < s->conds.count; j++) { s->conds.ptrs[j] = xmemdup(s->conds.ptrs[j], sizeof(Condition)); } } // Mark unvisited so that state that is used only as a return // state gets visited. s->visited = false; // Don't complain about unvisited copied states. s->copied = true; } for (size_t i = old_count; i < states->count; i++) { fix_conditions(syn, states->ptrs[i], m, prefix); if (m->delim) { update_state_colors(syn, states->ptrs[i]); } } m->subsyn->used = true; return states->ptrs[old_count]; } static void visit(State *s) { if (s->visited) { return; } s->visited = true; for (size_t i = 0, n = s->conds.count; i < n; i++) { Condition *cond = s->conds.ptrs[i]; if (cond->a.destination) { visit(cond->a.destination); } } if (s->a.destination) { visit(s->a.destination); } } static void free_condition(Condition *cond) { free(cond->a.emit_name); free(cond); } static void free_state(State *s) { free(s->name); free(s->emit_name); ptr_array_free_cb(&s->conds, FREE_FUNC(free_condition)); free(s->a.emit_name); free(s); } static void free_string_list(StringList *list) { hashset_free(&list->strings); free(list->name); free(list); } static void free_syntax(Syntax *syn) { ptr_array_free_cb(&syn->states, FREE_FUNC(free_state)); ptr_array_free_cb(&syn->string_lists, FREE_FUNC(free_string_list)); ptr_array_free_cb(&syn->default_colors, FREE_FUNC(free_string_array)); free(syn->name); free(syn); } void finalize_syntax(Syntax *syn, unsigned int saved_nr_errors) { if (syn->states.count == 0) { error_msg("Empty syntax"); } for (size_t i = 0, n = syn->states.count; i < n; i++) { State *s = syn->states.ptrs[i]; if (!s->defined) { // This state has been referenced but not defined error_msg("No such state %s", s->name); } } for (size_t i = 0, n = syn->string_lists.count; i < n; i++) { StringList *list = syn->string_lists.ptrs[i]; if (!list->defined) { error_msg("No such list %s", list->name); } } if (syn->heredoc && !is_subsyntax(syn)) { error_msg("heredocend can be used only in subsyntaxes"); } if (find_any_syntax(syn->name)) { error_msg("Syntax %s already exists", syn->name); } if (get_nr_errors() != saved_nr_errors) { free_syntax(syn); return; } // Unused states and lists cause warning only visit(syn->states.ptrs[0]); for (size_t i = 0, n = syn->states.count; i < n; i++) { State *s = syn->states.ptrs[i]; if (!s->visited && !s->copied) { error_msg("State %s is unreachable", s->name); } } for (size_t i = 0, n = syn->string_lists.count; i < n; i++) { StringList *list = syn->string_lists.ptrs[i]; if (!list->used) { error_msg("List %s never used", list->name); } } ptr_array_add(&syntaxes, syn); } Syntax *find_syntax(const char *name) { Syntax *syn = find_any_syntax(name); if (syn && is_subsyntax(syn)) { return NULL; } return syn; } static const char *find_default_color(Syntax *syn, const char *name) { for (size_t i = 0, n = syn->default_colors.count; i < n; i++) { char **strs = syn->default_colors.ptrs[i]; for (size_t j = 1; strs[j]; j++) { if (streq(strs[j], name)) { return strs[0]; } } } return NULL; } static void update_action_color(Syntax *syn, Action *a) { const char *name = a->emit_name; const char *def; char full[64]; if (!name) { name = a->destination->emit_name; } snprintf(full, sizeof(full), "%s.%s", syn->name, name); a->emit_color = find_color(full); if (a->emit_color) { return; } def = find_default_color(syn, name); if (!def) { return; } snprintf(full, sizeof(full), "%s.%s", syn->name, def); a->emit_color = find_color(full); } static void update_state_colors(Syntax *syn, State *s) { for (size_t i = 0, n = s->conds.count; i < n; i++) { Condition *c = s->conds.ptrs[i]; update_action_color(syn, &c->a); } update_action_color(syn, &s->a); } void update_syntax_colors(Syntax *syn) { if (is_subsyntax(syn)) { // No point to update colors of a sub-syntax return; } for (size_t i = 0, n = syn->states.count; i < n; i++) { update_state_colors(syn, syn->states.ptrs[i]); } } void update_all_syntax_colors(void) { for (size_t i = 0; i < syntaxes.count; i++) { update_syntax_colors(syntaxes.ptrs[i]); } } void find_unused_subsyntaxes(void) { // Don't complain multiple times about same unused subsyntaxes static size_t i; for (; i < syntaxes.count; i++) { Syntax *s = syntaxes.ptrs[i]; if (!s->used && is_subsyntax(s)) { error_msg("Subsyntax %s is unused", s->name); } } } dte-1.9.1/src/syntax/syntax.h000066400000000000000000000053421354415143300161210ustar00rootroot00000000000000#ifndef SYNTAX_SYNTAX_H #define SYNTAX_SYNTAX_H #include #include #include #include "bitset.h" #include "color.h" #include "../util/hashset.h" #include "../util/ptr-array.h" typedef enum { COND_BUFIS, COND_CHAR, COND_CHAR_BUFFER, COND_CHAR1, COND_INLIST, COND_RECOLOR, COND_RECOLOR_BUFFER, COND_STR, COND_STR2, COND_STR_ICASE, COND_HEREDOCEND, } ConditionType; typedef struct { struct State *destination; // If condition has no emit name this is set to destination state's // emit name or list name (COND_INLIST). char *emit_name; // Set after all colors have been added (config loaded). HlColor *emit_color; } Action; typedef struct { char *name; HashSet strings; bool used; bool defined; } StringList; typedef struct { union { struct { uint8_t len; bool icase; char str[30]; } cond_bufis; struct { BitSet bitset; } cond_char; struct { unsigned char ch; } cond_single_char; struct { StringList *list; } cond_inlist; struct { size_t len; } cond_recolor; struct { uint8_t len; char str[31]; } cond_str; struct { size_t len; char *str; } cond_heredocend; } u; Action a; ConditionType type; } Condition; typedef struct { char *name; PointerArray states; PointerArray string_lists; PointerArray default_colors; bool heredoc; bool used; } Syntax; typedef struct State { char *name; char *emit_name; PointerArray conds; bool defined; bool visited; bool copied; enum { STATE_INVALID = -1, STATE_EAT, STATE_NOEAT, STATE_NOEAT_BUFFER, STATE_HEREDOCBEGIN, } type; Action a; struct { Syntax *subsyntax; PointerArray states; } heredoc; } State; typedef struct { State *state; char *delim; size_t len; } HeredocState; typedef struct { Syntax *subsyn; State *return_state; const char *delim; size_t delim_len; } SyntaxMerge; static inline bool is_subsyntax(const Syntax *syn) { return syn->name[0] == '.'; } StringList *find_string_list(const Syntax *syn, const char *name); State *find_state(const Syntax *syn, const char *name); State *merge_syntax(Syntax *syn, SyntaxMerge *m); void finalize_syntax(Syntax *syn, unsigned int saved_nr_errors); Syntax *find_any_syntax(const char *name); Syntax *find_syntax(const char *name); void update_syntax_colors(Syntax *syn); void update_all_syntax_colors(void); void find_unused_subsyntaxes(void); #endif dte-1.9.1/src/tag.c000066400000000000000000000137621354415143300140200ustar00rootroot00000000000000#include #include #include #include #include "tag.h" #include "completion.h" #include "debug.h" #include "error.h" #include "util/path.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" static TagFile *current_tag_file; static char *current_filename; // For sorting tags static int visibility_cmp(const Tag *a, const Tag *b) { bool a_this_file = false; bool b_this_file = false; if (!a->local && !b->local) { return 0; } // Is tag visibility limited to the current file? if (a->local) { a_this_file = current_filename && streq(current_filename, a->filename); } if (b->local) { b_this_file = current_filename && streq(current_filename, b->filename); } // Tags local to other file than current are not interesting. if (a->local && !a_this_file) { // a is not interesting if (b->local && !b_this_file) { // b is equally uninteresting return 0; } // b is more interesting, sort it before a return 1; } if (b->local && !b_this_file) { // b is not interesting return -1; } // Both are NOT UNinteresting if (a->local && a_this_file) { if (b->local && b_this_file) { return 0; } // a is more interesting bacause it is local symbol return -1; } if (b->local && b_this_file) { // b is more interesting bacause it is local symbol return 1; } return 0; } static int kind_cmp(const Tag *a, const Tag *b) { if (a->kind == b->kind) { return 0; } // Struct member (m) is not very interesting. if (a->kind == 'm') { return 1; } if (b->kind == 'm') { return -1; } // Global variable (v) is not very interesting. if (a->kind == 'v') { return 1; } if (b->kind == 'v') { return -1; } // Struct (s), union (u) return 0; } static int tag_cmp(const void *ap, const void *bp) { const Tag *a = *(const Tag **)ap; const Tag *b = *(const Tag **)bp; int ret = visibility_cmp(a, b); if (ret) { return ret; } return kind_cmp(a, b); } // Find "tags" file from directory path and its parent directories static int open_tag_file(char *path) { const char tags[] = "tags"; while (*path) { size_t len = strlen(path); char *slash = strrchr(path, '/'); if (slash != path + len - 1) { path[len++] = '/'; } memcpy(path + len, tags, sizeof(tags)); int fd = open(path, O_RDONLY); if (fd >= 0) { return fd; } if (errno != ENOENT) { return -1; } *slash = 0; } errno = ENOENT; return -1; } static bool tag_file_changed ( const TagFile *tf, const char *filename, const struct stat *st ) { if (tf->mtime != st->st_mtime) { return true; } return !streq(tf->filename, filename); } static void tag_file_free(TagFile *tf) { free(tf->filename); free(tf->buf); free(tf); } TagFile *load_tag_file(void) { char path[4096]; if (!getcwd(path, sizeof(path) - 5)) { // 5 = length of "/tags" return NULL; } int fd = open_tag_file(path); if (fd < 0) { return NULL; } struct stat st; if (fstat(fd, &st) != 0 || st.st_size <= 0) { close(fd); return NULL; } if ( current_tag_file != NULL && tag_file_changed(current_tag_file, path, &st) ) { tag_file_free(current_tag_file); current_tag_file = NULL; } if (current_tag_file != NULL) { close(fd); return current_tag_file; } char *buf = xmalloc(st.st_size); ssize_t size = xread(fd, buf, st.st_size); close(fd); if (size < 0) { free(buf); return NULL; } TagFile *tf = xnew0(TagFile, 1); tf->filename = xstrdup(path); tf->buf = buf; tf->size = size; tf->mtime = st.st_mtime; current_tag_file = tf; return current_tag_file; } static void free_tags_cb(Tag *t) { free_tag(t); free(t); } void free_tags(PointerArray *tags) { ptr_array_free_cb(tags, FREE_FUNC(free_tags_cb)); } // Both parameters must be absolute and clean static char *path_relative(const char *filename, const char *dir) { size_t dlen = strlen(dir); if (!str_has_prefix(filename, dir)) { return NULL; } if (filename[dlen] == '\0') { // Equal strings return xmemdup_literal("."); } if (filename[dlen] != '/') { return NULL; } return xstrdup(filename + dlen + 1); } void tag_file_find_tags ( const TagFile *tf, const char *filename, const char *name, PointerArray *tags ) { Tag *t = xnew(Tag, 1); size_t pos = 0; while (next_tag(tf, &pos, name, true, t)) { ptr_array_add(tags, t); t = xnew(Tag, 1); } free(t); if (filename == NULL) { current_filename = NULL; } else { char *dir = path_dirname(tf->filename); current_filename = path_relative(filename, dir); free(dir); } ptr_array_sort(tags, tag_cmp); free(current_filename); current_filename = NULL; } char *tag_file_get_tag_filename(const TagFile *tagfile, const Tag *tag) { const StringView dir = path_slice_dirname(tagfile->filename); const size_t tag_filename_len = strlen(tag->filename); char *filename = xmalloc(dir.length + tag_filename_len + 2); memcpy(filename, dir.data, dir.length); filename[dir.length] = '/'; memcpy(filename + dir.length + 1, tag->filename, tag_filename_len + 1); return filename; } void collect_tags(const TagFile *tf, const char *prefix) { Tag t; size_t pos = 0; char *prev = NULL; while (next_tag(tf, &pos, prefix, false, &t)) { if (!prev || !streq(prev, t.name)) { add_completion(t.name); prev = t.name; t.name = NULL; } free_tag(&t); } } dte-1.9.1/src/tag.h000066400000000000000000000006171354415143300140200ustar00rootroot00000000000000#ifndef TAG_H #define TAG_H #include "ctags.h" #include "util/ptr-array.h" TagFile *load_tag_file(void); void free_tags(PointerArray *tags); char *tag_file_get_tag_filename(const TagFile *tf, const Tag *t); void collect_tags(const TagFile *tf, const char *prefix); void tag_file_find_tags ( const TagFile *tf, const char *filename, const char *name, PointerArray *tags ); #endif dte-1.9.1/src/terminal/000077500000000000000000000000001354415143300147035ustar00rootroot00000000000000dte-1.9.1/src/terminal/color.c000066400000000000000000000237331354415143300161750ustar00rootroot00000000000000#include #include #include "color.h" #include "../debug.h" #include "../error.h" #include "../util/ascii.h" #include "../util/strtonum.h" #define CMP(str, val) cmp_str = str; cmp_val = val; goto compare static unsigned int lookup_attr(const char *s, size_t len) { const char *cmp_str; unsigned int cmp_val; switch (len) { case 3: CMP("dim", ATTR_DIM); case 5: CMP("blink", ATTR_BLINK); case 6: CMP("italic", ATTR_ITALIC); case 7: CMP("reverse", ATTR_REVERSE); case 12: CMP("lowintensity", ATTR_DIM); case 13: CMP("strikethrough", ATTR_STRIKETHROUGH); case 4: switch (s[0]) { case 'b': CMP("bold", ATTR_BOLD); case 'k': CMP("keep", ATTR_KEEP); } break; case 9: switch (s[0]) { case 'i': CMP("invisible", ATTR_INVIS); case 'u': CMP("underline", ATTR_UNDERLINE); } break; } return 0; compare: return memcmp(s, cmp_str, len) ? 0 : cmp_val; } static int32_t lookup_color(const char *s, size_t len) { const char *cmp_str; int32_t cmp_val; switch (len) { case 3: CMP("red", COLOR_RED); case 6: CMP("yellow", COLOR_YELLOW); case 8: CMP("darkgray", 8); case 4: switch (s[0]) { case 'b': CMP("blue", COLOR_BLUE); case 'c': CMP("cyan", COLOR_CYAN); case 'g': CMP("gray", COLOR_GRAY); case 'k': CMP("keep", COLOR_KEEP); } break; case 5: switch (s[0]) { case 'b': CMP("black", COLOR_BLACK); case 'g': CMP("green", COLOR_GREEN); case 'w': CMP("white", 15); } break; case 7: switch (s[0]) { case 'd': CMP("default", COLOR_DEFAULT); case 'm': CMP("magenta", COLOR_MAGENTA); } break; } return COLOR_INVALID; compare: return memcmp(s, cmp_str, len) ? COLOR_INVALID : cmp_val; } static int32_t parse_rrggbb(const char *str) { uint8_t digits[6]; for (size_t i = 0; i < 6; i++) { int val = hex_decode(str[i]); if (val < 0) { return COLOR_INVALID; } digits[i] = val; } int32_t r = (digits[0] << 4) | digits[1]; int32_t g = (digits[2] << 4) | digits[3]; int32_t b = (digits[4] << 4) | digits[5]; return r << 16 | g << 8 | b | COLOR_FLAG_RGB; } UNITTEST { BUG_ON(parse_rrggbb("f01cff") != COLOR_RGB(0xf01cff)); BUG_ON(parse_rrggbb("011011") != COLOR_RGB(0x011011)); BUG_ON(parse_rrggbb("fffffg") != COLOR_INVALID); BUG_ON(parse_rrggbb(".") != COLOR_INVALID); BUG_ON(parse_rrggbb("11223") != COLOR_INVALID); } static int32_t parse_color(const char *str) { size_t len = strlen(str); if (len == 0) { return COLOR_INVALID; } // Parse #rrggbb if (str[0] == '#') { if (len != 7) { return COLOR_INVALID; } return parse_rrggbb(str + 1); } // Parse r/g/b if (len == 5 && str[1] == '/') { uint8_t r = ((uint8_t)str[0]) - '0'; uint8_t g = ((uint8_t)str[2]) - '0'; uint8_t b = ((uint8_t)str[4]) - '0'; if (r > 5 || g > 5 || b > 5 || str[3] != '/') { return COLOR_INVALID; } // Convert to color index 16..231 (xterm 6x6x6 color cube) return 16 + r * 36 + g * 6 + b; } // Parse -2 .. 255 if (len <= 3 && (str[0] == '-' || ascii_isdigit(str[0]))) { int x; if (!str_to_int(str, &x) || x < -2 || x > 255) { return COLOR_INVALID; } return x; } bool light = false; if (len >= 8 && memcmp(str, "light", 5) == 0) { light = true; str += 5; len -= 5; } const int32_t c = lookup_color(str, len); switch (c) { case COLOR_INVALID: return COLOR_INVALID; case COLOR_RED: case COLOR_GREEN: case COLOR_YELLOW: case COLOR_BLUE: case COLOR_MAGENTA: case COLOR_CYAN: return light ? c + 8 : c; default: return light ? COLOR_INVALID : c; } } static bool parse_attr(const char *str, unsigned int *attr) { const unsigned int a = lookup_attr(str, strlen(str)); if (a) { *attr |= a; return true; } return false; } bool parse_term_color(TermColor *color, char **strs) { color->fg = COLOR_DEFAULT; color->bg = COLOR_DEFAULT; color->attr = 0; for (size_t i = 0, count = 0; strs[i]; i++) { const char *const str = strs[i]; const int32_t val = parse_color(str); if (val != COLOR_INVALID) { if (count > 1) { if (val == COLOR_KEEP) { // "keep" is also a valid attribute color->attr |= ATTR_KEEP; } else { error_msg("too many colors"); return false; } } else { if (!count) { color->fg = val; } else { color->bg = val; } count++; } } else if (!parse_attr(str, &color->attr)) { error_msg("invalid color or attribute %s", str); return false; } } return true; } static int color_dist_sq ( uint8_t R, uint8_t G, uint8_t B, uint8_t r, uint8_t g, uint8_t b ) { return (R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b); } // Convert RGB color component (0-255) to nearest xterm color cube index (0-5) static uint8_t nearest_cube_index(uint8_t c) { if (c < 48) { return 0; } if (c < 114) { return 1; } return (c - 35) / 40; } static uint8_t color_rgb_to_256(uint32_t color, bool *exact) { static const uint8_t color_stops[6] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff }; if ((color & COLOR_FLAG_RGB) == 0) { BUG_ON(color > 255); *exact = true; return color; } uint8_t r, g, b; color_split_rgb(color, &r, &g, &b); uint8_t r_idx = nearest_cube_index(r); uint8_t g_idx = nearest_cube_index(g); uint8_t b_idx = nearest_cube_index(b); uint8_t r_stop = color_stops[r_idx]; uint8_t g_stop = color_stops[g_idx]; uint8_t b_stop = color_stops[b_idx]; if (r_stop == r && g_stop == g && b_stop == b) { *exact = true; return 16 + (36 * r_idx) + (6 * g_idx) + b_idx; } if (r >= 8 && r <= 238 && r == g && r == b) { uint8_t v = r - 8; if (v % 10 == 0) { *exact = true; return (v / 10) + 232; } } // Calculate closest gray int gray_avg = (r + g + b) / 3; int gray_idx = (gray_avg > 238) ? 23 : ((gray_avg - 3) / 10); int gray = 8 + (10 * gray_idx); int rgb_distance = color_dist_sq(r_stop, g_stop, b_stop, r, g, b); int gray_distance = color_dist_sq(gray, gray, gray, r, g, b); if (gray_distance < rgb_distance) { // Gray is closest match *exact = false; return 232 + gray_idx; } else { // RGB cube color is closest match *exact = false; return 16 + (36 * r_idx) + (6 * g_idx) + b_idx; } } // Convert a 24-bit RGB color to an xterm palette color if one matches // exactly, or otherwise return the original color unchanged. This is // useful for reducing the size of SGR sequences sent to the terminal. static int32_t color_rgb_optimize(int32_t color) { bool exact; int32_t new_color = color_rgb_to_256(color, &exact); return exact ? new_color : color; } static uint8_t color_256_to_16(uint8_t color) { enum { k = COLOR_BLACK, r = COLOR_RED, g = COLOR_GREEN, y = COLOR_YELLOW, b = COLOR_BLUE, m = COLOR_MAGENTA, c = COLOR_CYAN, a = COLOR_GRAY, A = COLOR_DARKGRAY, R = COLOR_LIGHTRED, G = COLOR_LIGHTGREEN, Y = COLOR_LIGHTYELLOW, B = COLOR_LIGHTBLUE, M = COLOR_LIGHTMAGENTA, C = COLOR_LIGHTCYAN, W = COLOR_WHITE }; static const uint8_t table[256] = { k, r, g, y, b, m, c, a, A, R, G, Y, B, M, C, W, // 0...15 k, b, b, b, B, B, g, c, b, b, B, B, g, g, c, b, // 16...31 B, B, g, g, g, c, B, B, G, G, G, C, C, B, G, G, // 32...47 G, G, C, C, r, m, m, m, m, B, y, A, b, b, B, B, // 48...63 g, g, c, b, B, B, g, g, g, c, B, B, G, G, G, G, // 64...79 C, B, G, G, G, G, G, C, r, m, m, m, m, m, y, r, // 80...95 m, m, m, m, y, y, A, b, B, B, g, g, g, c, B, B, // 96..111 G, G, G, G, C, B, G, G, G, G, G, C, r, r, m, m, // 112..127 m, m, r, r, r, m, M, M, y, y, r, m, M, M, y, y, // 128..143 y, a, B, B, G, G, G, G, C, B, G, G, G, G, G, C, // 144..159 R, R, R, m, M, M, R, R, M, M, M, M, R, R, R, R, // 160..175 M, M, y, y, y, M, M, M, Y, Y, Y, Y, a, B, Y, G, // 176..191 G, G, G, C, R, R, R, M, M, M, R, R, R, R, R, M, // 192..207 R, R, R, M, M, M, y, y, y, R, M, M, y, y, Y, Y, // 208..223 R, M, Y, Y, Y, Y, Y, W, k, k, k, k, k, k, A, A, // 224..239 A, A, A, A, a, a, a, a, a, a, W, W, W, W, W, W // 240..255 }; return table[color]; } static uint8_t color_any_to_256(int32_t color) { BUG_ON(color < 0); bool exact; return color_rgb_to_256(color, &exact); } static uint8_t color_any_to_16(int32_t color) { return color_256_to_16(color_any_to_256(color)); } static uint8_t color_any_to_8(int32_t color) { return color_any_to_16(color) & 7; } int32_t color_to_nearest(int32_t color, TermColorCapabilityType type) { if (color < 0) { return color; } switch (type) { case TERM_0_COLOR: return COLOR_DEFAULT; case TERM_8_COLOR: return color_any_to_8(color); case TERM_16_COLOR: return color_any_to_16(color); case TERM_256_COLOR: return color_any_to_256(color); case TERM_TRUE_COLOR: return color_rgb_optimize(color); } BUG("unexpected TermColorCapabilityType value"); // This should never be reached, but it silences compiler warnings // when DEBUG == 0 and __builtin_unreachable() isn't supported // (i.e. BUG() expands to nothing). return COLOR_DEFAULT; } dte-1.9.1/src/terminal/color.h000066400000000000000000000030611354415143300161720ustar00rootroot00000000000000#ifndef TERMINAL_COLOR_H #define TERMINAL_COLOR_H #include #include #include "../util/macros.h" #define COLOR_FLAG_RGB INT32_C(0x01000000) #define COLOR_RGB(x) (COLOR_FLAG_RGB | (x)) typedef enum { TERM_0_COLOR, TERM_8_COLOR, TERM_16_COLOR, TERM_256_COLOR, TERM_TRUE_COLOR } TermColorCapabilityType; enum { COLOR_INVALID = -3, COLOR_KEEP = -2, COLOR_DEFAULT = -1, COLOR_BLACK = 0, COLOR_RED = 1, COLOR_GREEN = 2, COLOR_YELLOW = 3, COLOR_BLUE = 4, COLOR_MAGENTA = 5, COLOR_CYAN = 6, COLOR_GRAY = 7, COLOR_DARKGRAY = 8, COLOR_LIGHTRED = 9, COLOR_LIGHTGREEN = 10, COLOR_LIGHTYELLOW = 11, COLOR_LIGHTBLUE = 12, COLOR_LIGHTMAGENTA = 13, COLOR_LIGHTCYAN = 14, COLOR_WHITE = 15 }; enum { ATTR_KEEP = 0x01, ATTR_UNDERLINE = 0x02, ATTR_REVERSE = 0x04, ATTR_BLINK = 0x08, ATTR_DIM = 0x10, ATTR_BOLD = 0x20, ATTR_INVIS = 0x40, ATTR_ITALIC = 0x80, ATTR_STRIKETHROUGH = 0x100, }; typedef struct { int32_t fg; int32_t bg; unsigned int attr; } TermColor; static inline void color_split_rgb(int32_t c, uint8_t *r, uint8_t *g, uint8_t *b) { *r = (c >> 16) & 0xff; *g = (c >> 8) & 0xff; *b = c & 0xff; } static inline bool same_color(const TermColor *c1, const TermColor *c2) { return c1->attr == c2->attr && c1->fg == c2->fg && c1->bg == c2->bg ; } bool parse_term_color(TermColor *color, char **strs); int32_t color_to_nearest(int32_t color, TermColorCapabilityType type); #endif dte-1.9.1/src/terminal/ecma48.c000066400000000000000000000071111354415143300161300ustar00rootroot00000000000000#include #include #include #undef CTRL // macro from sys/ttydefaults.h clashes with the one in key.h #include "ecma48.h" #include "no-op.h" #include "output.h" #include "terminfo.h" #include "xterm.h" #include "../util/ascii.h" #include "../util/macros.h" #include "../util/xsnprintf.h" static struct termios termios_save; void term_raw(void) { // Get and save current attributes struct termios termios; tcgetattr(STDIN_FILENO, &termios); termios_save = termios; // Enter "raw" mode (roughly equivalent to cfmakeraw(3) on Linux/BSD) termios.c_iflag &= ~( ICRNL | IXON | IXOFF | IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR ); termios.c_oflag &= ~OPOST; termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); termios.c_cflag &= ~(CSIZE | PARENB); termios.c_cflag |= CS8; // Read at least 1 char on each read() termios.c_cc[VMIN] = 1; // Read blocks until there are MIN(VMIN, requested) bytes available termios.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO, 0, &termios); } void term_cooked(void) { tcsetattr(STDIN_FILENO, 0, &termios_save); } void ecma48_clear_screen(void) { term_add_literal ( "\033[H" // Move cursor to 1,1 (done only to mimic terminfo(5) "clear") "\033[2J" // Clear whole screen (regardless of cursor position) ); } void ecma48_clear_to_eol(void) { term_add_literal("\033[K"); } void ecma48_move_cursor(int x, int y) { if (x < 0 || x >= 999 || y < 0 || y >= 999) { return; } term_sprintf ( "\033[%u;%uH", // x and y are zero-based ((unsigned int)y) + 1, ((unsigned int)x) + 1 ); } void ecma48_set_color(const TermColor *const color) { if (same_color(color, &obuf.color)) { return; } char buf[32] = "\033[0"; size_t i = 3; static_assert(sizeof(buf) >= STRLEN("\033[0;1;7;30;40m")); TermColor c = *color; if (c.attr & ATTR_BOLD) { buf[i++] = ';'; buf[i++] = '1'; } if (c.attr & ATTR_REVERSE) { buf[i++] = ';'; buf[i++] = '7'; } if (c.fg >= 0 && c.fg < 8) { buf[i++] = ';'; buf[i++] = '3'; buf[i++] = '0' + (char) c.fg; } if (c.bg >= 0 && c.bg < 8) { buf[i++] = ';'; buf[i++] = '4'; buf[i++] = '0' + (char) c.bg; } buf[i++] = 'm'; term_add_bytes(buf, i); obuf.color = *color; } void ecma48_repeat_byte(char ch, size_t count) { if (!ascii_isprint(ch) || count < 6 || count > 30000) { term_repeat_byte(ch, count); return; } term_sprintf("%c\033[%zub", ch, count - 1); } Terminal terminal = { .color_type = TERM_8_COLOR, .width = 80, .height = 24, .raw = &term_raw, .cooked = &term_cooked, .parse_key_sequence = &xterm_parse_key, .put_control_code = &term_add_string_view, .clear_screen = &ecma48_clear_screen, .clear_to_eol = &ecma48_clear_to_eol, .set_color = &ecma48_set_color, .move_cursor = &ecma48_move_cursor, .repeat_byte = &term_repeat_byte, .save_title = &no_op, .restore_title = &no_op, .set_title = &no_op_s, .control_codes = { .init = STRING_VIEW_INIT, .deinit = STRING_VIEW_INIT, .reset_colors = STRING_VIEW("\033[39;49m"), .reset_attrs = STRING_VIEW("\033[0m"), .keypad_off = STRING_VIEW_INIT, .keypad_on = STRING_VIEW_INIT, .cup_mode_off = STRING_VIEW_INIT, .cup_mode_on = STRING_VIEW_INIT, .show_cursor = STRING_VIEW_INIT, .hide_cursor = STRING_VIEW_INIT, } }; dte-1.9.1/src/terminal/ecma48.h000066400000000000000000000005451354415143300161410ustar00rootroot00000000000000#ifndef TERMINAL_ECMA48_H #define TERMINAL_ECMA48_H #include "color.h" #include "../util/string-view.h" void ecma48_clear_screen(void); void ecma48_clear_to_eol(void); void ecma48_move_cursor(int x, int y); void ecma48_set_color(const TermColor *color); void ecma48_repeat_byte(char ch, size_t count); void term_raw(void); void term_cooked(void); #endif dte-1.9.1/src/terminal/input.c000066400000000000000000000153401354415143300162110ustar00rootroot00000000000000#include #include #include #include #include #include #include #undef CTRL // undef glibc macro pollution from sys/ttydefaults.h #include "input.h" #include "terminal.h" #include "../editor.h" #include "../util/ascii.h" #include "../util/xmalloc.h" static char input_buf[256]; static size_t input_buf_fill; static bool input_can_be_truncated; static void consume_input(size_t len) { input_buf_fill -= len; if (input_buf_fill) { memmove(input_buf, input_buf + len, input_buf_fill); // Keys are sent faster than we can read input_can_be_truncated = true; } } static bool fill_buffer(void) { if (input_buf_fill == sizeof(input_buf)) { return false; } if (!input_buf_fill) { input_can_be_truncated = false; } ssize_t rc = read ( STDIN_FILENO, input_buf + input_buf_fill, sizeof(input_buf) - input_buf_fill ); if (rc <= 0) { return false; } input_buf_fill += (size_t)rc; return true; } static bool fill_buffer_timeout(void) { struct timeval tv = { .tv_sec = editor.options.esc_timeout / 1000, .tv_usec = (editor.options.esc_timeout % 1000) * 1000 }; fd_set set; FD_ZERO(&set); // The Clang static analyzer can't always determine that the // FD_ZERO() call above has initialized the fd_set -- in glibc // it's implemented via "__asm__ __volatile__". // // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Assign) FD_SET(STDIN_FILENO, &set); int rc = select(1, &set, NULL, NULL, &tv); if (rc > 0 && fill_buffer()) { return true; } return false; } static bool input_get_byte(unsigned char *ch) { if (!input_buf_fill && !fill_buffer()) { return false; } *ch = input_buf[0]; consume_input(1); return true; } static bool read_special(KeyCode *key) { ssize_t len = terminal.parse_key_sequence(input_buf, input_buf_fill, key); switch (len) { case -1: // Possibly truncated break; case 0: // No match return false; default: // Match consume_input(len); return true; } if (input_can_be_truncated && fill_buffer()) { return read_special(key); } return false; } static bool read_simple(KeyCode *key) { unsigned char ch = 0; // > 0 bytes in buf input_get_byte(&ch); // Normal key if (editor.term_utf8 && ch > 0x7f) { /* * 10xx xxxx invalid * 110x xxxx valid * 1110 xxxx valid * 1111 0xxx valid * 1111 1xxx invalid */ CodePoint bit = 1 << 6; int count = 0; while (ch & bit) { bit >>= 1; count++; } if (count == 0 || count > 3) { // Invalid first byte return false; } CodePoint u = ch & (bit - 1); do { if (!input_get_byte(&ch)) { return false; } if (ch >> 6 != 2) { return false; } u = (u << 6) | (ch & 0x3f); } while (--count); *key = u; } else { *key = keycode_normalize(ch); } return true; } static bool is_text(const char *const str, size_t len) { for (size_t i = 0; i < len; i++) { if (ascii_is_nonspace_cntrl(str[i])) { return false; } } return true; } bool term_read_key(KeyCode *key) { if (!input_buf_fill && !fill_buffer()) { return false; } if (input_buf_fill > 4 && is_text(input_buf, input_buf_fill)) { *key = KEY_PASTE; return true; } if (input_buf[0] == '\033') { if (input_buf_fill > 1 || input_can_be_truncated) { if (read_special(key)) { return true; } } if (input_buf_fill == 1) { // Sometimes alt-key gets split into two reads fill_buffer_timeout(); if (input_buf_fill > 1 && input_buf[1] == '\033') { /* * Double-esc (+ maybe some other characters) * * Treat the first esc as a single key to make * things like arrow keys work immediately after * leaving (esc) the command line. * * Special key can't start with double-esc so this * should be safe. * * This breaks the esc-key == alt-key rule for the * esc-esc case but it shouldn't matter. */ return read_simple(key); } } if (input_buf_fill > 1) { // Unknown escape sequence or 'esc key' / 'alt-key' // Throw escape away consume_input(1); const bool ok = read_simple(key); if (!ok) { return false; } if (input_buf_fill == 0 || input_buf[0] == '\033') { // 'esc key' or 'alt-key' *key |= MOD_META; return true; } // Unknown escape sequence; avoid inserting it input_buf_fill = 0; return false; } } return read_simple(key); } char *term_read_paste(size_t *size) { size_t alloc = ROUND_UP(input_buf_fill + 1, 1024); size_t count = 0; char *buf = xmalloc(alloc); if (input_buf_fill) { memcpy(buf, input_buf, input_buf_fill); count = input_buf_fill; input_buf_fill = 0; } while (1) { struct timeval tv = { .tv_sec = 0, .tv_usec = 0 }; fd_set set; FD_ZERO(&set); // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Assign) FD_SET(STDIN_FILENO, &set); int rc = select(1, &set, NULL, NULL, &tv); if (rc < 0 && errno == EINTR) { continue; } else if (rc <= 0) { break; } if (alloc - count < 256) { alloc *= 2; xrenew(buf, alloc); } ssize_t n; do { n = read(STDIN_FILENO, buf + count, alloc - count); } while (n < 0 && errno == EINTR); if (n <= 0) { break; } count += n; } for (size_t i = 0; i < count; i++) { if (buf[i] == '\r') { buf[i] = '\n'; } } *size = count; return buf; } void term_discard_paste(void) { size_t size; free(term_read_paste(&size)); } bool term_get_size(int *w, int *h) { struct winsize ws; if (ioctl(0, TIOCGWINSZ, &ws) != -1) { *w = ws.ws_col; *h = ws.ws_row; return true; } return false; } dte-1.9.1/src/terminal/input.h000066400000000000000000000004011354415143300162060ustar00rootroot00000000000000#ifndef TERMINAL_INPUT_H #define TERMINAL_INPUT_H #include #include #include "key.h" bool term_read_key(KeyCode *key); char *term_read_paste(size_t *size); void term_discard_paste(void); bool term_get_size(int *w, int *h); #endif dte-1.9.1/src/terminal/key.c000066400000000000000000000071141354415143300156420ustar00rootroot00000000000000#include #include "key.h" #include "../debug.h" #include "../util/ascii.h" #include "../util/utf8.h" // Note: these strings must be kept in sync with the enum in key.h static const char special_names[][8] = { "insert", "delete", "up", "down", "right", "left", "pgdown", "end", "pgup", "home", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", }; static_assert(ARRAY_COUNT(special_names) == NR_SPECIAL_KEYS); static size_t parse_modifiers(const char *const str, KeyCode *modifiersp) { KeyCode modifiers = 0; size_t i = 0; while (1) { KeyCode tmp; switch (str[i]) { case 'C': tmp = MOD_CTRL; break; case 'M': tmp = MOD_META; break; case 'S': tmp = MOD_SHIFT; break; default: goto end; } if (str[i + 1] != '-' || modifiers & tmp) { goto end; } modifiers |= tmp; i += 2; } end: *modifiersp = modifiers; return i; } bool parse_key(KeyCode *key, const char *str) { KeyCode modifiers; if (str[0] == '^' && str[1] != '\0') { modifiers = MOD_CTRL; str += 1; } else { str += parse_modifiers(str, &modifiers); } const size_t len = strlen(str); size_t i = 0; KeyCode ch = u_get_char(str, len, &i); if (u_is_unicode(ch) && i == len) { if (modifiers == MOD_CTRL) { // Normalize switch (ch) { case 'i': case 'I': ch = '\t'; modifiers = 0; break; case 'm': case 'M': ch = KEY_ENTER; modifiers = 0; break; } } *key = modifiers | ch; return true; } if (ascii_streq_icase(str, "space")) { *key = modifiers | ' '; return true; } if (ascii_streq_icase(str, "tab")) { *key = modifiers | '\t'; return true; } if (ascii_streq_icase(str, "enter")) { *key = modifiers | KEY_ENTER; return true; } for (i = 0; i < NR_SPECIAL_KEYS; i++) { if (ascii_streq_icase(str, special_names[i])) { *key = modifiers | (KEY_SPECIAL_MIN + i); return true; } } return false; } #define COPY(dest, src) memcpy(dest, src, STRLEN(src) + 1) const char *key_to_string(KeyCode k) { static char buf[32]; size_t i = 0; if (k & MOD_CTRL) { buf[i++] = 'C'; buf[i++] = '-'; } if (k & MOD_META) { buf[i++] = 'M'; buf[i++] = '-'; } if (k & MOD_SHIFT) { buf[i++] = 'S'; buf[i++] = '-'; } char *const ptr = buf + i; const KeyCode key = keycode_get_key(k); if (u_is_unicode(key)) { switch (key) { case '\t': COPY(ptr, "tab"); break; case KEY_ENTER: COPY(ptr, "enter"); break; case ' ': COPY(ptr, "space"); break; default: u_set_char(buf, &i, key); buf[i] = '\0'; } } else if (key >= KEY_SPECIAL_MIN && key <= KEY_SPECIAL_MAX) { static_assert(sizeof(special_names[0]) == 8); memcpy ( ptr, special_names[key - KEY_SPECIAL_MIN], sizeof(special_names[0]) ); } else if (key == KEY_PASTE) { COPY(ptr, "paste"); } else { COPY(ptr, "???"); } return buf; } dte-1.9.1/src/terminal/key.h000066400000000000000000000036171354415143300156530ustar00rootroot00000000000000#ifndef TERMINAL_KEY_H #define TERMINAL_KEY_H #include #include #include "../util/macros.h" enum { KEY_ENTER = '\n', // This is the maximum Unicode codepoint allowed by RFC 3629. // When stored in a 32-bit integer, it only requires the first // 21 low-order bits, leaving 11 high-order bits available to // be used as bit flags. KEY_UNICODE_MAX = 0x010FFFF, // In addition to the 11 unused, high-order bits, there are also // some unused values in the range from KEY_UNICODE_MAX + 1 to // (1 << 21) - 1, which can be used to represent special keys. KEY_SPECIAL_MIN = KEY_UNICODE_MAX + 1, // Note: these must be kept in sync with the array of names in key.c KEY_INSERT = KEY_SPECIAL_MIN, KEY_DELETE, KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, KEY_PAGE_DOWN, KEY_END, KEY_PAGE_UP, KEY_HOME, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_SPECIAL_MAX = KEY_F12, NR_SPECIAL_KEYS = KEY_SPECIAL_MAX - KEY_SPECIAL_MIN + 1, // Modifier bit flags (as described above) MOD_CTRL = 0x1000000, MOD_META = 0x2000000, MOD_SHIFT = 0x4000000, MOD_MASK = MOD_CTRL | MOD_META | MOD_SHIFT, KEY_PASTE = 0x8000000, }; typedef uint32_t KeyCode; static inline KeyCode keycode_get_key(KeyCode k) { return k & ~MOD_MASK; } static inline KeyCode keycode_get_modifiers(KeyCode k) { return k & MOD_MASK; } static inline KeyCode keycode_normalize(KeyCode k) { switch (k) { case '\t': return k; case '\r': return KEY_ENTER; case 0x7F: return MOD_CTRL | '?'; } if (k < 0x20) { return MOD_CTRL | k | 0x40; } return k; } #define CTRL(x) (MOD_CTRL | (x)) bool parse_key(KeyCode *key, const char *str); const char *key_to_string(KeyCode key) RETURNS_NONNULL; #endif dte-1.9.1/src/terminal/no-op.c000066400000000000000000000001621354415143300160760ustar00rootroot00000000000000#include "no-op.h" #include "../util/macros.h" void no_op(void) { } void no_op_s(const char* UNUSED_ARG(s)) { } dte-1.9.1/src/terminal/no-op.h000066400000000000000000000001521354415143300161020ustar00rootroot00000000000000#ifndef TERMINAL_NO_OP_H #define TERMINAL_NO_OP_H void no_op(void); void no_op_s(const char *s); #endif dte-1.9.1/src/terminal/output.c000066400000000000000000000152301354415143300164100ustar00rootroot00000000000000#include #include #include #include #include "output.h" #include "terminal.h" #include "../debug.h" #include "../util/ascii.h" #include "../util/utf8.h" #include "../util/xmalloc.h" #include "../util/xreadwrite.h" TermOutputBuffer obuf; static size_t obuf_avail(void) { return sizeof(obuf.buf) - obuf.count; } static void obuf_need_space(size_t count) { if (obuf_avail() < count) { term_output_flush(); } } void term_output_reset(size_t start_x, size_t width, size_t scroll_x) { obuf.x = 0; obuf.width = width; obuf.scroll_x = scroll_x; obuf.tab_width = 8; obuf.tab = TAB_CONTROL; obuf.can_clear = start_x + width == terminal.width; } // Does not update obuf.x void term_add_bytes(const char *const str, size_t count) { if (count > obuf_avail()) { term_output_flush(); if (count >= sizeof(obuf.buf)) { xwrite(STDOUT_FILENO, str, count); return; } } memcpy(obuf.buf + obuf.count, str, count); obuf.count += count; } void term_repeat_byte(char ch, size_t count) { while (count) { obuf_need_space(1); size_t avail = obuf_avail(); size_t n = (count > avail) ? avail : count; memset(obuf.buf + obuf.count, ch, n); obuf.count += n; count -= n; } } void term_set_bytes(char ch, size_t count) { if (obuf.x + count > obuf.scroll_x + obuf.width) { count = obuf.scroll_x + obuf.width - obuf.x; } ssize_t skip = obuf.scroll_x - obuf.x; if (skip > 0) { if (skip > count) { skip = count; } obuf.x += skip; count -= skip; } obuf.x += count; terminal.repeat_byte(ch, count); } // Does not update obuf.x void term_add_byte(char ch) { obuf_need_space(1); obuf.buf[obuf.count++] = ch; } void term_add_string_view(StringView sv) { if (sv.length) { term_add_bytes(sv.data, sv.length); } } VPRINTF(1) static void term_vsprintf(const char *fmt, va_list ap) { va_list ap2; va_copy(ap2, ap); // Calculate the required size int n = vsnprintf(NULL, 0, fmt, ap2); va_end(ap2); BUG_ON(n < 0); if (n >= obuf_avail()) { term_output_flush(); if (n >= sizeof(obuf.buf)) { char *tmp = xmalloc(n + 1); int wrote = vsnprintf(tmp, n + 1, fmt, ap); BUG_ON(wrote != n); xwrite(STDOUT_FILENO, tmp, n); free(tmp); return; } } int wrote = vsnprintf(obuf.buf + obuf.count, n + 1, fmt, ap); BUG_ON(wrote != n); obuf.count += wrote; } void term_sprintf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); term_vsprintf(fmt, ap); va_end(ap); } void term_add_str(const char *const str) { size_t i = 0; while (str[i]) { if (!term_put_char(u_str_get_char(str, &i))) { break; } } } void term_hide_cursor(void) { terminal.put_control_code(terminal.control_codes.hide_cursor); } void term_show_cursor(void) { terminal.put_control_code(terminal.control_codes.show_cursor); } void term_clear_eol(void) { if (obuf.x < obuf.scroll_x + obuf.width) { if ( obuf.can_clear && (obuf.color.bg < 0 || terminal.back_color_erase) ) { terminal.clear_to_eol(); obuf.x = obuf.scroll_x + obuf.width; } else { term_set_bytes(' ', obuf.scroll_x + obuf.width - obuf.x); } } } void term_output_flush(void) { if (obuf.count) { xwrite(STDOUT_FILENO, obuf.buf, obuf.count); obuf.count = 0; } } static void skipped_too_much(CodePoint u) { size_t n = obuf.x - obuf.scroll_x; obuf_need_space(8); if (u == '\t' && obuf.tab != TAB_CONTROL) { char ch = ' '; if (obuf.tab == TAB_SPECIAL) { ch = '-'; } memset(obuf.buf + obuf.count, ch, n); obuf.count += n; } else if (u < 0x20) { obuf.buf[obuf.count++] = u | 0x40; } else if (u == 0x7f) { obuf.buf[obuf.count++] = '?'; } else if (u_is_unprintable(u)) { char tmp[4]; size_t idx = 0; u_set_hex(tmp, &idx, u); memcpy(obuf.buf + obuf.count, tmp + 4 - n, n); obuf.count += n; } else { obuf.buf[obuf.count++] = '>'; } } static void buf_skip(CodePoint u) { if (u < 0x80) { if (!ascii_iscntrl(u)) { obuf.x++; } else if (u == '\t' && obuf.tab != TAB_CONTROL) { obuf.x += (obuf.x + obuf.tab_width) / obuf.tab_width * obuf.tab_width - obuf.x; } else { // Control obuf.x += 2; } } else { // u_char_width() needed to handle 0x80-0x9f even if term_utf8 is false obuf.x += u_char_width(u); } if (obuf.x > obuf.scroll_x) { skipped_too_much(u); } } static void print_tab(size_t width) { char ch = ' '; if (obuf.tab == TAB_SPECIAL) { obuf.buf[obuf.count++] = '>'; obuf.x++; width--; ch = '-'; } if (width > 0) { memset(obuf.buf + obuf.count, ch, width); obuf.count += width; obuf.x += width; } } bool term_put_char(CodePoint u) { size_t space = obuf.scroll_x + obuf.width - obuf.x; size_t width; if (obuf.x < obuf.scroll_x) { // Scrolled, char (at least partially) invisible buf_skip(u); return true; } if (!space) { return false; } obuf_need_space(8); if (u < 0x80) { if (!ascii_iscntrl(u)) { obuf.buf[obuf.count++] = u; obuf.x++; } else if (u == '\t' && obuf.tab != TAB_CONTROL) { width = (obuf.x + obuf.tab_width) / obuf.tab_width * obuf.tab_width - obuf.x; if (width > space) { width = space; } print_tab(width); } else { u_set_ctrl(obuf.buf, &obuf.count, u); obuf.x += 2; if (unlikely(space == 1)) { // Wrote too much obuf.count--; obuf.x--; } } } else { width = u_char_width(u); if (width <= space) { obuf.x += width; u_set_char(obuf.buf, &obuf.count, u); } else if (u_is_unprintable(u)) { // would not fit. // There's enough space in the buffer so render all 4 characters // but increment position less. size_t idx = obuf.count; u_set_hex(obuf.buf, &idx, u); obuf.count += space; obuf.x += space; } else { obuf.buf[obuf.count++] = '>'; obuf.x++; } } return true; } dte-1.9.1/src/terminal/output.h000066400000000000000000000024411354415143300164150ustar00rootroot00000000000000#ifndef TERMINAL_OUTPUT_H #define TERMINAL_OUTPUT_H #include #include #include #include "color.h" #include "../util/macros.h" #include "../util/string-view.h" #include "../util/unicode.h" typedef struct { char buf[8192]; size_t count; // Number of characters scrolled (x direction) size_t scroll_x; // Current x position (tab 1-8, double-width 2, invalid UTF-8 byte 4) // if smaller than scroll_x printed characters are not visible size_t x; size_t width; size_t tab_width; enum { TAB_NORMAL, TAB_SPECIAL, TAB_CONTROL, } tab; bool can_clear; TermColor color; } TermOutputBuffer; extern TermOutputBuffer obuf; #define term_add_literal(s) term_add_bytes(s, STRLEN(s)) void term_output_reset(size_t start_x, size_t width, size_t scroll_x); void term_add_byte(char ch); void term_add_bytes(const char *str, size_t count); void term_set_bytes(char ch, size_t count); void term_repeat_byte(char ch, size_t count); void term_add_string_view(StringView sv); void term_sprintf(const char *fmt, ...) PRINTF(1); void term_add_str(const char *str); void term_hide_cursor(void); void term_show_cursor(void); void term_clear_eol(void); void term_output_flush(void); bool term_put_char(CodePoint u); #endif dte-1.9.1/src/terminal/terminal.c000066400000000000000000000070431354415143300166660ustar00rootroot00000000000000#include #include #include #include "terminal.h" #include "ecma48.h" #include "terminfo.h" #include "xterm.h" #include "../debug.h" #include "../util/macros.h" #include "../util/str-util.h" #define S(str) str,STRLEN(str) typedef enum { TERM_OTHER, TERM_LINUX, TERM_SCREEN, TERM_ST, TERM_TMUX, TERM_URXVT, TERM_XTERM, TERM_KITTY, } TerminalType; static TerminalType get_term_type(const char *term) { static const struct { const char name[14]; uint8_t name_len; uint8_t type; } builtin_terminals[] = { {S("xterm-kitty"), TERM_KITTY}, {S("xterm"), TERM_XTERM}, {S("st"), TERM_ST}, {S("stterm"), TERM_ST}, {S("tmux"), TERM_TMUX}, {S("screen"), TERM_SCREEN}, {S("linux"), TERM_LINUX}, {S("rxvt-unicode"), TERM_URXVT}, }; const size_t term_len = strlen(term); for (size_t i = 0; i < ARRAY_COUNT(builtin_terminals); i++) { const size_t n = builtin_terminals[i].name_len; if (term_len >= n && memcmp(term, builtin_terminals[i].name, n) == 0) { if (term[n] == '-' || term[n] == '\0') { return builtin_terminals[i].type; } } } return TERM_OTHER; } UNITTEST { BUG_ON(get_term_type("xterm") != TERM_XTERM); BUG_ON(get_term_type("xterm-kitty") != TERM_KITTY); BUG_ON(get_term_type("tmux") != TERM_TMUX); BUG_ON(get_term_type("st") != TERM_ST); BUG_ON(get_term_type("stterm") != TERM_ST); BUG_ON(get_term_type("linux") != TERM_LINUX); BUG_ON(get_term_type("xterm-256color") != TERM_XTERM); BUG_ON(get_term_type("screen-256color") != TERM_SCREEN); BUG_ON(get_term_type("x") != TERM_OTHER); BUG_ON(get_term_type("xter") != TERM_OTHER); BUG_ON(get_term_type("xtermz") != TERM_OTHER); } NORETURN void term_init_fail(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); putc('\n', stderr); fflush(stderr); exit(1); } void term_init(void) { const char *const term = getenv("TERM"); if (term == NULL || term[0] == '\0') { term_init_fail("'TERM' not set"); } if (getenv("DTE_FORCE_TERMINFO")) { if (term_init_terminfo(term)) { return; } else { term_init_fail("'DTE_FORCE_TERMINFO' set but terminfo not linked"); } } switch (get_term_type(term)) { case TERM_XTERM: terminal = xterm; // terminal.repeat_byte = &ecma48_repeat_byte; break; case TERM_ST: case TERM_URXVT: terminal = xterm; break; case TERM_TMUX: case TERM_SCREEN: case TERM_KITTY: terminal = xterm; terminal.back_color_erase = false; break; case TERM_LINUX: // Use the default Terminal and just change the control codes terminal.control_codes.hide_cursor = xterm.control_codes.hide_cursor; terminal.control_codes.show_cursor = xterm.control_codes.show_cursor; break; case TERM_OTHER: if (term_init_terminfo(term)) { return; } break; } const char *colorterm = getenv("COLORTERM"); if (colorterm && streq(colorterm, "truecolor")) { terminal.color_type = TERM_TRUE_COLOR; return; } if ( terminal.color_type < TERM_256_COLOR && str_has_suffix(term, "256color") ) { terminal.color_type = TERM_256_COLOR; } else if (str_has_suffix(term, "-direct")) { terminal.color_type = TERM_TRUE_COLOR; } } dte-1.9.1/src/terminal/terminal.h000066400000000000000000000024321354415143300166700ustar00rootroot00000000000000#ifndef TERMINAL_TERMINAL_H #define TERMINAL_TERMINAL_H #include #include #include "color.h" #include "key.h" #include "../util/macros.h" #include "../util/string-view.h" typedef struct { StringView init; StringView deinit; StringView reset_colors; StringView reset_attrs; StringView keypad_off; StringView keypad_on; StringView cup_mode_off; StringView cup_mode_on; StringView show_cursor; StringView hide_cursor; } TermControlCodes; typedef struct { bool back_color_erase; TermColorCapabilityType color_type; int width; int height; unsigned int ncv_attributes; TermControlCodes control_codes; ssize_t (*parse_key_sequence)(const char *buf, size_t length, KeyCode *key); void (*put_control_code)(StringView code); void (*clear_screen)(void); void (*clear_to_eol)(void); void (*set_color)(const TermColor *color); void (*move_cursor)(int x, int y); void (*repeat_byte)(char ch, size_t count); void (*raw)(void); void (*cooked)(void); void (*save_title)(void); void (*restore_title)(void); void (*set_title)(const char *title); } Terminal; extern Terminal terminal; void term_init(void); NORETURN COLD PRINTF(1) void term_init_fail(const char *fmt, ...); #endif dte-1.9.1/src/terminal/terminfo.c000066400000000000000000000242631354415143300167010ustar00rootroot00000000000000#include "terminfo.h" #include "../debug.h" #include "../util/macros.h" #ifndef TERMINFO_DISABLE #include #include #include "key.h" #include "output.h" #include "terminal.h" #include "xterm.h" #include "../util/ascii.h" #include "../util/str-util.h" #include "../util/string-view.h" #define KEY(c, k) { \ .code = (c), \ .code_length = (sizeof(c) - 1), \ .key = (k) \ } #define XKEYS(p, key) \ KEY(p, key | MOD_SHIFT), \ KEY(p "3", key | MOD_META), \ KEY(p "4", key | MOD_SHIFT | MOD_META), \ KEY(p "5", key | MOD_CTRL), \ KEY(p "6", key | MOD_SHIFT | MOD_CTRL), \ KEY(p "7", key | MOD_META | MOD_CTRL), \ KEY(p "8", key | MOD_SHIFT | MOD_META | MOD_CTRL) static struct { const char *clear; const char *cup; const char *el; const char *setab; const char *setaf; const char *sgr; } terminfo; static struct TermKeyMap { const char *code; uint32_t code_length; KeyCode key; } keymap[] = { KEY("kcuu1", KEY_UP), KEY("kcud1", KEY_DOWN), KEY("kcub1", KEY_LEFT), KEY("kcuf1", KEY_RIGHT), KEY("kdch1", KEY_DELETE), KEY("kpp", KEY_PAGE_UP), KEY("knp", KEY_PAGE_DOWN), KEY("khome", KEY_HOME), KEY("kend", KEY_END), KEY("kich1", KEY_INSERT), KEY("kcbt", MOD_SHIFT | '\t'), KEY("kf1", KEY_F1), KEY("kf2", KEY_F2), KEY("kf3", KEY_F3), KEY("kf4", KEY_F4), KEY("kf5", KEY_F5), KEY("kf6", KEY_F6), KEY("kf7", KEY_F7), KEY("kf8", KEY_F8), KEY("kf9", KEY_F9), KEY("kf10", KEY_F10), KEY("kf11", KEY_F11), KEY("kf12", KEY_F12), XKEYS("kUP", KEY_UP), XKEYS("kDN", KEY_DOWN), XKEYS("kLFT", KEY_LEFT), XKEYS("kRIT", KEY_RIGHT), XKEYS("kDC", KEY_DELETE), XKEYS("kPRV", KEY_PAGE_UP), XKEYS("kNXT", KEY_PAGE_DOWN), XKEYS("kHOM", KEY_HOME), XKEYS("kEND", KEY_END), }; static_assert(ARRAY_COUNT(keymap) == 23 + (9 * 7)); static size_t keymap_length = 0; static ssize_t parse_key_from_keymap(const char *buf, size_t fill, KeyCode *key) { bool possibly_truncated = false; for (size_t i = 0; i < keymap_length; i++) { const struct TermKeyMap *const km = &keymap[i]; const char *const keycode = km->code; const size_t len = km->code_length; BUG_ON(keycode == NULL); BUG_ON(len == 0); if (len > fill) { // This might be a truncated escape sequence if ( possibly_truncated == false && memcmp(keycode, buf, fill) == 0 ) { possibly_truncated = true; } continue; } if (memcmp(keycode, buf, len) != 0) { continue; } *key = km->key; return len; } return possibly_truncated ? -1 : 0; } // These are normally declared in the and headers. // They are not included here because of the insane number of unprefixed // symbols they declare and because of previous bugs caused by using them. int setupterm(const char *term, int filedes, int *errret); int tigetflag(const char *capname); int tigetnum(const char *capname); char *tigetstr(const char *capname); int tputs(const char *str, int affcnt, int (*putc_fn)(int)); char *tparm(const char*, long, long, long, long, long, long, long, long, long); #define tparm_1(str, p1) tparm(str, p1, 0, 0, 0, 0, 0, 0, 0, 0) #define tparm_2(str, p1, p2) tparm(str, p1, p2, 0, 0, 0, 0, 0, 0, 0) static char *get_terminfo_string(const char *capname) { char *str = tigetstr(capname); if (str == (char *)-1) { // Not a string cap (bug?) return NULL; } // NULL = canceled or absent return str; } static StringView get_terminfo_string_view(const char *capname) { return string_view_from_cstring(get_terminfo_string(capname)); } static bool get_terminfo_flag(const char *capname) { switch (tigetflag(capname)) { case -1: // Not a boolean capability case 0: // Canceled or absent return false; } return true; } static int tputs_putc(int ch) { term_add_byte(ch); return ch; } static void tputs_control_code(StringView code) { if (code.length) { tputs(code.data, 1, tputs_putc); } } static void tputs_clear_screen(void) { if (terminfo.clear) { tputs(terminfo.clear, terminal.height, tputs_putc); } } static void tputs_clear_to_eol(void) { if (terminfo.el) { tputs(terminfo.el, 1, tputs_putc); } } static void tputs_move_cursor(int x, int y) { if (terminfo.cup) { const char *seq = tparm_2(terminfo.cup, y, x); if (seq) { tputs(seq, 1, tputs_putc); } } } static bool attr_is_set(const TermColor *color, unsigned int attr) { if (!(color->attr & attr)) { return false; } else if (terminal.ncv_attributes & attr) { // Terminal only allows attr when not using colors return color->fg == COLOR_DEFAULT && color->bg == COLOR_DEFAULT; } return true; } static void tputs_set_color(const TermColor *color) { if (same_color(color, &obuf.color)) { return; } if (terminfo.sgr) { const char *attrs = tparm ( terminfo.sgr, 0, // p1 = "standout" (unused) attr_is_set(color, ATTR_UNDERLINE), attr_is_set(color, ATTR_REVERSE), attr_is_set(color, ATTR_BLINK), attr_is_set(color, ATTR_DIM), attr_is_set(color, ATTR_BOLD), attr_is_set(color, ATTR_INVIS), 0, // p8 = "protect" (unused) 0 // p9 = "altcharset" (unused) ); tputs(attrs, 1, tputs_putc); } TermColor c = *color; if (terminfo.setaf && c.fg >= 0) { const char *seq = tparm_1(terminfo.setaf, c.fg); if (seq) { tputs(seq, 1, tputs_putc); } } if (terminfo.setab && c.bg >= 0) { const char *seq = tparm_1(terminfo.setab, c.bg); if (seq) { tputs(seq, 1, tputs_putc); } } obuf.color = *color; } static unsigned int convert_ncv_flags_to_attrs(unsigned int ncv) { // These flags should have values equal to their terminfo // counterparts: static_assert(ATTR_UNDERLINE == 2); static_assert(ATTR_REVERSE == 4); static_assert(ATTR_BLINK == 8); static_assert(ATTR_DIM == 16); static_assert(ATTR_BOLD == 32); static_assert(ATTR_INVIS == 64); // Mask flags to supported, common subset unsigned int attrs = ncv & ( ATTR_UNDERLINE | ATTR_REVERSE | ATTR_BLINK | ATTR_DIM | ATTR_BOLD | ATTR_INVIS ); // Italic is a special case; it occupies bit 16 in terminfo // but bit 7 here if (ncv & 0x8000) { attrs |= ATTR_ITALIC; } return attrs; } bool term_init_terminfo(const char *term) { // Initialize terminfo database (or call exit(3) on failure) setupterm(term, 1, (int*)0); terminal.put_control_code = &tputs_control_code; terminal.clear_screen = &tputs_clear_screen; terminal.clear_to_eol = &tputs_clear_to_eol; terminal.set_color = &tputs_set_color; terminal.move_cursor = &tputs_move_cursor; if (get_terminfo_flag("nxon")) { term_init_fail ( "TERM type '%s' not supported: 'nxon' flag is set", term ); } terminfo.cup = get_terminfo_string("cup"); if (terminfo.cup == NULL) { term_init_fail ( "TERM type '%s' not supported: no 'cup' capability", term ); } terminfo.clear = get_terminfo_string("clear"); terminfo.el = get_terminfo_string("el"); terminfo.setab = get_terminfo_string("setab"); terminfo.setaf = get_terminfo_string("setaf"); terminfo.sgr = get_terminfo_string("sgr"); terminal.back_color_erase = get_terminfo_flag("bce"); terminal.width = tigetnum("cols"); terminal.height = tigetnum("lines"); switch (tigetnum("colors")) { case 16777216: // Just use the built-in xterm_set_color() function if true color // support is indicated. This bypasses tputs(3), but no true color // terminal in existence actually depends on archaic tputs(3) // features (like e.g. baudrate-dependant padding). terminal.color_type = TERM_TRUE_COLOR; terminal.set_color = &xterm_set_color; break; case 256: terminal.color_type = TERM_256_COLOR; break; case 16: terminal.color_type = TERM_16_COLOR; break; case 88: case 8: terminal.color_type = TERM_8_COLOR; break; default: terminal.color_type = TERM_0_COLOR; break; } const int ncv = tigetnum("ncv"); if (ncv <= 0) { terminal.ncv_attributes = 0; } else { terminal.ncv_attributes = convert_ncv_flags_to_attrs(ncv); } terminal.control_codes = (TermControlCodes) { .reset_colors = get_terminfo_string_view("op"), .reset_attrs = get_terminfo_string_view("sgr0"), .keypad_off = get_terminfo_string_view("rmkx"), .keypad_on = get_terminfo_string_view("smkx"), .cup_mode_off = get_terminfo_string_view("rmcup"), .cup_mode_on = get_terminfo_string_view("smcup"), .show_cursor = get_terminfo_string_view("cnorm"), .hide_cursor = get_terminfo_string_view("civis") }; bool xterm_compatible_key_codes = true; for (size_t i = 0; i < ARRAY_COUNT(keymap); i++) { const char *const code = get_terminfo_string(keymap[i].code); if (code && code[0] != '\0') { const size_t code_len = strlen(code); const KeyCode key = keymap[i].key; keymap[keymap_length++] = (struct TermKeyMap) { .code = code, .code_length = code_len, .key = key }; KeyCode parsed_key; const ssize_t parsed_len = xterm_parse_key(code, code_len, &parsed_key); if (parsed_len <= 0 || parsed_key != key) { xterm_compatible_key_codes = false; } } } if (!xterm_compatible_key_codes) { terminal.parse_key_sequence = &parse_key_from_keymap; } return true; // Initialization succeeded } #else bool term_init_terminfo(const char* UNUSED_ARG(term)) { return false; // terminfo not available } #endif // ifndef TERMINFO_DISABLE dte-1.9.1/src/terminal/terminfo.h000066400000000000000000000002021354415143300166710ustar00rootroot00000000000000#ifndef TERMINAL_TERMINFO_H #define TERMINAL_TERMINFO_H #include bool term_init_terminfo(const char *term); #endif dte-1.9.1/src/terminal/xterm-keys.c000066400000000000000000000162751354415143300171720ustar00rootroot00000000000000// Escape sequence parser for xterm function keys. // Copyright 2018-2019 Craig Barnes. // SPDX-License-Identifier: GPL-2.0-only // See also: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html #include #include "xterm.h" #include "../util/ascii.h" #include "../util/macros.h" #include "../util/unicode.h" static const KeyCode modifiers[] = { [2] = MOD_SHIFT, [3] = MOD_META, [4] = MOD_SHIFT | MOD_META, [5] = MOD_CTRL, [6] = MOD_SHIFT | MOD_CTRL, [7] = MOD_META | MOD_CTRL, [8] = MOD_SHIFT | MOD_META | MOD_CTRL, }; static const KeyCode special_keys[] = { [1] = KEY_HOME, [2] = KEY_INSERT, [3] = KEY_DELETE, [4] = KEY_END, [5] = KEY_PAGE_UP, [6] = KEY_PAGE_DOWN, [7] = KEY_HOME, [8] = KEY_END, [11] = KEY_F1, [12] = KEY_F2, [13] = KEY_F3, [14] = KEY_F4, [15] = KEY_F5, [17] = KEY_F6, [18] = KEY_F7, [19] = KEY_F8, [20] = KEY_F9, [21] = KEY_F10, [23] = KEY_F11, [24] = KEY_F12, }; static KeyCode decode_modifiers(uint32_t n) { return (n >= ARRAY_COUNT(modifiers)) ? 0 : modifiers[n]; } static KeyCode decode_special_key(uint32_t n) { return (n >= ARRAY_COUNT(special_keys)) ? 0 : special_keys[n]; } // Fix quirky key codes sent when "modifyOtherKeys" is enabled static KeyCode normalize_modified_other_key(KeyCode mods, KeyCode key) { if (key > 0x20 && key < 0x80) { // The Shift modifier is never appropriate with the // printable ASCII range, since pressing Shift causes // the base key itself to change (i.e. "r" becomes "R', // "." becomes ">", etc.) mods &= ~MOD_SHIFT; if (mods & MOD_CTRL) { // The Ctrl modifier should always cause letters to // be uppercase -- this assumption is too ingrained // and causes too much breakage if not enforced key = ascii_toupper(key); } } return mods | keycode_normalize(key); } static ssize_t parse_ss3(const char *buf, size_t length, size_t i, KeyCode *k) { if (i >= length) { return -1; } const char ch = buf[i++]; switch (ch) { case 'A': // Up case 'B': // Down case 'C': // Right case 'D': // Left case 'F': // End case 'H': // Home *k = KEY_UP + (ch - 'A'); return i; case 'M': *k = KEY_ENTER; return i; case 'P': // F1 case 'Q': // F2 case 'R': // F3 case 'S': // F4 *k = KEY_F1 + (ch - 'P'); return i; case 'X': *k = '='; return i; case ' ': *k = ch; return i; case 'a': // Ctrl+Up (rxvt) case 'b': // Ctrl+Down (rxvt) case 'c': // Ctrl+Right (rxvt) case 'd': // Ctrl+Left (rxvt) *k = MOD_CTRL | (KEY_UP + (ch - 'a')); return i; case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'I': *k = ch - 64; return i; } return 0; } static ssize_t parse_csi_num(const char *buf, size_t len, size_t i, KeyCode *k) { uint32_t params[4] = {0, 0, 0, 0}; size_t nparams = 0; uint8_t final_byte = 0; uint32_t num = 0; size_t digits = 0; while (i < len) { const char ch = buf[i++]; switch (ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': num = (num * 10) + (ch - '0'); if (num > UNICODE_MAX_VALID_CODEPOINT) { return 0; } digits++; continue; case ';': params[nparams++] = num; if (nparams > 2) { return 0; } num = 0; digits = 0; continue; case 'A': case 'B': case 'C': case 'D': case 'F': case 'H': case 'P': case 'Q': case 'R': case 'S': case 'u': case '~': final_byte = ch; if (digits > 0) { params[nparams++] = num; } goto exit_loop; } return 0; } exit_loop: if (final_byte == 0) { return (i >= len) ? -1 : 0; } KeyCode mods = 0; KeyCode key; switch (nparams) { case 3: if (params[0] == 27 && final_byte == '~') { mods = decode_modifiers(params[1]); if (mods == 0) { return 0; } *k = normalize_modified_other_key(mods, params[2]); return i; } return 0; case 2: mods = decode_modifiers(params[1]); if (mods == 0) { return 0; } switch (final_byte) { case '~': goto check_first_param_is_special_key; case 'u': *k = normalize_modified_other_key(mods, params[0]); return i; case 'A': // Up case 'B': // Down case 'C': // Right case 'D': // Left case 'F': // End case 'H': // Home key = KEY_UP + (final_byte - 'A'); goto check_first_param_is_1; case 'P': // F1 case 'Q': // F2 case 'R': // F3 case 'S': // F4 key = KEY_F1 + (final_byte - 'P'); goto check_first_param_is_1; } return 0; case 1: if (final_byte == '~') { goto check_first_param_is_special_key; } return 0; } return 0; check_first_param_is_special_key: key = decode_special_key(params[0]); if (key == 0) { return 0; } goto set_k_and_return_i; check_first_param_is_1: if (params[0] != 1) { return 0; } set_k_and_return_i: *k = mods | key; return i; } static ssize_t parse_csi(const char *buf, size_t length, size_t i, KeyCode *k) { if (i >= length) { return -1; } char ch = buf[i++]; switch (ch) { case 'A': // Up case 'B': // Down case 'C': // Right case 'D': // Left case 'F': // End case 'H': // Home *k = KEY_UP + (ch - 'A'); return i; case 'a': // Shift+Up (rxvt) case 'b': // Shift+Down (rxvt) case 'c': // Shift+Right (rxvt) case 'd': // Shift+Left (rxvt) *k = MOD_SHIFT | (KEY_UP + (ch - 'a')); return i; case 'L': *k = KEY_INSERT; return i; case 'Z': *k = MOD_SHIFT | '\t'; return i; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return parse_csi_num(buf, length, i - 1, k); case '[': if (i >= length) { return -1; } switch (ch = buf[i++]) { // Linux console keys case 'A': // F1 case 'B': // F2 case 'C': // F3 case 'D': // F4 case 'E': // F5 *k = KEY_F1 + (ch - 'A'); return i; } return 0; } return 0; } ssize_t xterm_parse_key(const char *buf, size_t length, KeyCode *k) { if (length == 0 || buf[0] != '\033') { return 0; } else if (length == 1) { return -1; } switch (buf[1]) { case 'O': return parse_ss3(buf, length, 2, k); case '[': return parse_csi(buf, length, 2, k); } return 0; } dte-1.9.1/src/terminal/xterm.c000066400000000000000000000055741354415143300162210ustar00rootroot00000000000000#include #include "xterm.h" #include "ecma48.h" #include "output.h" void xterm_save_title(void) { term_add_literal("\033[22;2t"); } void xterm_restore_title(void) { term_add_literal("\033[23;2t"); } void xterm_set_title(const char *title) { term_add_literal("\033]2;"); term_add_bytes(title, strlen(title)); term_add_byte('\007'); } static void do_set_color(int32_t color, char ch) { if (color < 0) { return; } term_add_byte(';'); term_add_byte(ch); if (color < 8) { term_add_byte('0' + color); } else if (color < 256) { term_sprintf("8;5;%hhu", (uint8_t)color); } else { uint8_t r, g, b; color_split_rgb(color, &r, &g, &b); term_sprintf("8;2;%hhu;%hhu;%hhu", r, g, b); } } void xterm_set_color(const TermColor *color) { static const struct { char code; unsigned int attr; } attr_map[] = { {'1', ATTR_BOLD}, {'2', ATTR_DIM}, {'3', ATTR_ITALIC}, {'4', ATTR_UNDERLINE}, {'5', ATTR_BLINK}, {'7', ATTR_REVERSE}, {'8', ATTR_INVIS}, {'9', ATTR_STRIKETHROUGH} }; if (same_color(color, &obuf.color)) { return; } term_add_literal("\033[0"); for (size_t j = 0; j < ARRAY_COUNT(attr_map); j++) { if (color->attr & attr_map[j].attr) { term_add_byte(';'); term_add_byte(attr_map[j].code); } } do_set_color(color->fg, '3'); do_set_color(color->bg, '4'); term_add_byte('m'); obuf.color = *color; } const Terminal xterm = { .back_color_erase = true, .color_type = TERM_8_COLOR, .width = 80, .height = 24, .raw = &term_raw, .cooked = &term_cooked, .parse_key_sequence = &xterm_parse_key, .put_control_code = &term_add_string_view, .clear_screen = &ecma48_clear_screen, .clear_to_eol = &ecma48_clear_to_eol, .set_color = &xterm_set_color, .move_cursor = &ecma48_move_cursor, .repeat_byte = &term_repeat_byte, .save_title = &xterm_save_title, .restore_title = &xterm_restore_title, .set_title = &xterm_set_title, .control_codes = { // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html .init = STRING_VIEW ( // 1036 = metaSendsEscape // 1039 = altSendsEscape "\033[?1036;1039s" // Save "\033[?1036;1039h" // Enable ), .deinit = STRING_VIEW ( "\033[?1036;1039r" // Restore ), .reset_colors = STRING_VIEW("\033[39;49m"), .reset_attrs = STRING_VIEW("\033[0m"), .keypad_off = STRING_VIEW("\033[?1l\033>"), .keypad_on = STRING_VIEW("\033[?1h\033="), .cup_mode_off = STRING_VIEW("\033[?1049l"), .cup_mode_on = STRING_VIEW("\033[?1049h"), .hide_cursor = STRING_VIEW("\033[?25l"), .show_cursor = STRING_VIEW("\033[?25h"), } }; dte-1.9.1/src/terminal/xterm.h000066400000000000000000000006041354415143300162130ustar00rootroot00000000000000#ifndef TERMINAL_XTERM_H #define TERMINAL_XTERM_H #include #include "color.h" #include "key.h" #include "terminal.h" void xterm_save_title(void); void xterm_restore_title(void); void xterm_set_title(const char *title); void xterm_set_color(const TermColor *color); ssize_t xterm_parse_key(const char *buf, size_t length, KeyCode *k); extern const Terminal xterm; #endif dte-1.9.1/src/util/000077500000000000000000000000001354415143300140455ustar00rootroot00000000000000dte-1.9.1/src/util/ascii.c000066400000000000000000000060471354415143300153100ustar00rootroot00000000000000#include "ascii.h" enum { S = ASCII_SPACE, C = ASCII_CNTRL, s = ASCII_SPACE | ASCII_CNTRL, L = ASCII_LOWER, U = ASCII_UPPER, D = ASCII_DIGIT, u = ASCII_UNDERSCORE, N = ASCII_NONASCII, R = ASCII_REGEX, }; const uint8_t ascii_table[256] = { [0x00] = C, [0x01] = C, [0x02] = C, [0x03] = C, [0x04] = C, [0x05] = C, [0x06] = C, ['\a'] = C, ['\b'] = C, ['\t'] = s, ['\n'] = s, ['\v'] = C, ['\f'] = C, ['\r'] = s, [0x0E] = C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, // 0x0F .. 0x1F [' '] = S, ['_'] = u, ['('] = R, [')'] = R, ['*'] = R, ['+'] = R, ['.'] = R, ['?'] = R, ['['] = R, ['{'] = R, ['|'] = R, ['\\'] = R, ['0'] = D, ['1'] = D, ['2'] = D, ['3'] = D, ['4'] = D, ['5'] = D, ['6'] = D, ['7'] = D, ['8'] = D, ['9'] = D, ['A'] = U, ['B'] = U, ['C'] = U, ['D'] = U, ['E'] = U, ['F'] = U, ['G'] = U, ['H'] = U, ['I'] = U, ['J'] = U, ['K'] = U, ['L'] = U, ['M'] = U, ['N'] = U, ['O'] = U, ['P'] = U, ['Q'] = U, ['R'] = U, ['S'] = U, ['T'] = U, ['U'] = U, ['V'] = U, ['W'] = U, ['X'] = U, ['Y'] = U, ['Z'] = U, ['a'] = L, ['b'] = L, ['c'] = L, ['d'] = L, ['e'] = L, ['f'] = L, ['g'] = L, ['h'] = L, ['i'] = L, ['j'] = L, ['k'] = L, ['l'] = L, ['m'] = L, ['n'] = L, ['o'] = L, ['p'] = L, ['q'] = L, ['r'] = L, ['s'] = L, ['t'] = L, ['u'] = L, ['v'] = L, ['w'] = L, ['x'] = L, ['y'] = L, ['z'] = L, [0x7F] = C, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0x80 .. 0x8F N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0x90 .. 0x9F N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xA0 .. 0xAF N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xB0 .. 0xBF N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xC0 .. 0xCF N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xD0 .. 0xDF N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xE0 .. 0xEF N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xF0 .. 0xFF }; const int8_t hex_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; dte-1.9.1/src/util/ascii.h000066400000000000000000000054141354415143300153120ustar00rootroot00000000000000#ifndef UTIL_ASCII_H #define UTIL_ASCII_H #include #include #include #include "macros.h" extern const uint8_t ascii_table[256]; extern const int8_t hex_table[256]; #define ASCII_SPACE 0x01 #define ASCII_DIGIT 0x02 #define ASCII_CNTRL 0x04 #define ASCII_REGEX 0x08 #define ASCII_LOWER 0x10 #define ASCII_UPPER 0x20 #define ASCII_UNDERSCORE 0x40 #define ASCII_NONASCII 0x80 #define ASCII_ALPHA (ASCII_LOWER | ASCII_UPPER) #define ASCII_ALNUM (ASCII_ALPHA | ASCII_DIGIT) #define ASCII_WORDBYTE (ASCII_ALNUM | ASCII_UNDERSCORE | ASCII_NONASCII) #define ascii_test(x, mask) ((ascii_table[(unsigned char)(x)] & (mask)) != 0) #define ascii_isspace(x) ascii_test(x, ASCII_SPACE) #define ascii_isdigit(x) ascii_test(x, ASCII_DIGIT) #define ascii_iscntrl(x) ascii_test(x, ASCII_CNTRL) #define ascii_islower(x) ascii_test(x, ASCII_LOWER) #define ascii_isupper(x) ascii_test(x, ASCII_UPPER) #define ascii_isalpha(x) ascii_test(x, ASCII_ALPHA) #define ascii_isalnum(x) ascii_test(x, ASCII_ALNUM) #define ascii_isprint(x) (!ascii_test(x, ASCII_CNTRL | ASCII_NONASCII)) #define ascii_isxdigit(x) (hex_decode(x) != -1) #define is_alpha_or_underscore(x) ascii_test(x, ASCII_ALPHA | ASCII_UNDERSCORE) #define is_alnum_or_underscore(x) ascii_test(x, ASCII_ALNUM | ASCII_UNDERSCORE) #define is_regex_special_char(x) ascii_test(x, ASCII_REGEX) #define is_word_byte(x) ascii_test(x, ASCII_WORDBYTE) static inline bool ascii_isblank(unsigned char c) { return c == ' ' || c == '\t'; } static inline bool ascii_is_nonspace_cntrl(unsigned char c) { return ascii_table[c] == ASCII_CNTRL; } static inline unsigned char ascii_tolower(unsigned char c) { return c + (ascii_table[c] & ASCII_UPPER); } static inline unsigned char ascii_toupper(unsigned char c) { return c - ((ascii_table[c] & ASCII_LOWER) << 1); } NONNULL_ARGS static inline bool ascii_streq_icase(const char *s1, const char *s2) { unsigned char c1, c2; bool chars_equal; size_t i = 0; // Iterate to the index where the strings differ or a NUL byte is found do { c1 = ascii_tolower(s1[i]); c2 = ascii_tolower(s2[i]); chars_equal = (c1 == c2); i++; } while (c1 && chars_equal); // If the loop terminated because a NUL byte was found and the // last characters were the same, both strings terminate in the // same place and are therefore equal return chars_equal; } NONNULL_ARGS static inline bool mem_equal_icase(const void *p1, const void *p2, size_t n) { const unsigned char *s1 = p1; const unsigned char *s2 = p2; while (n) { if (ascii_tolower(*s1++) != ascii_tolower(*s2++)) { return false; } n--; } return true; } static inline int hex_decode(unsigned char c) { return hex_table[c]; } #endif dte-1.9.1/src/util/bit.h000066400000000000000000000044511354415143300150000ustar00rootroot00000000000000#ifndef UTIL_BIT_H #define UTIL_BIT_H #include #include "macros.h" #include "../debug.h" #define U64 UINT64_C #define U32 UINT32_C #if GNUC_AT_LEAST(3, 4) #define USE_BUILTIN(fn, arg) \ if (__builtin_types_compatible_p(__typeof__(arg), unsigned long long)) { \ return __builtin_ ## fn ## ll(arg); \ } else if (__builtin_types_compatible_p(__typeof__(arg), unsigned long)) { \ return __builtin_ ## fn ## l(arg); \ } else if (__builtin_types_compatible_p(__typeof__(arg), unsigned int)) { \ return __builtin_ ## fn(arg); \ } #else #define USE_BUILTIN(fn, arg) #endif static inline unsigned int bit_popcount_u64(uint64_t n) { USE_BUILTIN(popcount, n); n -= ((n >> 1) & U64(0x5555555555555555)); n = (n & U64(0x3333333333333333)) + ((n >> 2) & U64(0x3333333333333333)); n = (n + (n >> 4)) & U64(0x0F0F0F0F0F0F0F0F); return (n * U64(0x0101010101010101)) >> 56; } static inline unsigned int bit_popcount_u32(uint32_t n) { USE_BUILTIN(popcount, n); n -= ((n >> 1) & U32(0x55555555)); n = (n & U32(0x33333333)) + ((n >> 2) & U32(0x33333333)); n = (n + (n >> 4)) & U32(0x0F0F0F0F); return (n * U32(0x01010101)) >> 24; } static inline unsigned int bit_count_leading_zeros_u64(uint64_t n) { BUG_ON(n == 0); USE_BUILTIN(clz, n); n |= (n >> 1); n |= (n >> 2); n |= (n >> 4); n |= (n >> 8); n |= (n >> 16); n |= (n >> 32); return bit_popcount_u64(~n); } static inline unsigned int bit_count_leading_zeros_u32(uint32_t n) { BUG_ON(n == 0); USE_BUILTIN(clz, n); n |= (n >> 1); n |= (n >> 2); n |= (n >> 4); n |= (n >> 8); n |= (n >> 16); return bit_popcount_u32(~n); } static inline unsigned int bit_count_trailing_zeros_u64(uint64_t n) { BUG_ON(n == 0); USE_BUILTIN(ctz, n); return bit_popcount_u64(~n & (n - 1)); } static inline unsigned int bit_count_trailing_zeros_u32(uint32_t n) { BUG_ON(n == 0); USE_BUILTIN(ctz, n); return bit_popcount_u32(~n & (n - 1)); } static inline unsigned int bit_find_first_set_u64(uint64_t n) { USE_BUILTIN(ffs, n); return n ? bit_count_trailing_zeros_u64(n) + 1 : 0; } static inline unsigned int bit_find_first_set_u32(uint32_t n) { USE_BUILTIN(ffs, n); return n ? bit_count_trailing_zeros_u32(n) + 1 : 0; } #endif dte-1.9.1/src/util/checked-arith.h000066400000000000000000000027351354415143300167200ustar00rootroot00000000000000#ifndef UTIL_CHECKED_ARITH_H #define UTIL_CHECKED_ARITH_H #include #include #include #include "macros.h" #define HAS_GNUC5_OR_BUILTIN(b) (GNUC_AT_LEAST(5, 0) || HAS_BUILTIN(b)) NONNULL_ARGS static inline bool size_add_overflows(size_t a, size_t b, size_t *result) { #if HAS_GNUC5_OR_BUILTIN(__builtin_add_overflow) return __builtin_add_overflow(a, b, result); #else if (unlikely(b > SIZE_MAX - a)) { return true; } *result = a + b; return false; #endif } NONNULL_ARGS static inline bool size_multiply_overflows(size_t a, size_t b, size_t *result) { #if HAS_GNUC5_OR_BUILTIN(__builtin_mul_overflow) return __builtin_mul_overflow(a, b, result); #else if (unlikely(a > 0 && b > SIZE_MAX / a)) { return true; } *result = a * b; return false; #endif } NONNULL_ARGS static inline bool umax_add_overflows(uintmax_t a, uintmax_t b, uintmax_t *result) { #if HAS_GNUC5_OR_BUILTIN(__builtin_add_overflow) return __builtin_add_overflow(a, b, result); #else if (unlikely(b > UINTMAX_MAX - a)) { return true; } *result = a + b; return false; #endif } NONNULL_ARGS static inline bool umax_multiply_overflows(uintmax_t a, uintmax_t b, uintmax_t *result) { #if HAS_GNUC5_OR_BUILTIN(__builtin_mul_overflow) return __builtin_mul_overflow(a, b, result); #else if (unlikely(a > 0 && b > UINTMAX_MAX / a)) { return true; } *result = a * b; return false; #endif } #endif dte-1.9.1/src/util/exec.c000066400000000000000000000067001354415143300151400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "exec.h" void close_on_exec(int fd) { fcntl(fd, F_SETFD, FD_CLOEXEC); } int pipe_close_on_exec(int fd[2]) { int ret = pipe(fd); if (ret == 0) { close_on_exec(fd[0]); close_on_exec(fd[1]); } return ret; } static int dup_close_on_exec(int oldfd, int newfd) { if (dup2(oldfd, newfd) < 0) { return -1; } close_on_exec(newfd); return newfd; } static void reset_signal_handler(int signum) { struct sigaction act; memset(&act, 0, sizeof(struct sigaction)); sigemptyset(&act.sa_mask); act.sa_handler = SIG_DFL; sigaction(signum, &act, NULL); } static void handle_child(char **argv, int fd[3], int error_fd) { int error; int nr_fds = 3; bool move = error_fd < nr_fds; int max = error_fd; // Find if we must move fds out of the way for (int i = 0; i < nr_fds; i++) { if (fd[i] > max) { max = fd[i]; } if (fd[i] < i) { move = true; } } if (move) { int next_free = max + 1; if (error_fd < nr_fds) { error_fd = dup_close_on_exec(error_fd, next_free++); if (error_fd < 0) { goto out; } } for (int i = 0; i < nr_fds; i++) { if (fd[i] < i) { fd[i] = dup_close_on_exec(fd[i], next_free++); if (fd[i] < 0) { goto out; } } } } // Now it is safe to duplicate fds in this order for (int i = 0; i < nr_fds; i++) { if (i == fd[i]) { // Clear FD_CLOEXEC flag fcntl(fd[i], F_SETFD, 0); } else { if (dup2(fd[i], i) < 0) { goto out; } } } // Unignore signals (see man page exec(3p) for more information) reset_signal_handler(SIGINT); reset_signal_handler(SIGQUIT); execvp(argv[0], argv); out: error = errno; error = write(error_fd, &error, sizeof(error)); exit(42); } pid_t fork_exec(char **argv, int fd[3]) { int error = 0; int ep[2]; if (pipe_close_on_exec(ep)) { return -1; } const pid_t pid = fork(); if (pid < 0) { error = errno; close(ep[0]); close(ep[1]); errno = error; return pid; } if (!pid) { handle_child(argv, fd, ep[1]); } close(ep[1]); const ssize_t rc = read(ep[0], &error, sizeof(error)); if (rc > 0 && rc != sizeof(error)) { error = EPIPE; } if (rc < 0) { error = errno; } close(ep[0]); if (!rc) { // Child exec was successful return pid; } int status; while (waitpid(pid, &status, 0) < 0 && errno == EINTR) { ; } errno = error; return -1; } int wait_child(pid_t pid) { int status; while (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) { continue; } return -errno; } if (WIFEXITED(status)) { return WEXITSTATUS(status) & 0xFF; } if (WIFSIGNALED(status)) { return WTERMSIG(status) << 8; } if (WIFSTOPPED(status)) { return WSTOPSIG(status) << 8; } #if defined(WIFCONTINUED) if (WIFCONTINUED(status)) { return SIGCONT << 8; } #endif return -EINVAL; } dte-1.9.1/src/util/exec.h000066400000000000000000000003551354415143300151450ustar00rootroot00000000000000#ifndef UTIL_EXEC_H #define UTIL_EXEC_H #include #include "macros.h" void close_on_exec(int fd); int pipe_close_on_exec(int fd[2]); pid_t fork_exec(char **argv, int fd[3]) NONNULL_ARGS; int wait_child(pid_t pid); #endif dte-1.9.1/src/util/hash.h000066400000000000000000000023201354415143300151360ustar00rootroot00000000000000#ifndef UTIL_HASH_H #define UTIL_HASH_H #include #include #include "ascii.h" #define FNV_32_INIT UINT32_C(2166136261) #define FNV_64_INIT UINT64_C(14695981039346656037) #define FNV_32_PRIME UINT32_C(16777619) #define FNV_64_PRIME UINT64_C(1099511628211) static inline uint32_t fnv_1a_32_hash(const char *str, size_t len) { uint32_t hash = FNV_32_INIT; while (len--) { uint32_t c = *str++; hash ^= c; hash *= FNV_32_PRIME; } return hash; } static inline uint32_t fnv_1a_32_hash_icase(const char *str, size_t len) { uint32_t hash = FNV_32_INIT; while (len--) { uint32_t c = ascii_tolower(*str++); hash ^= c; hash *= FNV_32_PRIME; } return hash; } static inline uint64_t fnv_1a_64_hash(const char *str, size_t len) { uint64_t hash = FNV_64_INIT; while (len--) { uint64_t c = *str++; hash ^= c; hash *= FNV_64_PRIME; } return hash; } static inline uint64_t fnv_1a_64_hash_icase(const char *str, size_t len) { uint64_t hash = FNV_64_INIT; while (len--) { uint64_t c = ascii_tolower(*str++); hash ^= c; hash *= FNV_64_PRIME; } return hash; } #endif dte-1.9.1/src/util/hashset.c000066400000000000000000000063631354415143300156600ustar00rootroot00000000000000#include #include #include "hashset.h" #include "hash.h" #include "str-util.h" #include "xmalloc.h" static void alloc_table(HashSet *set, size_t size) { set->table_size = size; set->table = xnew0(HashSetEntry*, size); set->grow_at = size - (size / 4); // 75% load factor (size * 0.75) } void hashset_init(HashSet *set, size_t size, bool icase) { if (size < 8) { size = 8; } // Accomodate the 75% load factor in the table size, to allow filling // the set to the requested size without needing to rehash() size += size / 3; // Round up the allocation to the next power of 2, to allow using // simple bitwise ops (instead of modulo) in get_slot() size = round_size_to_next_power_of_2(size); alloc_table(set, size); set->nr_entries = 0; if (icase) { set->hash = fnv_1a_32_hash_icase; set->equal = mem_equal_icase; } else { set->hash = fnv_1a_32_hash; set->equal = mem_equal; } } void hashset_free(HashSet *set) { for (size_t i = 0, n = set->table_size; i < n; i++) { HashSetEntry *h = set->table[i]; while (h) { HashSetEntry *next = h->next; free(h); h = next; } } free(set->table); } static size_t get_slot(const HashSet *set, const char *str, size_t str_len) { const uint32_t hash = set->hash(str, str_len); return (size_t)hash & (set->table_size - 1); } HashSetEntry *hashset_get(const HashSet *set, const char *str, size_t str_len) { const size_t slot = get_slot(set, str, str_len); HashSetEntry *h = set->table[slot]; while (h) { if (str_len == h->str_len && set->equal(str, h->str, str_len)) { return h; } h = h->next; } return NULL; } static void rehash(HashSet *set, size_t newsize) { size_t oldsize = set->table_size; HashSetEntry **oldtable = set->table; alloc_table(set, newsize); for (size_t i = 0; i < oldsize; i++) { HashSetEntry *e = oldtable[i]; while (e) { HashSetEntry *next = e->next; const size_t slot = get_slot(set, e->str, e->str_len); e->next = set->table[slot]; set->table[slot] = e; e = next; } } free(oldtable); } HashSetEntry *hashset_add(HashSet *set, const char *str, size_t str_len) { HashSetEntry *h = hashset_get(set, str, str_len); if (h) { return h; } const size_t slot = get_slot(set, str, str_len); h = xmalloc(sizeof(*h) + str_len + 1); h->next = set->table[slot]; h->str_len = str_len; memcpy(h->str, str, str_len); h->str[str_len] = '\0'; set->table[slot] = h; if (++set->nr_entries > set->grow_at) { rehash(set, set->table_size << 1); } return h; } void hashset_add_many(HashSet *set, char **strings, size_t nstrings) { for (size_t i = 0; i < nstrings; i++) { const char *str = strings[i]; const size_t str_len = strlen(str); hashset_add(set, str, str_len); } } const void *mem_intern(const void *data, size_t len) { static HashSet pool; if (!pool.table_size) { hashset_init(&pool, 32, false); } HashSetEntry *e = hashset_add(&pool, data, len); return e->str; } dte-1.9.1/src/util/hashset.h000066400000000000000000000022211354415143300156520ustar00rootroot00000000000000#ifndef UTIL_HASHSET_H #define UTIL_HASHSET_H #include #include #include #include #include "macros.h" // This is a container type for holding a set of related strings. // It uses hashing for primary lookups and separate chaining for // collision resolution. typedef struct { struct HashSetEntry **table; size_t table_size; size_t nr_entries; size_t grow_at; uint32_t (*hash)(const char *str, size_t len); bool (*equal)(const void *s1, const void *s2, size_t n); } HashSet; typedef struct HashSetEntry { struct HashSetEntry *next; size_t str_len; char str[]; } HashSetEntry; void hashset_init(HashSet *set, size_t initial_size, bool icase); void hashset_free(HashSet *set); HashSetEntry *hashset_get(const HashSet *set, const char *str, size_t str_len); HashSetEntry *hashset_add(HashSet *set, const char *str, size_t str_len); void hashset_add_many(HashSet *set, char **strings, size_t nstrings); const void *mem_intern(const void *data, size_t len) NONNULL_ARGS_AND_RETURN; static inline const char *str_intern(const char *str) { return mem_intern(str, strlen(str)); } #endif dte-1.9.1/src/util/list.h000066400000000000000000000017061354415143300151750ustar00rootroot00000000000000#ifndef UTIL_LIST_H #define UTIL_LIST_H #include // offsetof #include #include "macros.h" typedef struct ListHead { struct ListHead *next, *prev; } ListHead; static inline void list_init(ListHead *head) { head->next = head; head->prev = head; } static inline void list_add(ListHead *new, ListHead *prev, ListHead *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } static inline void list_add_before(ListHead *new, ListHead *item) { list_add(new, item->prev, item); } static inline void list_add_after(ListHead *new, ListHead *item) { list_add(new, item, item->next); } static inline void list_del(ListHead *entry) { entry->next->prev = entry->prev; entry->prev->next = entry->next; entry->next = (void*)0x00100100; entry->prev = (void*)0x00200200; } static inline bool list_empty(const ListHead *const head) { return head->next == head; } #endif dte-1.9.1/src/util/macros.h000066400000000000000000000146541354415143300155140ustar00rootroot00000000000000#ifndef UTIL_MACROS_H #define UTIL_MACROS_H #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) #error C99 compiler required #endif #define STRLEN(x) (sizeof("" x "") - 1) #define PASTE(a, b) a##b #define XPASTE(a, b) PASTE(a, b) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define IS_POWER_OF_2(x) (((x) > 0) && (((x) & ((x) - 1)) == 0)) #define DO_PRAGMA(x) _Pragma(#x) // Calculate the number of elements in an array. // The extra division on the third line is a trick to help prevent // passing a pointer to the first element of an array instead of a // reference to the array itself. #define ARRAY_COUNT(x) ( \ (sizeof(x) / sizeof((x)[0])) \ / ((size_t)(!(sizeof(x) % sizeof((x)[0])))) \ ) #ifdef __GNUC__ #define GNUC_AT_LEAST(major, minor) ( \ (__GNUC__ > major) \ || ((__GNUC__ == major) && (__GNUC_MINOR__ >= minor)) ) #else #define GNUC_AT_LEAST(major, minor) 0 #endif // __has_extension is a Clang macro used to determine if a feature is // available even if not standardized in the current "-std" mode. #ifdef __has_extension #define HAS_EXTENSION(x) __has_extension(x) #else #define HAS_EXTENSION(x) 0 #endif #ifdef __has_attribute #define HAS_ATTRIBUTE(x) __has_attribute(x) #else #define HAS_ATTRIBUTE(x) 0 #endif #ifdef __has_builtin #define HAS_BUILTIN(x) __has_builtin(x) #else #define HAS_BUILTIN(x) 0 #endif #ifdef __has_warning #define HAS_WARNING(x) __has_warning(x) #else #define HAS_WARNING(x) 0 #endif #ifdef __has_feature #define HAS_FEATURE(x) __has_feature(x) #else #define HAS_FEATURE(x) 0 #endif #if defined(__SANITIZE_ADDRESS__) || HAS_FEATURE(address_sanitizer) #define ASAN_ENABLED 1 #endif #if defined(__clang__) && HAS_FEATURE(address_sanitizer) #define CLANG_ASAN_ENABLED 1 #endif #if GNUC_AT_LEAST(3, 0) || defined(__TINYC__) #define UNUSED __attribute__((__unused__)) #else #define UNUSED #endif #if GNUC_AT_LEAST(3, 0) #define ALIGNED(x) __attribute__((__aligned__(x))) #define MALLOC __attribute__((__malloc__)) #define PRINTF(x) __attribute__((__format__(__printf__, (x), (x + 1)))) #define VPRINTF(x) __attribute__((__format__(__printf__, (x), 0))) #define PURE __attribute__((__pure__)) #define CONST_FN __attribute__((__const__)) #define CONSTRUCTOR __attribute__((__constructor__)) #define DESTRUCTOR __attribute__((__destructor__)) #else #define ALIGNED(x) #define MALLOC #define PRINTF(x) #define VPRINTF(x) #define PURE #define CONST_FN #define CONSTRUCTOR UNUSED #define DESTRUCTOR UNUSED #endif #define UNUSED_ARG(x) unused__ ## x UNUSED #ifdef __COUNTER__ // Supported by GCC 4.3+ and Clang #define COUNTER_ __COUNTER__ #else #define COUNTER_ __LINE__ #endif #if defined(DEBUG) && (DEBUG > 0) #define UNITTEST static void CONSTRUCTOR XPASTE(unittest_, COUNTER_)(void) #else #define UNITTEST static void UNUSED XPASTE(unittest_, COUNTER_)(void) #endif #if GNUC_AT_LEAST(3, 0) && defined(__OPTIMIZE__) #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #else #define likely(x) (x) #define unlikely(x) (x) #endif #if GNUC_AT_LEAST(3, 0) && defined(__ELF__) #define SECTION(x) __attribute__((__section__(x))) #else #define SECTION(x) #endif #if GNUC_AT_LEAST(3, 1) || HAS_BUILTIN(__builtin_prefetch) #define PREFETCH(addr, ...) __builtin_prefetch(addr, __VA_ARGS__) #else #define PREFETCH(addr, ...) #endif #if GNUC_AT_LEAST(3, 3) || HAS_ATTRIBUTE(nonnull) #define NONNULL_ARGS __attribute__((__nonnull__)) #define NONNULL_ARG(...) __attribute__((__nonnull__(__VA_ARGS__))) #else #define NONNULL_ARGS #define NONNULL_ARG(...) #endif #if GNUC_AT_LEAST(3, 4) || HAS_ATTRIBUTE(warn_unused_result) #define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) #else #define WARN_UNUSED_RESULT #endif #if GNUC_AT_LEAST(4, 3) || HAS_ATTRIBUTE(alloc_size) #define ALLOC_SIZE(...) __attribute__((__alloc_size__(__VA_ARGS__))) #else #define ALLOC_SIZE(...) #endif #if GNUC_AT_LEAST(4, 3) || HAS_ATTRIBUTE(hot) #define HOT __attribute__((__hot__)) #else #define HOT #endif #if GNUC_AT_LEAST(4, 3) || HAS_ATTRIBUTE(cold) #define COLD __attribute__((__cold__)) #else #define COLD #endif #if GNUC_AT_LEAST(4, 5) || HAS_BUILTIN(__builtin_unreachable) #define UNREACHABLE() __builtin_unreachable() #else #define UNREACHABLE() #endif #if GNUC_AT_LEAST(5, 0) || HAS_ATTRIBUTE(returns_nonnull) #define RETURNS_NONNULL __attribute__((__returns_nonnull__)) #else #define RETURNS_NONNULL #endif #if GNUC_AT_LEAST(6, 0) || HAS_ATTRIBUTE(target_clones) #define TARGET_CLONES(x) __attribute__((__target_clones__(x))) #else #define TARGET_CLONES(x) #endif #if GNUC_AT_LEAST(8, 0) || HAS_ATTRIBUTE(nonstring) #define NONSTRING __attribute__((__nonstring__)) #else #define NONSTRING #endif #if HAS_ATTRIBUTE(diagnose_if) #define DIAGNOSE_IF(x) __attribute__((diagnose_if((x), (#x), "error"))) #else #define DIAGNOSE_IF(x) #endif #if defined(__x86_64__) && !defined(__SSE4_2__) #define FMV_SSE42 TARGET_CLONES("sse4.2,default") #else #define FMV_SSE42 #endif #define XSTRDUP MALLOC RETURNS_NONNULL NONNULL_ARGS #define XMALLOC MALLOC RETURNS_NONNULL #define NONNULL_ARGS_AND_RETURN RETURNS_NONNULL NONNULL_ARGS #if __STDC_VERSION__ >= 201112L #define alignof(t) _Alignof(t) #define NORETURN _Noreturn #elif GNUC_AT_LEAST(3, 0) #define alignof(t) __alignof__(t) #define NORETURN __attribute__((__noreturn__)) #else #define alignof(t) MIN(sizeof(t), offsetof(struct{char c; t x;}, x)) #define NORETURN #endif #if (__STDC_VERSION__ >= 201112L) || HAS_EXTENSION(c_static_assert) #define static_assert(x) _Static_assert((x), #x) #else #define static_assert(x) #endif #if GNUC_AT_LEAST(4, 2) || defined(__clang__) #define DISABLE_WARNING(wflag) DO_PRAGMA(GCC diagnostic ignored wflag) #else #define DISABLE_WARNING(wflag) #endif #ifdef __clang__ #define IGNORE_WARNING(wflag) \ DO_PRAGMA(clang diagnostic push) \ DO_PRAGMA(clang diagnostic ignored wflag) #define UNIGNORE_WARNINGS DO_PRAGMA(clang diagnostic pop) #elif GNUC_AT_LEAST(4, 6) #define IGNORE_WARNING(wflag) \ DO_PRAGMA(GCC diagnostic push) \ DO_PRAGMA(GCC diagnostic ignored wflag) #define UNIGNORE_WARNINGS DO_PRAGMA(GCC diagnostic pop) #else #define IGNORE_WARNING(wflag) #define UNIGNORE_WARNINGS #endif #endif dte-1.9.1/src/util/path.c000066400000000000000000000133071354415143300151510ustar00rootroot00000000000000#include #include #include #include "path.h" static bool make_absolute(char *dst, size_t size, const char *src) { size_t pos = 0; if (!path_is_absolute(src)) { if (!getcwd(dst, size)) { return false; } pos = strlen(dst); dst[pos++] = '/'; } const size_t len = strlen(src); if (pos + len + 1 > size) { errno = ENAMETOOLONG; return false; } memcpy(dst + pos, src, len + 1); return true; } static size_t remove_double_slashes(char *str) { size_t d = 0; for (size_t s = 0; str[s]; s++) { if (str[s] != '/' || str[s + 1] != '/') { str[d++] = str[s]; } } str[d] = '\0'; return d; } /* * Canonicalizes path name * * - Replaces double-slashes with one slash * - Removes any "." or ".." path components * - Makes path absolute * - Expands symbolic links * - Checks that all but the last expanded path component are directories * - Last path component is allowed to not exist */ char *path_absolute(const char *filename) { unsigned int depth = 0; char buf[8192]; if (!make_absolute(buf, sizeof(buf), filename)) { return NULL; } remove_double_slashes(buf); // For each component: // * Remove "." // * Remove ".." and previous component // * If symlink then replace with link destination and start over char *sp = buf + 1; while (*sp) { char *ep = strchr(sp, '/'); bool last = !ep; if (ep) { *ep = 0; } if (sp[0] == '.' && sp[1] == '\0') { if (last) { *sp = 0; break; } memmove(sp, ep + 1, strlen(ep + 1) + 1); continue; } if (sp[0] == '.' && sp[1] == '.' && sp[2] == '\0') { if (sp != buf + 1) { // Not first component, remove previous component sp--; while (sp[-1] != '/') { sp--; } } if (last) { *sp = 0; break; } memmove(sp, ep + 1, strlen(ep + 1) + 1); continue; } struct stat st; int rc = lstat(buf, &st); if (rc) { if (last && errno == ENOENT) { break; } return NULL; } if (S_ISLNK(st.st_mode)) { char target[8192]; char tmp[8192]; size_t total_len = 0; size_t buf_len = sp - 1 - buf; size_t rest_len = 0; size_t pos = 0; const char *rest = NULL; if (!last) { rest = ep + 1; rest_len = strlen(rest); } if (++depth > 8) { errno = ELOOP; return NULL; } ssize_t target_len = readlink(buf, target, sizeof(target)); if (target_len < 0) { return NULL; } if (target_len == sizeof(target)) { errno = ENAMETOOLONG; return NULL; } target[target_len] = '\0'; // Calculate length if (target[0] != '/') { total_len = buf_len + 1; } total_len += target_len; if (rest) { total_len += 1 + rest_len; } if (total_len + 1 > sizeof(tmp)) { errno = ENAMETOOLONG; return NULL; } // Build new path if (target[0] != '/') { memcpy(tmp, buf, buf_len); pos += buf_len; tmp[pos++] = '/'; } memcpy(tmp + pos, target, target_len); pos += target_len; if (rest) { tmp[pos++] = '/'; memcpy(tmp + pos, rest, rest_len); pos += rest_len; } tmp[pos] = '\0'; pos = remove_double_slashes(tmp); // Restart memcpy(buf, tmp, pos + 1); sp = buf + 1; continue; } if (last) { break; } if (!S_ISDIR(st.st_mode)) { errno = ENOTDIR; return NULL; } *ep = '/'; sp = ep + 1; } return xstrdup(buf); } static bool path_component(const char *path, size_t pos) { return path[pos] == '\0' || pos == 0 || path[pos - 1] == '/'; } char *relative_filename(const char *f, const char *cwd) { // Annoying special case if (cwd[1] == '\0') { if (f[1] == '\0') { return xstrdup(f); } return xstrdup(f + 1); } // Length of common path size_t clen = 0; while (cwd[clen] && cwd[clen] == f[clen]) { clen++; } if (!cwd[clen] && f[clen] == '/') { // cwd = /home/user // abs = /home/user/project-a/file.c // common = /home/user return xstrdup(f + clen + 1); } // Common path components if (!path_component(cwd, clen) || !path_component(f, clen)) { while (clen > 0 && f[clen - 1] != '/') { clen--; } } // Number of "../" needed size_t dotdot = 1; for (size_t i = clen + 1; cwd[i]; i++) { if (cwd[i] == '/') { dotdot++; } } if (dotdot > 2) { return xstrdup(f); } size_t tlen = strlen(f + clen); size_t len = dotdot * 3 + tlen; char *filename = xmalloc(len + 1); for (size_t i = 0; i < dotdot; i++) { memcpy(filename + i * 3, "../", 3); } memcpy(filename + dotdot * 3, f + clen, tlen + 1); return filename; } dte-1.9.1/src/util/path.h000066400000000000000000000037711354415143300151620ustar00rootroot00000000000000#ifndef UTIL_PATH_H #define UTIL_PATH_H #include #include #include #include "macros.h" #include "string-view.h" #include "xmalloc.h" #include "../debug.h" NONNULL_ARGS static inline bool path_is_absolute(const char *path) { return path[0] == '/'; } // filename must not contain trailing slashes (but it can be "/") NONNULL_ARGS_AND_RETURN static inline const char *path_basename(const char *filename) { const char *slash = strrchr(filename, '/'); return slash ? slash + 1 : filename; } NONNULL_ARGS static inline StringView path_slice_dirname(const char *filename) { const char *const slash = strrchr(filename, '/'); if (slash == NULL) { return string_view(".", 1); } bool slash_is_root_dir = (slash == filename); size_t dirname_length = slash_is_root_dir ? 1 : slash - filename; return string_view(filename, dirname_length); } XSTRDUP static inline char *path_dirname(const char *filename) { const StringView dir = path_slice_dirname(filename); return xstrcut(dir.data, dir.length); } // If path is the root directory, return false. Otherwise, mutate // the path argument to become its parent directory and return true. // Note: path *must* be canonical (i.e. as returned by path_absolute()). static inline bool path_parent(StringView *path) { const char *data = path->data; size_t length = path->length; BUG_ON(length == 0); BUG_ON(data[0] != '/'); if (length == 1) { return false; // Root dir } // Remove up to 1 trailing slash if (data[length - 1] == '/') { path->length = --length; BUG_ON(data[length - 1] == '/'); } const char *slash = string_view_memrchr(path, '/'); BUG_ON(slash == NULL); length = (size_t)(slash - data); BUG_ON(length && data[length - 1] == '/'); path->length = length ? length : 1; return true; } char *path_absolute(const char *filename) MALLOC NONNULL_ARGS; char *relative_filename(const char *f, const char *cwd) XSTRDUP; #endif dte-1.9.1/src/util/ptr-array.c000066400000000000000000000043301354415143300161320ustar00rootroot00000000000000#include #include "ptr-array.h" void ptr_array_add(PointerArray *array, void *ptr) { size_t alloc = array->alloc; if (alloc == array->count) { // NOTE: if alloc was 1 then new alloc would be 1*3/2 = 1! alloc *= 3; alloc /= 2; if (alloc < 8) { alloc = 8; } xrenew(array->ptrs, alloc); array->alloc = alloc; } array->ptrs[array->count++] = ptr; } void ptr_array_insert(PointerArray *array, void *ptr, size_t pos) { size_t count = array->count - pos; ptr_array_add(array, NULL); memmove(array->ptrs + pos + 1, array->ptrs + pos, count * sizeof(void *)); array->ptrs[pos] = ptr; } void ptr_array_free_cb(PointerArray *array, FreeFunction free_ptr) { for (size_t i = 0; i < array->count; i++) { free_ptr(array->ptrs[i]); array->ptrs[i] = NULL; } ptr_array_free_array(array); } void ptr_array_remove(PointerArray *array, void *ptr) { size_t pos = ptr_array_idx(array, ptr); ptr_array_remove_idx(array, pos); } void *ptr_array_remove_idx(PointerArray *array, size_t pos) { void **ptrs = array->ptrs; void *removed = ptrs[pos]; array->count--; memmove(ptrs + pos, ptrs + pos + 1, (array->count - pos) * sizeof(void*)); return removed; } size_t ptr_array_idx(const PointerArray *array, const void *ptr) { for (size_t i = 0, n = array->count; i < n; i++) { if (array->ptrs[i] == ptr) { return i; } } return -1; } void *ptr_array_rel(const PointerArray *array, const void *ptr, size_t offset) { size_t i = ptr_array_idx(array, ptr); return array->ptrs[(i + offset + array->count) % array->count]; } // Trim all leading NULLs and all but one trailing NULL (if any) void ptr_array_trim_nulls(PointerArray *array) { size_t n = array->count; if (n == 0) { return; } void **ptrs = array->ptrs; while (n > 0 && ptrs[n - 1] == NULL) { n--; } if (n != array->count) { // Leave 1 trailing NULL n++; } size_t i = 0; while (i < n && ptrs[i] == NULL) { i++; } if (i > 0) { n -= i; memmove(ptrs, ptrs + i, n * sizeof(void*)); } array->count = n; } dte-1.9.1/src/util/ptr-array.h000066400000000000000000000045261354415143300161460ustar00rootroot00000000000000#ifndef UTIL_PTR_ARRAY_H #define UTIL_PTR_ARRAY_H #include #include "macros.h" #include "xmalloc.h" typedef struct { void **ptrs; size_t alloc; size_t count; } PointerArray; #define PTR_ARRAY_INIT { \ .ptrs = NULL, \ .alloc = 0, \ .count = 0 \ } typedef int (*CompareFunction)(const void *, const void *); typedef void (*FreeFunction)(void *ptr); #define FREE_FUNC(f) (FreeFunction)f void ptr_array_add(PointerArray *array, void *ptr) NONNULL_ARG(1); void ptr_array_insert(PointerArray *array, void *ptr, size_t pos) NONNULL_ARG(1); void ptr_array_free_cb(PointerArray *array, FreeFunction free_ptr) NONNULL_ARGS; void ptr_array_remove(PointerArray *array, void *ptr) NONNULL_ARG(1); void *ptr_array_remove_idx(PointerArray *array, size_t pos) NONNULL_ARG(1); size_t ptr_array_idx(const PointerArray *array, const void *ptr) NONNULL_ARG(1); void *ptr_array_rel(const PointerArray *array, const void *ptr, size_t offset) NONNULL_ARG(1); void ptr_array_trim_nulls(PointerArray *array) NONNULL_ARGS; NONNULL_ARGS static inline void ptr_array_init(PointerArray *array, size_t capacity) { capacity = ROUND_UP(capacity, 8); array->count = 0; array->ptrs = capacity ? xnew(array->ptrs, capacity) : NULL; array->alloc = capacity; } NONNULL_ARG(1) static inline void *ptr_array_next(const PointerArray *array, const void *ptr) { return ptr_array_rel(array, ptr, 1); } NONNULL_ARG(1) static inline void *ptr_array_prev(const PointerArray *array, const void *ptr) { return ptr_array_rel(array, ptr, -1); } // Free each pointer and then free the array. NONNULL_ARGS static inline void ptr_array_free(PointerArray *array) { ptr_array_free_cb(array, free); } // Free the array itself but not the pointers. Useful when the pointers // are "borrowed" references. NONNULL_ARGS static inline void ptr_array_free_array(PointerArray *array) { free(array->ptrs); *array = (PointerArray) PTR_ARRAY_INIT; } static inline void ptr_array_sort ( const PointerArray *array, CompareFunction compare ) { if (array->count >= 2) { qsort(array->ptrs, array->count, sizeof(*array->ptrs), compare); } } static inline void *ptr_array_bsearch ( const PointerArray array, const void *ptr, CompareFunction compare ) { return bsearch(&ptr, array.ptrs, array.count, sizeof(*array.ptrs), compare); } #endif dte-1.9.1/src/util/readfile.c000066400000000000000000000027011354415143300157640ustar00rootroot00000000000000#include #include #include #include #include "readfile.h" #include "xmalloc.h" #include "xreadwrite.h" #include "../debug.h" ssize_t stat_read_file(const char *filename, char **bufp, struct stat *st) { int fd = open(filename, O_RDONLY); *bufp = NULL; if (fd == -1) { return -1; } if (fstat(fd, st) == -1) { close(fd); return -1; } if (S_ISDIR(st->st_mode)) { close(fd); errno = EISDIR; return -1; } char *buf = xmalloc(st->st_size + 1); ssize_t r = xread(fd, buf, st->st_size); close(fd); if (r > 0) { buf[r] = '\0'; *bufp = buf; } else { free(buf); } return r; } char *buf_next_line(char *buf, size_t *posp, size_t size) { size_t pos = *posp; BUG_ON(pos >= size); size_t avail = size - pos; char *line = buf + pos; char *nl = memchr(line, '\n', avail); if (nl) { *nl = '\0'; *posp += nl - line + 1; } else { line[avail] = '\0'; *posp += avail; } return line; } StringView buf_slice_next_line(const char *buf, size_t *posp, size_t size) { size_t pos = *posp; BUG_ON(pos >= size); size_t avail = size - pos; const char *line = buf + pos; const char *nl = memchr(line, '\n', avail); size_t line_length = nl ? (nl - line + 1) : avail; *posp += line_length; return string_view(line, line_length); } dte-1.9.1/src/util/readfile.h000066400000000000000000000011641354415143300157730ustar00rootroot00000000000000#ifndef UTIL_READFILE_H #define UTIL_READFILE_H #include #include #include #include "macros.h" #include "string-view.h" ssize_t stat_read_file(const char *filename, char **bufp, struct stat *st); // Returns size of file or -1 on error. // For empty files *bufp is NULL, otherwise *bufp is NUL-terminated. static inline ssize_t read_file(const char *filename, char **bufp) { struct stat st; return stat_read_file(filename, bufp, &st); } char *buf_next_line(char *buf, size_t *posp, size_t size); StringView buf_slice_next_line(const char *buf, size_t *posp, size_t size); #endif dte-1.9.1/src/util/str-util.h000066400000000000000000000033001354415143300157750ustar00rootroot00000000000000#ifndef STR_UTIL_H #define STR_UTIL_H #include #include #include #include #include "macros.h" #include "xmalloc.h" #define MEMZERO(ptr) memset((ptr), 0, sizeof(*(ptr))) NONNULL_ARGS static inline bool streq(const char *a, const char *b) { return strcmp(a, b) == 0; } static inline bool xstreq(const char *a, const char *b) { if (a == b) { return true; } else if (a == NULL || b == NULL) { return false; } return streq(a, b); } NONNULL_ARGS static inline bool str_has_prefix(const char *str, const char *prefix) { return strncmp(str, prefix, strlen(prefix)) == 0; } NONNULL_ARGS static inline bool str_has_suffix(const char *str, const char *suffix) { size_t l1 = strlen(str); size_t l2 = strlen(suffix); if (l2 > l1) { return false; } return memcmp(str + l1 - l2, suffix, l2) == 0; } NONNULL_ARGS static inline bool mem_equal(const void *s1, const void *s2, size_t n) { return memcmp(s1, s2, n) == 0; } NONNULL_ARGS static inline size_t count_nl(const char *buf, size_t size) { const char *end = buf + size; size_t nl = 0; while (buf < end) { buf = memchr(buf, '\n', end - buf); if (!buf) { break; } buf++; nl++; } return nl; } static inline char **copy_string_array(char **src, size_t count) { char **dst = xnew(char*, count + 1); for (size_t i = 0; i < count; i++) { dst[i] = xstrdup(src[i]); } dst[count] = NULL; return dst; } NONNULL_ARGS static inline void free_string_array(char **strings) { for (size_t i = 0; strings[i]; i++) { free(strings[i]); } free(strings); } #endif dte-1.9.1/src/util/string-view.h000066400000000000000000000062361354415143300165030ustar00rootroot00000000000000#ifndef UTIL_STRING_VIEW_H #define UTIL_STRING_VIEW_H #include #include #include #include "ascii.h" #include "macros.h" // A non-owning, length-bounded "view" into another string, similar to // the C++17 string_view class. The .data member will usually *not* be // null-terminated and the underlying string *must* outlive the view. typedef struct { const char NONSTRING *data; size_t length; } StringView; #define STRING_VIEW_INIT { \ .data = NULL, \ .length = 0 \ } #define STRING_VIEW(s) { \ .data = s, \ .length = STRLEN(s) \ } #define string_view_equal_literal(sv, str) ( \ string_view_equal_strn((sv), (str), STRLEN(str)) \ ) #define string_view_equal_literal_icase(sv, str) ( \ string_view_equal_strn_icase((sv), (str), STRLEN(str)) \ ) #define string_view_has_literal_prefix(sv, prefix) ( \ string_view_has_prefix((sv), (prefix), STRLEN(prefix)) \ ) #define string_view_has_literal_prefix_icase(sv, prefix) ( \ string_view_has_prefix_icase((sv), (prefix), STRLEN(prefix)) \ ) static inline StringView string_view(const char *str, size_t length) { return (StringView) { .data = str, .length = length }; } static inline StringView string_view_from_cstring(const char *str) { return (StringView) { .data = str, .length = str ? strlen(str) : 0 }; } NONNULL_ARGS static inline bool string_view_equal(const StringView *a, const StringView *b) { return a->length == b->length && memcmp(a->data, b->data, a->length) == 0; } NONNULL_ARGS static inline bool string_view_equal_strn ( const StringView *sv, const char *str, size_t len ) { return len == sv->length && memcmp(sv->data, str, len) == 0; } NONNULL_ARGS static inline bool string_view_equal_strn_icase ( const StringView *sv, const char *str, size_t len ) { return len == sv->length && mem_equal_icase(sv->data, str, len); } NONNULL_ARGS static inline bool string_view_equal_cstr(const StringView *sv, const char *str) { return string_view_equal_strn(sv, str, strlen(str)); } NONNULL_ARGS static inline bool string_view_has_prefix ( const StringView *sv, const char *str, size_t length ) { return sv->length >= length && memcmp(sv->data, str, length) == 0; } NONNULL_ARGS static inline bool string_view_has_prefix_icase ( const StringView *sv, const char *str, size_t length ) { return sv->length >= length && mem_equal_icase(sv->data, str, length); } NONNULL_ARGS static inline void *string_view_memchr(const StringView *sv, int c) { return memchr(sv->data, c, sv->length); } NONNULL_ARGS static inline void *string_view_memrchr(const StringView *sv, int c) { const unsigned char *s = sv->data; size_t n = sv->length; c = (int)(unsigned char)c; while (n--) { if (s[n] == c) { return (void*)(s + n); } } return NULL; } NONNULL_ARGS static inline void string_view_trim_left(StringView *sv) { const char *data = sv->data; const size_t len = sv->length; size_t i = 0; while (i < len && ascii_isblank(data[i])) { i++; } sv->data = data + i; sv->length = len - i; } #endif dte-1.9.1/src/util/string.c000066400000000000000000000066311354415143300155250ustar00rootroot00000000000000#include #include #include #include #include "string.h" #include "utf8.h" #include "xmalloc.h" #include "../debug.h" static void string_grow(String *s, size_t more) { const size_t len = s->len + more; size_t alloc = s->alloc; if (alloc >= len) { return; } while (alloc < len) { alloc = (alloc * 3 + 2) / 2; } alloc = ROUND_UP(alloc, 16); xrenew(s->buffer, alloc); s->alloc = alloc; } void string_free(String *s) { free(s->buffer); string_init(s); } void string_add_byte(String *s, unsigned char byte) { string_grow(s, 1); s->buffer[s->len++] = byte; } size_t string_add_ch(String *s, CodePoint u) { size_t len = u_char_size(u); string_grow(s, len); u_set_char_raw(s->buffer, &s->len, u); return len; } size_t string_insert_ch(String *s, size_t pos, CodePoint u) { size_t len = u_char_size(u); string_make_space(s, pos, len); u_set_char_raw(s->buffer, &pos, u); return len; } void string_add_str(String *s, const char *str) { string_add_buf(s, str, strlen(str)); } void string_add_string_view(String *s, const StringView *sv) { string_add_buf(s, sv->data, sv->length); } void string_add_buf(String *s, const char *ptr, size_t len) { if (!len) { return; } string_grow(s, len); memcpy(s->buffer + s->len, ptr, len); s->len += len; } VPRINTF(2) static void string_vsprintf(String *s, const char *fmt, va_list ap) { va_list ap2; va_copy(ap2, ap); // Calculate the required size int n = vsnprintf(NULL, 0, fmt, ap2); va_end(ap2); BUG_ON(n < 0); string_grow(s, n + 1); int wrote = vsnprintf(s->buffer + s->len, n + 1, fmt, ap); BUG_ON(wrote != n); s->len += wrote; } void string_sprintf(String *s, const char *fmt, ...) { va_list ap; va_start(ap, fmt); string_vsprintf(s, fmt, ap); va_end(ap); } char *string_steal(String *s, size_t *len) { char *b = s->buffer; *len = s->len; string_init(s); return b; } char *string_steal_cstring(String *s) { string_add_byte(s, '\0'); char *b = s->buffer; string_init(s); return b; } char *string_clone_cstring(const String *s) { const size_t len = s->len; char *b = xmalloc(len + 1); if (len > 0) { BUG_ON(!s->buffer); memcpy(b, s->buffer, len); } b[len] = '\0'; return b; } void string_ensure_null_terminated(String *s) { string_grow(s, 1); s->buffer[s->len] = '\0'; } /* * This method first ensures that the String buffer is null-terminated * and then returns a const pointer to it, without doing any copying. * * NOTE: the returned pointer only remains valid so long as no other * methods are called on the String. There are no exceptions to this * rule. If the buffer is realloc'd, the pointer will be dangling and * using it will invoke undefined behaviour. If unsure, just use * string_clone_cstring() instead. */ const char *string_borrow_cstring(String *s) { string_ensure_null_terminated(s); return s->buffer; } void string_make_space(String *s, size_t pos, size_t len) { BUG_ON(pos > s->len); string_grow(s, len); memmove(s->buffer + pos + len, s->buffer + pos, s->len - pos); s->len += len; } void string_remove(String *s, size_t pos, size_t len) { BUG_ON(pos + len > s->len); memmove(s->buffer + pos, s->buffer + pos + len, s->len - pos - len); s->len -= len; } dte-1.9.1/src/util/string.h000066400000000000000000000033271354415143300155310ustar00rootroot00000000000000#ifndef UTIL_STR_H #define UTIL_STR_H #include #include "macros.h" #include "string-view.h" #include "unicode.h" #include "xmalloc.h" typedef struct { unsigned char NONSTRING *buffer; size_t alloc; size_t len; } String; #define STRING_INIT { \ .buffer = NULL, \ .alloc = 0, \ .len = 0 \ } #define string_add_literal(s, l) ( \ string_add_buf(s, l, STRLEN(l)) \ ) static inline NONNULL_ARGS void string_init(String *s) { *s = (String) STRING_INIT; } static inline String string_new(size_t size) { size = ROUND_UP(size, 16); return (String) { .buffer = size ? xmalloc(size) : NULL, .alloc = size, .len = 0 }; } static inline NONNULL_ARGS void string_clear(String *s) { s->len = 0; } void string_free(String *s) NONNULL_ARGS; void string_add_byte(String *s, unsigned char byte) NONNULL_ARGS; size_t string_add_ch(String *s, CodePoint u) NONNULL_ARGS; void string_add_str(String *s, const char *str) NONNULL_ARGS; void string_add_string_view(String *s, const StringView *sv) NONNULL_ARGS; void string_add_buf(String *s, const char *ptr, size_t len) NONNULL_ARGS; size_t string_insert_ch(String *s, size_t pos, CodePoint u) NONNULL_ARGS; void string_sprintf(String *s, const char *fmt, ...) PRINTF(2) NONNULL_ARGS; char *string_steal(String *s, size_t *len) NONNULL_ARGS; char *string_steal_cstring(String *s) NONNULL_ARGS_AND_RETURN; char *string_clone_cstring(const String *s) XSTRDUP; const char *string_borrow_cstring(String *s) NONNULL_ARGS_AND_RETURN; void string_ensure_null_terminated(String *s) NONNULL_ARGS; void string_make_space(String *s, size_t pos, size_t len) NONNULL_ARGS; void string_remove(String *s, size_t pos, size_t len) NONNULL_ARGS; #endif dte-1.9.1/src/util/strtonum.c000066400000000000000000000061721354415143300161120ustar00rootroot00000000000000#include #include #include "strtonum.h" #include "ascii.h" #include "checked-arith.h" int number_width(long n) { int width = 0; if (n < 0) { n *= -1; width++; } do { n /= 10; width++; } while (n); return width; } size_t buf_parse_uintmax(const char *str, size_t size, uintmax_t *valp) { if (size == 0 || !ascii_isdigit(str[0])) { return 0; } uintmax_t val = str[0] - '0'; size_t i = 1; while (i < size && ascii_isdigit(str[i])) { if (umax_multiply_overflows(val, 10, &val)) { return 0; } if (umax_add_overflows(val, str[i++] - '0', &val)) { return 0; } } *valp = val; return i; } size_t buf_parse_ulong(const char *str, size_t size, unsigned long *valp) { uintmax_t val; size_t n = buf_parse_uintmax(str, size, &val); if (n == 0 || val > ULONG_MAX) { return 0; } *valp = (unsigned long)val; return n; } size_t buf_parse_uint(const char *str, size_t size, unsigned int *valp) { uintmax_t val; size_t n = buf_parse_uintmax(str, size, &val); if (n == 0 || val > UINT_MAX) { return 0; } *valp = (unsigned int)val; return n; } static size_t buf_parse_long(const char *str, size_t size, long *valp) { bool negative = false; size_t skipped = 0; if (size == 0) { return 0; } else { switch (str[0]) { case '-': negative = true; // Fallthrough case '+': skipped = 1; str++; size--; break; } } uintmax_t val; size_t n = buf_parse_uintmax(str, size, &val); if (n == 0 || val > LONG_MAX) { return 0; } if (negative) { *valp = -((long)val); } else { *valp = (long)val; } return n + skipped; } bool str_to_int(const char *str, int *valp) { const size_t len = strlen(str); if (len == 0) { return false; } long val; const size_t n = buf_parse_long(str, len, &val); if (n != len || val < INT_MIN || val > INT_MAX) { return false; } *valp = (int)val; return true; } bool str_to_uint(const char *str, unsigned int *valp) { const size_t len = strlen(str); if (len == 0) { return false; } uintmax_t val; const size_t n = buf_parse_uintmax(str, len, &val); if (n != len || val > UINT_MAX) { return false; } *valp = (unsigned int)val; return true; } bool str_to_size(const char *str, size_t *valp) { const size_t len = strlen(str); if (len == 0) { return false; } uintmax_t val; const size_t n = buf_parse_uintmax(str, len, &val); if (n != len || val > SIZE_MAX) { return false; } *valp = (size_t)val; return true; } bool str_to_ulong(const char *str, unsigned long *valp) { const size_t len = strlen(str); if (len == 0) { return false; } unsigned long val; const size_t n = buf_parse_ulong(str, len, &val); if (n != len) { return false; } *valp = val; return true; } dte-1.9.1/src/util/strtonum.h000066400000000000000000000013021354415143300161050ustar00rootroot00000000000000#ifndef UTIL_STRTONUM_H #define UTIL_STRTONUM_H #include #include #include #include "macros.h" int number_width(long n) CONST_FN; size_t buf_parse_uintmax(const char *str, size_t size, uintmax_t *valp) NONNULL_ARG(1); size_t buf_parse_ulong(const char *str, size_t size, unsigned long *valp) NONNULL_ARG(1); size_t buf_parse_uint(const char *str, size_t size, unsigned int *valp) NONNULL_ARG(1); bool str_to_int(const char *str, int *valp) NONNULL_ARG(1); bool str_to_uint(const char *str, unsigned int *valp) NONNULL_ARG(1); bool str_to_size(const char *str, size_t *valp) NONNULL_ARG(1); bool str_to_ulong(const char *str, unsigned long *valp) NONNULL_ARG(1); #endif dte-1.9.1/src/util/unicode.c000066400000000000000000000053421354415143300156430ustar00rootroot00000000000000#include #include "unicode.h" #include "ascii.h" #include "../editor.h" #define BISEARCH(u, arr) bisearch((u), (arr), ARRAY_COUNT(arr) - 1) typedef struct { CodePoint first, last; } CodepointRange; #include "wcwidth.c" static bool bisearch ( CodePoint u, const CodepointRange *const range, size_t max ) { if (u < range[0].first || u > range[max].last) { return false; } size_t min = 0; while (max >= min) { const size_t mid = (min + max) / 2; if (u > range[mid].last) { min = mid + 1; } else if (u < range[mid].first) { max = mid - 1; } else { return true; } } return false; } // Returns true for any whitespace character that isn't "non-breaking", // i.e. one that is used purely to separate words and may, for example, // be "broken" (changed to a newline) by hard wrapping. bool u_is_breakable_whitespace(CodePoint u) { switch (u) { case '\t': case '\n': case '\v': case '\f': case '\r': case ' ': case 0x2000: // En quad case 0x2001: // Em quad case 0x2002: // En space case 0x2003: // Em space case 0x2004: // 3-per-em space case 0x2005: // 4-per-em space case 0x2006: // 6-per-em space case 0x2009: // Thin space case 0x200a: // Hair space case 0x3000: // Ideographic space return true; } return false; } bool u_is_word_char(CodePoint u) { return u >= 0x80 || is_alnum_or_underscore(u); } static bool u_is_default_ignorable(CodePoint u) { return BISEARCH(u, default_ignorable); } bool u_is_unprintable(CodePoint u) { if (BISEARCH(u, unprintable)) { return true; } if (editor.options.display_invisible && u_is_default_ignorable(u)) { return true; } return !u_is_unicode(u); } bool u_is_special_whitespace(CodePoint u) { return BISEARCH(u, special_whitespace); } static bool u_is_nonspacing_mark(CodePoint u) { return BISEARCH(u, nonspacing_mark); } bool u_is_zero_width(CodePoint u) { if (u_is_nonspacing_mark(u)) { return true; } if (!editor.options.display_invisible && u_is_default_ignorable(u)) { return true; } return false; } static bool u_is_double_width(CodePoint u) { return BISEARCH(u, double_width); } unsigned int u_char_width(CodePoint u) { if (u < 0x80) { if (ascii_iscntrl(u)) { return 2; // Rendered in caret notation (e.g. ^@) } return 1; } else if (u_is_zero_width(u)) { return 0; } else if (u_is_unprintable(u)) { return 4; // Rendered as } else if (u < 0x1100U) { return 1; } else if (u_is_double_width(u)) { return 2; } return 1; } dte-1.9.1/src/util/unicode.h000066400000000000000000000015131354415143300156440ustar00rootroot00000000000000#ifndef UTIL_UNICODE_H #define UTIL_UNICODE_H #include #include #include "macros.h" #define UNICODE_MAX_VALID_CODEPOINT UINT32_C(0x10ffff) typedef uint32_t CodePoint; static inline bool u_is_unicode(CodePoint u) { return u <= UNICODE_MAX_VALID_CODEPOINT; } static inline bool u_is_cntrl(CodePoint u) { return (u < 0x20) || (u >= 0x7F && u <= 0x9F); } static inline bool u_is_upper(CodePoint u) { return (u - 'A') < 26; } static inline CodePoint u_to_lower(CodePoint u) { return u_is_upper(u) ? u + 32 : u; } bool u_is_breakable_whitespace(CodePoint u) CONST_FN; bool u_is_word_char(CodePoint u) CONST_FN; bool u_is_unprintable(CodePoint u) CONST_FN; bool u_is_special_whitespace(CodePoint u) CONST_FN; bool u_is_zero_width(CodePoint u); unsigned int u_char_width(CodePoint uch) CONST_FN; #endif dte-1.9.1/src/util/utf8.c000066400000000000000000000145061354415143300151050ustar00rootroot00000000000000#include #include "utf8.h" #include "ascii.h" static int u_seq_len(unsigned int first_byte) { if (first_byte < 0x80) { return 1; } if (first_byte < 0xc0) { return 0; } if (first_byte < 0xe0) { return 2; } if (first_byte < 0xf0) { return 3; } // Could be 0xf8 but RFC 3629 doesn't allow codepoints above 0x10ffff if (first_byte < 0xf5) { return 4; } return -1; } static bool u_is_continuation(CodePoint u) { return (u & 0xc0) == 0x80; } static bool u_seq_len_ok(CodePoint u, int len) { return u_char_size(u) == len; } /* * Len Mask Note * ------------------------------------------------- * 1 0111 1111 Not supported by this function! * 2 0001 1111 * 3 0000 1111 * 4 0000 0111 * 5 0000 0011 Forbidden by RFC 3629 * 6 0000 0001 Forbidden by RFC 3629 */ static unsigned int u_get_first_byte_mask(unsigned int len) { return (1U << 7U >> len) - 1; } size_t u_str_width(const unsigned char *str) { size_t i = 0, w = 0; while (str[i]) { w += u_char_width(u_str_get_char(str, &i)); } return w; } CodePoint u_prev_char(const unsigned char *buf, size_t *idx) { size_t i = *idx; unsigned int count, shift; CodePoint u; u = buf[--i]; if (u < 0x80) { *idx = i; return u; } if (!u_is_continuation(u)) { goto invalid; } u &= 0x3f; count = 1; shift = 6; while (i) { unsigned int ch = buf[--i]; unsigned int len = u_seq_len(ch); count++; if (len == 0) { if (count == 4) { // Too long sequence break; } u |= (ch & 0x3f) << shift; shift += 6; } else if (count != len) { // Incorrect length break; } else { u |= (ch & u_get_first_byte_mask(len)) << shift; if (!u_seq_len_ok(u, len)) { break; } *idx = i; return u; } } invalid: *idx = *idx - 1; u = buf[*idx]; return -u; } CodePoint u_str_get_char(const unsigned char *str, size_t *idx) { size_t i = *idx; CodePoint u = str[i]; if (u < 0x80) { *idx = i + 1; return u; } return u_get_nonascii(str, i + 4, idx); } CodePoint u_get_char(const unsigned char *buf, size_t size, size_t *idx) { size_t i = *idx; CodePoint u = buf[i]; if (u < 0x80) { *idx = i + 1; return u; } return u_get_nonascii(buf, size, idx); } CodePoint u_get_nonascii(const unsigned char *buf, size_t size, size_t *idx) { size_t i = *idx; int len, c; unsigned int first, u; first = buf[i++]; len = u_seq_len(first); if (unlikely(len < 2 || len > size - i + 1)) { goto invalid; } u = first & u_get_first_byte_mask(len); c = len - 1; do { CodePoint ch = buf[i++]; if (!u_is_continuation(ch)) { goto invalid; } u = (u << 6) | (ch & 0x3f); } while (--c); if (!u_seq_len_ok(u, len)) { goto invalid; } *idx = i; return u; invalid: *idx += 1; return -first; } void u_set_char_raw(char *str, size_t *idx, CodePoint u) { size_t i = *idx; if (u <= 0x7f) { str[i++] = u; } else if (u <= 0x7ff) { str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; str[i + 0] = u | 0xc0; i += 2; } else if (u <= 0xffff) { str[i + 2] = (u & 0x3f) | 0x80; u >>= 6; str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; str[i + 0] = u | 0xe0; i += 3; } else if (u <= 0x10ffff) { str[i + 3] = (u & 0x3f) | 0x80; u >>= 6; str[i + 2] = (u & 0x3f) | 0x80; u >>= 6; str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; str[i + 0] = u | 0xf0; i += 4; } else { // Invalid byte value str[i++] = u & 0xff; } *idx = i; } void u_set_char(char *str, size_t *idx, CodePoint u) { size_t i = *idx; if (u < 0x80) { if (ascii_iscntrl(u)) { u_set_ctrl(str, idx, u); } else { str[i++] = u; *idx = i; } } else if (u_is_unprintable(u)) { u_set_hex(str, idx, u); } else if (u <= 0x7ff) { str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; str[i + 0] = u | 0xc0; i += 2; *idx = i; } else if (u <= 0xffff) { str[i + 2] = (u & 0x3f) | 0x80; u >>= 6; str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; str[i + 0] = u | 0xe0; i += 3; *idx = i; } else if (u <= 0x10ffff) { str[i + 3] = (u & 0x3f) | 0x80; u >>= 6; str[i + 2] = (u & 0x3f) | 0x80; u >>= 6; str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; str[i + 0] = u | 0xf0; i += 4; *idx = i; } } void u_set_hex(char *str, size_t *idx, CodePoint u) { static const char hex_tab[16] = "0123456789abcdef"; char *p = str + *idx; p[0] = '<'; if (!u_is_unicode(u)) { // Invalid byte (negated) u *= -1; p[1] = hex_tab[(u >> 4) & 0x0f]; p[2] = hex_tab[u & 0x0f]; } else { p[1] = '?'; p[2] = '?'; } p[3] = '>'; *idx += 4; } size_t u_skip_chars(const char *str, int *width) { int w = *width; size_t idx = 0; while (str[idx] && w > 0) { w -= u_char_width(u_str_get_char(str, &idx)); } // Add 1..3 if skipped 'too much' (the last char was double // width or invalid ()) *width -= w; return idx; } static bool has_prefix(const char *str, const char *prefix_lcase) { size_t ni = 0; size_t hi = 0; CodePoint pc; while ((pc = u_str_get_char(prefix_lcase, &ni))) { CodePoint sc = u_str_get_char(str, &hi); if (sc != pc && u_to_lower(sc) != pc) { return false; } } return true; } ssize_t u_str_index(const char *haystack, const char *needle_lcase) { size_t hi = 0; size_t ni = 0; CodePoint nc = u_str_get_char(needle_lcase, &ni); if (!nc) { return 0; } while (haystack[hi]) { size_t prev = hi; CodePoint hc = u_str_get_char(haystack, &hi); if ( (hc == nc || u_to_lower(hc) == nc) && has_prefix(haystack + hi, needle_lcase + ni) ) { return prev; } } return -1; } dte-1.9.1/src/util/utf8.h000066400000000000000000000027501354415143300151100ustar00rootroot00000000000000#ifndef UTIL_UCHAR_H #define UTIL_UCHAR_H #include #include "unicode.h" static inline size_t u_char_size(CodePoint u) { if (u <= UINT32_C(0x7f)) { return 1; } else if (u <= UINT32_C(0x7ff)) { return 2; } else if (u <= UINT32_C(0xffff)) { return 3; } else if (u <= UINT32_C(0x10ffff)) { return 4; } // Invalid byte in UTF-8 byte sequence. return 1; } NONNULL_ARGS static inline void u_set_ctrl(char *buf, size_t *idx, CodePoint u) { size_t i = *idx; buf[i++] = '^'; buf[i++] = (u == 0x7F) ? '?' : u | 0x40; *idx = i; } size_t u_str_width(const unsigned char *str); CodePoint u_prev_char(const unsigned char *buf, size_t *idx); CodePoint u_str_get_char(const unsigned char *str, size_t *idx); CodePoint u_get_char(const unsigned char *buf, size_t size, size_t *idx); CodePoint u_get_nonascii(const unsigned char *buf, size_t size, size_t *idx); void u_set_char_raw(char *str, size_t *idx, CodePoint u); void u_set_char(char *str, size_t *idx, CodePoint u); void u_set_hex(char *str, size_t *idx, CodePoint u); /* * Total width of skipped characters is stored back to @width. * * Stored @width can be 1 more than given width if the last skipped * character was double width or even 3 more if the last skipped * character was invalid (). * * Returns number of bytes skipped. */ size_t u_skip_chars(const char *str, int *width); ssize_t u_str_index(const char *haystack, const char *needle_lcase); #endif dte-1.9.1/src/util/wbuf.c000066400000000000000000000025741354415143300151640ustar00rootroot00000000000000#include #include "macros.h" #include "wbuf.h" #include "xreadwrite.h" #define XFLUSH(buf) do { \ ssize_t rc_ = wbuf_flush(buf); \ if (unlikely(rc_ < 0)) { \ return rc_; \ } \ } while (0) NONNULL_ARGS static size_t wbuf_avail(WriteBuffer *wbuf) { return sizeof(wbuf->buf) - wbuf->fill; } ssize_t wbuf_flush(WriteBuffer *wbuf) { if (wbuf->fill) { ssize_t rc = xwrite(wbuf->fd, wbuf->buf, wbuf->fill); if (unlikely(rc < 0)) { return rc; } wbuf->fill = 0; } return 0; } void wbuf_need_space(WriteBuffer *wbuf, size_t count) { if (wbuf_avail(wbuf) < count) { wbuf_flush(wbuf); } } ssize_t wbuf_write(WriteBuffer *wbuf, const char *buf, size_t count) { if (count > wbuf_avail(wbuf)) { XFLUSH(wbuf); if (unlikely(count >= sizeof(wbuf->buf))) { ssize_t rc = xwrite(wbuf->fd, buf, count); if (unlikely(rc < 0)) { return rc; } return 0; } } memcpy(wbuf->buf + wbuf->fill, buf, count); wbuf->fill += count; return 0; } ssize_t wbuf_write_str(WriteBuffer *wbuf, const char *str) { return wbuf_write(wbuf, str, strlen(str)); } ssize_t wbuf_write_ch(WriteBuffer *wbuf, char ch) { if (wbuf_avail(wbuf) == 0) { XFLUSH(wbuf); } wbuf->buf[wbuf->fill++] = ch; return 0; } dte-1.9.1/src/util/wbuf.h000066400000000000000000000010741354415143300151630ustar00rootroot00000000000000#ifndef UTIL_WBUF_H #define UTIL_WBUF_H #include #include "macros.h" typedef struct { size_t fill; int fd; char buf[8192]; } WriteBuffer; #define WBUF_INIT { \ .fill = 0, \ .fd = -1 \ } ssize_t wbuf_flush(WriteBuffer *wbuf) NONNULL_ARGS; ssize_t wbuf_write(WriteBuffer *wbuf, const char *buf, size_t count) NONNULL_ARGS; ssize_t wbuf_write_str(WriteBuffer *wbuf, const char *str) NONNULL_ARGS; ssize_t wbuf_write_ch(WriteBuffer *wbuf, char ch) NONNULL_ARGS; void wbuf_need_space(WriteBuffer *wbuf, size_t count) NONNULL_ARGS; #endif dte-1.9.1/src/util/wcwidth.c000066400000000000000000000622751354415143300156760ustar00rootroot00000000000000static const CodepointRange special_whitespace[] = { {0x00a0, 0x00a0}, {0x00ad, 0x00ad}, {0x2000, 0x200a}, {0x2028, 0x2029}, {0x202f, 0x202f}, {0x205f, 0x205f}, }; static const CodepointRange default_ignorable[] = { {0x034f, 0x034f}, {0x061c, 0x061c}, {0x115f, 0x1160}, {0x17b4, 0x17b5}, {0x180b, 0x180e}, {0x200b, 0x200f}, {0x202a, 0x202e}, {0x2060, 0x206f}, {0x3164, 0x3164}, {0xfe00, 0xfe0f}, {0xfeff, 0xfeff}, {0xffa0, 0xffa0}, {0xfff0, 0xfff8}, {0x1bca0, 0x1bca3}, {0x1d173, 0x1d17a}, {0xe0000, 0xe0fff}, }; static const CodepointRange nonspacing_mark[] = { {0x0300, 0x036f}, {0x0483, 0x0489}, {0x0591, 0x05bd}, {0x05bf, 0x05bf}, {0x05c1, 0x05c2}, {0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a}, {0x064b, 0x065f}, {0x0670, 0x0670}, {0x06d6, 0x06dc}, {0x06df, 0x06e4}, {0x06e7, 0x06e8}, {0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a}, {0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x07fd, 0x07fd}, {0x0816, 0x0819}, {0x081b, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082d}, {0x0859, 0x085b}, {0x08d3, 0x08e1}, {0x08e3, 0x0902}, {0x093a, 0x093a}, {0x093c, 0x093c}, {0x0941, 0x0948}, {0x094d, 0x094d}, {0x0951, 0x0957}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09bc, 0x09bc}, {0x09c1, 0x09c4}, {0x09cd, 0x09cd}, {0x09e2, 0x09e3}, {0x09fe, 0x09fe}, {0x0a01, 0x0a02}, {0x0a3c, 0x0a3c}, {0x0a41, 0x0a42}, {0x0a47, 0x0a48}, {0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, {0x0a70, 0x0a71}, {0x0a75, 0x0a75}, {0x0a81, 0x0a82}, {0x0abc, 0x0abc}, {0x0ac1, 0x0ac5}, {0x0ac7, 0x0ac8}, {0x0acd, 0x0acd}, {0x0ae2, 0x0ae3}, {0x0afa, 0x0aff}, {0x0b01, 0x0b01}, {0x0b3c, 0x0b3c}, {0x0b3f, 0x0b3f}, {0x0b41, 0x0b44}, {0x0b4d, 0x0b4d}, {0x0b56, 0x0b56}, {0x0b62, 0x0b63}, {0x0b82, 0x0b82}, {0x0bc0, 0x0bc0}, {0x0bcd, 0x0bcd}, {0x0c00, 0x0c00}, {0x0c04, 0x0c04}, {0x0c3e, 0x0c40}, {0x0c46, 0x0c48}, {0x0c4a, 0x0c4d}, {0x0c55, 0x0c56}, {0x0c62, 0x0c63}, {0x0c81, 0x0c81}, {0x0cbc, 0x0cbc}, {0x0cbf, 0x0cbf}, {0x0cc6, 0x0cc6}, {0x0ccc, 0x0ccd}, {0x0ce2, 0x0ce3}, {0x0d00, 0x0d01}, {0x0d3b, 0x0d3c}, {0x0d41, 0x0d44}, {0x0d4d, 0x0d4d}, {0x0d62, 0x0d63}, {0x0dca, 0x0dca}, {0x0dd2, 0x0dd4}, {0x0dd6, 0x0dd6}, {0x0e31, 0x0e31}, {0x0e34, 0x0e3a}, {0x0e47, 0x0e4e}, {0x0eb1, 0x0eb1}, {0x0eb4, 0x0ebc}, {0x0ec8, 0x0ecd}, {0x0f18, 0x0f19}, {0x0f35, 0x0f35}, {0x0f37, 0x0f37}, {0x0f39, 0x0f39}, {0x0f71, 0x0f7e}, {0x0f80, 0x0f84}, {0x0f86, 0x0f87}, {0x0f8d, 0x0f97}, {0x0f99, 0x0fbc}, {0x0fc6, 0x0fc6}, {0x102d, 0x1030}, {0x1032, 0x1037}, {0x1039, 0x103a}, {0x103d, 0x103e}, {0x1058, 0x1059}, {0x105e, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082}, {0x1085, 0x1086}, {0x108d, 0x108d}, {0x109d, 0x109d}, {0x135d, 0x135f}, {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, {0x17b4, 0x17b5}, {0x17b7, 0x17bd}, {0x17c6, 0x17c6}, {0x17c9, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180d}, {0x1885, 0x1886}, {0x18a9, 0x18a9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193b}, {0x1a17, 0x1a18}, {0x1a1b, 0x1a1b}, {0x1a56, 0x1a56}, {0x1a58, 0x1a5e}, {0x1a60, 0x1a60}, {0x1a62, 0x1a62}, {0x1a65, 0x1a6c}, {0x1a73, 0x1a7c}, {0x1a7f, 0x1a7f}, {0x1ab0, 0x1abe}, {0x1b00, 0x1b03}, {0x1b34, 0x1b34}, {0x1b36, 0x1b3a}, {0x1b3c, 0x1b3c}, {0x1b42, 0x1b42}, {0x1b6b, 0x1b73}, {0x1b80, 0x1b81}, {0x1ba2, 0x1ba5}, {0x1ba8, 0x1ba9}, {0x1bab, 0x1bad}, {0x1be6, 0x1be6}, {0x1be8, 0x1be9}, {0x1bed, 0x1bed}, {0x1bef, 0x1bf1}, {0x1c2c, 0x1c33}, {0x1c36, 0x1c37}, {0x1cd0, 0x1cd2}, {0x1cd4, 0x1ce0}, {0x1ce2, 0x1ce8}, {0x1ced, 0x1ced}, {0x1cf4, 0x1cf4}, {0x1cf8, 0x1cf9}, {0x1dc0, 0x1df9}, {0x1dfb, 0x1dff}, {0x20d0, 0x20f0}, {0x2cef, 0x2cf1}, {0x2d7f, 0x2d7f}, {0x2de0, 0x2dff}, {0x302a, 0x302d}, {0x3099, 0x309a}, {0xa66f, 0xa672}, {0xa674, 0xa67d}, {0xa69e, 0xa69f}, {0xa6f0, 0xa6f1}, {0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b}, {0xa825, 0xa826}, {0xa8c4, 0xa8c5}, {0xa8e0, 0xa8f1}, {0xa8ff, 0xa8ff}, {0xa926, 0xa92d}, {0xa947, 0xa951}, {0xa980, 0xa982}, {0xa9b3, 0xa9b3}, {0xa9b6, 0xa9b9}, {0xa9bc, 0xa9bd}, {0xa9e5, 0xa9e5}, {0xaa29, 0xaa2e}, {0xaa31, 0xaa32}, {0xaa35, 0xaa36}, {0xaa43, 0xaa43}, {0xaa4c, 0xaa4c}, {0xaa7c, 0xaa7c}, {0xaab0, 0xaab0}, {0xaab2, 0xaab4}, {0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1}, {0xaaec, 0xaaed}, {0xaaf6, 0xaaf6}, {0xabe5, 0xabe5}, {0xabe8, 0xabe8}, {0xabed, 0xabed}, {0xfb1e, 0xfb1e}, {0xfe00, 0xfe0f}, {0xfe20, 0xfe2f}, {0x101fd, 0x101fd}, {0x102e0, 0x102e0}, {0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06}, {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f}, {0x10ae5, 0x10ae6}, {0x10d24, 0x10d27}, {0x10f46, 0x10f50}, {0x11001, 0x11001}, {0x11038, 0x11046}, {0x1107f, 0x11081}, {0x110b3, 0x110b6}, {0x110b9, 0x110ba}, {0x11100, 0x11102}, {0x11127, 0x1112b}, {0x1112d, 0x11134}, {0x11173, 0x11173}, {0x11180, 0x11181}, {0x111b6, 0x111be}, {0x111c9, 0x111cc}, {0x1122f, 0x11231}, {0x11234, 0x11234}, {0x11236, 0x11237}, {0x1123e, 0x1123e}, {0x112df, 0x112df}, {0x112e3, 0x112ea}, {0x11300, 0x11301}, {0x1133b, 0x1133c}, {0x11340, 0x11340}, {0x11366, 0x1136c}, {0x11370, 0x11374}, {0x11438, 0x1143f}, {0x11442, 0x11444}, {0x11446, 0x11446}, {0x1145e, 0x1145e}, {0x114b3, 0x114b8}, {0x114ba, 0x114ba}, {0x114bf, 0x114c0}, {0x114c2, 0x114c3}, {0x115b2, 0x115b5}, {0x115bc, 0x115bd}, {0x115bf, 0x115c0}, {0x115dc, 0x115dd}, {0x11633, 0x1163a}, {0x1163d, 0x1163d}, {0x1163f, 0x11640}, {0x116ab, 0x116ab}, {0x116ad, 0x116ad}, {0x116b0, 0x116b5}, {0x116b7, 0x116b7}, {0x1171d, 0x1171f}, {0x11722, 0x11725}, {0x11727, 0x1172b}, {0x1182f, 0x11837}, {0x11839, 0x1183a}, {0x119d4, 0x119d7}, {0x119da, 0x119db}, {0x119e0, 0x119e0}, {0x11a01, 0x11a0a}, {0x11a33, 0x11a38}, {0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a56}, {0x11a59, 0x11a5b}, {0x11a8a, 0x11a96}, {0x11a98, 0x11a99}, {0x11c30, 0x11c36}, {0x11c38, 0x11c3d}, {0x11c3f, 0x11c3f}, {0x11c92, 0x11ca7}, {0x11caa, 0x11cb0}, {0x11cb2, 0x11cb3}, {0x11cb5, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a}, {0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47}, {0x11d90, 0x11d91}, {0x11d95, 0x11d95}, {0x11d97, 0x11d97}, {0x11ef3, 0x11ef4}, {0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16f4f, 0x16f4f}, {0x16f8f, 0x16f92}, {0x1bc9d, 0x1bc9e}, {0x1d167, 0x1d169}, {0x1d17b, 0x1d182}, {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, {0x1d242, 0x1d244}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c}, {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f}, {0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018}, {0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a}, {0x1e130, 0x1e136}, {0x1e2ec, 0x1e2ef}, {0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0100, 0xe01ef}, }; static const CodepointRange double_width[] = { {0x1100, 0x115f}, {0x231a, 0x231b}, {0x2329, 0x232a}, {0x23e9, 0x23ec}, {0x23f0, 0x23f0}, {0x23f3, 0x23f3}, {0x25fd, 0x25fe}, {0x2614, 0x2615}, {0x2648, 0x2653}, {0x267f, 0x267f}, {0x2693, 0x2693}, {0x26a1, 0x26a1}, {0x26aa, 0x26ab}, {0x26bd, 0x26be}, {0x26c4, 0x26c5}, {0x26ce, 0x26ce}, {0x26d4, 0x26d4}, {0x26ea, 0x26ea}, {0x26f2, 0x26f3}, {0x26f5, 0x26f5}, {0x26fa, 0x26fa}, {0x26fd, 0x26fd}, {0x2705, 0x2705}, {0x270a, 0x270b}, {0x2728, 0x2728}, {0x274c, 0x274c}, {0x274e, 0x274e}, {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797}, {0x27b0, 0x27b0}, {0x27bf, 0x27bf}, {0x2b1b, 0x2b1c}, {0x2b50, 0x2b50}, {0x2b55, 0x2b55}, {0x2e80, 0x2e99}, {0x2e9b, 0x2ef3}, {0x2f00, 0x2fd5}, {0x2ff0, 0x2ffb}, {0x3000, 0x303e}, {0x3041, 0x3096}, {0x3099, 0x30ff}, {0x3105, 0x312f}, {0x3131, 0x318e}, {0x3190, 0x31ba}, {0x31c0, 0x31e3}, {0x31f0, 0x321e}, {0x3220, 0x3247}, {0x3250, 0x4dbf}, {0x4e00, 0xa48c}, {0xa490, 0xa4c6}, {0xa960, 0xa97c}, {0xac00, 0xd7a3}, {0xf900, 0xfaff}, {0xfe10, 0xfe19}, {0xfe30, 0xfe52}, {0xfe54, 0xfe66}, {0xfe68, 0xfe6b}, {0xff01, 0xff60}, {0xffe0, 0xffe6}, {0x16fe0, 0x16fe3}, {0x17000, 0x187f7}, {0x18800, 0x18af2}, {0x1b000, 0x1b11e}, {0x1b150, 0x1b152}, {0x1b164, 0x1b167}, {0x1b170, 0x1b2fb}, {0x1f004, 0x1f004}, {0x1f0cf, 0x1f0cf}, {0x1f18e, 0x1f18e}, {0x1f191, 0x1f19a}, {0x1f200, 0x1f202}, {0x1f210, 0x1f23b}, {0x1f240, 0x1f248}, {0x1f250, 0x1f251}, {0x1f260, 0x1f265}, {0x1f300, 0x1f320}, {0x1f32d, 0x1f335}, {0x1f337, 0x1f37c}, {0x1f37e, 0x1f393}, {0x1f3a0, 0x1f3ca}, {0x1f3cf, 0x1f3d3}, {0x1f3e0, 0x1f3f0}, {0x1f3f4, 0x1f3f4}, {0x1f3f8, 0x1f43e}, {0x1f440, 0x1f440}, {0x1f442, 0x1f4fc}, {0x1f4ff, 0x1f53d}, {0x1f54b, 0x1f54e}, {0x1f550, 0x1f567}, {0x1f57a, 0x1f57a}, {0x1f595, 0x1f596}, {0x1f5a4, 0x1f5a4}, {0x1f5fb, 0x1f64f}, {0x1f680, 0x1f6c5}, {0x1f6cc, 0x1f6cc}, {0x1f6d0, 0x1f6d2}, {0x1f6d5, 0x1f6d5}, {0x1f6eb, 0x1f6ec}, {0x1f6f4, 0x1f6fa}, {0x1f7e0, 0x1f7eb}, {0x1f90d, 0x1f971}, {0x1f973, 0x1f976}, {0x1f97a, 0x1f9a2}, {0x1f9a5, 0x1f9aa}, {0x1f9ae, 0x1f9ca}, {0x1f9cd, 0x1f9ff}, {0x1fa70, 0x1fa73}, {0x1fa78, 0x1fa7a}, {0x1fa80, 0x1fa82}, {0x1fa90, 0x1fa95}, {0x20000, 0x2fffd}, {0x30000, 0x3fffd}, }; static const CodepointRange unprintable[] = { {0x0080, 0x009f}, {0x0378, 0x0379}, {0x0380, 0x0383}, {0x038b, 0x038b}, {0x038d, 0x038d}, {0x03a2, 0x03a2}, {0x0530, 0x0530}, {0x0557, 0x0558}, {0x058b, 0x058c}, {0x0590, 0x0590}, {0x05c8, 0x05cf}, {0x05eb, 0x05ee}, {0x05f5, 0x05ff}, {0x061d, 0x061d}, {0x070e, 0x070e}, {0x074b, 0x074c}, {0x07b2, 0x07bf}, {0x07fb, 0x07fc}, {0x082e, 0x082f}, {0x083f, 0x083f}, {0x085c, 0x085d}, {0x085f, 0x085f}, {0x086b, 0x089f}, {0x08b5, 0x08b5}, {0x08be, 0x08d2}, {0x0984, 0x0984}, {0x098d, 0x098e}, {0x0991, 0x0992}, {0x09a9, 0x09a9}, {0x09b1, 0x09b1}, {0x09b3, 0x09b5}, {0x09ba, 0x09bb}, {0x09c5, 0x09c6}, {0x09c9, 0x09ca}, {0x09cf, 0x09d6}, {0x09d8, 0x09db}, {0x09de, 0x09de}, {0x09e4, 0x09e5}, {0x09ff, 0x0a00}, {0x0a04, 0x0a04}, {0x0a0b, 0x0a0e}, {0x0a11, 0x0a12}, {0x0a29, 0x0a29}, {0x0a31, 0x0a31}, {0x0a34, 0x0a34}, {0x0a37, 0x0a37}, {0x0a3a, 0x0a3b}, {0x0a3d, 0x0a3d}, {0x0a43, 0x0a46}, {0x0a49, 0x0a4a}, {0x0a4e, 0x0a50}, {0x0a52, 0x0a58}, {0x0a5d, 0x0a5d}, {0x0a5f, 0x0a65}, {0x0a77, 0x0a80}, {0x0a84, 0x0a84}, {0x0a8e, 0x0a8e}, {0x0a92, 0x0a92}, {0x0aa9, 0x0aa9}, {0x0ab1, 0x0ab1}, {0x0ab4, 0x0ab4}, {0x0aba, 0x0abb}, {0x0ac6, 0x0ac6}, {0x0aca, 0x0aca}, {0x0ace, 0x0acf}, {0x0ad1, 0x0adf}, {0x0ae4, 0x0ae5}, {0x0af2, 0x0af8}, {0x0b00, 0x0b00}, {0x0b04, 0x0b04}, {0x0b0d, 0x0b0e}, {0x0b11, 0x0b12}, {0x0b29, 0x0b29}, {0x0b31, 0x0b31}, {0x0b34, 0x0b34}, {0x0b3a, 0x0b3b}, {0x0b45, 0x0b46}, {0x0b49, 0x0b4a}, {0x0b4e, 0x0b55}, {0x0b58, 0x0b5b}, {0x0b5e, 0x0b5e}, {0x0b64, 0x0b65}, {0x0b78, 0x0b81}, {0x0b84, 0x0b84}, {0x0b8b, 0x0b8d}, {0x0b91, 0x0b91}, {0x0b96, 0x0b98}, {0x0b9b, 0x0b9b}, {0x0b9d, 0x0b9d}, {0x0ba0, 0x0ba2}, {0x0ba5, 0x0ba7}, {0x0bab, 0x0bad}, {0x0bba, 0x0bbd}, {0x0bc3, 0x0bc5}, {0x0bc9, 0x0bc9}, {0x0bce, 0x0bcf}, {0x0bd1, 0x0bd6}, {0x0bd8, 0x0be5}, {0x0bfb, 0x0bff}, {0x0c0d, 0x0c0d}, {0x0c11, 0x0c11}, {0x0c29, 0x0c29}, {0x0c3a, 0x0c3c}, {0x0c45, 0x0c45}, {0x0c49, 0x0c49}, {0x0c4e, 0x0c54}, {0x0c57, 0x0c57}, {0x0c5b, 0x0c5f}, {0x0c64, 0x0c65}, {0x0c70, 0x0c76}, {0x0c8d, 0x0c8d}, {0x0c91, 0x0c91}, {0x0ca9, 0x0ca9}, {0x0cb4, 0x0cb4}, {0x0cba, 0x0cbb}, {0x0cc5, 0x0cc5}, {0x0cc9, 0x0cc9}, {0x0cce, 0x0cd4}, {0x0cd7, 0x0cdd}, {0x0cdf, 0x0cdf}, {0x0ce4, 0x0ce5}, {0x0cf0, 0x0cf0}, {0x0cf3, 0x0cff}, {0x0d04, 0x0d04}, {0x0d0d, 0x0d0d}, {0x0d11, 0x0d11}, {0x0d45, 0x0d45}, {0x0d49, 0x0d49}, {0x0d50, 0x0d53}, {0x0d64, 0x0d65}, {0x0d80, 0x0d81}, {0x0d84, 0x0d84}, {0x0d97, 0x0d99}, {0x0db2, 0x0db2}, {0x0dbc, 0x0dbc}, {0x0dbe, 0x0dbf}, {0x0dc7, 0x0dc9}, {0x0dcb, 0x0dce}, {0x0dd5, 0x0dd5}, {0x0dd7, 0x0dd7}, {0x0de0, 0x0de5}, {0x0df0, 0x0df1}, {0x0df5, 0x0e00}, {0x0e3b, 0x0e3e}, {0x0e5c, 0x0e80}, {0x0e83, 0x0e83}, {0x0e85, 0x0e85}, {0x0e8b, 0x0e8b}, {0x0ea4, 0x0ea4}, {0x0ea6, 0x0ea6}, {0x0ebe, 0x0ebf}, {0x0ec5, 0x0ec5}, {0x0ec7, 0x0ec7}, {0x0ece, 0x0ecf}, {0x0eda, 0x0edb}, {0x0ee0, 0x0eff}, {0x0f48, 0x0f48}, {0x0f6d, 0x0f70}, {0x0f98, 0x0f98}, {0x0fbd, 0x0fbd}, {0x0fcd, 0x0fcd}, {0x0fdb, 0x0fff}, {0x10c6, 0x10c6}, {0x10c8, 0x10cc}, {0x10ce, 0x10cf}, {0x1249, 0x1249}, {0x124e, 0x124f}, {0x1257, 0x1257}, {0x1259, 0x1259}, {0x125e, 0x125f}, {0x1289, 0x1289}, {0x128e, 0x128f}, {0x12b1, 0x12b1}, {0x12b6, 0x12b7}, {0x12bf, 0x12bf}, {0x12c1, 0x12c1}, {0x12c6, 0x12c7}, {0x12d7, 0x12d7}, {0x1311, 0x1311}, {0x1316, 0x1317}, {0x135b, 0x135c}, {0x137d, 0x137f}, {0x139a, 0x139f}, {0x13f6, 0x13f7}, {0x13fe, 0x13ff}, {0x169d, 0x169f}, {0x16f9, 0x16ff}, {0x170d, 0x170d}, {0x1715, 0x171f}, {0x1737, 0x173f}, {0x1754, 0x175f}, {0x176d, 0x176d}, {0x1771, 0x1771}, {0x1774, 0x177f}, {0x17de, 0x17df}, {0x17ea, 0x17ef}, {0x17fa, 0x17ff}, {0x180f, 0x180f}, {0x181a, 0x181f}, {0x1879, 0x187f}, {0x18ab, 0x18af}, {0x18f6, 0x18ff}, {0x191f, 0x191f}, {0x192c, 0x192f}, {0x193c, 0x193f}, {0x1941, 0x1943}, {0x196e, 0x196f}, {0x1975, 0x197f}, {0x19ac, 0x19af}, {0x19ca, 0x19cf}, {0x19db, 0x19dd}, {0x1a1c, 0x1a1d}, {0x1a5f, 0x1a5f}, {0x1a7d, 0x1a7e}, {0x1a8a, 0x1a8f}, {0x1a9a, 0x1a9f}, {0x1aae, 0x1aaf}, {0x1abf, 0x1aff}, {0x1b4c, 0x1b4f}, {0x1b7d, 0x1b7f}, {0x1bf4, 0x1bfb}, {0x1c38, 0x1c3a}, {0x1c4a, 0x1c4c}, {0x1c89, 0x1c8f}, {0x1cbb, 0x1cbc}, {0x1cc8, 0x1ccf}, {0x1cfb, 0x1cff}, {0x1dfa, 0x1dfa}, {0x1f16, 0x1f17}, {0x1f1e, 0x1f1f}, {0x1f46, 0x1f47}, {0x1f4e, 0x1f4f}, {0x1f58, 0x1f58}, {0x1f5a, 0x1f5a}, {0x1f5c, 0x1f5c}, {0x1f5e, 0x1f5e}, {0x1f7e, 0x1f7f}, {0x1fb5, 0x1fb5}, {0x1fc5, 0x1fc5}, {0x1fd4, 0x1fd5}, {0x1fdc, 0x1fdc}, {0x1ff0, 0x1ff1}, {0x1ff5, 0x1ff5}, {0x1fff, 0x1fff}, {0x2065, 0x2065}, {0x2072, 0x2073}, {0x208f, 0x208f}, {0x209d, 0x209f}, {0x20c0, 0x20cf}, {0x20f1, 0x20ff}, {0x218c, 0x218f}, {0x2427, 0x243f}, {0x244b, 0x245f}, {0x2b74, 0x2b75}, {0x2b96, 0x2b97}, {0x2c2f, 0x2c2f}, {0x2c5f, 0x2c5f}, {0x2cf4, 0x2cf8}, {0x2d26, 0x2d26}, {0x2d28, 0x2d2c}, {0x2d2e, 0x2d2f}, {0x2d68, 0x2d6e}, {0x2d71, 0x2d7e}, {0x2d97, 0x2d9f}, {0x2da7, 0x2da7}, {0x2daf, 0x2daf}, {0x2db7, 0x2db7}, {0x2dbf, 0x2dbf}, {0x2dc7, 0x2dc7}, {0x2dcf, 0x2dcf}, {0x2dd7, 0x2dd7}, {0x2ddf, 0x2ddf}, {0x2e50, 0x2e7f}, {0x2e9a, 0x2e9a}, {0x2ef4, 0x2eff}, {0x2fd6, 0x2fef}, {0x2ffc, 0x2fff}, {0x3040, 0x3040}, {0x3097, 0x3098}, {0x3100, 0x3104}, {0x3130, 0x3130}, {0x318f, 0x318f}, {0x31bb, 0x31bf}, {0x31e4, 0x31ef}, {0x321f, 0x321f}, {0x4db6, 0x4dbf}, {0x9ff0, 0x9fff}, {0xa48d, 0xa48f}, {0xa4c7, 0xa4cf}, {0xa62c, 0xa63f}, {0xa6f8, 0xa6ff}, {0xa7c0, 0xa7c1}, {0xa7c7, 0xa7f6}, {0xa82c, 0xa82f}, {0xa83a, 0xa83f}, {0xa878, 0xa87f}, {0xa8c6, 0xa8cd}, {0xa8da, 0xa8df}, {0xa954, 0xa95e}, {0xa97d, 0xa97f}, {0xa9ce, 0xa9ce}, {0xa9da, 0xa9dd}, {0xa9ff, 0xa9ff}, {0xaa37, 0xaa3f}, {0xaa4e, 0xaa4f}, {0xaa5a, 0xaa5b}, {0xaac3, 0xaada}, {0xaaf7, 0xab00}, {0xab07, 0xab08}, {0xab0f, 0xab10}, {0xab17, 0xab1f}, {0xab27, 0xab27}, {0xab2f, 0xab2f}, {0xab68, 0xab6f}, {0xabee, 0xabef}, {0xabfa, 0xabff}, {0xd7a4, 0xd7af}, {0xd7c7, 0xd7ca}, {0xd7fc, 0xf8ff}, {0xfa6e, 0xfa6f}, {0xfada, 0xfaff}, {0xfb07, 0xfb12}, {0xfb18, 0xfb1c}, {0xfb37, 0xfb37}, {0xfb3d, 0xfb3d}, {0xfb3f, 0xfb3f}, {0xfb42, 0xfb42}, {0xfb45, 0xfb45}, {0xfbc2, 0xfbd2}, {0xfd40, 0xfd4f}, {0xfd90, 0xfd91}, {0xfdc8, 0xfdef}, {0xfdfe, 0xfdff}, {0xfe1a, 0xfe1f}, {0xfe53, 0xfe53}, {0xfe67, 0xfe67}, {0xfe6c, 0xfe6f}, {0xfe75, 0xfe75}, {0xfefd, 0xfefe}, {0xff00, 0xff00}, {0xffbf, 0xffc1}, {0xffc8, 0xffc9}, {0xffd0, 0xffd1}, {0xffd8, 0xffd9}, {0xffdd, 0xffdf}, {0xffe7, 0xffe7}, {0xffef, 0xfff8}, {0xfffe, 0xffff}, {0x1000c, 0x1000c}, {0x10027, 0x10027}, {0x1003b, 0x1003b}, {0x1003e, 0x1003e}, {0x1004e, 0x1004f}, {0x1005e, 0x1007f}, {0x100fb, 0x100ff}, {0x10103, 0x10106}, {0x10134, 0x10136}, {0x1018f, 0x1018f}, {0x1019c, 0x1019f}, {0x101a1, 0x101cf}, {0x101fe, 0x1027f}, {0x1029d, 0x1029f}, {0x102d1, 0x102df}, {0x102fc, 0x102ff}, {0x10324, 0x1032c}, {0x1034b, 0x1034f}, {0x1037b, 0x1037f}, {0x1039e, 0x1039e}, {0x103c4, 0x103c7}, {0x103d6, 0x103ff}, {0x1049e, 0x1049f}, {0x104aa, 0x104af}, {0x104d4, 0x104d7}, {0x104fc, 0x104ff}, {0x10528, 0x1052f}, {0x10564, 0x1056e}, {0x10570, 0x105ff}, {0x10737, 0x1073f}, {0x10756, 0x1075f}, {0x10768, 0x107ff}, {0x10806, 0x10807}, {0x10809, 0x10809}, {0x10836, 0x10836}, {0x10839, 0x1083b}, {0x1083d, 0x1083e}, {0x10856, 0x10856}, {0x1089f, 0x108a6}, {0x108b0, 0x108df}, {0x108f3, 0x108f3}, {0x108f6, 0x108fa}, {0x1091c, 0x1091e}, {0x1093a, 0x1093e}, {0x10940, 0x1097f}, {0x109b8, 0x109bb}, {0x109d0, 0x109d1}, {0x10a04, 0x10a04}, {0x10a07, 0x10a0b}, {0x10a14, 0x10a14}, {0x10a18, 0x10a18}, {0x10a36, 0x10a37}, {0x10a3b, 0x10a3e}, {0x10a49, 0x10a4f}, {0x10a59, 0x10a5f}, {0x10aa0, 0x10abf}, {0x10ae7, 0x10aea}, {0x10af7, 0x10aff}, {0x10b36, 0x10b38}, {0x10b56, 0x10b57}, {0x10b73, 0x10b77}, {0x10b92, 0x10b98}, {0x10b9d, 0x10ba8}, {0x10bb0, 0x10bff}, {0x10c49, 0x10c7f}, {0x10cb3, 0x10cbf}, {0x10cf3, 0x10cf9}, {0x10d28, 0x10d2f}, {0x10d3a, 0x10e5f}, {0x10e7f, 0x10eff}, {0x10f28, 0x10f2f}, {0x10f5a, 0x10fdf}, {0x10ff7, 0x10fff}, {0x1104e, 0x11051}, {0x11070, 0x1107e}, {0x110c2, 0x110cc}, {0x110ce, 0x110cf}, {0x110e9, 0x110ef}, {0x110fa, 0x110ff}, {0x11135, 0x11135}, {0x11147, 0x1114f}, {0x11177, 0x1117f}, {0x111ce, 0x111cf}, {0x111e0, 0x111e0}, {0x111f5, 0x111ff}, {0x11212, 0x11212}, {0x1123f, 0x1127f}, {0x11287, 0x11287}, {0x11289, 0x11289}, {0x1128e, 0x1128e}, {0x1129e, 0x1129e}, {0x112aa, 0x112af}, {0x112eb, 0x112ef}, {0x112fa, 0x112ff}, {0x11304, 0x11304}, {0x1130d, 0x1130e}, {0x11311, 0x11312}, {0x11329, 0x11329}, {0x11331, 0x11331}, {0x11334, 0x11334}, {0x1133a, 0x1133a}, {0x11345, 0x11346}, {0x11349, 0x1134a}, {0x1134e, 0x1134f}, {0x11351, 0x11356}, {0x11358, 0x1135c}, {0x11364, 0x11365}, {0x1136d, 0x1136f}, {0x11375, 0x113ff}, {0x1145a, 0x1145a}, {0x1145c, 0x1145c}, {0x11460, 0x1147f}, {0x114c8, 0x114cf}, {0x114da, 0x1157f}, {0x115b6, 0x115b7}, {0x115de, 0x115ff}, {0x11645, 0x1164f}, {0x1165a, 0x1165f}, {0x1166d, 0x1167f}, {0x116b9, 0x116bf}, {0x116ca, 0x116ff}, {0x1171b, 0x1171c}, {0x1172c, 0x1172f}, {0x11740, 0x117ff}, {0x1183c, 0x1189f}, {0x118f3, 0x118fe}, {0x11900, 0x1199f}, {0x119a8, 0x119a9}, {0x119d8, 0x119d9}, {0x119e5, 0x119ff}, {0x11a48, 0x11a4f}, {0x11aa3, 0x11abf}, {0x11af9, 0x11bff}, {0x11c09, 0x11c09}, {0x11c37, 0x11c37}, {0x11c46, 0x11c4f}, {0x11c6d, 0x11c6f}, {0x11c90, 0x11c91}, {0x11ca8, 0x11ca8}, {0x11cb7, 0x11cff}, {0x11d07, 0x11d07}, {0x11d0a, 0x11d0a}, {0x11d37, 0x11d39}, {0x11d3b, 0x11d3b}, {0x11d3e, 0x11d3e}, {0x11d48, 0x11d4f}, {0x11d5a, 0x11d5f}, {0x11d66, 0x11d66}, {0x11d69, 0x11d69}, {0x11d8f, 0x11d8f}, {0x11d92, 0x11d92}, {0x11d99, 0x11d9f}, {0x11daa, 0x11edf}, {0x11ef9, 0x11fbf}, {0x11ff2, 0x11ffe}, {0x1239a, 0x123ff}, {0x1246f, 0x1246f}, {0x12475, 0x1247f}, {0x12544, 0x12fff}, {0x1342f, 0x1342f}, {0x13439, 0x143ff}, {0x14647, 0x167ff}, {0x16a39, 0x16a3f}, {0x16a5f, 0x16a5f}, {0x16a6a, 0x16a6d}, {0x16a70, 0x16acf}, {0x16aee, 0x16aef}, {0x16af6, 0x16aff}, {0x16b46, 0x16b4f}, {0x16b5a, 0x16b5a}, {0x16b62, 0x16b62}, {0x16b78, 0x16b7c}, {0x16b90, 0x16e3f}, {0x16e9b, 0x16eff}, {0x16f4b, 0x16f4e}, {0x16f88, 0x16f8e}, {0x16fa0, 0x16fdf}, {0x16fe4, 0x16fff}, {0x187f8, 0x187ff}, {0x18af3, 0x1afff}, {0x1b11f, 0x1b14f}, {0x1b153, 0x1b163}, {0x1b168, 0x1b16f}, {0x1b2fc, 0x1bbff}, {0x1bc6b, 0x1bc6f}, {0x1bc7d, 0x1bc7f}, {0x1bc89, 0x1bc8f}, {0x1bc9a, 0x1bc9b}, {0x1bca4, 0x1cfff}, {0x1d0f6, 0x1d0ff}, {0x1d127, 0x1d128}, {0x1d1e9, 0x1d1ff}, {0x1d246, 0x1d2df}, {0x1d2f4, 0x1d2ff}, {0x1d357, 0x1d35f}, {0x1d379, 0x1d3ff}, {0x1d455, 0x1d455}, {0x1d49d, 0x1d49d}, {0x1d4a0, 0x1d4a1}, {0x1d4a3, 0x1d4a4}, {0x1d4a7, 0x1d4a8}, {0x1d4ad, 0x1d4ad}, {0x1d4ba, 0x1d4ba}, {0x1d4bc, 0x1d4bc}, {0x1d4c4, 0x1d4c4}, {0x1d506, 0x1d506}, {0x1d50b, 0x1d50c}, {0x1d515, 0x1d515}, {0x1d51d, 0x1d51d}, {0x1d53a, 0x1d53a}, {0x1d53f, 0x1d53f}, {0x1d545, 0x1d545}, {0x1d547, 0x1d549}, {0x1d551, 0x1d551}, {0x1d6a6, 0x1d6a7}, {0x1d7cc, 0x1d7cd}, {0x1da8c, 0x1da9a}, {0x1daa0, 0x1daa0}, {0x1dab0, 0x1dfff}, {0x1e007, 0x1e007}, {0x1e019, 0x1e01a}, {0x1e022, 0x1e022}, {0x1e025, 0x1e025}, {0x1e02b, 0x1e0ff}, {0x1e12d, 0x1e12f}, {0x1e13e, 0x1e13f}, {0x1e14a, 0x1e14d}, {0x1e150, 0x1e2bf}, {0x1e2fa, 0x1e2fe}, {0x1e300, 0x1e7ff}, {0x1e8c5, 0x1e8c6}, {0x1e8d7, 0x1e8ff}, {0x1e94c, 0x1e94f}, {0x1e95a, 0x1e95d}, {0x1e960, 0x1ec70}, {0x1ecb5, 0x1ed00}, {0x1ed3e, 0x1edff}, {0x1ee04, 0x1ee04}, {0x1ee20, 0x1ee20}, {0x1ee23, 0x1ee23}, {0x1ee25, 0x1ee26}, {0x1ee28, 0x1ee28}, {0x1ee33, 0x1ee33}, {0x1ee38, 0x1ee38}, {0x1ee3a, 0x1ee3a}, {0x1ee3c, 0x1ee41}, {0x1ee43, 0x1ee46}, {0x1ee48, 0x1ee48}, {0x1ee4a, 0x1ee4a}, {0x1ee4c, 0x1ee4c}, {0x1ee50, 0x1ee50}, {0x1ee53, 0x1ee53}, {0x1ee55, 0x1ee56}, {0x1ee58, 0x1ee58}, {0x1ee5a, 0x1ee5a}, {0x1ee5c, 0x1ee5c}, {0x1ee5e, 0x1ee5e}, {0x1ee60, 0x1ee60}, {0x1ee63, 0x1ee63}, {0x1ee65, 0x1ee66}, {0x1ee6b, 0x1ee6b}, {0x1ee73, 0x1ee73}, {0x1ee78, 0x1ee78}, {0x1ee7d, 0x1ee7d}, {0x1ee7f, 0x1ee7f}, {0x1ee8a, 0x1ee8a}, {0x1ee9c, 0x1eea0}, {0x1eea4, 0x1eea4}, {0x1eeaa, 0x1eeaa}, {0x1eebc, 0x1eeef}, {0x1eef2, 0x1efff}, {0x1f02c, 0x1f02f}, {0x1f094, 0x1f09f}, {0x1f0af, 0x1f0b0}, {0x1f0c0, 0x1f0c0}, {0x1f0d0, 0x1f0d0}, {0x1f0f6, 0x1f0ff}, {0x1f10d, 0x1f10f}, {0x1f16d, 0x1f16f}, {0x1f1ad, 0x1f1e5}, {0x1f203, 0x1f20f}, {0x1f23c, 0x1f23f}, {0x1f249, 0x1f24f}, {0x1f252, 0x1f25f}, {0x1f266, 0x1f2ff}, {0x1f6d6, 0x1f6df}, {0x1f6ed, 0x1f6ef}, {0x1f6fb, 0x1f6ff}, {0x1f774, 0x1f77f}, {0x1f7d9, 0x1f7df}, {0x1f7ec, 0x1f7ff}, {0x1f80c, 0x1f80f}, {0x1f848, 0x1f84f}, {0x1f85a, 0x1f85f}, {0x1f888, 0x1f88f}, {0x1f8ae, 0x1f8ff}, {0x1f90c, 0x1f90c}, {0x1f972, 0x1f972}, {0x1f977, 0x1f979}, {0x1f9a3, 0x1f9a4}, {0x1f9ab, 0x1f9ad}, {0x1f9cb, 0x1f9cc}, {0x1fa54, 0x1fa5f}, {0x1fa6e, 0x1fa6f}, {0x1fa74, 0x1fa77}, {0x1fa7b, 0x1fa7f}, {0x1fa83, 0x1fa8f}, {0x1fa96, 0x1ffff}, {0x2a6d7, 0x2a6ff}, {0x2b735, 0x2b73f}, {0x2b81e, 0x2b81f}, {0x2cea2, 0x2ceaf}, {0x2ebe1, 0x2f7ff}, {0x2fa1e, 0xe0000}, {0xe0002, 0xe001f}, {0xe0080, 0xe00ff}, {0xe01f0, 0x10ffff}, }; dte-1.9.1/src/util/wcwidth.lua000066400000000000000000000121661354415143300162270ustar00rootroot00000000000000local progname = arg[0] local RangeTable = {} RangeTable.__index = RangeTable function RangeTable.new() return setmetatable({n = 0}, RangeTable) end function RangeTable:insert(min, max) local n = self.n + 1 self[n] = {min = assert(min), max = assert(max)} self.n = n end function RangeTable:remove(index) table.remove(self, index) self.n = self.n - 1 end function RangeTable:contains(codepoint) for i = 1, self.n do local range = self[i] if codepoint >= range.min and codepoint <= range.max then return true end end return false end function RangeTable:merge_adjacent() for i = self.n, 1, -1 do local cur, prev = self[i], self[i - 1] if prev and 1 + prev.max == cur.min then prev.max = cur.max self:remove(i) end end return self end function RangeTable:print_ranges(name, output) local n = assert(self.n) output:write("static const CodepointRange ", name, "[] = {\n") for i = 1, n do local min = assert(self[i].min) local max = assert(self[i].max) output:write((" {0x%04x, 0x%04x},\n"):format(min, max)) end output:write("};\n\n") end local function read_ucd(path, pattern) assert(pattern) if not path then io.stderr:write ( "Usage: ", progname, " UnicodeData.txt EastAsianWidth.txt\n", "(available from: https://unicode.org/Public/11.0.0/ucd/)\n" ) os.exit(1) end local file = assert(io.open(path, "r")) local text = assert(file:read("*a")) file:close() assert(text:find(pattern) == 1, "Unrecognized input format") return text end local unidata = read_ucd(arg[1], "^0000;") local eaw = read_ucd(arg[2], "^# EastAsianWidth") local dcp = read_ucd(arg[3], "^# DerivedCoreProperties") local nonspacing_mark = RangeTable.new() local special_whitespace = RangeTable.new() local unprintable = RangeTable.new() local default_ignorable = RangeTable.new() local double_width = RangeTable.new() local exclude = RangeTable.new() local mappings = { Zs = special_whitespace, -- Space_Separator (various non-zero width spaces) Zl = special_whitespace, -- Line_Separator (U+2028 only) Zp = special_whitespace, -- Paragraph_Separator (U+2029 only) Cc = unprintable, -- Control Cs = unprintable, -- Surrogate Co = unprintable, -- Private_Use Cn = unprintable, -- Unassigned Me = nonspacing_mark, -- Enclosing_Mark Mn = nonspacing_mark, -- Nonspacing_Mark -- This is a "default ignorable" character in the "Cf" (Format) -- category, but it's included in special_whitespace because it -- looks exactly like a normal space when using a monospace font: [0x00AD] = special_whitespace, -- Soft hyphen -- These are "Zs" characters, but don't need to be in special_whitespace -- because they can easily be distinguished from a normal space: [0x1680] = exclude, -- Ogham space mark (looks like an em dash) [0x3000] = exclude, -- Ideographic space (double width space) } -- ASCII for u = 0x00, 0x7F do mappings[u] = exclude end for min, max, property in dcp:gmatch "\n(%x+)%.*(%x*) *; *([%w_]+)" do if property == "Default_Ignorable_Code_Point" then min = assert(tonumber(min, 16)) max = tonumber(max, 16) if not mappings[min] then default_ignorable:insert(min, max or min) end end end local prev_codepoint = -1 local range = false for codepoint, name, category in unidata:gmatch "(%x+);([^;]*);(%u%a);[^\n]*\n" do codepoint = assert(tonumber(codepoint, 16)) assert(codepoint > prev_codepoint) assert(name) assert(category) -- Omitted codepoints default to the "Cn" (unassigned) category -- https://www.unicode.org/reports/tr44/tr44-21.html#Default_Values_Table if not range and prev_codepoint < codepoint - 1 then unprintable:insert(prev_codepoint + 1, codepoint - 1) end local t = mappings[codepoint] or mappings[category] if t == unprintable and default_ignorable:contains(codepoint) then -- Don't add codepoints to the unprintable table if they've -- already been added to the default_ignorable table elseif range then assert(name:match("^<.*, Last>$")) range = false if t then t:insert(prev_codepoint, codepoint) end elseif name:match("^<.*, First>$") then range = true elseif t then t:insert(codepoint, codepoint) end prev_codepoint = codepoint end assert(prev_codepoint == 0x10FFFD) assert(exclude.n == 127 + 3) unprintable:insert(prev_codepoint + 1, 0x10FFFF) for min, max in eaw:gmatch "\n(%x+)%.*(%x*);[WF]" do min = assert(tonumber(min, 16)) max = tonumber(max, 16) double_width:insert(min, max or min) end local stdout = io.stdout special_whitespace:merge_adjacent():print_ranges("special_whitespace", stdout) default_ignorable:merge_adjacent():print_ranges("default_ignorable", stdout) nonspacing_mark:merge_adjacent():print_ranges("nonspacing_mark", stdout) double_width:merge_adjacent():print_ranges("double_width", stdout) unprintable:merge_adjacent():print_ranges("unprintable", stdout) dte-1.9.1/src/util/xmalloc.c000066400000000000000000000042011354415143300156450ustar00rootroot00000000000000#include #include #include #include "checked-arith.h" #include "xmalloc.h" #include "ascii.h" #include "../debug.h" static void *check_alloc(void *alloc) { if (unlikely(alloc == NULL)) { fatal_error(__func__, ENOMEM); } return alloc; } size_t size_multiply_(size_t a, size_t b) { size_t result; if (unlikely(size_multiply_overflows(a, b, &result))) { fatal_error(__func__, EOVERFLOW); } return result; } size_t size_add(size_t a, size_t b) { size_t result; if (unlikely(size_add_overflows(a, b, &result))) { fatal_error(__func__, EOVERFLOW); } return result; } void *xmalloc(size_t size) { BUG_ON(size == 0); return check_alloc(malloc(size)); } void *xcalloc(size_t size) { BUG_ON(size == 0); return check_alloc(calloc(1, size)); } void *xrealloc(void *ptr, size_t size) { BUG_ON(size == 0); return check_alloc(realloc(ptr, size)); } char *xstrdup(const char *str) { return check_alloc(strdup(str)); } char *xstrndup(const char *str, size_t n) { return check_alloc(strndup(str, n)); } char *xstrdup_toupper(const char *str) { const size_t len = strlen(str); char *upper_str = xmalloc(len + 1); for (size_t i = 0; i < len; i++) { upper_str[i] = ascii_toupper(str[i]); } upper_str[len] = '\0'; return upper_str; } char *xstrcut(const char *str, size_t size) { char *s = xmalloc(size + 1); memcpy(s, str, size); s[size] = '\0'; return s; } VPRINTF(2) static int xvasprintf_(char **strp, const char *format, va_list ap) { va_list ap2; va_copy(ap2, ap); int n = vsnprintf(NULL, 0, format, ap2); if (unlikely(n < 0)) { fatal_error("vsnprintf", EILSEQ); } va_end(ap2); *strp = xmalloc(n + 1); int m = vsnprintf(*strp, n + 1, format, ap); BUG_ON(m != n); return n; } char *xvasprintf(const char *format, va_list ap) { char *str; xvasprintf_(&str, format, ap); return str; } char *xasprintf(const char *format, ...) { va_list ap; va_start(ap, format); char *str = xvasprintf(format, ap); va_end(ap); return str; } dte-1.9.1/src/util/xmalloc.h000066400000000000000000000040451354415143300156600ustar00rootroot00000000000000#ifndef UTIL_XMALLOC_H #define UTIL_XMALLOC_H #include #include #include #include #include "macros.h" #define xnew(type, n) xmalloc(size_multiply(sizeof(type), (n))) #define xnew0(type, n) xcalloc(size_multiply(sizeof(type), (n))) #define xrenew(mem, n) do { \ mem = xrealloc(mem, size_multiply(sizeof(*mem), (n))); \ } while (0) #define xmemdup_literal(l) xmemdup(l, sizeof("" l "")) void *xmalloc(size_t size) XMALLOC ALLOC_SIZE(1); void *xcalloc(size_t size) XMALLOC ALLOC_SIZE(1); void *xrealloc(void *ptr, size_t size) RETURNS_NONNULL ALLOC_SIZE(2); char *xstrdup(const char *str) XSTRDUP; char *xstrndup(const char *str, size_t n) XSTRDUP; char *xstrdup_toupper(const char *str) XSTRDUP; char *xstrcut(const char *str, size_t size) XSTRDUP; char *xvasprintf(const char *format, va_list ap) VPRINTF(1) XMALLOC; char *xasprintf(const char *format, ...) PRINTF(1) XMALLOC; size_t size_multiply_(size_t a, size_t b); size_t size_add(size_t a, size_t b); static inline size_t size_multiply(size_t a, size_t b) { // If either argument is 1, the multiplication can't overflow // and is thus safe to be inlined without checks. if (a == 1 || b == 1) { return a * b; } // Otherwise, emit a call to the checked implementation (which is // extern, because it may call fatal_error()). return size_multiply_(a, b); } NONNULL_ARGS_AND_RETURN ALLOC_SIZE(2) static inline void *xmemdup(const void *ptr, size_t size) { void *buf = xmalloc(size); memcpy(buf, ptr, size); return buf; } // Round x up to a multiple of r (which *must* be a power of 2) static inline size_t ROUND_UP(size_t x, size_t r) DIAGNOSE_IF(!IS_POWER_OF_2(r)) { r--; return (x + r) & ~r; } static inline size_t round_size_to_next_power_of_2(size_t x) { x--; for (size_t i = 1, n = sizeof(size_t) * CHAR_BIT; i < n; i <<= 1) { x |= x >> i; } return x + 1; } XSTRDUP static inline char *xstrslice(const char *str, size_t pos, size_t end) { return xstrcut(str + pos, end - pos); } #endif dte-1.9.1/src/util/xreadwrite.c000066400000000000000000000015511354415143300163710ustar00rootroot00000000000000#include #include #include "xreadwrite.h" ssize_t xread(int fd, void *buf, size_t count) { char *b = buf; size_t pos = 0; do { ssize_t rc = read(fd, b + pos, count - pos); if (rc == -1) { if (errno == EINTR) { continue; } return -1; } if (rc == 0) { // eof break; } pos += rc; } while (count - pos > 0); return pos; } ssize_t xwrite(int fd, const void *buf, size_t count) { const char *b = buf; const size_t count_save = count; do { ssize_t rc = write(fd, b, count); if (rc == -1) { if (errno == EINTR) { continue; } return -1; } b += rc; count -= rc; } while (count > 0); return count_save; } dte-1.9.1/src/util/xreadwrite.h000066400000000000000000000003521354415143300163740ustar00rootroot00000000000000#ifndef UTIL_XREADWRITE_H #define UTIL_XREADWRITE_H #include #include "macros.h" ssize_t xread(int fd, void *buf, size_t count) NONNULL_ARGS; ssize_t xwrite(int fd, const void *buf, size_t count) NONNULL_ARGS; #endif dte-1.9.1/src/util/xsnprintf.c000066400000000000000000000012461354415143300162470ustar00rootroot00000000000000#include #include #include #include "xsnprintf.h" #include "../debug.h" size_t xvsnprintf ( char *restrict buf, size_t len, const char *restrict format, va_list ap ) { if (unlikely(len > INT_MAX)) { fatal_error(__func__, EOVERFLOW); } const int n = vsnprintf(buf, len, format, ap); if (unlikely(n < 0 || n >= (int)len)) { fatal_error(__func__, ERANGE); } return (size_t)n; } size_t xsnprintf(char *restrict buf, size_t len, const char *restrict format, ...) { va_list ap; va_start(ap, format); const size_t n = xvsnprintf(buf, len, format, ap); va_end(ap); return n; } dte-1.9.1/src/util/xsnprintf.h000066400000000000000000000005221354415143300162500ustar00rootroot00000000000000#ifndef UTIL_XSNPRINTF_H #define UTIL_XSNPRINTF_H #include #include #include "macros.h" PRINTF(3) NONNULL_ARGS size_t xsnprintf(char *restrict buf, size_t len, const char *restrict fmt, ...); VPRINTF(3) NONNULL_ARGS size_t xvsnprintf(char *restrict buf, size_t len, const char *restrict fmt, va_list ap); #endif dte-1.9.1/src/view.c000066400000000000000000000073771354415143300142240ustar00rootroot00000000000000#include "view.h" #include "buffer.h" #include "debug.h" #include "util/ascii.h" #include "util/str-util.h" #include "util/utf8.h" #include "util/xmalloc.h" #include "window.h" View *view; void view_update_cursor_y(View *v) { Buffer *b = v->buffer; Block *blk; size_t nl = 0; block_for_each(blk, &b->blocks) { if (blk == v->cursor.blk) { nl += count_nl(blk->data, v->cursor.offset); v->cy = nl; return; } nl += blk->nl; } BUG_ON(1); } void view_update_cursor_x(View *v) { unsigned int tw = v->buffer->options.tab_width; size_t idx = 0; LineRef lr; long c = 0; long w = 0; v->cx = fetch_this_line(&v->cursor, &lr); while (idx < v->cx) { CodePoint u = lr.line[idx++]; c++; if (u < 0x80) { if (!ascii_iscntrl(u)) { w++; } else if (u == '\t') { w = (w + tw) / tw * tw; } else { w += 2; } } else { idx--; u = u_get_nonascii(lr.line, lr.size, &idx); w += u_char_width(u); } } v->cx_char = c; v->cx_display = w; } static bool view_is_cursor_visible(const View *v) { return v->cy < v->vy || v->cy > v->vy + v->window->edit_h - 1; } static void view_center_to_cursor(View *v) { size_t lines = v->buffer->nl; Window *w = v->window; unsigned int hh = w->edit_h / 2; if (w->edit_h >= lines || v->cy < hh) { v->vy = 0; return; } v->vy = v->cy - hh; if (v->vy + w->edit_h > lines) { // -1 makes one ~ line visible so that you know where the EOF is v->vy -= v->vy + w->edit_h - lines - 1; } } static void view_update_vx(View *v) { Window *w = v->window; unsigned int c = 8; if (v->cx_display - v->vx >= w->edit_w) { v->vx = (v->cx_display - w->edit_w + c) / c * c; } if (v->cx_display < v->vx) { v->vx = v->cx_display / c * c; } } static void view_update_vy(View *v) { Window *w = v->window; int margin = window_get_scroll_margin(w); long max_y = v->vy + w->edit_h - 1 - margin; if (v->cy < v->vy + margin) { v->vy = v->cy - margin; if (v->vy < 0) { v->vy = 0; } } else if (v->cy > max_y) { v->vy += v->cy - max_y; max_y = v->buffer->nl - w->edit_h + 1; if (v->vy > max_y && max_y >= 0) { v->vy = max_y; } } } void view_update(View *v) { view_update_vx(v); if (v->force_center || (v->center_on_scroll && view_is_cursor_visible(v))) { view_center_to_cursor(v); } else { view_update_vy(v); } v->force_center = false; v->center_on_scroll = false; } long view_get_preferred_x(View *v) { if (v->preferred_x < 0) { view_update_cursor_x(v); v->preferred_x = v->cx_display; } return v->preferred_x; } bool view_can_close(const View *v) { if (!buffer_modified(v->buffer)) { return true; } // Open in another window? return v->buffer->views.count > 1; } char *view_get_word_under_cursor(const View *v) { LineRef lr; size_t i, ei, si = fetch_this_line(&v->cursor, &lr); while (si < lr.size) { i = si; if (u_is_word_char(u_get_char(lr.line, lr.size, &i))) { break; } si = i; } if (si == lr.size) { return NULL; } ei = si; while (si > 0) { i = si; if (!u_is_word_char(u_prev_char(lr.line, &i))) { break; } si = i; } while (ei < lr.size) { i = ei; if (!u_is_word_char(u_get_char(lr.line, lr.size, &i))) { break; } ei = i; } return xstrslice(lr.line, si, ei); } dte-1.9.1/src/view.h000066400000000000000000000032361354415143300142170ustar00rootroot00000000000000#ifndef VIEW_H #define VIEW_H #include #include #include "block-iter.h" typedef enum { SELECT_NONE, SELECT_CHARS, SELECT_LINES, } SelectionType; typedef struct View { struct Buffer *buffer; struct Window *window; BlockIter cursor; // Cursor position long cx, cy; // Visual cursor x (char widths: wide 2, tab 1-8, control 2, invalid char 4) long cx_display; // Cursor x in characters (invalid UTF-8 character (byte) is one char) long cx_char; // Top left corner long vx, vy; // Preferred cursor x (preferred value for cx_display) long preferred_x; // Tab title int tt_width; int tt_truncated_width; SelectionType selection; bool next_movement_cancels_selection; // Cursor offset when selection was started ssize_t sel_so; // If sel_eo is UINT_MAX that means the offset must be calculated from // the cursor iterator. Otherwise the offset is precalculated and may // not be same as cursor position (see search/replace code). ssize_t sel_eo; // Center view to cursor if scrolled bool center_on_scroll; // Force centering view to cursor bool force_center; // These are used to save cursor state when there are multiple views // sharing same buffer. bool restore_cursor; size_t saved_cursor_offset; } View; static inline void view_reset_preferred_x(View *v) { v->preferred_x = -1; } void view_update_cursor_y(View *v); void view_update_cursor_x(View *v); void view_update(View *v); long view_get_preferred_x(View *v); bool view_can_close(const View *v); char *view_get_word_under_cursor(const View *v); #endif dte-1.9.1/src/window.c000066400000000000000000000315231354415143300145470ustar00rootroot00000000000000#include #include #include "window.h" #include "editor.h" #include "error.h" #include "file-history.h" #include "load-save.h" #include "lock.h" #include "move.h" #include "util/path.h" #include "util/str-util.h" #include "util/strtonum.h" #include "util/xmalloc.h" Window *window; Window *new_window(void) { return xnew0(Window, 1); } View *window_add_buffer(Window *w, Buffer *b) { View *v = xnew0(View, 1); v->buffer = b; v->window = w; v->cursor.head = &b->blocks; v->cursor.blk = BLOCK(b->blocks.next); ptr_array_add(&b->views, v); ptr_array_add(&w->views, v); w->update_tabbar = true; return v; } View *window_open_empty_buffer(Window *w) { return window_add_buffer(w, open_empty_buffer()); } View *window_open_buffer ( Window *w, const char *filename, bool must_exist, const Encoding *encoding ) { char *absolute; bool dir_missing = false; Buffer *b = NULL; if (filename[0] == '\0') { error_msg("Empty filename not allowed"); return NULL; } absolute = path_absolute(filename); if (absolute == NULL) { // Let load_buffer() create error message dir_missing = errno == ENOENT; } else { // Already open? b = find_buffer(absolute); } if (b) { if (!streq(absolute, b->abs_filename)) { char *s = short_filename(absolute); info_msg("%s and %s are the same file", s, b->display_filename); free(s); } free(absolute); return window_get_view(w, b); } /* /proc/$PID/fd/ contains symbolic links to files that have been opened by process $PID. Some of the files may have been deleted but can still be opened using the symbolic link but not by using the absolute path. # create file mkdir /tmp/x echo foo > /tmp/x/file # in another shell: keep the file open tail -f /tmp/x/file # make the absolute path unavailable rm /tmp/x/file rmdir /tmp/x # this should still succeed dte /proc/$(pidof tail)/fd/3 */ b = buffer_new(encoding); if (load_buffer(b, must_exist, filename)) { free_buffer(b); free(absolute); return NULL; } if (b->st.st_mode == 0 && dir_missing) { // New file in non-existing directory. This is usually a mistake. error_msg("Error opening %s: Directory does not exist", filename); free_buffer(b); free(absolute); return NULL; } b->abs_filename = absolute; if (b->abs_filename == NULL) { // FIXME: obviously wrong b->abs_filename = xstrdup(filename); } update_short_filename(b); if (editor.options.lock_files) { if (lock_file(b->abs_filename)) { b->readonly = true; } else { b->locked = true; } } if (b->st.st_mode != 0 && !b->readonly && access(filename, W_OK)) { error_msg("No write permission to %s, marking read-only.", filename); b->readonly = true; } return window_add_buffer(w, b); } View *window_get_view(Window *w, Buffer *b) { View *v = window_find_view(w, b); if (v == NULL) { // Open the buffer in other window to this window v = window_add_buffer(w, b); v->cursor = ((View *)b->views.ptrs[0])->cursor; } return v; } View *window_find_view(Window *w, Buffer *b) { for (size_t i = 0, n = b->views.count; i < n; i++) { View *v = b->views.ptrs[i]; if (v->window == w) { return v; } } // Buffer isn't open in this window return NULL; } View *window_find_unclosable_view(Window *w, bool (*can_close)(const View *)) { // Check active view first if (w->view != NULL && !can_close(w->view)) { return w->view; } for (size_t i = 0, n = w->views.count; i < n; i++) { View *v = w->views.ptrs[i]; if (!can_close(v)) { return v; } } return NULL; } static void window_remove_views(Window *w) { while (w->views.count > 0) { View *v = w->views.ptrs[w->views.count - 1]; remove_view(v); } } // NOTE: w->frame isn't removed void window_free(Window *w) { window_remove_views(w); free(w->views.ptrs); w->frame = NULL; free(w); } // Remove view from v->window and v->buffer->views and free it. void remove_view(View *v) { Window *w = v->window; Buffer *b = v->buffer; if (v == w->prev_view) { w->prev_view = NULL; } // FIXME: globals if (v == view) { view = NULL; buffer = NULL; } ptr_array_remove(&w->views, v); w->update_tabbar = true; ptr_array_remove(&b->views, v); if (b->views.count == 0) { if (b->options.file_history && b->abs_filename) { add_file_history(v->cy + 1, v->cx_char + 1, b->abs_filename); } free_buffer(b); } free(v); } void window_close_current(void) { Window *next; if (window->frame->parent == NULL) { // Don't close last window window_remove_views(window); set_view(window_open_empty_buffer(window)); return; } next = next_window(window); remove_frame(window->frame); window = NULL; set_view(next->view); mark_everything_changed(); debug_frames(); } void window_close_current_view(Window *w) { size_t idx = ptr_array_idx(&w->views, w->view); remove_view(w->view); if (w->prev_view != NULL) { w->view = w->prev_view; w->prev_view = NULL; return; } if (w->views.count == 0) { window_open_empty_buffer(w); } if (w->views.count == idx) { idx--; } w->view = w->views.ptrs[idx]; } static void restore_cursor_from_history(View *v) { unsigned long row, col; if (find_file_in_history(v->buffer->abs_filename, &row, &col)) { move_to_line(v, row); move_to_column(v, col); } } void set_view(View *v) { if (view == v) { return; } // Forget previous view when changing view using any other command but open if (window != NULL) { window->prev_view = NULL; } view = v; buffer = v->buffer; window = v->window; window->view = v; if (!v->buffer->setup) { buffer_setup(v->buffer); if ( v->buffer->options.file_history && v->buffer->abs_filename != NULL ) { restore_cursor_from_history(v); } } // view.cursor can be invalid if same buffer was modified from another view if (v->restore_cursor) { v->cursor.blk = BLOCK(v->buffer->blocks.next); block_iter_goto_offset(&v->cursor, v->saved_cursor_offset); v->restore_cursor = false; v->saved_cursor_offset = 0; } // Save cursor states of views sharing same buffer for (size_t i = 0, n = v->buffer->views.count; i < n; i++) { View *other = v->buffer->views.ptrs[i]; if (other != v) { other->saved_cursor_offset = block_iter_get_offset(&other->cursor); other->restore_cursor = true; } } } View *window_open_new_file(Window *w) { View *prev = w->view; View *v = window_open_empty_buffer(w); // FIXME: should not call set_view() set_view(v); w->prev_view = prev; return v; } /* If window contains only one buffer and it is untouched then it will be closed after opening another file. This is done because closing last buffer causes an empty buffer to be opened (window must contain at least one buffer). */ static bool is_useless_empty_view(View *v) { if (v == NULL) { return false; } if (v->window->views.count != 1) { return false; } // Touched? if ( v->buffer->abs_filename != NULL || v->buffer->change_head.nr_prev != 0 ) { return false; } return true; } View *window_open_file(Window *w, const char *filename, const Encoding *encoding) { View *prev = w->view; bool useless = is_useless_empty_view(prev); View *v = window_open_buffer(w, filename, false, encoding); if (v == NULL) { return NULL; } // FIXME: should not call set_view() set_view(v); if (useless) { remove_view(prev); } else { w->prev_view = prev; } return v; } void window_open_files(Window *w, char **filenames, const Encoding *encoding) { View *empty = w->view; bool useless = is_useless_empty_view(empty); bool first = true; for (size_t i = 0; filenames[i]; i++) { View *v = window_open_buffer(w, filenames[i], false, encoding); if (v && first) { // FIXME: should not call set_view() set_view(v); first = false; } } if (useless && w->view != empty) { remove_view(empty); } } void mark_buffer_tabbars_changed(Buffer *b) { for (size_t i = 0, n = b->views.count; i < n; i++) { View *v = b->views.ptrs[i]; v->window->update_tabbar = true; } } static int calc_vertical_tabbar_width(const Window *win) { // Line numbers are included in min_edit_w int min_edit_w = 80; int w = editor.options.tab_bar_width; if (win->w - w < min_edit_w) { w = win->w - min_edit_w; } if (w < TAB_BAR_MIN_WIDTH) { w = 0; } return w; } TabBarMode tabbar_visibility(const Window *win) { switch (editor.options.tab_bar) { case TAB_BAR_HIDDEN: case TAB_BAR_HORIZONTAL: return editor.options.tab_bar; case TAB_BAR_VERTICAL: if (calc_vertical_tabbar_width(win) == 0) { // Not enough space return TAB_BAR_HIDDEN; } return TAB_BAR_VERTICAL; case TAB_BAR_AUTO: if (calc_vertical_tabbar_width(win) == 0) { // Not enough space return TAB_BAR_HORIZONTAL; } return TAB_BAR_VERTICAL; } return 0; } int vertical_tabbar_width(const Window *win) { if (tabbar_visibility(win) == TAB_BAR_VERTICAL) { return calc_vertical_tabbar_width(win); } return 0; } static int line_numbers_width(const Window *win) { int w = 0; if (editor.options.show_line_numbers && win->view) { const int min_w = 5; w = number_width(win->view->buffer->nl) + 1; if (w < min_w) { w = min_w; } } return w; } static int edit_x_offset(const Window *win) { return line_numbers_width(win) + vertical_tabbar_width(win); } static int edit_y_offset(const Window *win) { if (tabbar_visibility(win) == TAB_BAR_HORIZONTAL) { return 1; } return 0; } static void set_edit_size(Window *win) { int xo = edit_x_offset(win); int yo = edit_y_offset(win); win->edit_w = win->w - xo; win->edit_h = win->h - yo - 1; // statusline win->edit_x = win->x + xo; } void calculate_line_numbers(Window *win) { int w = line_numbers_width(win); if (w != win->line_numbers.width) { win->line_numbers.width = w; win->line_numbers.first = 0; win->line_numbers.last = 0; mark_all_lines_changed(win->view->buffer); } set_edit_size(win); } void set_window_coordinates(Window *win, int x, int y) { win->x = x; win->y = y; win->edit_x = x + edit_x_offset(win); win->edit_y = y + edit_y_offset(win); } void set_window_size(Window *win, int w, int h) { win->w = w; win->h = h; calculate_line_numbers(win); } int window_get_scroll_margin(const Window *w) { int max = (w->edit_h - 1) / 2; if (editor.options.scroll_margin > max) { return max; } return editor.options.scroll_margin; } static void frame_for_each_window ( Frame *f, void (*func)(Window *, void *), void *data ) { if (f->window != NULL) { func(f->window, data); return; } for (size_t i = 0; i < f->frames.count; i++) { frame_for_each_window(f->frames.ptrs[i], func, data); } } static void for_each_window_data ( void (*func)(Window *, void *), void *data ) { frame_for_each_window(root_frame, func, data); } // Conversion from a void* pointer to a function pointer is not defined // by the ISO C standard, but POSIX explicitly requires it: // https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html IGNORE_WARNING("-Wpedantic") static void call_data(Window *w, void *data) { void (*func)(Window *) = data; func(w); } void for_each_window(void (*func)(Window *w)) { for_each_window_data(call_data, func); } UNIGNORE_WARNINGS static void collect_window(Window *w, void *data) { ptr_array_add(data, w); } Window *prev_window(Window *w) { PointerArray windows = PTR_ARRAY_INIT; for_each_window_data(collect_window, &windows); w = ptr_array_prev(&windows, w); free(windows.ptrs); return w; } Window *next_window(Window *w) { PointerArray windows = PTR_ARRAY_INIT; for_each_window_data(collect_window, &windows); w = ptr_array_next(&windows, w); free(windows.ptrs); return w; } dte-1.9.1/src/window.h000066400000000000000000000034401354415143300145510ustar00rootroot00000000000000#ifndef WINDOW_H #define WINDOW_H #include "buffer.h" #include "frame.h" #include "view.h" typedef struct Window { PointerArray views; Frame *frame; // Current view View *view; // Previous view if set View *prev_view; // Coordinates and size of entire window including tabbar and status line int x, y; int w, h; // Coordinates and size of editable area int edit_x, edit_y; int edit_w, edit_h; struct { int width; long first; long last; } line_numbers; size_t first_tab_idx; bool update_tabbar; } Window; extern Window *window; Window *new_window(void); View *window_add_buffer(Window *w, Buffer *b); View *window_open_empty_buffer(Window *w); View *window_open_buffer(Window *w, const char *filename, bool must_exist, const Encoding *encoding); View *window_get_view(Window *w, Buffer *b); View *window_find_view(Window *w, Buffer *b); View *window_find_unclosable_view(Window *w, bool (*can_close)(const View *)); void window_free(Window *w); void remove_view(View *v); void window_close_current(void); void window_close_current_view(Window *w); void set_view(View *v); View *window_open_new_file(Window *w); View *window_open_file(Window *w, const char *filename, const Encoding *encoding); void window_open_files(Window *w, char **filenames, const Encoding *encoding); void mark_buffer_tabbars_changed(Buffer *b); TabBarMode tabbar_visibility(const Window *win); int vertical_tabbar_width(const Window *win); void calculate_line_numbers(Window *win); void set_window_coordinates(Window *win, int x, int y); void set_window_size(Window *win, int w, int h); int window_get_scroll_margin(const Window *w); void for_each_window(void (*func)(Window *w)); Window *prev_window(Window *w); Window *next_window(Window *w); #endif dte-1.9.1/test/000077500000000000000000000000001354415143300132605ustar00rootroot00000000000000dte-1.9.1/test/cmdline.c000066400000000000000000000113011354415143300150330ustar00rootroot00000000000000#include #include "test.h" #include "../src/cmdline.h" #include "../src/completion.h" #include "../src/editor.h" #define EXPECT_STRING_EQ(s, cstr) \ EXPECT_STREQ(string_borrow_cstring(&(s)), (cstr)) static void test_cmdline_handle_key(void) { CommandLine *c = &editor.cmdline; PointerArray *h = &editor.command_history; int ret = cmdline_handle_key(c, h, 'a'); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 1); EXPECT_STRING_EQ(c->buf, "a"); ret = cmdline_handle_key(c, h, 0x1F999); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 5); EXPECT_STRING_EQ(c->buf, "a\xF0\x9F\xA6\x99"); // Delete at end-of-line should do nothing ret = cmdline_handle_key(c, h, KEY_DELETE); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 5); EXPECT_EQ(c->buf.len, 5); ret = cmdline_handle_key(c, h, CTRL('H')); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 1); EXPECT_STRING_EQ(c->buf, "a"); ret = cmdline_handle_key(c, h, CTRL('?')); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); EXPECT_STRING_EQ(c->buf, ""); cmdline_set_text(c, "word1 word2 word3 word4"); ret = cmdline_handle_key(c, h, KEY_END); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 23); ret = cmdline_handle_key(c, h, MOD_META | MOD_CTRL | 'H'); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 18); EXPECT_STRING_EQ(c->buf, "word1 word2 word3 "); ret = cmdline_handle_key(c, h, MOD_META | MOD_CTRL | '?'); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 12); EXPECT_STRING_EQ(c->buf, "word1 word2 "); ret = cmdline_handle_key(c, h, CTRL(KEY_LEFT)); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 6); ret = cmdline_handle_key(c, h, CTRL(KEY_RIGHT)); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 12); ret = cmdline_handle_key(c, h, KEY_HOME); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); ret = cmdline_handle_key(c, h, MOD_META | KEY_DELETE); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); EXPECT_STRING_EQ(c->buf, "word2 "); ret = cmdline_handle_key(c, h, KEY_RIGHT); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 1); ret = cmdline_handle_key(c, h, CTRL('U')); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); EXPECT_STRING_EQ(c->buf, "ord2 "); ret = cmdline_handle_key(c, h, KEY_DELETE); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); EXPECT_STRING_EQ(c->buf, "rd2 "); ret = cmdline_handle_key(c, h, CTRL('K')); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); EXPECT_EQ(c->buf.len, 0); // Left arrow at beginning-of-line should do nothing ret = cmdline_handle_key(c, h, KEY_LEFT); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); cmdline_set_text(c, "..."); EXPECT_EQ(c->pos, 3); EXPECT_EQ(c->buf.len, 3); ret = cmdline_handle_key(c, h, CTRL('A')); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); EXPECT_EQ(c->buf.len, 3); ret = cmdline_handle_key(c, h, KEY_RIGHT); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 1); ret = cmdline_handle_key(c, h, CTRL(KEY_LEFT)); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 0); ret = cmdline_handle_key(c, h, CTRL('E')); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); EXPECT_EQ(c->pos, 3); ret = cmdline_handle_key(c, h, CTRL('G')); EXPECT_EQ(ret, CMDLINE_CANCEL); EXPECT_EQ(c->pos, 0); EXPECT_EQ(c->buf.len, 0); } #define ENV_VAR_PREFIX "D__p_tYmz3_" #define ENV_VAR_NAME ENV_VAR_PREFIX "_VAR" static void test_complete_command(void) { complete_command(); EXPECT_STRING_EQ(editor.cmdline.buf, "alias"); reset_completion(); cmdline_set_text(&editor.cmdline, "wrap"); complete_command(); EXPECT_STRING_EQ(editor.cmdline.buf, "wrap-paragraph "); reset_completion(); cmdline_set_text(&editor.cmdline, "open test/data/.e"); complete_command(); EXPECT_STRING_EQ(editor.cmdline.buf, "open test/data/.editorconfig "); reset_completion(); cmdline_set_text(&editor.cmdline, "toggle "); complete_command(); EXPECT_STRING_EQ(editor.cmdline.buf, "toggle auto-indent"); reset_completion(); ASSERT_EQ(setenv(ENV_VAR_NAME, "xyz", true), 0); cmdline_set_text(&editor.cmdline, "insert $" ENV_VAR_PREFIX); complete_command(); EXPECT_STRING_EQ(editor.cmdline.buf, "insert $" ENV_VAR_NAME); reset_completion(); ASSERT_EQ(unsetenv(ENV_VAR_NAME), 0); } DISABLE_WARNING("-Wmissing-prototypes") void test_cmdline(void) { test_cmdline_handle_key(); test_complete_command(); } dte-1.9.1/test/command.c000066400000000000000000000105101354415143300150370ustar00rootroot00000000000000#include "test.h" #include "../src/command.h" #include "../src/debug.h" #include "../src/parse-args.h" static void test_parse_command_arg(void) { #define PARSE_ARG_LITERAL(s) parse_command_arg(s, STRLEN(s), false) char *arg = PARSE_ARG_LITERAL(""); EXPECT_STREQ(arg, ""); free(arg); arg = PARSE_ARG_LITERAL("\"\\u148A\"xyz'foo'\"\\x5A\"\\;\tbar"); EXPECT_STREQ(arg, "\xE1\x92\x8AxyzfooZ;"); free(arg); #undef PARSE_ARG_LITERAL } static void test_parse_commands(void) { PointerArray array = PTR_ARRAY_INIT; CommandParseError err = 0; EXPECT_TRUE(parse_commands(&array, " left -c;;", &err)); EXPECT_EQ(array.count, 5); EXPECT_STREQ(array.ptrs[0], "left"); EXPECT_STREQ(array.ptrs[1], "-c"); EXPECT_NULL(array.ptrs[2]); EXPECT_NULL(array.ptrs[3]); EXPECT_NULL(array.ptrs[4]); EXPECT_EQ(err, 0); ptr_array_free(&array); EXPECT_TRUE(parse_commands(&array, "save -e UTF-8 file.c; close -q", &err)); EXPECT_EQ(array.count, 8); EXPECT_STREQ(array.ptrs[0], "save"); EXPECT_STREQ(array.ptrs[1], "-e"); EXPECT_STREQ(array.ptrs[2], "UTF-8"); EXPECT_STREQ(array.ptrs[3], "file.c"); EXPECT_NULL(array.ptrs[4]); EXPECT_STREQ(array.ptrs[5], "close"); EXPECT_STREQ(array.ptrs[6], "-q"); EXPECT_NULL(array.ptrs[7]); EXPECT_EQ(err, 0); ptr_array_free(&array); EXPECT_TRUE(parse_commands(&array, "\n ; ; \t\n ", &err)); EXPECT_EQ(array.count, 3); EXPECT_NULL(array.ptrs[0]); EXPECT_NULL(array.ptrs[1]); EXPECT_NULL(array.ptrs[2]); EXPECT_EQ(err, 0); ptr_array_free(&array); EXPECT_TRUE(parse_commands(&array, "", &err)); EXPECT_EQ(array.count, 1); EXPECT_NULL(array.ptrs[0]); EXPECT_EQ(err, 0); ptr_array_free(&array); EXPECT_FALSE(parse_commands(&array, "insert '... ", &err)); EXPECT_EQ(err, CMDERR_UNCLOSED_SINGLE_QUOTE); ptr_array_free(&array); err = 0; EXPECT_FALSE(parse_commands(&array, "insert \" ", &err)); EXPECT_EQ(err, CMDERR_UNCLOSED_DOUBLE_QUOTE); ptr_array_free(&array); err = 0; EXPECT_FALSE(parse_commands(&array, "insert \"\\\" ", &err)); EXPECT_EQ(err, CMDERR_UNCLOSED_DOUBLE_QUOTE); ptr_array_free(&array); err = 0; EXPECT_FALSE(parse_commands(&array, "insert \\", &err)); EXPECT_EQ(err, CMDERR_UNEXPECTED_EOF); ptr_array_free(&array); } static void test_parse_args(void) { const char *cmd_str = "open -g file.c file.h *.mk -e UTF-8"; PointerArray array = PTR_ARRAY_INIT; CommandParseError err = 0; ASSERT_TRUE(parse_commands(&array, cmd_str, &err)); ASSERT_EQ(array.count, 8); const Command *cmd = find_command(commands, array.ptrs[0]); ASSERT_NONNULL(cmd); EXPECT_STREQ(cmd->name, "open"); CommandArgs a = {.args = (char**)array.ptrs + 1}; ASSERT_TRUE(parse_args(cmd, &a)); EXPECT_EQ(a.nr_flags, 2); EXPECT_EQ(a.flags[0], 'g'); EXPECT_EQ(a.flags[1], 'e'); EXPECT_EQ(a.flags[2], '\0'); EXPECT_EQ(a.nr_args, 3); EXPECT_STREQ(a.args[0], "UTF-8"); EXPECT_STREQ(a.args[1], "file.c"); EXPECT_STREQ(a.args[2], "file.h"); EXPECT_STREQ(a.args[3], "*.mk"); EXPECT_NULL(a.args[4]); ptr_array_free(&array); } static void test_commands_array(void) { static const size_t name_size = ARRAY_COUNT(commands[0].name); static const size_t flags_size = ARRAY_COUNT(commands[0].flags); size_t n = 0; while (commands[n].cmd) { n++; BUG_ON(n > 500); } for (size_t i = 1; i < n; i++) { const Command *const cmd = &commands[i]; // Check that fixed-size arrays are null-terminated within bounds ASSERT_EQ(cmd->name[name_size - 1], '\0'); ASSERT_EQ(cmd->flags[flags_size - 1], '\0'); // Check that array is sorted by name field, in binary searchable order IEXPECT_TRUE(strcmp(cmd->name, commands[i - 1].name) > 0); } } static void test_command_struct_layout(void) { const Command *cmd = find_command(commands, "filter"); EXPECT_STREQ(cmd->name, "filter"); EXPECT_STREQ(cmd->flags, "-"); EXPECT_EQ(cmd->min_args, 1); EXPECT_EQ(cmd->max_args, CMD_ARG_MAX); EXPECT_NONNULL(cmd->cmd); } DISABLE_WARNING("-Wmissing-prototypes") void test_command(void) { test_parse_command_arg(); test_parse_commands(); test_parse_args(); test_commands_array(); test_command_struct_layout(); } dte-1.9.1/test/config.c000066400000000000000000000050161354415143300146730ustar00rootroot00000000000000#include #include #include #include "test.h" #include "../src/config.h" #include "../src/debug.h" #include "../src/editor.h" #include "../src/frame.h" #include "../src/encoding/convert.h" #include "../src/terminal/no-op.h" #include "../src/terminal/terminal.h" #include "../src/util/readfile.h" #include "../src/util/str-util.h" #include "../src/util/string-view.h" #include "../src/window.h" #include "../build/test/data.h" DISABLE_WARNING("-Wmissing-prototypes") static const char extra_rc[] = "set lock-files false\n" // Regression test for unquoted variables in rc files "bind M-p \"insert \"$WORD\n" "bind M-p \"insert \"$FILE\n" ; void init_headless_mode(void) { MEMZERO(&terminal.control_codes); terminal.cooked = &no_op; terminal.raw = &no_op; editor.resize = &no_op; editor.ui_end = &no_op; exec_reset_colors_rc(); read_config(commands, "rc", CFG_MUST_EXIST | CFG_BUILTIN); fill_builtin_colors(); window = new_window(); root_frame = new_root_frame(window); exec_config(commands, extra_rc, sizeof(extra_rc) - 1); set_view(window_open_empty_buffer(window)); } static void expect_files_equal(const char *path1, const char *path2) { char *buf1; ssize_t size1 = read_file(path1, &buf1); if (size1 < 0) { TEST_FAIL("Error reading '%s': %s", path1, strerror(errno)); return; } char *buf2; ssize_t size2 = read_file(path2, &buf2); if (size2 < 0) { free(buf1); TEST_FAIL("Error reading '%s': %s", path2, strerror(errno)); return; } if (size1 != size2 || memcmp(buf1, buf2, size1) != 0) { TEST_FAIL("Files differ: '%s', '%s'", path1, path2); } free(buf1); free(buf2); } void test_exec_config(void) { ASSERT_NONNULL(window); FOR_EACH_I(i, builtin_configs) { const BuiltinConfig config = builtin_configs[i]; exec_config(commands, config.text.data, config.text.length); } expect_files_equal("build/test/env.txt", "test/data/env.txt"); expect_files_equal("build/test/crlf.txt", "test/data/crlf.txt"); expect_files_equal("build/test/thai-utf8.txt", "test/data/thai-utf8.txt"); EXPECT_EQ(unlink("build/test/env.txt"), 0); EXPECT_EQ(unlink("build/test/crlf.txt"), 0); EXPECT_EQ(unlink("build/test/thai-utf8.txt"), 0); if (encoding_supported_by_iconv("TIS-620")) { expect_files_equal("build/test/thai-tis620.txt", "test/data/thai-tis620.txt"); EXPECT_EQ(unlink("build/test/thai-tis620.txt"), 0); } } dte-1.9.1/test/data/000077500000000000000000000000001354415143300141715ustar00rootroot00000000000000dte-1.9.1/test/data/.editorconfig000066400000000000000000000005501354415143300166460ustar00rootroot00000000000000# Note: this file serves purely as input data for testing the # editorconfig implementation. Any real editorconfig properties # should be added to the file in the base directory. root = true [*.?{foo,bar}.[xyz]] # inline comment indent_style = space # another inline comment indent_size = 3 max_line_length = 68 = property with no name dte-1.9.1/test/data/3lines.txt000066400000000000000000000000321354415143300161220ustar00rootroot00000000000000line #1 line #2 line #3dte-1.9.1/test/data/crlf.dterc000066400000000000000000000001251354415143300161400ustar00rootroot00000000000000open -e UTF-8 insert "line 1\nline 2\nline 3\n" save -f -d build/test/crlf.txt close dte-1.9.1/test/data/crlf.txt000066400000000000000000000000301354415143300156510ustar00rootroot00000000000000line 1 line 2 line 3 dte-1.9.1/test/data/detect-indent.ini000066400000000000000000000002031354415143300174140ustar00rootroot00000000000000# This is a .gitconfig-like file for testing detect_indent() [color "status"] added = green changed = red untracked = yellow dte-1.9.1/test/data/env.dterc000066400000000000000000000003771354415143300160130ustar00rootroot00000000000000open setenv z "__TEST__\n" setenv _Z "...\n" setenv test_123 "123\n" setenv 1A "1A\n" insert -m $z insert -m $_Z insert -m $test_123 insert -m $non__existent__var___/#"\n" insert -m $1"\n" insert -m $."_Z\n" insert -m $1A save -f build/test/env.txt close dte-1.9.1/test/data/env.txt000066400000000000000000000000351354415143300155200ustar00rootroot00000000000000__TEST__ ... 123 /# 1 ._Z 1A dte-1.9.1/test/data/fuzz1.dterc000066400000000000000000000021001354415143300162640ustar00rootroot00000000000000run mkdir -p build/test/ cd build/test/ insert -km "1\n2\n3\n" bof bol bolsf case clear copy paste -c cut delete delete-eol delete-word down eof eol eolsf erase erase-bol erase-word filter sort -r ft ext filetype hi default red black bold include -b binding/shift-select insert-builtin syntax/dte bof select eof #wrap-paragraph 72 join left line 1000 load-syntax c load-syntax python load-syntax css load-syntax html msg new-line next paste pgdown pgup prev refresh insert "hello world\n" repeat 2 replace l r right run true scroll-down scroll-pgdown scroll-pgup scroll-up search -H '^ *b' search -n search -p select repeat 5 right cut paste -c set set-window-title true setenv SETENVTEST 1111 insert $SETENVTEST shift +2 clear #tag toggle set-window-title up view 1 wflip wnext word-bwd word-fwd wprev wresize wsplit wswap save -f out pipe-from echo pipe-from-test select pgdown unselect repeat 1000 undo redo undo compile -1s gitgrep sh -c 'grep -n ^# ../../mk/*' close wclose repeat 20 open repeat 6 move-tab left repeat 3 move-tab right move-tab 17 open -g ../../docs/* cd - #suspend dte-1.9.1/test/data/syntax-lint.dterc000066400000000000000000000003241354415143300175050ustar00rootroot00000000000000syntax .test-string state string char "\"" END string eat string syntax test state code char -b a-z ident char \" .test-string:code eat code state ident char -b a-z ident noeat ident dte-1.9.1/test/data/thai-tis620.txt000066400000000000000000000000201354415143300166740ustar00rootroot00000000000000 Testing dte-1.9.1/test/data/thai-utf8.txt000066400000000000000000000000361354415143300165420ustar00rootroot00000000000000ภาษาไทย Testing dte-1.9.1/test/data/thai.dterc000066400000000000000000000003641354415143300161440ustar00rootroot00000000000000alias เปิด open เปิด -e UTF-8 insert "ภาษาไทย\n" up copy paste bol delete-eol insert "Testing" run mkdir -p build/test/ save -f -e UTF-8 build/test/thai-utf8.txt save -f -e TIS-620 build/test/thai-tis620.txt close dte-1.9.1/test/editorconfig.c000066400000000000000000000061211354415143300161000ustar00rootroot00000000000000#include #include "test.h" #include "../src/editorconfig/editorconfig.h" #include "../src/editorconfig/match.h" #include "../src/util/path.h" static void test_editorconfig_pattern_match(void) { #define patmatch(s, f) (ec_pattern_match(s, STRLEN(s), f)) EXPECT_TRUE(patmatch("*", "file.c")); EXPECT_TRUE(patmatch("*.{c,h}", "file.c")); EXPECT_TRUE(patmatch("*.{foo}", "file.foo")); EXPECT_TRUE(patmatch("*.{foo{bar,baz}}", "file.foobaz")); EXPECT_FALSE(patmatch("*.{foo{bar,baz}}", "file.foo")); EXPECT_TRUE(patmatch("a/**/b/c/*.[ch]", "a/zzz/yyy/foo/b/c/file.h")); EXPECT_FALSE(patmatch("a/*/b/c/*.[ch]", "a/zzz/yyy/foo/b/c/file.h")); EXPECT_TRUE(patmatch("}*.{x,y}", "}foo.y")); EXPECT_FALSE(patmatch("}*.{x,y}", "foo.y")); EXPECT_TRUE(patmatch("{}*.{x,y}", "foo.y")); EXPECT_TRUE(patmatch("*.[xyz]", "foo.z")); EXPECT_FALSE(patmatch("*.[xyz", "foo.z")); EXPECT_TRUE(patmatch("*.[xyz", "foo.[xyz")); EXPECT_TRUE(patmatch("*.[!xyz]", "foo.a")); EXPECT_FALSE(patmatch("*.[!xyz]", "foo.z")); EXPECT_TRUE(patmatch("*.[", "foo.[")); EXPECT_TRUE(patmatch("*.[a", "foo.[a")); EXPECT_TRUE(patmatch("*.[abc]def", "foo.bdef")); EXPECT_TRUE(patmatch("x{{foo,},}", "x")); EXPECT_TRUE(patmatch("x{{foo,},}", "xfoo")); EXPECT_TRUE(patmatch("file.{,,x,,y,,}", "file.x")); EXPECT_TRUE(patmatch("file.{,,x,,y,,}", "file.")); EXPECT_FALSE(patmatch("file.{,,x,,y,,}", "file.z")); EXPECT_TRUE(patmatch("file.{{{a,b,{c,,d}}}}", "file.d")); EXPECT_TRUE(patmatch("file.{{{a,b,{c,,d}}}}", "file.")); EXPECT_FALSE(patmatch("file.{{{a,b,{c,d}}}}", "file.")); EXPECT_TRUE(patmatch("a?b.c", "a_b.c")); EXPECT_FALSE(patmatch("a?b.c", "a/b.c")); EXPECT_TRUE(patmatch("a\\[.abc", "a[.abc")); EXPECT_TRUE(patmatch("a\\{.abc", "a{.abc")); EXPECT_TRUE(patmatch("a\\*.abc", "a*.abc")); EXPECT_TRUE(patmatch("a\\?.abc", "a?.abc")); EXPECT_FALSE(patmatch("a\\*.abc", "az.abc")); EXPECT_FALSE(patmatch("a\\?.abc", "az.abc")); EXPECT_TRUE(patmatch("{{{a}}}", "a")); EXPECT_FALSE(patmatch("{{{a}}", "a")); #undef patmatch } static void test_get_editorconfig_options(void) { EditorConfigOptions opts = editorconfig_options_init(); char *path = path_absolute("test/data/file.0foo.z"); ASSERT_NONNULL(path); EXPECT_EQ(get_editorconfig_options(path, &opts), 0); free(path); EXPECT_EQ(opts.indent_style, INDENT_STYLE_SPACE); EXPECT_EQ(opts.indent_size, 3); EXPECT_EQ(opts.tab_width, 3); EXPECT_EQ(opts.max_line_length, 68); EXPECT_FALSE(opts.indent_size_is_tab); path = path_absolute("test/data/file.foo"); ASSERT_NONNULL(path); EXPECT_EQ(get_editorconfig_options(path, &opts), 0); free(path); EXPECT_EQ(opts.indent_style, INDENT_STYLE_UNSPECIFIED); EXPECT_EQ(opts.indent_size, 0); EXPECT_EQ(opts.tab_width, 0); EXPECT_EQ(opts.max_line_length, 0); EXPECT_FALSE(opts.indent_size_is_tab); } DISABLE_WARNING("-Wmissing-prototypes") void test_editorconfig(void) { test_editorconfig_pattern_match(); test_get_editorconfig_options(); } dte-1.9.1/test/encoding.c000066400000000000000000000027661354415143300152250ustar00rootroot00000000000000#include "test.h" #include "../src/encoding/bom.h" #include "../src/encoding/encoding.h" static void test_detect_encoding_from_bom(void) { static const struct bom_test { EncodingType encoding; const unsigned char *text; size_t size; } tests[] = { {UTF8, "\xef\xbb\xbfHello", 8}, {UTF32BE, "\x00\x00\xfe\xffHello", 9}, {UTF32LE, "\xff\xfe\x00\x00Hello", 9}, {UTF16BE, "\xfe\xffHello", 7}, {UTF16LE, "\xff\xfeHello", 7}, {UNKNOWN_ENCODING, "\x00\xef\xbb\xbfHello", 9}, {UNKNOWN_ENCODING, "\xef\xbb", 2}, }; FOR_EACH_I(i, tests) { const struct bom_test *t = &tests[i]; EncodingType result = detect_encoding_from_bom(t->text, t->size); IEXPECT_EQ(result, t->encoding); } } static void test_lookup_encoding(void) { static const struct lookup_encoding_test { EncodingType encoding; const char *name; } tests[] = { {UTF8, "UTF-8"}, {UTF8, "UTF8"}, {UTF8, "utf-8"}, {UTF8, "utf8"}, {UTF8, "Utf8"}, {UTF16, "UTF16"}, {UTF32LE, "utf-32le"}, {UTF32LE, "ucs-4le"}, {UNKNOWN_ENCODING, "UTF8_"}, {UNKNOWN_ENCODING, "UTF"}, }; FOR_EACH_I(i, tests) { EncodingType result = lookup_encoding(tests[i].name); IEXPECT_EQ(result, tests[i].encoding); } } DISABLE_WARNING("-Wmissing-prototypes") void test_encoding(void) { test_detect_encoding_from_bom(); test_lookup_encoding(); } dte-1.9.1/test/filetype.c000066400000000000000000000136431354415143300152540ustar00rootroot00000000000000#include #include "test.h" #include "../src/filetype.h" static void test_find_ft_filename(void) { static const struct ft_filename_test { const char *filename, *expected_filetype; } tests[] = { {"/usr/local/include/lib.h", "c"}, {"test.cc~", "c"}, {"test.c++", "c"}, {"test.lua", "lua"}, {"test.py", "python"}, {"test.rs", "rust"}, {"test.C", "c"}, {"test.H", "c"}, {"test.S", "asm"}, {"test.s", "asm"}, {"test.d", "d"}, {"test.l", "lex"}, {"test.m", "objc"}, {"test.v", "verilog"}, {"test.y", "yacc"}, {"makefile", "make"}, {"GNUmakefile", "make"}, {".file.yml", "yaml"}, {"/etc/nginx.conf", "nginx"}, {"file..rb", "ruby"}, {"file.rb", "ruby"}, {"/etc/hosts", "config"}, {"/etc/fstab", "config"}, {"/boot/grub/menu.lst", "config"}, {"/etc/krb5.conf", "ini"}, {"/etc/ld.so.conf", "config"}, {"/etc/default/grub", "sh"}, {"/etc/systemd/user.conf", "ini"}, {"/etc/nginx/mime.types", "nginx"}, {"/etc/iptables/iptables.rules", "config"}, {"/etc/iptables/ip6tables.rules", "config"}, {"/root/.bash_profile", "sh"}, {".bash_profile", "sh"}, {".clang-format", "yaml"}, {".drirc", "xml"}, {".editorconfig", "ini"}, {".gitattributes", "config"}, {".gitconfig", "ini"}, {".gitmodules", "ini"}, {".jshintrc", "json"}, {".zshrc", "sh"}, {"zshrc", "sh"}, {"file.flatpakref", "ini"}, {"file.flatpakrepo", "ini"}, {"file.automount", "ini"}, {"file.nginxconf", "nginx"}, {"meson_options.txt", "meson"}, {"Makefile.am", "make"}, {"Makefile.in", "make"}, {"Makefile.i_", NULL}, {"Makefile.a_", NULL}, {"Makefile._m", NULL}, {"M_______.am", NULL}, {"file.glslf", "glsl"}, {"file.glslv", "glsl"}, {"file.gl_lv", NULL}, {"file.gls_v", NULL}, {"file.gl__v", NULL}, {"._", NULL}, {".1234", NULL}, {".12345", NULL}, {".123456", NULL}, {".doa_", NULL}, {"", NULL}, {"/", NULL}, {"/etc../etc.c.old/c.old", NULL}, // TODO: {".c~", NULL}, {"test.c.bak", "c"}, {"test.c.new", "c"}, {"test.c.old~", "c"}, {"test.c.orig", "c"}, {"test.c.pacnew", "c"}, {"test.c.pacorig", "c"}, {"test.c.pacsave", "c"}, {"test.c.dpkg-dist", "c"}, {"test.c.dpkg-old", "c"}, {"test.c.rpmnew", "c"}, {"test.c.rpmsave", "c"}, {".c", NULL}, {"test.@", NULL}, {NULL, NULL}, }; const StringView empty_line = STRING_VIEW_INIT; FOR_EACH_I(i, tests) { const struct ft_filename_test *t = &tests[i]; const char *result = find_ft(t->filename, empty_line); IEXPECT_STREQ(result, t->expected_filetype); } } static void test_find_ft_firstline(void) { static const struct ft_firstline_test { const char *line, *expected_filetype; } tests[] = { {"", "html"}, {"", "xml"}, {"%YAML 1.1", "yaml"}, {"[wrap-file]", "ini"}, {"[wrap-file", NULL}, {"diff --git a/example.txt b/example.txt", "diff"}, {".TH DTE 1", NULL}, {"", NULL}, {" ", NULL}, {" line)); IEXPECT_STREQ(result, t->expected_filetype); } } DISABLE_WARNING("-Wmissing-prototypes") void test_filetype(void) { test_find_ft_filename(); test_find_ft_firstline(); } dte-1.9.1/test/main.c000066400000000000000000000071311354415143300143520ustar00rootroot00000000000000#include #include #include #include #include "test.h" #include "../src/bind.h" #include "../src/buffer.h" #include "../src/command.h" #include "../src/editor.h" #include "../src/regexp.h" #include "../src/util/path.h" #include "../src/util/xmalloc.h" void test_cmdline(void); void test_command(void); void test_editorconfig(void); void test_encoding(void); void test_filetype(void); void test_syntax(void); void test_terminal(void); void test_util(void); void init_headless_mode(void); void test_exec_config(void); static void test_relative_filename(void) { static const struct rel_test { const char *cwd, *path, *result; } tests[] = { // NOTE: at most 2 ".." components allowed in relative name { "/", "/", "/" }, { "/", "/file", "file" }, { "/a/b/c/d", "/a/b/file", "../../file" }, { "/a/b/c/d/e", "/a/b/file", "/a/b/file" }, { "/a/foobar", "/a/foo/file", "../foo/file" }, }; FOR_EACH_I(i, tests) { const struct rel_test *t = &tests[i]; char *result = relative_filename(t->path, t->cwd); IEXPECT_STREQ(t->result, result); free(result); } } static void test_detect_indent(void) { EXPECT_FALSE(editor.options.detect_indent); EXPECT_FALSE(editor.options.expand_tab); EXPECT_EQ(editor.options.indent_width, 8); handle_command ( commands, "option -r '/test/data/detect-indent\\.ini$' detect-indent 2,4,8;" "open test/data/detect-indent.ini" ); EXPECT_EQ(buffer->options.detect_indent, 1 << 1 | 1 << 3 | 1 << 7); EXPECT_TRUE(buffer->options.expand_tab); EXPECT_EQ(buffer->options.indent_width, 2); handle_command(commands, "close"); } static void test_handle_binding(void) { handle_command(commands, "bind ^A 'insert zzz'; open"); // Bound command should be cached const KeyBinding *binding = lookup_binding(MOD_CTRL | 'A'); ASSERT_NONNULL(binding); EXPECT_PTREQ(binding->cmd, find_command(commands, "insert")); EXPECT_EQ(binding->a.nr_flags, 0); EXPECT_EQ(binding->a.nr_args, 1); EXPECT_STREQ(binding->a.args[0], "zzz"); EXPECT_NULL(binding->a.args[1]); handle_binding(MOD_CTRL | 'A'); const Block *block = BLOCK(buffer->blocks.next); ASSERT_NONNULL(block); ASSERT_EQ(block->size, 4); EXPECT_EQ(block->nl, 1); EXPECT_EQ(memcmp(block->data, "zzz\n", 4), 0); EXPECT_TRUE(undo()); EXPECT_EQ(block->size, 0); EXPECT_EQ(block->nl, 0); EXPECT_FALSE(undo()); handle_command(commands, "close"); } static void test_regexp_match(void) { static const char buf[] = "fn(a);\n"; PointerArray a = PTR_ARRAY_INIT; bool matched = regexp_match("^[a-z]+\\(", buf, sizeof(buf) - 1, &a); EXPECT_TRUE(matched); EXPECT_EQ(a.count, 1); EXPECT_STREQ(a.ptrs[0], "fn("); ptr_array_free(&a); ptr_array_init(&a, 0); matched = regexp_match("^[a-z]+\\([0-9]", buf, sizeof(buf) - 1, &a); EXPECT_FALSE(matched); EXPECT_EQ(a.count, 0); ptr_array_free(&a); } static void test_posix_sanity(void) { // This is not guaranteed by ISO C99, but it is required by POSIX // and is relied upon by this codebase: ASSERT_EQ(CHAR_BIT, 8); } int main(void) { test_posix_sanity(); init_editor_state(); test_relative_filename(); test_command(); test_editorconfig(); test_encoding(); test_filetype(); test_util(); test_terminal(); test_cmdline(); test_regexp_match(); test_syntax(); init_headless_mode(); test_exec_config(); test_detect_indent(); test_handle_binding(); return failed ? 1 : 0; } dte-1.9.1/test/syntax.c000066400000000000000000000010511354415143300147470ustar00rootroot00000000000000#include #include "test.h" #include "../src/syntax/bitset.h" static void test_bitset_invert(void) { BitSet set = {0}; ASSERT_EQ(sizeof(set), 32); ASSERT_EQ(ARRAY_COUNT(set), 32); memset(set, 0, sizeof(set)); for (size_t i = 0; i < ARRAY_COUNT(set); i++) { EXPECT_UINT_EQ(set[i], 0); } bitset_invert(set); for (size_t i = 0; i < ARRAY_COUNT(set); i++) { EXPECT_UINT_EQ(set[i], UINT8_MAX); } } DISABLE_WARNING("-Wmissing-prototypes") void test_syntax(void) { test_bitset_invert(); } dte-1.9.1/test/terminal.c000066400000000000000000000401061354415143300152400ustar00rootroot00000000000000#include #include "test.h" #include "../src/debug.h" #include "../src/terminal/color.h" #include "../src/terminal/key.h" #include "../src/terminal/xterm.h" #include "../src/util/unicode.h" static void test_parse_term_color(void) { static const struct { const char *const strs[4]; const TermColor expected_color; } tests[] = { {{"bold", "red", "yellow"}, {COLOR_RED, COLOR_YELLOW, ATTR_BOLD}}, {{"#ff0000"}, {COLOR_RGB(0xff0000), -1, 0}}, {{"#f00a9c", "reverse"}, {COLOR_RGB(0xf00a9c), -1, ATTR_REVERSE}}, {{"black", "#00ffff"}, {COLOR_BLACK, COLOR_RGB(0x00ffff), 0}}, {{"#123456", "#abcdef"}, {COLOR_RGB(0x123456), COLOR_RGB(0xabcdef), 0}}, {{"red", "strikethrough"}, {COLOR_RED, -1, ATTR_STRIKETHROUGH}}, {{"5/5/5"}, {231, COLOR_DEFAULT, 0}}, {{"1/3/0", "0/5/2", "italic"}, {70, 48, ATTR_ITALIC}}, {{"-1", "-2"}, {COLOR_DEFAULT, COLOR_KEEP, 0}}, {{"keep", "red", "keep"}, {-2, COLOR_RED, ATTR_KEEP}}, {{"bold", "blink"}, {-1, -1, ATTR_BOLD | ATTR_BLINK}}, {{"0", "255", "underline"}, {COLOR_BLACK, 255, ATTR_UNDERLINE}}, {{"white", "green", "dim"}, {COLOR_WHITE, COLOR_GREEN, ATTR_DIM}}, {{"lightred", "lightyellow"}, {COLOR_LIGHTRED, COLOR_LIGHTYELLOW, 0}}, {{"darkgray", "lightgreen"}, {COLOR_DARKGRAY, COLOR_LIGHTGREEN, 0}}, {{"lightblue", "lightcyan"}, {COLOR_LIGHTBLUE, COLOR_LIGHTCYAN, 0}}, {{"lightmagenta"}, {COLOR_LIGHTMAGENTA, COLOR_DEFAULT, 0}}, {{"keep", "254", "keep"}, {COLOR_KEEP, 254, ATTR_KEEP}}, {{"red", "green", "keep"}, {COLOR_RED, COLOR_GREEN, ATTR_KEEP}}, {{"1", "2", "invisible"}, {COLOR_RED, COLOR_GREEN, ATTR_INVIS}}, }; FOR_EACH_I(i, tests) { TermColor parsed; bool ok = parse_term_color(&parsed, (char**)tests[i].strs); IEXPECT_TRUE(ok); if (ok) { const TermColor expected = tests[i].expected_color; IEXPECT_EQ(parsed.fg, expected.fg); IEXPECT_EQ(parsed.bg, expected.bg); IEXPECT_EQ(parsed.attr, expected.attr); } } } static void test_color_to_nearest(void) { static const struct { int32_t input; int32_t expected_rgb; int32_t expected_256; int32_t expected_16; } tests[] = { // ECMA-48 colors {0, 0, 0, 0}, {5, 5, 5, 5}, {7, 7, 7, 7}, // aixterm-style colors {8, 8, 8, 8}, {10, 10, 10, 10}, {15, 15, 15, 15}, // xterm 256 palette colors {25, 25, 25, COLOR_BLUE}, {87, 87, 87, COLOR_LIGHTCYAN}, {88, 88, 88, COLOR_RED}, {90, 90, 90, COLOR_MAGENTA}, {96, 96, 96, COLOR_MAGENTA}, {178, 178, 178, COLOR_YELLOW}, {179, 179, 179, COLOR_YELLOW}, // RGB colors with exact xterm 6x6x6 cube equivalents {COLOR_RGB(0x000000), 16, 16, COLOR_BLACK}, {COLOR_RGB(0x000087), 18, 18, COLOR_BLUE}, {COLOR_RGB(0x0000FF), 21, 21, COLOR_LIGHTBLUE}, {COLOR_RGB(0x00AF87), 36, 36, COLOR_GREEN}, {COLOR_RGB(0x00FF00), 46, 46, COLOR_LIGHTGREEN}, {COLOR_RGB(0x870000), 88, 88, COLOR_RED}, {COLOR_RGB(0xFF0000), 196, 196, COLOR_LIGHTRED}, {COLOR_RGB(0xFFD700), 220, 220, COLOR_YELLOW}, {COLOR_RGB(0xFFFF5F), 227, 227, COLOR_LIGHTYELLOW}, {COLOR_RGB(0xFFFFFF), 231, 231, COLOR_WHITE}, // RGB colors with exact xterm grayscale equivalents {COLOR_RGB(0x080808), 232, 232, COLOR_BLACK}, {COLOR_RGB(0x121212), 233, 233, COLOR_BLACK}, {COLOR_RGB(0x6C6C6C), 242, 242, COLOR_DARKGRAY}, {COLOR_RGB(0xA8A8A8), 248, 248, COLOR_GRAY}, {COLOR_RGB(0xB2B2B2), 249, 249, COLOR_GRAY}, {COLOR_RGB(0xBCBCBC), 250, 250, COLOR_WHITE}, {COLOR_RGB(0xEEEEEE), 255, 255, COLOR_WHITE}, // RGB colors with NO exact xterm equivalents {COLOR_RGB(0x00FF88), COLOR_RGB(0x00FF88), 48, COLOR_LIGHTGREEN}, {COLOR_RGB(0xFF0001), COLOR_RGB(0xFF0001), 196, COLOR_LIGHTRED}, {COLOR_RGB(0xAABBCC), COLOR_RGB(0xAABBCC), 146, COLOR_LIGHTBLUE}, {COLOR_RGB(0x080809), COLOR_RGB(0x080809), 232, COLOR_BLACK}, {COLOR_RGB(0xBABABA), COLOR_RGB(0xBABABA), 250, COLOR_WHITE}, {COLOR_RGB(0xEEEEED), COLOR_RGB(0xEEEEED), 255, COLOR_WHITE}, }; FOR_EACH_I(i, tests) { const int32_t c = tests[i].input; IEXPECT_EQ(color_to_nearest(c, TERM_TRUE_COLOR), tests[i].expected_rgb); IEXPECT_EQ(color_to_nearest(c, TERM_256_COLOR), tests[i].expected_256); IEXPECT_EQ(color_to_nearest(c, TERM_16_COLOR), tests[i].expected_16); IEXPECT_EQ(color_to_nearest(c, TERM_8_COLOR), tests[i].expected_16 & 7); IEXPECT_EQ(color_to_nearest(c, TERM_0_COLOR), COLOR_DEFAULT); } } static void test_xterm_parse_key(void) { static const struct xterm_key_test { const char *escape_sequence; ssize_t expected_length; KeyCode expected_key; } tests[] = { {"\033[Z", 3, MOD_SHIFT | '\t'}, {"\033[1;2A", 6, MOD_SHIFT | KEY_UP}, {"\033[1;2B", 6, MOD_SHIFT | KEY_DOWN}, {"\033[1;2C", 6, MOD_SHIFT | KEY_RIGHT}, {"\033[1;2D", 6, MOD_SHIFT | KEY_LEFT}, {"\033[1;2F", 6, MOD_SHIFT | KEY_END}, {"\033[1;2H", 6, MOD_SHIFT | KEY_HOME}, {"\033[1;8H", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_HOME}, {"\033[1;8H~", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_HOME}, {"\033[1;8H~_", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_HOME}, {"\033", -1, 0}, {"\033[", -1, 0}, {"\033]", 0, 0}, {"\033[1", -1, 0}, {"\033[9", -1, 0}, {"\033[1;", -1, 0}, {"\033[1[", 0, 0}, {"\033[1;2", -1, 0}, {"\033[1;8", -1, 0}, {"\033[1;9", -1, 0}, {"\033[1;_", 0, 0}, {"\033[1;8Z", 0, 0}, {"\033O", -1, 0}, {"\033[\033", 0, 0}, {"\033[A", 3, KEY_UP}, {"\033[B", 3, KEY_DOWN}, {"\033[C", 3, KEY_RIGHT}, {"\033[D", 3, KEY_LEFT}, {"\033[F", 3, KEY_END}, {"\033[H", 3, KEY_HOME}, {"\033[L", 3, KEY_INSERT}, {"\033[1~", 4, KEY_HOME}, {"\033[2~", 4, KEY_INSERT}, {"\033[3~", 4, KEY_DELETE}, {"\033[4~", 4, KEY_END}, {"\033[5~", 4, KEY_PAGE_UP}, {"\033[6~", 4, KEY_PAGE_DOWN}, {"\033O ", 3, ' '}, {"\033OA", 3, KEY_UP}, {"\033OB", 3, KEY_DOWN}, {"\033OC", 3, KEY_RIGHT}, {"\033OD", 3, KEY_LEFT}, {"\033OF", 3, KEY_END}, {"\033OH", 3, KEY_HOME}, {"\033OI", 3, '\t'}, {"\033OM", 3, KEY_ENTER}, {"\033OP", 3, KEY_F1}, {"\033OQ", 3, KEY_F2}, {"\033OR", 3, KEY_F3}, {"\033OS", 3, KEY_F4}, {"\033OX", 3, '='}, {"\033Oj", 3, '*'}, {"\033Ok", 3, '+'}, {"\033Ol", 3, ','}, {"\033Om", 3, '-'}, {"\033On", 3, '.'}, {"\033Oo", 3, '/'}, {"\033Op", 3, '0'}, {"\033Oq", 3, '1'}, {"\033Or", 3, '2'}, {"\033Os", 3, '3'}, {"\033Ot", 3, '4'}, {"\033Ou", 3, '5'}, {"\033Ov", 3, '6'}, {"\033Ow", 3, '7'}, {"\033Ox", 3, '8'}, {"\033Oy", 3, '9'}, {"\033[10~", 0, 0}, {"\033[11~", 5, KEY_F1}, {"\033[12~", 5, KEY_F2}, {"\033[13~", 5, KEY_F3}, {"\033[14~", 5, KEY_F4}, {"\033[15~", 5, KEY_F5}, {"\033[16~", 0, 0}, {"\033[17~", 5, KEY_F6}, {"\033[18~", 5, KEY_F7}, {"\033[19~", 5, KEY_F8}, {"\033[20~", 5, KEY_F9}, {"\033[21~", 5, KEY_F10}, {"\033[22~", 0, 0}, {"\033[23~", 5, KEY_F11}, {"\033[24~", 5, KEY_F12}, {"\033[25~", 0, 0}, {"\033[6;3~", 6, MOD_META | KEY_PAGE_DOWN}, {"\033[6;5~", 6, MOD_CTRL | KEY_PAGE_DOWN}, {"\033[6;8~", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_PAGE_DOWN}, // Linux console {"\033[[A", 4, KEY_F1}, {"\033[[B", 4, KEY_F2}, {"\033[[C", 4, KEY_F3}, {"\033[[D", 4, KEY_F4}, {"\033[[E", 4, KEY_F5}, // rxvt {"\033Oa", 3, MOD_CTRL | KEY_UP}, {"\033Ob", 3, MOD_CTRL | KEY_DOWN}, {"\033Oc", 3, MOD_CTRL | KEY_RIGHT}, {"\033Od", 3, MOD_CTRL | KEY_LEFT}, {"\033[a", 3, MOD_SHIFT | KEY_UP}, {"\033[b", 3, MOD_SHIFT | KEY_DOWN}, {"\033[c", 3, MOD_SHIFT | KEY_RIGHT}, {"\033[d", 3, MOD_SHIFT | KEY_LEFT}, // xterm + `modifyOtherKeys` option {"\033[27;5;9~", 9, MOD_CTRL | '\t'}, {"\033[27;5;13~", 10, MOD_CTRL | KEY_ENTER}, {"\033[27;6;13~", 10, MOD_CTRL | MOD_SHIFT |KEY_ENTER}, {"\033[27;2;127~", 11, MOD_CTRL | '?'}, {"\033[27;6;127~", 11, MOD_CTRL | '?'}, {"\033[27;8;127~", 11, MOD_CTRL | MOD_META | '?'}, {"\033[27;6;82~", 10, MOD_CTRL | 'R'}, {"\033[27;5;114~", 11, MOD_CTRL | 'R'}, {"\033[27;3;82~", 10, MOD_META | 'R'}, {"\033[27;3;114~", 11, MOD_META | 'r'}, {"\033[27;4;62~", 10, MOD_META | '>'}, {"\033[27;5;46~", 10, MOD_CTRL | '.'}, {"\033[27;3;1114111~", 15, MOD_META | UNICODE_MAX_VALID_CODEPOINT}, {"\033[27;3;1114112~", 0, 0}, {"\033[27;999999999999999999999;123~", 0, 0}, {"\033[27;123;99999999999999999~", 0, 0}, // www.leonerd.org.uk/hacks/fixterms/ {"\033[13;3u", 7, MOD_META | KEY_ENTER}, {"\033[9;5u", 6, MOD_CTRL | '\t'}, {"\033[65;3u", 7, MOD_META | 'A'}, {"\033[108;5u", 8, MOD_CTRL | 'L'}, {"\033[127765;3u", 11, MOD_META | 127765ul}, {"\033[1114111;3u", 12, MOD_META | UNICODE_MAX_VALID_CODEPOINT}, {"\033[1114112;3u", 0, 0}, {"\033[11141110;3u", 0, 0}, {"\033[11141111;3u", 0, 0}, {"\033[2147483647;3u", 0, 0}, // INT32_MAX {"\033[2147483648;3u", 0, 0}, // INT32_MAX + 1 {"\033[4294967295;3u", 0, 0}, // UINT32_MAX {"\033[4294967296;3u", 0, 0}, // UINT32_MAX + 1 {"\033[-1;3u", 0, 0}, {"\033[-2;3u", 0, 0}, }; FOR_EACH_I(i, tests) { const char *seq = tests[i].escape_sequence; const size_t seq_length = strlen(seq); BUG_ON(seq_length == 0); KeyCode key = 0x18; ssize_t parsed_length = xterm_parse_key(seq, seq_length, &key); ssize_t expected_length = tests[i].expected_length; IEXPECT_EQ(parsed_length, expected_length); if (parsed_length <= 0) { // If nothing was parsed, key should be unmodified IEXPECT_EQ(key, 0x18); continue; } IEXPECT_EQ(key, tests[i].expected_key); // Ensure that parsing any truncated sequence returns -1: key = 0x18; for (size_t n = expected_length - 1; n != 0; n--) { parsed_length = xterm_parse_key(seq, n, &key); IEXPECT_EQ(parsed_length, -1); IEXPECT_EQ(key, 0x18); } } } static void test_xterm_parse_key_combo(void) { static const struct { char escape_sequence[8]; KeyCode key; } templates[] = { {"\033[1;_A", KEY_UP}, {"\033[1;_B", KEY_DOWN}, {"\033[1;_C", KEY_RIGHT}, {"\033[1;_D", KEY_LEFT}, {"\033[1;_F", KEY_END}, {"\033[1;_H", KEY_HOME}, {"\033[2;_~", KEY_INSERT}, {"\033[3;_~", KEY_DELETE}, {"\033[5;_~", KEY_PAGE_UP}, {"\033[6;_~", KEY_PAGE_DOWN}, {"\033[1;_P", KEY_F1}, {"\033[1;_Q", KEY_F2}, {"\033[1;_R", KEY_F3}, {"\033[1;_S", KEY_F4}, {"\033[15;_~", KEY_F5}, {"\033[17;_~", KEY_F6}, {"\033[18;_~", KEY_F7}, {"\033[19;_~", KEY_F8}, {"\033[20;_~", KEY_F9}, {"\033[21;_~", KEY_F10}, {"\033[23;_~", KEY_F11}, {"\033[24;_~", KEY_F12}, }; static const struct { char ch; KeyCode mask; } modifiers[] = { {'2', MOD_SHIFT}, {'3', MOD_META}, {'4', MOD_SHIFT | MOD_META}, {'5', MOD_CTRL}, {'6', MOD_SHIFT | MOD_CTRL}, {'7', MOD_META | MOD_CTRL}, {'8', MOD_SHIFT | MOD_META | MOD_CTRL} }; FOR_EACH_I(i, templates) { FOR_EACH_I(j, modifiers) { char seq[8]; memcpy(seq, templates[i].escape_sequence, 8); BUG_ON(seq[7] != '\0'); char *underscore = strchr(seq, '_'); ASSERT_NONNULL(underscore); *underscore = modifiers[j].ch; size_t seq_length = strlen(seq); KeyCode key; ssize_t parsed_length = xterm_parse_key(seq, seq_length, &key); EXPECT_EQ(parsed_length, seq_length); EXPECT_EQ(key, modifiers[j].mask | templates[i].key); // Truncated key = 0x18; for (size_t n = seq_length - 1; n != 0; n--) { parsed_length = xterm_parse_key(seq, n, &key); EXPECT_EQ(parsed_length, -1); EXPECT_EQ(key, 0x18); } // Overlength key = 0x18; seq[seq_length] = '~'; parsed_length = xterm_parse_key(seq, seq_length + 1, &key); EXPECT_EQ(parsed_length, seq_length); EXPECT_EQ(key, modifiers[j].mask | templates[i].key); } } } static void test_xterm_parse_key_combo_rxvt(void) { static const struct { char escape_sequence[8]; KeyCode key; } templates[] = { {"\033[3_", KEY_DELETE}, {"\033[5_", KEY_PAGE_UP}, {"\033[6_", KEY_PAGE_DOWN}, {"\033[7_", KEY_HOME}, {"\033[8_", KEY_END}, }; static const struct { char ch; KeyCode mask; } modifiers[] = { {'~', 0}, /* Note: the tests for these non-standard rxvt modifiers have been disabled, because using '$' as a final byte is a violation of the ECMA-48 spec: {'^', MOD_CTRL}, {'$', MOD_SHIFT}, {'@', MOD_SHIFT | MOD_CTRL}, For the rxvt developers to invent a new key encoding schemes where a perfectly good, de facto standard (xterm) already existed was foolish. To then violate the spec in the process was pure stupidity. */ }; FOR_EACH_I(i, templates) { FOR_EACH_I(j, modifiers) { char seq[8]; memcpy(seq, templates[i].escape_sequence, 8); BUG_ON(seq[7] != '\0'); char *underscore = strchr(seq, '_'); ASSERT_NONNULL(underscore); *underscore = modifiers[j].ch; size_t seq_length = strlen(seq); KeyCode key; ssize_t parsed_length = xterm_parse_key(seq, seq_length, &key); EXPECT_EQ(parsed_length, seq_length); EXPECT_EQ(key, modifiers[j].mask | templates[i].key); } } } static void test_key_to_string(void) { static const struct key_to_string_test { const char *str; KeyCode key; } tests[] = { {"a", 'a'}, {"Z", 'Z'}, {"0", '0'}, {"{", '{'}, {"space", ' '}, {"enter", KEY_ENTER}, {"tab", '\t'}, {"insert", KEY_INSERT}, {"delete", KEY_DELETE}, {"home", KEY_HOME}, {"end", KEY_END}, {"pgup", KEY_PAGE_UP}, {"pgdown", KEY_PAGE_DOWN}, {"left", KEY_LEFT}, {"right", KEY_RIGHT}, {"up", KEY_UP}, {"down", KEY_DOWN}, {"C-A", MOD_CTRL | 'A'}, {"M-S-{", MOD_META | MOD_SHIFT | '{'}, {"C-S-A", MOD_CTRL | MOD_SHIFT | 'A'}, {"F1", KEY_F1}, {"F12", KEY_F12}, {"M-enter", MOD_META | KEY_ENTER}, {"M-space", MOD_META | ' '}, {"S-tab", MOD_SHIFT | '\t'}, {"C-M-S-F12", MOD_CTRL | MOD_META | MOD_SHIFT | KEY_F12}, {"C-M-S-up", MOD_CTRL | MOD_META | MOD_SHIFT | KEY_UP}, {"C-M-delete", MOD_CTRL | MOD_META | KEY_DELETE}, {"C-home", MOD_CTRL | KEY_HOME}, }; FOR_EACH_I(i, tests) { const char *str = key_to_string(tests[i].key); IEXPECT_STREQ(str, tests[i].str); } } DISABLE_WARNING("-Wmissing-prototypes") void test_terminal(void) { test_parse_term_color(); test_color_to_nearest(); test_xterm_parse_key(); test_xterm_parse_key_combo(); test_xterm_parse_key_combo_rxvt(); test_key_to_string(); } dte-1.9.1/test/test.c000066400000000000000000000057711354415143300144150ustar00rootroot00000000000000#include #include #include #include "test.h" #include "../src/util/str-util.h" unsigned int failed; void test_fail(const char *file, int line, const char *format, ...) { fprintf(stderr, "%s:%d: ", file, line); va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); putc('\n', stderr); fflush(stderr); failed += 1; } void expect_streq(const char *file, int line, const char *s1, const char *s2) { if (unlikely(!xstreq(s1, s2))) { s1 = s1 ? s1 : "(null)"; s2 = s2 ? s2 : "(null)"; test_fail(file, line, "Strings not equal: '%s', '%s'", s1, s2); } } void expect_ptreq(const char *file, int line, const void *p1, const void *p2) { if (unlikely(p1 != p2)) { test_fail(file, line, "Pointers not equal: %p, %p", p1, p2); } } void expect_eq(const char *file, int line, intmax_t a, intmax_t b) { if (unlikely(a != b)) { test_fail(file, line, "Values not equal: %jd, %jd", a, b); } } void expect_uint_eq(const char *file, int line, uintmax_t a, uintmax_t b) { if (unlikely(a != b)) { test_fail(file, line, "Values not equal: %ju, %ju", a, b); } } void expect_true(const char *file, int line, bool x) { if (unlikely(!x)) { test_fail(file, line, "Unexpected false value"); } } void expect_false(const char *file, int line, bool x) { if (unlikely(x)) { test_fail(file, line, "Unexpected true value"); } } void expect_null(const char *file, int line, const void *ptr) { if (unlikely(ptr != NULL)) { test_fail(file, line, "Expected NULL, but got: %p", ptr); } } void expect_nonnull(const char *file, int line, const void *ptr) { if (unlikely(ptr == NULL)) { test_fail(file, line, "Unexpected NULL pointer"); } } void iexpect_streq(const char *f, int l, size_t i, const char *a, const char *b) { if (unlikely(!xstreq(a, b))) { a = a ? a : "(null)"; b = b ? b : "(null)"; i++; test_fail(f, l, "Test #%zu: strings not equal: '%s', '%s'", i, a, b); } } void iexpect_eq(const char *file, int line, size_t i, intmax_t a, intmax_t b) { if (unlikely(a != b)) { i++; test_fail(file, line, "Test #%zu: values not equal: %jd, %jd", i, a, b); } } void iexpect_true(const char *file, int line, size_t i, bool x) { if (unlikely(!x)) { i++; test_fail(file, line, "Test #%zu: unexpected false value", i); } } void assert_eq(const char *file, int line, intmax_t a, intmax_t b) { if (unlikely(a != b)) { test_fail(file, line, "ERROR: Values not equal: %jd, %jd", a, b); abort(); } } void assert_true(const char *file, int line, bool x) { if (unlikely(!x)) { test_fail(file, line, "ERROR: Unexpected false value"); abort(); } } void assert_nonnull(const char *file, int line, const void *ptr) { if (unlikely(ptr == NULL)) { test_fail(file, line, "ERROR: Unexpected NULL pointer"); abort(); } } dte-1.9.1/test/test.h000066400000000000000000000043321354415143300144120ustar00rootroot00000000000000#ifndef TEST_TEST_H #define TEST_TEST_H #include #include #include #include "../src/util/macros.h" #define FOR_EACH_I(i, array) \ for (size_t i = 0; i < ARRAY_COUNT(array); i++) #define TEST_FAIL(...) test_fail(__FILE__, __LINE__, __VA_ARGS__) #define EXPECT_STREQ(s1, s2) expect_streq(__FILE__, __LINE__, s1, s2) #define EXPECT_PTREQ(p1, p2) expect_ptreq(__FILE__, __LINE__, p1, p2) #define EXPECT_EQ(a, b) expect_eq(__FILE__, __LINE__, a, b) #define EXPECT_UINT_EQ(a, b) expect_uint_eq(__FILE__, __LINE__, a, b) #define EXPECT_NULL(p) expect_null(__FILE__, __LINE__, p) #define EXPECT_NONNULL(p) expect_nonnull(__FILE__, __LINE__, p) #define EXPECT_TRUE(x) expect_true(__FILE__, __LINE__, x) #define EXPECT_FALSE(x) expect_false(__FILE__, __LINE__, x) #define IEXPECT_EQ(a, b) iexpect_eq(__FILE__, __LINE__, i, a, b) #define IEXPECT_STREQ(s1, s2) iexpect_streq(__FILE__, __LINE__, i, s1, s2) #define IEXPECT_TRUE(x) iexpect_true(__FILE__, __LINE__, i, x) #define ASSERT_EQ(a, b) assert_eq(__FILE__, __LINE__, a, b) #define ASSERT_TRUE(x) assert_true(__FILE__, __LINE__, x) #define ASSERT_NONNULL(ptr) assert_nonnull(__FILE__, __LINE__, ptr) extern unsigned int failed; void test_fail(const char *file, int line, const char *format, ...) PRINTF(3); void expect_streq(const char *file, int line, const char *s1, const char *s2); void expect_ptreq(const char *file, int line, const void *p1, const void *p2); void expect_eq(const char *file, int line, intmax_t a, intmax_t b); void expect_uint_eq(const char *file, int line, uintmax_t a, uintmax_t b); void expect_true(const char *file, int line, bool x); void expect_false(const char *file, int line, bool x); void expect_null(const char *file, int line, const void *p); void expect_nonnull(const char *file, int line, const void *p); void iexpect_streq(const char *file, int line, size_t i, const char *s1, const char *s2); void iexpect_eq(const char *file, int line, size_t i, intmax_t a, intmax_t b); void iexpect_true(const char *file, int line, size_t i, bool x); void assert_eq(const char *file, int line, intmax_t a, intmax_t b); void assert_true(const char *file, int line, bool x); void assert_nonnull(const char *file, int line, const void *ptr); #endif dte-1.9.1/test/util.c000066400000000000000000001073361354415143300144130ustar00rootroot00000000000000#include #include #include #include #include #include "test.h" #include "../src/util/ascii.h" #include "../src/util/bit.h" #include "../src/util/checked-arith.h" #include "../src/util/hashset.h" #include "../src/util/path.h" #include "../src/util/ptr-array.h" #include "../src/util/readfile.h" #include "../src/util/str-util.h" #include "../src/util/string-view.h" #include "../src/util/string.h" #include "../src/util/strtonum.h" #include "../src/util/unicode.h" #include "../src/util/utf8.h" #include "../src/util/xmalloc.h" #include "../src/util/xsnprintf.h" static void test_macros(void) { EXPECT_EQ(STRLEN(""), 0); EXPECT_EQ(STRLEN("a"), 1); EXPECT_EQ(STRLEN("123456789"), 9); EXPECT_EQ(ARRAY_COUNT(""), 1); EXPECT_EQ(ARRAY_COUNT("a"), 2); EXPECT_EQ(ARRAY_COUNT("123456789"), 10); EXPECT_EQ(MIN(0, 1), 0); EXPECT_EQ(MIN(99, 100), 99); EXPECT_EQ(MIN(-10, 10), -10); EXPECT_TRUE(IS_POWER_OF_2(1)); EXPECT_TRUE(IS_POWER_OF_2(2)); EXPECT_TRUE(IS_POWER_OF_2(4)); EXPECT_TRUE(IS_POWER_OF_2(8)); EXPECT_TRUE(IS_POWER_OF_2(4096)); EXPECT_TRUE(IS_POWER_OF_2(8192)); EXPECT_TRUE(IS_POWER_OF_2(1ULL << 63)); EXPECT_FALSE(IS_POWER_OF_2(0)); EXPECT_FALSE(IS_POWER_OF_2(3)); EXPECT_FALSE(IS_POWER_OF_2(12)); EXPECT_FALSE(IS_POWER_OF_2(-10)); } static void test_ascii(void) { EXPECT_EQ(ascii_tolower('A'), 'a'); EXPECT_EQ(ascii_tolower('F'), 'f'); EXPECT_EQ(ascii_tolower('Z'), 'z'); EXPECT_EQ(ascii_tolower('a'), 'a'); EXPECT_EQ(ascii_tolower('f'), 'f'); EXPECT_EQ(ascii_tolower('z'), 'z'); EXPECT_EQ(ascii_tolower('9'), '9'); EXPECT_EQ(ascii_tolower('~'), '~'); EXPECT_EQ(ascii_tolower('\0'), '\0'); EXPECT_EQ(ascii_toupper('a'), 'A'); EXPECT_EQ(ascii_toupper('f'), 'F'); EXPECT_EQ(ascii_toupper('z'), 'Z'); EXPECT_EQ(ascii_toupper('A'), 'A'); EXPECT_EQ(ascii_toupper('F'), 'F'); EXPECT_EQ(ascii_toupper('Z'), 'Z'); EXPECT_EQ(ascii_toupper('9'), '9'); EXPECT_EQ(ascii_toupper('~'), '~'); EXPECT_EQ(ascii_toupper('\0'), '\0'); EXPECT_TRUE(ascii_isspace(' ')); EXPECT_TRUE(ascii_isspace('\t')); EXPECT_TRUE(ascii_isspace('\r')); EXPECT_TRUE(ascii_isspace('\n')); EXPECT_FALSE(ascii_isspace('\0')); EXPECT_FALSE(ascii_isspace('a')); EXPECT_FALSE(ascii_isspace(0x7F)); EXPECT_FALSE(ascii_isspace(0x80)); EXPECT_TRUE(ascii_iscntrl('\0')); EXPECT_TRUE(ascii_iscntrl('\a')); EXPECT_TRUE(ascii_iscntrl('\b')); EXPECT_TRUE(ascii_iscntrl('\t')); EXPECT_TRUE(ascii_iscntrl('\n')); EXPECT_TRUE(ascii_iscntrl('\v')); EXPECT_TRUE(ascii_iscntrl('\f')); EXPECT_TRUE(ascii_iscntrl('\r')); EXPECT_TRUE(ascii_iscntrl(0x0E)); EXPECT_TRUE(ascii_iscntrl(0x1F)); EXPECT_TRUE(ascii_iscntrl(0x7F)); EXPECT_FALSE(ascii_iscntrl(' ')); EXPECT_FALSE(ascii_iscntrl('a')); EXPECT_FALSE(ascii_iscntrl(0x7E)); EXPECT_FALSE(ascii_iscntrl(0x80)); EXPECT_FALSE(ascii_iscntrl(0xFF)); EXPECT_TRUE(ascii_isdigit('0')); EXPECT_TRUE(ascii_isdigit('1')); EXPECT_TRUE(ascii_isdigit('9')); EXPECT_FALSE(ascii_isdigit('a')); EXPECT_FALSE(ascii_isdigit('f')); EXPECT_FALSE(ascii_isdigit('/')); EXPECT_FALSE(ascii_isdigit(':')); EXPECT_FALSE(ascii_isdigit('\0')); EXPECT_FALSE(ascii_isdigit(0xFF)); EXPECT_TRUE(ascii_isxdigit('0')); EXPECT_TRUE(ascii_isxdigit('1')); EXPECT_TRUE(ascii_isxdigit('9')); EXPECT_TRUE(ascii_isxdigit('a')); EXPECT_TRUE(ascii_isxdigit('A')); EXPECT_TRUE(ascii_isxdigit('f')); EXPECT_TRUE(ascii_isxdigit('F')); EXPECT_FALSE(ascii_isxdigit('g')); EXPECT_FALSE(ascii_isxdigit('G')); EXPECT_FALSE(ascii_isxdigit('@')); EXPECT_FALSE(ascii_isxdigit('/')); EXPECT_FALSE(ascii_isxdigit(':')); EXPECT_FALSE(ascii_isxdigit('\0')); EXPECT_FALSE(ascii_isxdigit(0xFF)); EXPECT_TRUE(ascii_is_nonspace_cntrl('\0')); EXPECT_TRUE(ascii_is_nonspace_cntrl('\a')); EXPECT_TRUE(ascii_is_nonspace_cntrl('\b')); EXPECT_TRUE(ascii_is_nonspace_cntrl(0x0E)); EXPECT_TRUE(ascii_is_nonspace_cntrl(0x1F)); EXPECT_TRUE(ascii_is_nonspace_cntrl(0x7F)); EXPECT_FALSE(ascii_is_nonspace_cntrl('\t')); EXPECT_FALSE(ascii_is_nonspace_cntrl('\n')); EXPECT_FALSE(ascii_is_nonspace_cntrl('\r')); EXPECT_FALSE(ascii_is_nonspace_cntrl(' ')); EXPECT_FALSE(ascii_is_nonspace_cntrl('a')); EXPECT_FALSE(ascii_is_nonspace_cntrl(0x7E)); EXPECT_FALSE(ascii_is_nonspace_cntrl(0x80)); EXPECT_FALSE(ascii_is_nonspace_cntrl(0xFF)); EXPECT_TRUE(ascii_isprint(' ')); EXPECT_TRUE(ascii_isprint('!')); EXPECT_TRUE(ascii_isprint('/')); EXPECT_TRUE(ascii_isprint('a')); EXPECT_TRUE(ascii_isprint('z')); EXPECT_TRUE(ascii_isprint('0')); EXPECT_TRUE(ascii_isprint('_')); EXPECT_TRUE(ascii_isprint('~')); EXPECT_FALSE(ascii_isprint('\0')); EXPECT_FALSE(ascii_isprint('\t')); EXPECT_FALSE(ascii_isprint('\n')); EXPECT_FALSE(ascii_isprint('\r')); EXPECT_FALSE(ascii_isprint(0x1F)); EXPECT_FALSE(ascii_isprint(0x7F)); EXPECT_FALSE(ascii_isprint(0x80)); EXPECT_FALSE(ascii_isprint(0xFF)); EXPECT_TRUE(is_word_byte('a')); EXPECT_TRUE(is_word_byte('z')); EXPECT_TRUE(is_word_byte('A')); EXPECT_TRUE(is_word_byte('Z')); EXPECT_TRUE(is_word_byte('0')); EXPECT_TRUE(is_word_byte('9')); EXPECT_TRUE(is_word_byte('_')); EXPECT_TRUE(is_word_byte(0x80)); EXPECT_TRUE(is_word_byte(0xFF)); EXPECT_FALSE(is_word_byte('-')); EXPECT_FALSE(is_word_byte('.')); EXPECT_FALSE(is_word_byte(0x7F)); EXPECT_FALSE(is_word_byte(0x00)); EXPECT_TRUE(is_regex_special_char('(')); EXPECT_TRUE(is_regex_special_char(')')); EXPECT_TRUE(is_regex_special_char('*')); EXPECT_TRUE(is_regex_special_char('+')); EXPECT_TRUE(is_regex_special_char('.')); EXPECT_TRUE(is_regex_special_char('?')); EXPECT_TRUE(is_regex_special_char('[')); EXPECT_TRUE(is_regex_special_char('{')); EXPECT_TRUE(is_regex_special_char('|')); EXPECT_TRUE(is_regex_special_char('\\')); EXPECT_FALSE(is_regex_special_char('"')); EXPECT_FALSE(is_regex_special_char('$')); EXPECT_FALSE(is_regex_special_char('&')); EXPECT_FALSE(is_regex_special_char(',')); EXPECT_FALSE(is_regex_special_char('0')); EXPECT_FALSE(is_regex_special_char('@')); EXPECT_FALSE(is_regex_special_char('A')); EXPECT_FALSE(is_regex_special_char('\'')); EXPECT_FALSE(is_regex_special_char(']')); EXPECT_FALSE(is_regex_special_char('^')); EXPECT_FALSE(is_regex_special_char('_')); EXPECT_FALSE(is_regex_special_char('z')); EXPECT_FALSE(is_regex_special_char('}')); EXPECT_FALSE(is_regex_special_char('~')); EXPECT_FALSE(is_regex_special_char(0x00)); EXPECT_FALSE(is_regex_special_char(0x80)); EXPECT_FALSE(is_regex_special_char(0xFF)); EXPECT_EQ(hex_decode('0'), 0); EXPECT_EQ(hex_decode('9'), 9); EXPECT_EQ(hex_decode('a'), 10); EXPECT_EQ(hex_decode('A'), 10); EXPECT_EQ(hex_decode('f'), 15); EXPECT_EQ(hex_decode('F'), 15); EXPECT_EQ(hex_decode('g'), -1); EXPECT_EQ(hex_decode('G'), -1); EXPECT_EQ(hex_decode(' '), -1); EXPECT_EQ(hex_decode('\0'), -1); EXPECT_EQ(hex_decode('~'), -1); EXPECT_TRUE(ascii_streq_icase("", "")); EXPECT_TRUE(ascii_streq_icase("a", "a")); EXPECT_TRUE(ascii_streq_icase("a", "A")); EXPECT_TRUE(ascii_streq_icase("z", "Z")); EXPECT_TRUE(ascii_streq_icase("cx", "CX")); EXPECT_TRUE(ascii_streq_icase("ABC..XYZ", "abc..xyz")); EXPECT_TRUE(ascii_streq_icase("Ctrl", "CTRL")); EXPECT_FALSE(ascii_streq_icase("a", "")); EXPECT_FALSE(ascii_streq_icase("", "a")); EXPECT_FALSE(ascii_streq_icase("Ctrl+", "CTRL")); EXPECT_FALSE(ascii_streq_icase("Ctrl", "CTRL+")); EXPECT_FALSE(ascii_streq_icase("Ctrl", "Ctr")); EXPECT_FALSE(ascii_streq_icase("Ctrl", "CtrM")); const char s1[8] = "Ctrl+Up"; const char s2[8] = "CTRL+U_"; EXPECT_TRUE(mem_equal_icase(s1, s2, 0)); EXPECT_TRUE(mem_equal_icase(s1, s2, 1)); EXPECT_TRUE(mem_equal_icase(s1, s2, 2)); EXPECT_TRUE(mem_equal_icase(s1, s2, 3)); EXPECT_TRUE(mem_equal_icase(s1, s2, 4)); EXPECT_TRUE(mem_equal_icase(s1, s2, 5)); EXPECT_TRUE(mem_equal_icase(s1, s2, 6)); EXPECT_FALSE(mem_equal_icase(s1, s2, 7)); EXPECT_FALSE(mem_equal_icase(s1, s2, 8)); char *saved_locale = xstrdup(setlocale(LC_CTYPE, NULL)); setlocale(LC_CTYPE, "C"); for (int i = -1; i < 256; i++) { EXPECT_EQ(!!ascii_isalpha(i), !!isalpha(i)); EXPECT_EQ(!!ascii_isalnum(i), !!isalnum(i)); EXPECT_EQ(!!ascii_islower(i), !!islower(i)); EXPECT_EQ(!!ascii_isupper(i), !!isupper(i)); EXPECT_EQ(!!ascii_iscntrl(i), !!iscntrl(i)); EXPECT_EQ(!!ascii_isdigit(i), !!isdigit(i)); EXPECT_EQ(!!ascii_isblank(i), !!isblank(i)); EXPECT_EQ(!!ascii_isprint(i), !!isprint(i)); EXPECT_EQ(!!ascii_isxdigit(i), !!isxdigit(i)); if (i != '\v' && i != '\f') { EXPECT_EQ(!!ascii_isspace(i), !!isspace(i)); } } setlocale(LC_CTYPE, saved_locale); free(saved_locale); } static void test_string(void) { String s = STRING_INIT; EXPECT_EQ(s.len, 0); EXPECT_EQ(s.alloc, 0); EXPECT_PTREQ(s.buffer, NULL); char *cstr = string_clone_cstring(&s); EXPECT_STREQ(cstr, ""); free(cstr); EXPECT_EQ(s.len, 0); EXPECT_EQ(s.alloc, 0); EXPECT_PTREQ(s.buffer, NULL); string_insert_ch(&s, 0, 0x1F4AF); EXPECT_EQ(s.len, 4); EXPECT_STREQ(string_borrow_cstring(&s), "\xF0\x9F\x92\xAF"); string_add_str(&s, "test"); EXPECT_EQ(s.len, 8); EXPECT_STREQ(string_borrow_cstring(&s), "\xF0\x9F\x92\xAFtest"); string_remove(&s, 0, 5); EXPECT_EQ(s.len, 3); EXPECT_STREQ(string_borrow_cstring(&s), "est"); string_make_space(&s, 0, 1); EXPECT_EQ(s.len, 4); s.buffer[0] = 't'; EXPECT_STREQ(string_borrow_cstring(&s), "test"); string_clear(&s); EXPECT_EQ(s.len, 0); string_insert_ch(&s, 0, 0x0E01); EXPECT_EQ(s.len, 3); EXPECT_STREQ(string_borrow_cstring(&s), "\xE0\xB8\x81"); string_clear(&s); string_sprintf(&s, "%d %s\n", 88, "test"); EXPECT_EQ(s.len, 8); EXPECT_STREQ(string_borrow_cstring(&s), "88 test\n"); string_free(&s); EXPECT_EQ(s.len, 0); EXPECT_EQ(s.alloc, 0); EXPECT_PTREQ(s.buffer, NULL); for (size_t i = 0; i < 40; i++) { string_add_byte(&s, 'a'); } EXPECT_EQ(s.len, 40); cstr = string_steal_cstring(&s); EXPECT_EQ(s.len, 0); EXPECT_EQ(s.alloc, 0); EXPECT_PTREQ(s.buffer, NULL); EXPECT_STREQ(cstr, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); free(cstr); } static void test_string_view(void) { const StringView sv1 = STRING_VIEW("testing"); EXPECT_TRUE(string_view_equal_cstr(&sv1, "testing")); EXPECT_FALSE(string_view_equal_cstr(&sv1, "testin")); EXPECT_FALSE(string_view_equal_cstr(&sv1, "TESTING")); EXPECT_TRUE(string_view_has_literal_prefix(&sv1, "test")); EXPECT_TRUE(string_view_has_literal_prefix_icase(&sv1, "TEst")); EXPECT_FALSE(string_view_has_literal_prefix(&sv1, "TEst")); EXPECT_FALSE(string_view_has_literal_prefix_icase(&sv1, "TEst_")); const StringView sv2 = string_view(sv1.data, sv1.length); EXPECT_TRUE(string_view_equal(&sv1, &sv2)); const StringView sv3 = STRING_VIEW("\0test\0 ..."); EXPECT_TRUE(string_view_equal_strn(&sv3, "\0test\0 ...", 10)); EXPECT_TRUE(string_view_equal_literal(&sv3, "\0test\0 ...")); EXPECT_TRUE(string_view_has_prefix(&sv3, "\0test", 5)); EXPECT_TRUE(string_view_has_literal_prefix(&sv3, "\0test\0")); EXPECT_FALSE(string_view_equal_cstr(&sv3, "\0test\0 ...")); const StringView sv4 = sv3; EXPECT_TRUE(string_view_equal(&sv4, &sv3)); const StringView sv5 = string_view_from_cstring("foobar"); EXPECT_TRUE(string_view_equal_literal(&sv5, "foobar")); EXPECT_TRUE(string_view_has_literal_prefix(&sv5, "foo")); EXPECT_FALSE(string_view_equal_cstr(&sv5, "foo")); } static void test_number_width(void) { EXPECT_EQ(number_width(0), 1); EXPECT_EQ(number_width(-1), 2); EXPECT_EQ(number_width(420), 3); EXPECT_EQ(number_width(2147483647), 10); EXPECT_EQ(number_width(-2147483647), 11); } static void test_buf_parse_ulong(void) { { char ulong_max[64]; size_t ulong_max_len = xsnprintf(ulong_max, 64, "%lu", ULONG_MAX); unsigned long val = 88; size_t digits = buf_parse_ulong(ulong_max, ulong_max_len, &val); EXPECT_EQ(digits, ulong_max_len); EXPECT_EQ(val, ULONG_MAX); val = 99; ulong_max[ulong_max_len++] = '1'; digits = buf_parse_ulong(ulong_max, ulong_max_len, &val); EXPECT_EQ(digits, 0); EXPECT_EQ(val, 99); } unsigned long val; EXPECT_EQ(buf_parse_ulong("0", 1, &val), 1); EXPECT_EQ(val, 0); EXPECT_EQ(buf_parse_ulong("9876", 4, &val), 4); EXPECT_EQ(val, 9876); } static void test_str_to_int(void) { int val = 0; EXPECT_TRUE(str_to_int("-1", &val)); EXPECT_EQ(val, -1); EXPECT_TRUE(str_to_int("+1", &val)); EXPECT_EQ(val, 1); EXPECT_TRUE(str_to_int("0", &val)); EXPECT_EQ(val, 0); EXPECT_TRUE(str_to_int("1", &val)); EXPECT_EQ(val, 1); EXPECT_TRUE(str_to_int("+00299", &val)); EXPECT_EQ(val, 299); EXPECT_FALSE(str_to_int("", &val)); EXPECT_FALSE(str_to_int("100x", &val)); EXPECT_FALSE(str_to_int("+-100", &val)); EXPECT_FALSE(str_to_int("99999999999999999999999999999999", &val)); } static void test_str_to_size(void) { size_t val = 0; EXPECT_TRUE(str_to_size("100", &val)); EXPECT_EQ(val, 100); EXPECT_TRUE(str_to_size("0", &val)); EXPECT_EQ(val, 0); EXPECT_TRUE(str_to_size("000000001003", &val)); EXPECT_EQ(val, 1003); EXPECT_TRUE(str_to_size("29132", &val)); EXPECT_EQ(val, 29132); EXPECT_FALSE(str_to_size("", &val)); EXPECT_FALSE(str_to_size("100x", &val)); EXPECT_FALSE(str_to_size("-100", &val)); EXPECT_FALSE(str_to_size("99999999999999999999999999999999", &val)); } static void test_u_char_width(void) { // ASCII (1 column) EXPECT_EQ(u_char_width('a'), 1); EXPECT_EQ(u_char_width('z'), 1); EXPECT_EQ(u_char_width('A'), 1); EXPECT_EQ(u_char_width('Z'), 1); EXPECT_EQ(u_char_width(' '), 1); EXPECT_EQ(u_char_width('!'), 1); EXPECT_EQ(u_char_width('/'), 1); EXPECT_EQ(u_char_width('^'), 1); EXPECT_EQ(u_char_width('`'), 1); EXPECT_EQ(u_char_width('~'), 1); // Rendered in caret notation (2 columns) EXPECT_EQ(u_char_width('\0'), 2); EXPECT_EQ(u_char_width('\r'), 2); EXPECT_EQ(u_char_width(0x1F), 2); // Unprintable (rendered as -- 4 columns) EXPECT_EQ(u_char_width(0x0080), 4); EXPECT_EQ(u_char_width(0xDFFF), 4); // Zero width (0 columns) EXPECT_EQ(u_char_width(0xAA31), 0); EXPECT_EQ(u_char_width(0xAA32), 0); // Double width (2 columns) EXPECT_EQ(u_char_width(0x2757), 2); EXPECT_EQ(u_char_width(0x312F), 2); // Double width but unassigned (rendered as -- 4 columns) EXPECT_EQ(u_char_width(0x30000), 4); EXPECT_EQ(u_char_width(0x3A009), 4); EXPECT_EQ(u_char_width(0x3FFFD), 4); // 1 column character >= 0x1100 EXPECT_EQ(u_char_width(0x104B3), 1); } static void test_u_to_lower(void) { EXPECT_EQ(u_to_lower('A'), 'a'); EXPECT_EQ(u_to_lower('Z'), 'z'); EXPECT_EQ(u_to_lower('a'), 'a'); EXPECT_EQ(u_to_lower('0'), '0'); EXPECT_EQ(u_to_lower('~'), '~'); EXPECT_EQ(u_to_lower('@'), '@'); EXPECT_EQ(u_to_lower('\0'), '\0'); } static void test_u_is_upper(void) { EXPECT_TRUE(u_is_upper('A')); EXPECT_TRUE(u_is_upper('Z')); EXPECT_FALSE(u_is_upper('a')); EXPECT_FALSE(u_is_upper('z')); EXPECT_FALSE(u_is_upper('0')); EXPECT_FALSE(u_is_upper('9')); EXPECT_FALSE(u_is_upper('@')); EXPECT_FALSE(u_is_upper('[')); EXPECT_FALSE(u_is_upper('{')); EXPECT_FALSE(u_is_upper('\0')); EXPECT_FALSE(u_is_upper('\t')); EXPECT_FALSE(u_is_upper(' ')); EXPECT_FALSE(u_is_upper(0x00E0)); EXPECT_FALSE(u_is_upper(0x00E7)); EXPECT_FALSE(u_is_upper(0x1D499)); EXPECT_FALSE(u_is_upper(0x1F315)); EXPECT_FALSE(u_is_upper(0x10ffff)); } static void test_u_is_cntrl(void) { EXPECT_TRUE(u_is_cntrl(0x00)); EXPECT_TRUE(u_is_cntrl(0x09)); EXPECT_TRUE(u_is_cntrl(0x0D)); EXPECT_TRUE(u_is_cntrl(0x1F)); EXPECT_TRUE(u_is_cntrl(0x7F)); EXPECT_TRUE(u_is_cntrl(0x80)); EXPECT_TRUE(u_is_cntrl(0x81)); EXPECT_TRUE(u_is_cntrl(0x9E)); EXPECT_TRUE(u_is_cntrl(0x9F)); EXPECT_FALSE(u_is_cntrl(0x20)); EXPECT_FALSE(u_is_cntrl(0x21)); EXPECT_FALSE(u_is_cntrl(0x7E)); EXPECT_FALSE(u_is_cntrl(0xA0)); EXPECT_FALSE(u_is_cntrl(0x41)); EXPECT_FALSE(u_is_cntrl(0x61)); EXPECT_FALSE(u_is_cntrl(0xFF)); } static void test_u_is_zero_width(void) { // Default ignorable codepoints: EXPECT_TRUE(u_is_zero_width(0x034F)); EXPECT_TRUE(u_is_zero_width(0x061C)); EXPECT_TRUE(u_is_zero_width(0x115F)); EXPECT_TRUE(u_is_zero_width(0x1160)); EXPECT_TRUE(u_is_zero_width(0x180B)); EXPECT_TRUE(u_is_zero_width(0x200B)); EXPECT_TRUE(u_is_zero_width(0x202E)); EXPECT_TRUE(u_is_zero_width(0xFEFF)); EXPECT_TRUE(u_is_zero_width(0xE0000)); EXPECT_TRUE(u_is_zero_width(0xE0FFF)); // Non-spacing marks: EXPECT_TRUE(u_is_zero_width(0x0300)); EXPECT_TRUE(u_is_zero_width(0x0730)); EXPECT_TRUE(u_is_zero_width(0x11839)); EXPECT_TRUE(u_is_zero_width(0x1183A)); EXPECT_TRUE(u_is_zero_width(0xE01EF)); // Not zero-width: EXPECT_FALSE(u_is_zero_width(0x0000)); EXPECT_FALSE(u_is_zero_width('Z')); } static void test_u_is_special_whitespace(void) { EXPECT_FALSE(u_is_special_whitespace(' ')); EXPECT_FALSE(u_is_special_whitespace('\t')); EXPECT_FALSE(u_is_special_whitespace('\n')); EXPECT_FALSE(u_is_special_whitespace('a')); EXPECT_FALSE(u_is_special_whitespace(0x1680)); EXPECT_FALSE(u_is_special_whitespace(0x3000)); EXPECT_TRUE(u_is_special_whitespace(0x00A0)); EXPECT_TRUE(u_is_special_whitespace(0x00AD)); EXPECT_TRUE(u_is_special_whitespace(0x2000)); EXPECT_TRUE(u_is_special_whitespace(0x200a)); EXPECT_TRUE(u_is_special_whitespace(0x2028)); EXPECT_TRUE(u_is_special_whitespace(0x2029)); EXPECT_TRUE(u_is_special_whitespace(0x202f)); EXPECT_TRUE(u_is_special_whitespace(0x205f)); } static void test_u_is_unprintable(void) { // Private-use characters ------------------------------------------ // (https://www.unicode.org/faq/private_use.html#pua2) // There are three ranges of private-use characters in the standard. // The main range in the BMP is U+E000..U+F8FF, containing 6,400 // private-use characters. EXPECT_TRUE(u_is_unprintable(0xE000)); EXPECT_TRUE(u_is_unprintable(0xF8FF)); // ... there are also two large ranges of supplementary private-use // characters, consisting of most of the code points on planes 15 // and 16: U+F0000..U+FFFFD and U+100000..U+10FFFD. Together those // ranges allocate another 131,068 private-use characters. EXPECT_TRUE(u_is_unprintable(0xF0000)); EXPECT_TRUE(u_is_unprintable(0xFFFFD)); EXPECT_TRUE(u_is_unprintable(0x100000)); EXPECT_TRUE(u_is_unprintable(0x10FFFd)); // Non-characters -------------------------------------------------- // (https://www.unicode.org/faq/private_use.html#noncharacters) unsigned int noncharacter_count = 0; // "A contiguous range of 32 noncharacters: U+FDD0..U+FDEF in the BMP" for (CodePoint u = 0xFDD0; u <= 0xFDEF; u++) { EXPECT_TRUE(u_is_unprintable(u)); noncharacter_count++; } // "The last two code points of the BMP, U+FFFE and U+FFFF" EXPECT_TRUE(u_is_unprintable(0xFFFE)); EXPECT_TRUE(u_is_unprintable(0xFFFF)); noncharacter_count += 2; // "The last two code points of each of the 16 supplementary planes: // U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, ... U+10FFFE, U+10FFFF" for (CodePoint step = 1; step <= 16; step++) { const CodePoint u = (0x10000 * step) + 0xFFFE; EXPECT_TRUE(u_is_unprintable(u)); EXPECT_TRUE(u_is_unprintable(u + 1)); noncharacter_count += 2; } // "Q: How many noncharacters does Unicode have?" // "A: Exactly 66" EXPECT_EQ(noncharacter_count, 66); } static void test_u_str_width(void) { EXPECT_EQ(u_str_width("foo"), 3); EXPECT_EQ ( 7, u_str_width ( "\xE0\xB8\x81\xE0\xB8\xB3\xE0\xB9\x81\xE0\xB8\x9E\xE0\xB8" "\x87\xE0\xB8\xA1\xE0\xB8\xB5\xE0\xB8\xAB\xE0\xB8\xB9" ) ); } static void test_u_set_char(void) { char buf[16]; size_t i; MEMZERO(&buf); i = 0; u_set_char(buf, &i, 'a'); EXPECT_EQ(i, 1); EXPECT_EQ(buf[0], 'a'); i = 0; u_set_char(buf, &i, 0x00DF); EXPECT_EQ(i, 2); EXPECT_EQ(memcmp(buf, "\xC3\x9F", 2), 0); i = 0; u_set_char(buf, &i, 0x0E01); EXPECT_EQ(i, 3); EXPECT_EQ(memcmp(buf, "\xE0\xB8\x81", 3), 0); i = 0; u_set_char(buf, &i, 0x1F914); EXPECT_EQ(i, 4); EXPECT_EQ(memcmp(buf, "\xF0\x9F\xA4\x94", 4), 0); i = 0; u_set_char(buf, &i, 0x10FFFF); EXPECT_EQ(i, 4); // Note: string separated to prevent "-Wtrigraphs" warning EXPECT_EQ(memcmp(buf, "<" "?" "?" ">", 4), 0); } static void test_u_set_ctrl(void) { char buf[16]; size_t i; MEMZERO(&buf); i = 0; u_set_ctrl(buf, &i, '\0'); EXPECT_EQ(i, 2); EXPECT_EQ(memcmp(buf, "^@", 3), 0); i = 0; u_set_ctrl(buf, &i, '\t'); EXPECT_EQ(i, 2); EXPECT_EQ(memcmp(buf, "^I", 3), 0); i = 0; u_set_ctrl(buf, &i, 0x1F); EXPECT_EQ(i, 2); EXPECT_EQ(memcmp(buf, "^_", 3), 0); i = 0; u_set_ctrl(buf, &i, 0x7F); EXPECT_EQ(i, 2); EXPECT_EQ(memcmp(buf, "^?", 3), 0); } static void test_u_prev_char(void) { const unsigned char *buf = "深圳市"; size_t idx = 9; CodePoint c = u_prev_char(buf, &idx); EXPECT_EQ(c, 0x5E02); EXPECT_EQ(idx, 6); c = u_prev_char(buf, &idx); EXPECT_EQ(c, 0x5733); EXPECT_EQ(idx, 3); c = u_prev_char(buf, &idx); EXPECT_EQ(c, 0x6DF1); EXPECT_EQ(idx, 0); idx = 1; c = u_prev_char(buf, &idx); EXPECT_EQ(c, -((CodePoint)0xE6UL)); EXPECT_EQ(idx, 0); buf = "Shenzhen"; idx = 8; c = u_prev_char(buf, &idx); EXPECT_EQ(c, 'n'); EXPECT_EQ(idx, 7); c = u_prev_char(buf, &idx); EXPECT_EQ(c, 'e'); EXPECT_EQ(idx, 6); buf = "🥣🥤"; idx = 8; c = u_prev_char(buf, &idx); EXPECT_EQ(c, 0x1F964); EXPECT_EQ(idx, 4); c = u_prev_char(buf, &idx); EXPECT_EQ(c, 0x1F963); EXPECT_EQ(idx, 0); buf = "\xF0\xF5"; idx = 2; c = u_prev_char(buf, &idx); EXPECT_EQ(c, -((CodePoint)buf[1])); EXPECT_EQ(idx, 1); c = u_prev_char(buf, &idx); EXPECT_EQ(c, -((CodePoint)buf[0])); EXPECT_EQ(idx, 0); buf = "\xF5\xF0"; idx = 2; c = u_prev_char(buf, &idx); EXPECT_EQ(c, -((CodePoint)buf[1])); EXPECT_EQ(idx, 1); c = u_prev_char(buf, &idx); EXPECT_EQ(c, -((CodePoint)buf[0])); EXPECT_EQ(idx, 0); } static void test_ptr_array(void) { PointerArray a = PTR_ARRAY_INIT; ptr_array_add(&a, NULL); ptr_array_add(&a, NULL); ptr_array_add(&a, xstrdup("foo")); ptr_array_add(&a, NULL); ptr_array_add(&a, xstrdup("bar")); ptr_array_add(&a, NULL); ptr_array_add(&a, NULL); EXPECT_EQ(a.count, 7); ptr_array_trim_nulls(&a); EXPECT_EQ(a.count, 4); EXPECT_STREQ(a.ptrs[0], "foo"); EXPECT_NULL(a.ptrs[1]); EXPECT_STREQ(a.ptrs[2], "bar"); EXPECT_NULL(a.ptrs[3]); ptr_array_trim_nulls(&a); EXPECT_EQ(a.count, 4); ptr_array_free(&a); EXPECT_EQ(a.count, 0); ptr_array_trim_nulls(&a); EXPECT_EQ(a.count, 0); } static void test_hashset(void) { static const char *const strings[] = { "foo", "Foo", "bar", "quux", "etc", "\t\xff\x80\b", "\t\t\t", "\x01\x02\x03\xfe\xff", #if __STDC_VERSION__ >= 201112L u8"ภาษาไทย", u8"中文", u8"日本語", #endif }; HashSet set; hashset_init(&set, ARRAY_COUNT(strings), false); EXPECT_NULL(hashset_get(&set, "foo", 3)); hashset_add_many(&set, (char**)strings, ARRAY_COUNT(strings)); EXPECT_NONNULL(hashset_get(&set, "\t\xff\x80\b", 4)); EXPECT_NONNULL(hashset_get(&set, "foo", 3)); EXPECT_NONNULL(hashset_get(&set, "Foo", 3)); EXPECT_NULL(hashset_get(&set, "FOO", 3)); EXPECT_NULL(hashset_get(&set, "", 0)); EXPECT_NULL(hashset_get(&set, NULL, 0)); EXPECT_NULL(hashset_get(&set, "\0", 1)); const char *last_string = strings[ARRAY_COUNT(strings) - 1]; EXPECT_NONNULL(hashset_get(&set, last_string, strlen(last_string))); FOR_EACH_I(i, strings) { const char *str = strings[i]; const size_t len = strlen(str); EXPECT_NONNULL(hashset_get(&set, str, len)); EXPECT_NULL(hashset_get(&set, str, len - 1)); EXPECT_NULL(hashset_get(&set, str + 1, len - 1)); } hashset_free(&set); MEMZERO(&set); hashset_init(&set, ARRAY_COUNT(strings), true); hashset_add_many(&set, (char**)strings, ARRAY_COUNT(strings)); EXPECT_NONNULL(hashset_get(&set, "foo", 3)); EXPECT_NONNULL(hashset_get(&set, "FOO", 3)); EXPECT_NONNULL(hashset_get(&set, "fOO", 3)); hashset_free(&set); MEMZERO(&set); // Check that hashset_add() returns existing entries instead of // inserting duplicates hashset_init(&set, 0, false); EXPECT_EQ(set.nr_entries, 0); HashSetEntry *e1 = hashset_add(&set, "foo", 3); EXPECT_EQ(e1->str_len, 3); EXPECT_STREQ(e1->str, "foo"); EXPECT_EQ(set.nr_entries, 1); HashSetEntry *e2 = hashset_add(&set, "foo", 3); EXPECT_PTREQ(e1, e2); EXPECT_EQ(set.nr_entries, 1); hashset_free(&set); } static void test_round_up(void) { EXPECT_EQ(ROUND_UP(3, 8), 8); EXPECT_EQ(ROUND_UP(8, 8), 8); EXPECT_EQ(ROUND_UP(9, 8), 16); EXPECT_EQ(ROUND_UP(0, 8), 0); EXPECT_EQ(ROUND_UP(0, 16), 0); EXPECT_EQ(ROUND_UP(1, 16), 16); EXPECT_EQ(ROUND_UP(123, 16), 128); EXPECT_EQ(ROUND_UP(4, 64), 64); EXPECT_EQ(ROUND_UP(80, 64), 128); EXPECT_EQ(ROUND_UP(256, 256), 256); EXPECT_EQ(ROUND_UP(257, 256), 512); EXPECT_EQ(ROUND_UP(8000, 256), 8192); } static void test_round_size_to_next_power_of_2(void) { EXPECT_UINT_EQ(round_size_to_next_power_of_2(0), 0); EXPECT_UINT_EQ(round_size_to_next_power_of_2(1), 1); EXPECT_UINT_EQ(round_size_to_next_power_of_2(2), 2); EXPECT_UINT_EQ(round_size_to_next_power_of_2(3), 4); EXPECT_UINT_EQ(round_size_to_next_power_of_2(4), 4); EXPECT_UINT_EQ(round_size_to_next_power_of_2(5), 8); EXPECT_UINT_EQ(round_size_to_next_power_of_2(8), 8); EXPECT_UINT_EQ(round_size_to_next_power_of_2(9), 16); EXPECT_UINT_EQ(round_size_to_next_power_of_2(17), 32); EXPECT_UINT_EQ(round_size_to_next_power_of_2(61), 64); EXPECT_UINT_EQ(round_size_to_next_power_of_2(64), 64); EXPECT_UINT_EQ(round_size_to_next_power_of_2(200), 256); EXPECT_UINT_EQ(round_size_to_next_power_of_2(1000), 1024); EXPECT_UINT_EQ(round_size_to_next_power_of_2(5500), 8192); } static void test_bitop(void) { EXPECT_EQ(bit_popcount_u32(0), 0); EXPECT_EQ(bit_popcount_u32(1), 1); EXPECT_EQ(bit_popcount_u32(11), 3); EXPECT_EQ(bit_popcount_u32(128), 1); EXPECT_EQ(bit_popcount_u32(255), 8); EXPECT_EQ(bit_popcount_u32(UINT32_MAX), 32); EXPECT_EQ(bit_popcount_u32(UINT32_MAX - 1), 31); EXPECT_EQ(bit_popcount_u64(0), 0); EXPECT_EQ(bit_popcount_u64(1), 1); EXPECT_EQ(bit_popcount_u64(255), 8); EXPECT_EQ(bit_popcount_u64(UINT64_MAX), 64); EXPECT_EQ(bit_popcount_u64(UINT64_MAX - 1), 63); EXPECT_EQ(bit_popcount_u64(U64(0xFFFFFFFFFF)), 40); EXPECT_EQ(bit_popcount_u64(U64(0x10000000000)), 1); EXPECT_EQ(bit_count_leading_zeros_u64(1), 63); EXPECT_EQ(bit_count_leading_zeros_u64(4), 61); EXPECT_EQ(bit_count_leading_zeros_u64(127), 57); EXPECT_EQ(bit_count_leading_zeros_u64(128), 56); EXPECT_EQ(bit_count_leading_zeros_u64(UINT64_MAX), 0); EXPECT_EQ(bit_count_leading_zeros_u64(UINT64_MAX - 1), 0); EXPECT_EQ(bit_count_leading_zeros_u32(1), 31); EXPECT_EQ(bit_count_leading_zeros_u32(4), 29); EXPECT_EQ(bit_count_leading_zeros_u32(127), 25); EXPECT_EQ(bit_count_leading_zeros_u32(128), 24); EXPECT_EQ(bit_count_leading_zeros_u32(UINT32_MAX), 0); EXPECT_EQ(bit_count_leading_zeros_u32(UINT32_MAX - 1), 0); EXPECT_EQ(bit_count_trailing_zeros_u32(1), 0); EXPECT_EQ(bit_count_trailing_zeros_u32(2), 1); EXPECT_EQ(bit_count_trailing_zeros_u32(3), 0); EXPECT_EQ(bit_count_trailing_zeros_u32(4), 2); EXPECT_EQ(bit_count_trailing_zeros_u32(8), 3); EXPECT_EQ(bit_count_trailing_zeros_u32(13), 0); EXPECT_EQ(bit_count_trailing_zeros_u32(16), 4); EXPECT_EQ(bit_count_trailing_zeros_u32(U32(0xFFFFFFFE)), 1); EXPECT_EQ(bit_count_trailing_zeros_u32(U32(0x10000000)), 28); EXPECT_EQ(bit_count_trailing_zeros_u32(UINT32_MAX), 0); EXPECT_EQ(bit_count_trailing_zeros_u32(UINT32_MAX - 0xFF), 8); EXPECT_EQ(bit_count_trailing_zeros_u64(1), 0); EXPECT_EQ(bit_count_trailing_zeros_u64(2), 1); EXPECT_EQ(bit_count_trailing_zeros_u64(3), 0); EXPECT_EQ(bit_count_trailing_zeros_u64(16), 4); EXPECT_EQ(bit_count_trailing_zeros_u64(U64(0xFFFFFFFE)), 1); EXPECT_EQ(bit_count_trailing_zeros_u64(U64(0x10000000)), 28); EXPECT_EQ(bit_count_trailing_zeros_u64(U64(0x100000000000)), 44); EXPECT_EQ(bit_count_trailing_zeros_u64(UINT64_MAX), 0); EXPECT_EQ(bit_find_first_set_u32(0), 0); EXPECT_EQ(bit_find_first_set_u32(1), 1); EXPECT_EQ(bit_find_first_set_u32(2), 2); EXPECT_EQ(bit_find_first_set_u32(3), 1); EXPECT_EQ(bit_find_first_set_u32(64), 7); EXPECT_EQ(bit_find_first_set_u32(U32(1) << 31), 32); EXPECT_EQ(bit_find_first_set_u64(0), 0); EXPECT_EQ(bit_find_first_set_u64(1), 1); EXPECT_EQ(bit_find_first_set_u64(2), 2); EXPECT_EQ(bit_find_first_set_u64(3), 1); EXPECT_EQ(bit_find_first_set_u64(64), 7); EXPECT_EQ(bit_find_first_set_u64(U64(1) << 63), 64); } static void test_path_dirname_and_path_basename(void) { static const struct { const char *path; const char *dirname; const char *basename; } tests[] = { {"/home/user/example.txt", "/home/user", "example.txt"}, {"./../dir/example.txt", "./../dir", "example.txt"}, {"/usr/bin/", "/usr/bin", ""}, {"example.txt", ".", "example.txt"}, {"/usr/lib", "/usr", "lib"}, {"/usr", "/", "usr"}, {"usr", ".", "usr"}, {"/", "/", ""}, {".", ".", "."}, {"..", ".", ".."}, {"", ".", ""}, }; FOR_EACH_I(i, tests) { char *dir = path_dirname(tests[i].path); IEXPECT_STREQ(dir, tests[i].dirname); free(dir); IEXPECT_STREQ(path_basename(tests[i].path), tests[i].basename); } } static void test_path_absolute(void) { char *path = path_absolute("///dev///"); ASSERT_NONNULL(path); EXPECT_STREQ(path, "/dev/"); free(path); const char *linkpath = "./build/../build/test/test-symlink"; if (symlink("../../README.md", linkpath) != 0) { TEST_FAIL("symlink() failed: %s", strerror(errno)); return; } path = path_absolute(linkpath); EXPECT_EQ(unlink(linkpath), 0); ASSERT_NONNULL(path); EXPECT_STREQ(path_basename(path), "README.md"); free(path); char buf[8192 + 1]; memset(buf, 'a', sizeof(buf)); buf[0] = '/'; buf[8192] = '\0'; errno = 0; EXPECT_NULL(path_absolute(buf)); EXPECT_EQ(errno, ENAMETOOLONG); } static void test_path_parent(void) { StringView sv = STRING_VIEW("/a/foo/bar/etc/file"); EXPECT_EQ(sv.length, 19); EXPECT_TRUE(path_parent(&sv)); EXPECT_EQ(sv.length, 14); EXPECT_TRUE(path_parent(&sv)); EXPECT_EQ(sv.length, 10); EXPECT_TRUE(path_parent(&sv)); EXPECT_EQ(sv.length, 6); EXPECT_TRUE(path_parent(&sv)); EXPECT_EQ(sv.length, 2); EXPECT_TRUE(path_parent(&sv)); EXPECT_EQ(sv.length, 1); EXPECT_FALSE(path_parent(&sv)); EXPECT_EQ(sv.length, 1); StringView sv2 = STRING_VIEW("/etc/foo/x/y/"); EXPECT_EQ(sv2.length, 13); EXPECT_TRUE(path_parent(&sv2)); EXPECT_EQ(sv2.length, 10); EXPECT_TRUE(path_parent(&sv2)); EXPECT_EQ(sv2.length, 8); EXPECT_TRUE(path_parent(&sv2)); EXPECT_EQ(sv2.length, 4); EXPECT_TRUE(path_parent(&sv2)); EXPECT_EQ(sv2.length, 1); EXPECT_FALSE(path_parent(&sv2)); EXPECT_EQ(sv2.length, 1); } static void test_size_multiply_overflows(void) { size_t r = 0; EXPECT_FALSE(size_multiply_overflows(10, 20, &r)); EXPECT_UINT_EQ(r, 200); EXPECT_FALSE(size_multiply_overflows(0, 0, &r)); EXPECT_UINT_EQ(r, 0); EXPECT_FALSE(size_multiply_overflows(1, 0, &r)); EXPECT_UINT_EQ(r, 0); EXPECT_FALSE(size_multiply_overflows(0, 1, &r)); EXPECT_UINT_EQ(r, 0); EXPECT_FALSE(size_multiply_overflows(0, SIZE_MAX, &r)); EXPECT_UINT_EQ(r, 0); EXPECT_FALSE(size_multiply_overflows(SIZE_MAX, 0, &r)); EXPECT_UINT_EQ(r, 0); EXPECT_FALSE(size_multiply_overflows(1, SIZE_MAX, &r)); EXPECT_UINT_EQ(r, SIZE_MAX); EXPECT_FALSE(size_multiply_overflows(2, SIZE_MAX / 3, &r)); EXPECT_UINT_EQ(r, 2 * (SIZE_MAX / 3)); EXPECT_TRUE(size_multiply_overflows(SIZE_MAX, 2, &r)); EXPECT_TRUE(size_multiply_overflows(2, SIZE_MAX, &r)); EXPECT_TRUE(size_multiply_overflows(3, SIZE_MAX / 2, &r)); EXPECT_TRUE(size_multiply_overflows(32767, SIZE_MAX, &r)); EXPECT_TRUE(size_multiply_overflows(SIZE_MAX, SIZE_MAX, &r)); EXPECT_TRUE(size_multiply_overflows(SIZE_MAX, SIZE_MAX / 2, &r)); } static void test_size_add_overflows(void) { size_t r = 0; EXPECT_FALSE(size_add_overflows(10, 20, &r)); EXPECT_UINT_EQ(r, 30); EXPECT_FALSE(size_add_overflows(SIZE_MAX, 0, &r)); EXPECT_UINT_EQ(r, SIZE_MAX); EXPECT_TRUE(size_add_overflows(SIZE_MAX, 1, &r)); EXPECT_TRUE(size_add_overflows(SIZE_MAX, 16, &r)); EXPECT_TRUE(size_add_overflows(SIZE_MAX, SIZE_MAX, &r)); EXPECT_TRUE(size_add_overflows(SIZE_MAX, SIZE_MAX / 2, &r)); } static void test_mem_intern(void) { const char *ptrs[256]; char str[8]; for (size_t i = 0; i < ARRAY_COUNT(ptrs); i++) { size_t len = xsnprintf(str, sizeof str, "%zu", i); ptrs[i] = mem_intern(str, len); } EXPECT_STREQ(ptrs[0], "0"); EXPECT_STREQ(ptrs[1], "1"); EXPECT_STREQ(ptrs[101], "101"); EXPECT_STREQ(ptrs[255], "255"); for (size_t i = 0; i < ARRAY_COUNT(ptrs); i++) { size_t len = xsnprintf(str, sizeof str, "%zu", i); const char *ptr = mem_intern(str, len); EXPECT_PTREQ(ptr, ptrs[i]); } } static void test_read_file(void) { char *buf = NULL; ssize_t size = read_file("/dev", &buf); EXPECT_EQ(size, -1); EXPECT_EQ(errno, EISDIR); EXPECT_NULL(buf); size = read_file("test/data/3lines.txt", &buf); ASSERT_NONNULL(buf); EXPECT_EQ(size, 26); size_t pos = 0; const char *line = buf_next_line(buf, &pos, size); EXPECT_STREQ(line, "line #1"); EXPECT_EQ(pos, 8); line = buf_next_line(buf, &pos, size); EXPECT_STREQ(line, " line #2"); EXPECT_EQ(pos, 17); line = buf_next_line(buf, &pos, size); EXPECT_STREQ(line, " line #3"); EXPECT_EQ(pos, 26); free(buf); } DISABLE_WARNING("-Wmissing-prototypes") void test_util(void) { test_macros(); test_ascii(); test_string(); test_string_view(); test_number_width(); test_buf_parse_ulong(); test_str_to_int(); test_str_to_size(); test_u_char_width(); test_u_to_lower(); test_u_is_upper(); test_u_is_cntrl(); test_u_is_zero_width(); test_u_is_special_whitespace(); test_u_is_unprintable(); test_u_str_width(); test_u_set_char(); test_u_set_ctrl(); test_u_prev_char(); test_ptr_array(); test_hashset(); test_round_up(); test_round_size_to_next_power_of_2(); test_bitop(); test_path_dirname_and_path_basename(); test_path_absolute(); test_path_parent(); test_size_multiply_overflows(); test_size_add_overflows(); test_mem_intern(); test_read_file(); }