pax_global_header00006660000000000000000000000064144335161470014522gustar00rootroot0000000000000052 comment=064147fc83e5587bf793cb8ccbfe51525c6b5489 mle-1.7.2/000077500000000000000000000000001443351614700123065ustar00rootroot00000000000000mle-1.7.2/.github/000077500000000000000000000000001443351614700136465ustar00rootroot00000000000000mle-1.7.2/.github/workflows/000077500000000000000000000000001443351614700157035ustar00rootroot00000000000000mle-1.7.2/.github/workflows/mle_test.yml000066400000000000000000000003301443351614700202360ustar00rootroot00000000000000name: mle_test on: [push, pull_request] jobs: mle_test_job: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: submodules: recursive - run: make test mle_vendor=1 mle-1.7.2/.gitignore000066400000000000000000000000251443351614700142730ustar00rootroot00000000000000.gdb_history *.o mle mle-1.7.2/.gitmodules000066400000000000000000000004001443351614700144550ustar00rootroot00000000000000[submodule "uthash"] path = vendor/uthash url = https://github.com/troydhanson/uthash.git [submodule "lua"] path = vendor/lua url = https://github.com/lua/lua.git [submodule "pcre2"] path = vendor/pcre2 url = https://github.com/PhilipHazel/pcre2.git mle-1.7.2/LICENSE000066400000000000000000000261361443351614700133230ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. mle-1.7.2/Makefile000066400000000000000000000045141443351614700137520ustar00rootroot00000000000000prefix?=/usr/local mle_cflags:=-std=c99 -Wall -Wextra -pedantic -Wno-pointer-arith -Wno-unused-result -Wno-unused-parameter -g -O0 -D_GNU_SOURCE -DPCRE2_CODE_UNIT_WIDTH=8 -I. $(CFLAGS) mle_ldflags:=$(LDFLAGS) mle_dynamic_libs:=-lpcre2-8 -llua5.4 mle_static_libs:=vendor/pcre2/.libs/libpcre2-8.a vendor/lua/liblua5.4.a mle_ldlibs:=-lm $(LDLIBS) mle_objects:=$(patsubst %.c,%.o,$(wildcard *.c)) mle_objects_no_main:=$(filter-out main.o,$(mle_objects)) mle_func_tests:=$(wildcard tests/func/test_*.sh)) mle_unit_tests:=$(patsubst %.c,%,$(wildcard tests/unit/test_*.c)) mle_unit_test_objects:=$(patsubst %.c,%.o,$(wildcard tests/unit/test_*.c)) mle_unit_test_all:=tests/unit/test mle_vendor_deps:= mle_static_var:= ifdef mle_static mle_static_var:=-static endif ifdef mle_vendor mle_ldlibs:=$(mle_static_libs) $(mle_ldlibs) mle_cflags:=-Ivendor/pcre2/src -Ivendor -Ivendor/uthash/src $(mle_cflags) mle_vendor_deps:=$(mle_static_libs) else mle_ldlibs:=$(mle_dynamic_libs) $(mle_ldlibs) endif all: mle mle: $(mle_vendor_deps) $(mle_objects) termbox2.h $(CC) $(mle_static_var) $(mle_cflags) $(mle_objects) $(mle_ldflags) $(mle_ldlibs) -o mle $(mle_objects): %.o: %.c $(CC) -c $(mle_cflags) $< -o $@ $(mle_vendor_deps): $(MAKE) -C vendor $(mle_unit_test_objects): %.o: %.c $(CC) -DTEST_NAME=$(basename $(notdir $<)) -c $(mle_cflags) -rdynamic $< -o $@ $(mle_unit_test_all): $(mle_objects_no_main) $(mle_unit_test_objects) $(mle_unit_test_all).c $(CC) $(mle_cflags) -rdynamic $(mle_unit_test_all).c $(mle_objects_no_main) $(mle_unit_test_objects) $(mle_ldflags) $(mle_ldlibs) -ldl -o $@ $(mle_unit_tests): %: $(mle_unit_test_all) { echo "#!/bin/sh"; echo "$(abspath $(mle_unit_test_all)) $(notdir $@)"; } >$@ && chmod +x $@ test: mle $(mle_unit_tests) ./mle -v && export MLE=$$(pwd)/mle && $(MAKE) -C tests sloc: find . -name '*.c' -or -name '*.h' | \ grep -Pv '(termbox|test|ut|lua)' | \ xargs -rn1 cat | \ wc -l install: mle install -v -d $(DESTDIR)$(prefix)/bin install -v -m 755 mle $(DESTDIR)$(prefix)/bin/mle uscript: php uscript.inc.php >uscript.inc clean_quick: rm -f mle $(mle_objects) $(mle_unit_test_objects) $(mle_unit_tests) $(mle_unit_test_all) clean: rm -f mle $(mle_objects) $(mle_vendor_deps) $(mle_unit_test_objects) $(mle_unit_tests) $(mle_unit_test_all) $(MAKE) -C vendor clean .PHONY: all test sloc install uscript clean mle-1.7.2/README.md000066400000000000000000000142671443351614700135770ustar00rootroot00000000000000# mle mle is a small, flexible, terminal-based text editor written in C. Runs on Linux, Windows (Cygwin or WSL), FreeBSD, macOS, and more. [![Build Status](https://travis-ci.org/adsr/mle.svg?branch=master)](https://travis-ci.org/adsr/mle) [![Packaging status](https://repology.org/badge/vertical-allrepos/mle.svg)](https://repology.org/project/mle/versions) ### Demos [![asciicast](https://i.imgur.com/PZocaOT.png)](https://asciinema.org/a/162536) * [Emacs-style jump](https://i.imgur.com/atS11HX.gif) * [Large file benchmark](http://i.imgur.com/VGGMmGg.gif) * [Older demos](http://imgur.com/a/ZBmmQ) ### Aims * Keep codebase small and hackable * Minimize build-time and run-time dependencies * Make extensible and configurable * Favor simplicity over portability * Use shell commands to enhance functionality (e.g., grep, tree) ### Features * Small codebase (~10k sloc) * Full UTF-8 support * Syntax highlighting * Stackable key maps (modes) * Extensible via [Lua](https://www.lua.org) * Scriptable rc file * Key macros * Multiple splittable windows * Regex search and replace * Large file support * Incremental search * Linear undo and redo * Multiple cursors * Auto indent * Headless mode * Navigation via [ctags](https://github.com/universal-ctags/ctags) * Movement via [less](https://www.gnu.org/software/less/) * Fuzzy file search via [fzf](https://github.com/junegunn/fzf) * File browsing via [tree](http://mama.indstate.edu/users/ice/tree/) * File grep via [grep](https://www.gnu.org/software/grep/) * String manip via [perl](https://www.perl.org/) ### Building $ sudo apt install git build-essential libtool automake # or equivalent $ $ git clone --recursive https://github.com/adsr/mle.git $ cd mle $ make mle_vendor=1 To build a completely static binary, try `make mle_vendor=1 mle_static=1`. You can also run plain `make` to link against system libraries instead of `vendor/`. Note this requires the following packages to be installed: uthash-dev liblua5.4-dev libpcre2-dev To install to `/usr/local/bin`: $ make install To install to a custom directory, supply `prefix`, e.g.: $ make install prefix=/usr # /usr/bin/mle ### Installing from a repo mle may be available to install via your system's package manager. # apt install mle # Ubuntu and Debian-based distros # dnf install mle # CentOS, RHEL, Fedora-based distros # pkg install mle # FreeBSD # yay -S mle # Arch (via AUR) # snap install mle # all major Linux distros # nix-env -i mle # NixOS (via nixpkgs) # apk add mle # Alpine # xbps-install mle # Void # brew install mle # macOS (Homebrew) # port install mle # macOS (MacPorts) # setup-x86.exe -q -P mle # Cygwin ### Basic usage $ mle # Open blank buffer $ mle one.c # Edit one.c $ mle one.c:100 # Edit one.c at line 100 $ mle one.c two.c # Edit one.c and two.c $ mle -h # Show command line help The default key bindings are intuitive. Input text as normal, use directional keys to move around, use `Ctrl-S` to save, `Ctrl-O` to open, `Ctrl-X` to exit. Press `F2` for full help. ### Advanced usage: mlerc mle is customized via command line options. Run `mle -h` to view all cli options. To set default options, make an rc file named `~/.mlerc` (or `/etc/mlerc`). The contents of the rc file are any number of cli options separated by newlines. Lines that begin with a semi-colon are interpretted as comments. If `~/.mlerc` is executable, mle executes it and interprets the resulting stdout as described above. For example, consider the following snippet from an executable `~/.mlerc` bash(1) script: # Define 'test' kmap echo '-Ktest,,1' # M-q: replace grep with git grep if `.git` exists if [ -d ".git" ]; then echo '-kcmd_grep,M-q,git grep --color=never -P -i -I -n %s 2>/dev/null' fi # Set default kmap echo '-n test' This overrides the built-in grep command with `git grep` if `.git` exists in the current working directory. ### Shell command integration The following programs will enable or enhance certain features of mle if they exist in `PATH`. * [bash](https://www.gnu.org/software/bash/) (tab completion) * [fzf](https://github.com/junegunn/fzf) (fuzzy file search) * [grep](https://www.gnu.org/software/grep/) (file grep) * [less](https://www.gnu.org/software/less/) (less integration) * [perl](https://www.perl.org/) (perl 1-liners) * [readtags](https://github.com/universal-ctags/ctags) (ctags integration) * [tree](http://mama.indstate.edu/users/ice/tree/) (file browsing) Arbitrary shell commands can also be run via `cmd_shell` (M-e by default). If any text is selected, it is sent to stdin of the command. Any resulting stdout is inserted into the text buffer. ### Advanced usage: Headless mode mle provides support for non-interactive editing which may be useful for using the editor as a regular command line tool. In headless mode, mle reads stdin into a buffer, applies a startup macro if specified, and then writes the buffer contents to stdout. For example: $ echo -n hello | mle -M 'test C-e space w o r l d enter' -p test hello world If stdin is a pipe, mle goes into headless mode automatically. Headless mode can be explicitly enabled or disabled with the `-H` option. If stdin is a pipe and headless mode is disabled via `-H0`, mle reads stdin into a new buffer and then runs as normal in interactive mode. ### Advanced usage: Scripting mle is extensible via the [Lua](https://www.lua.org) programming language. Scripts are loaded via the `-x` cli option. Commands registered by scripts can be mapped to keys as normal via `-k`. See `uscript.lua` for a simple example. There is also a `wren` branch with [Wren](http://wren.io) scripting support. That work is on pause. ### Forks * [eon](https://github.com/tomas/eon) - mouse support and Notepad-like selections * [turbo-mle](https://github.com/magiblot/turbo-mle) - Turbo Vision port ### Acknowledgments mle makes extensive use of the following libraries. * [uthash](https://troydhanson.github.io/uthash) for hash maps and linked lists * [termbox2](https://github.com/termbox/termbox2) for TUI * [PCRE2](http://www.pcre.org/) for syntax highlighting and search mle-1.7.2/buffer.c000066400000000000000000001716051443351614700137350ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "mlbuf.h" static int _buffer_open_mmap(buffer_t *self, int fd, size_t size); static int _buffer_open_read(buffer_t *self, int fd, size_t size); static int _buffer_bline_unslab(bline_t *self); static void _buffer_stat(buffer_t *self); static int _buffer_baction_do(buffer_t *self, bline_t *bline, baction_t *action, int is_redo, bint_t *opt_repeat_offset); static int _buffer_update(buffer_t *self, baction_t *action); static int _buffer_undo(buffer_t *self, int by_group); static int _buffer_redo(buffer_t *self, int by_group); static int _buffer_truncate_undo_stack(buffer_t *self, baction_t *action_from); static int _buffer_add_to_undo_stack(buffer_t *self, baction_t *action); static int _buffer_apply_styles_all(bline_t *bline, bint_t min_nlines); static int _buffer_match_srule(bline_t *bline, bint_t look_offset, srule_t *srule, int end_rule, bint_t *ret_start, bint_t *ret_stop); static void _buffer_bline_reset_styles(bline_t *bline); static void _buffer_bline_style(bline_t *bline, bint_t start, bint_t stop, sblock_t *style); static bline_t *_buffer_bline_new(buffer_t *self); static int _buffer_bline_free(bline_t *bline, bline_t *maybe_mark_line, bint_t col_delta); static bline_t *_buffer_bline_break(bline_t *bline, bint_t col); static void _buffer_find_end_pos(bline_t *start_line, bint_t start_col, bint_t num_chars, bline_t **ret_end_line, bint_t *ret_end_col, bint_t *ret_safe_num_chars); static void _buffer_bline_replace(bline_t *bline, bint_t start_col, char *data, bint_t data_len, str_t *del_data); static bint_t _buffer_bline_insert(bline_t *bline, bint_t col, char *data, bint_t data_len, int move_marks); static bint_t _buffer_bline_delete(bline_t *bline, bint_t col, bint_t num_chars); static bint_t _buffer_bline_col_to_index(bline_t *bline, bint_t col); static bint_t _buffer_bline_index_to_col(bline_t *bline, bint_t index); static int _buffer_munmap(buffer_t *self); static int _baction_destroy(baction_t *action); static void _bline_advance_col(bline_t **self, bint_t *col); // Make a new buffer and return it buffer_t *buffer_new() { buffer_t *buffer; bline_t *bline; buffer = calloc(1, sizeof(buffer_t)); buffer->tab_width = 4; bline = _buffer_bline_new(buffer); buffer->first_line = bline; buffer->last_line = bline; buffer->line_count = 1; buffer->mmap_fd = -1; return buffer; } // Wrapper for buffer_new + buffer_open buffer_t *buffer_new_open(char *path) { buffer_t *self; int rc; self = buffer_new(); if ((rc = buffer_open(self, path)) != MLBUF_OK) { buffer_destroy(self); return NULL; } return self; } // Read buffer from path int buffer_open(buffer_t *self, char *path) { int rc; struct stat st; int fd; fd = -1; rc = MLBUF_OK; do { // Exit early if path is empty if (!path || strlen(path) < 1) { rc = MLBUF_ERR; break; } // Open file for reading if ((fd = open(path, O_RDONLY)) < 0) { rc = MLBUF_ERR; break; } // Stat file if (fstat(fd, &st) < 0) { rc = MLBUF_ERR; break; } // Read or mmap file into buffer self->is_in_open = 1; if (st.st_size >= MLBUF_LARGE_FILE_SIZE) { if (_buffer_open_mmap(self, fd, st.st_size) != MLBUF_OK) { // mmap failed so fallback to normal read if (_buffer_open_read(self, fd, st.st_size) != MLBUF_OK) { rc = MLBUF_ERR; break; } } } else { if (_buffer_open_read(self, fd, st.st_size) != MLBUF_OK) { rc = MLBUF_ERR; break; } } self->is_in_open = 0; } while(0); if (fd >= 0) close(fd); if (rc == MLBUF_ERR) return rc; // Set path if (self->path) free(self->path); self->path = strdup(path); self->is_unsaved = 0; // Remember stat _buffer_stat(self); return MLBUF_OK; } // Write buffer to path int buffer_save(buffer_t *self) { return buffer_save_as(self, self->path, NULL); } // Write buffer to specified path int buffer_save_as(buffer_t *self, char *path, bint_t *optret_nbytes) { FILE *fp; size_t nbytes; if (optret_nbytes) *optret_nbytes = 0; // Exit early if path is empty if (!path || strlen(path) < 1) { return MLBUF_ERR; } // Open file for writing if (!(fp = fopen(path, "wb"))) { return MLBUF_ERR; } // Write data to file nbytes = 0; buffer_write_to_file(self, fp, &nbytes); fclose(fp); if (optret_nbytes) *optret_nbytes = (bint_t)nbytes; if ((bint_t)nbytes != self->byte_count) return MLBUF_ERR; // Set path if (self->path != path) { if (self->path) free(self->path); self->path = strdup(path); } self->is_unsaved = 0; // Remember stat _buffer_stat(self); return MLBUF_OK; } // Write buffer data to FILE* int buffer_write_to_file(buffer_t *self, FILE *fp, size_t *optret_nbytes) { return buffer_write_to_fd(self, fileno(fp), optret_nbytes); } // Write buffer data to file descriptor int buffer_write_to_fd(buffer_t *self, int fd, size_t *optret_nbytes) { bline_t *bline; size_t nbytes; ssize_t write_rc; nbytes = 0; #define MLBUF_BUFFER_WRITE_CHECK(pd, pl) do { \ write_rc = write(fd, (pd), (pl)); \ if (write_rc < (pl)) { \ return MLBUF_ERR; \ } \ nbytes += write_rc; \ } while(0) for (bline = self->first_line; bline; bline = bline->next) { if (bline->data_len > 0) MLBUF_BUFFER_WRITE_CHECK(bline->data, bline->data_len); if (bline->next) MLBUF_BUFFER_WRITE_CHECK("\n", 1); } if (optret_nbytes) *optret_nbytes = nbytes; return MLBUF_OK; } // Free a buffer int buffer_destroy(buffer_t *self) { bline_t *line; bline_t *line_tmp; baction_t *action; baction_t *action_tmp; char c; for (line = self->last_line; line; ) { line_tmp = line->prev; _buffer_bline_free(line, NULL, 0); line = line_tmp; } if (self->data) free(self->data); if (self->path) free(self->path); DL_FOREACH_SAFE(self->actions, action, action_tmp) { DL_DELETE(self->actions, action); _baction_destroy(action); } for (c = 'a'; c <= 'z'; c++) buffer_register_clear(self, c); _buffer_munmap(self); if (self->slabbed_blines) free(self->slabbed_blines); if (self->slabbed_chars) free(self->slabbed_chars); free(self); return MLBUF_OK; } // Add a mark to this buffer and return it mark_t *buffer_add_mark(buffer_t *self, bline_t *maybe_line, bint_t maybe_col) { return buffer_add_mark_ex(self, '\0', maybe_line, maybe_col); } // If letter is [a-z], add a lettered mark and return it. // If letter is \0, add a non-lettered mark and return it. // Otherwise do nothing and return NULL. mark_t *buffer_add_mark_ex(buffer_t *self, char letter, bline_t *maybe_line, bint_t maybe_col) { mark_t *mark; mark_t *mark_tmp; if (!((letter >= 'a' && letter <= 'z') || letter == '\0')) { return NULL; } mark = calloc(1, sizeof(mark_t)); mark->letter = letter; MLBUF_MAKE_GT_EQ0(maybe_col); if (maybe_line != NULL) { MLBUF_BLINE_ENSURE_CHARS(maybe_line); mark->bline = maybe_line; mark->col = maybe_col < 0 ? 0 : MLBUF_MIN(maybe_line->char_count, maybe_col); } else { mark->bline = self->first_line; mark->col = 0; } DL_APPEND(mark->bline->marks, mark); if (mark->letter) { if ((mark_tmp = MLBUF_LETT_MARK(self, mark->letter)) != NULL) { buffer_destroy_mark(self, mark_tmp); } MLBUF_LETT_MARK(self, mark->letter) = mark; } return mark; } // Return lettered mark or NULL if it does not exist int buffer_get_lettered_mark(buffer_t *self, char letter, mark_t **ret_mark) { MLBUF_ENSURE_AZ(letter); *ret_mark = MLBUF_LETT_MARK(self, letter); return MLBUF_OK; } // Remove mark from buffer and free it, removing any range srules that use it int buffer_destroy_mark(buffer_t *self, mark_t *mark) { srule_node_t *node; srule_node_t *node_tmp; DL_DELETE(mark->bline->marks, mark); if (mark->letter) MLBUF_LETT_MARK(self, mark->letter) = NULL; DL_FOREACH_SAFE(self->range_srules, node, node_tmp) { if (node->srule->range_a == mark || node->srule->range_b == mark ) { buffer_remove_srule(self, node->srule); } } free(mark); return MLBUF_OK; } // Get buffer contents and length int buffer_get(buffer_t *self, char **ret_data, bint_t *ret_data_len) { bline_t *bline; char *data_cursor; bint_t alloc_size; if (self->is_data_dirty) { // Refresh self->data alloc_size = self->byte_count + 2; self->data = self->data != NULL ? realloc(self->data, alloc_size) : malloc(alloc_size); data_cursor = self->data; for (bline = self->first_line; bline != NULL; bline = bline->next) { if (bline->data_len > 0) { memcpy(data_cursor, bline->data, bline->data_len); data_cursor += bline->data_len; self->data_len += bline->data_len; } if (bline->next) { *data_cursor = '\n'; data_cursor += 1; } } *data_cursor = '\0'; self->data_len = (bint_t)(data_cursor - self->data); self->is_data_dirty = 0; } *ret_data = self->data; *ret_data_len = self->data_len; return MLBUF_OK; } int buffer_clear(buffer_t *self) { return buffer_delete(self, 0, self->byte_count); } // Set buffer contents int buffer_set(buffer_t *self, char *data, bint_t data_len) { int rc; MLBUF_MAKE_GT_EQ0(data_len); if ((rc = buffer_clear(self)) != MLBUF_OK) { return rc; } rc = buffer_insert(self, 0, data, data_len, NULL); if (self->actions) _buffer_truncate_undo_stack(self, self->actions); return rc; } // Set buffer contents more efficiently int buffer_set_mmapped(buffer_t *self, char *data, bint_t data_len) { bint_t nlines; bint_t line_num; bline_t *blines; bint_t data_remaining_len; char *data_cursor; char *data_newline; bint_t line_len; if (buffer_clear(self) != MLBUF_OK) { return MLBUF_ERR; } // Count number of lines nlines = 1; data_cursor = data; data_remaining_len = data_len; while (data_remaining_len > 0 && (data_newline = memchr(data_cursor, '\n', data_remaining_len)) != NULL) { data_remaining_len -= (bint_t)(data_newline - data_cursor) + 1; data_cursor = data_newline + 1; nlines += 1; } // Allocate blines and chars. These are freed in buffer_destroy. self->slabbed_chars = calloc(data_len, sizeof(bline_char_t)); self->slabbed_blines = malloc(nlines * sizeof(bline_t)); blines = self->slabbed_blines; // Populate blines line_num = 0; data_cursor = data; data_remaining_len = data_len; while (1) { data_newline = data_remaining_len > 0 ? memchr(data_cursor, '\n', data_remaining_len) : NULL; line_len = data_newline ? (bint_t)(data_newline - data_cursor) : data_remaining_len; blines[line_num] = (bline_t){ .buffer = self, .data = data_cursor, .data_len = line_len, .data_cap = line_len, .line_index = line_num, .char_count = line_len, .char_vwidth = line_len, .chars = (self->slabbed_chars + (data_len - data_remaining_len)), .chars_cap = line_len, .marks = NULL, .eol_rule = NULL, .is_chars_dirty = 1, .is_slabbed = 1, .is_data_slabbed = 1, .next = NULL, .prev = NULL }; if (line_num > 0) { blines[line_num-1].next = blines + line_num; blines[line_num].prev = blines + (line_num-1); } if (data_newline) { data_remaining_len -= line_len + 1; data_cursor = data_newline + 1; line_num += 1; } else { break; } } if (self->first_line) _buffer_bline_free(self->first_line, NULL, 0); self->first_line = blines; self->last_line = blines + line_num; self->byte_count = data_len; self->line_count = line_num + 1; self->is_data_dirty = 1; return MLBUF_OK; } // Insert data into buffer given a buffer offset int buffer_insert(buffer_t *self, bint_t offset, char *data, bint_t data_len, bint_t *optret_num_chars) { int rc; bline_t *start_line; bint_t start_col; MLBUF_MAKE_GT_EQ0(offset); // Find start line and col if ((rc = buffer_get_bline_col(self, offset, &start_line, &start_col)) != MLBUF_OK) { return rc; } return buffer_insert_w_bline(self, start_line, start_col, data, data_len, optret_num_chars); } // Insert data into buffer given a bline/col int buffer_insert_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, char *data, bint_t data_len, bint_t *optret_num_chars) { bline_t *cur_line; bint_t cur_col; bline_t *new_line; char *data_cursor; char *data_newline; bint_t data_remaining_len; bint_t insert_len; bint_t num_lines_added; char *ins_data; bint_t ins_data_len; bint_t ins_data_nchars; baction_t *action; MLBUF_MAKE_GT_EQ0(data_len); // Exit early if no data if (data_len < 1) { return MLBUF_OK; } // Insert lines data_cursor = data; data_remaining_len = data_len; cur_line = start_line; cur_col = start_col; num_lines_added = 0; while (data_remaining_len > 0 && (data_newline = memchr(data_cursor, '\n', data_remaining_len)) != NULL) { insert_len = (bint_t)(data_newline - data_cursor); new_line = _buffer_bline_break(cur_line, cur_col); num_lines_added += 1; if (insert_len > 0) { _buffer_bline_insert(cur_line, cur_col, data_cursor, insert_len, 1); } data_remaining_len -= (data_newline - data_cursor) + 1; data_cursor = data_newline + 1; cur_line = new_line; cur_col = 0; } if (data_remaining_len > 0) { cur_col += _buffer_bline_insert(cur_line, cur_col, data_cursor, data_remaining_len, 1); } // Get inserted data buffer_substr(self, start_line, start_col, cur_line, cur_col, &ins_data, &ins_data_len, &ins_data_nchars); // Add baction action = calloc(1, sizeof(baction_t)); action->type = MLBUF_BACTION_TYPE_INSERT; action->buffer = self; action->start_line = start_line; action->start_line_index = start_line->line_index; action->start_col = start_col; action->maybe_end_line = cur_line; action->maybe_end_line_index = action->start_line_index + num_lines_added; action->maybe_end_col = cur_col; action->byte_delta = (bint_t)ins_data_len; action->char_delta = (bint_t)ins_data_nchars; action->line_delta = (bint_t)num_lines_added; action->data = ins_data; action->data_len = ins_data_len; _buffer_update(self, action); if (optret_num_chars) *optret_num_chars = ins_data_nchars; return MLBUF_OK; } // Delete data from buffer given an offset int buffer_delete(buffer_t *self, bint_t offset, bint_t num_chars) { bline_t *start_line; bint_t start_col; MLBUF_MAKE_GT_EQ0(offset); buffer_get_bline_col(self, offset, &start_line, &start_col); return buffer_delete_w_bline(self, start_line, start_col, num_chars); } // Delete data from buffer given a bline/col int buffer_delete_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, bint_t num_chars) { bline_t *end_line; bint_t end_col; bline_t *tmp_line; bline_t *swap_line; bline_t *next_line; bint_t tmp_len; char *del_data; bint_t del_data_len; bint_t del_data_nchars; bint_t num_lines_removed; bint_t safe_num_chars; bint_t orig_char_count; baction_t *action; MLBUF_MAKE_GT_EQ0(num_chars); // Find end line and col _buffer_find_end_pos(start_line, start_col, num_chars, &end_line, &end_col, &num_chars); // Exit early if there is nothing to delete MLBUF_BLINE_ENSURE_CHARS(self->last_line); if (start_line == end_line && start_col >= end_col) { return MLBUF_OK; } else if (start_line == self->last_line && start_col == self->last_line->char_count) { return MLBUF_OK; } // Get deleted data buffer_substr(self, start_line, start_col, end_line, end_col, &del_data, &del_data_len, &del_data_nchars); // Delete suffix starting at start_line:start_col MLBUF_BLINE_ENSURE_CHARS(start_line); safe_num_chars = MLBUF_MIN(num_chars, start_line->char_count - start_col); if (safe_num_chars > 0) { _buffer_bline_delete(start_line, start_col, safe_num_chars); } // Copy remaining portion of end_line to start_line:start_col MLBUF_BLINE_ENSURE_CHARS(start_line); orig_char_count = start_line->char_count; if (start_line != end_line && (tmp_len = end_line->data_len - _buffer_bline_col_to_index(end_line, end_col)) > 0) { _buffer_bline_insert( start_line, start_col, end_line->data + (end_line->data_len - tmp_len), tmp_len, 0 ); } // Remove lines after start_line thru end_line // Relocate marks to end of start_line num_lines_removed = 0; swap_line = end_line->next; next_line = NULL; tmp_line = start_line->next; while (tmp_line != NULL && tmp_line != swap_line) { next_line = tmp_line->next; _buffer_bline_free(tmp_line, start_line, orig_char_count - end_col); num_lines_removed += 1; tmp_line = next_line; } start_line->next = swap_line; if (swap_line) swap_line->prev = start_line; // Add baction action = calloc(1, sizeof(baction_t)); action->type = MLBUF_BACTION_TYPE_DELETE; action->buffer = self; action->start_line = start_line; action->start_line_index = start_line->line_index; action->start_col = start_col; action->byte_delta = -1 * (bint_t)del_data_len; action->char_delta = -1 * (bint_t)del_data_nchars; action->line_delta = -1 * (bint_t)num_lines_removed; action->data = del_data; action->data_len = del_data_len; _buffer_update(self, action); return MLBUF_OK; } // Replace num_chars in buffer at offset with data int buffer_replace(buffer_t *self, bint_t offset, bint_t num_chars, char *data, bint_t data_len) { int rc; bline_t *start_line; bint_t start_col; MLBUF_MAKE_GT_EQ0(offset); // Find start line and col if ((rc = buffer_get_bline_col(self, offset, &start_line, &start_col)) != MLBUF_OK) { return rc; } return buffer_replace_w_bline(self, start_line, start_col, num_chars, data, data_len); } // Replace num_chars from start_line:start_col with data int buffer_replace_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, bint_t del_chars, char *data, bint_t data_len) { bline_t *cur_line; bint_t cur_col; bint_t insert_rem; bint_t delete_rem; bint_t data_linelen; bint_t nchars_ins; bint_t nlines; char *data_cursor; char *data_newline; baction_t *action; str_t del_data = {0}; // Replace data on common lines insert_rem = data_len; delete_rem = del_chars; cur_line = start_line; cur_col = start_col; data_cursor = data; nchars_ins = 0; nlines = 0; MLBUF_BLINE_ENSURE_CHARS(cur_line); while (insert_rem > 0 && delete_rem > (cur_line->char_count - cur_col)) { data_newline = memchr(data_cursor, '\n', insert_rem); data_linelen = data_newline ? (bint_t)(data_newline - data_cursor) : insert_rem; delete_rem -= cur_line->char_count - cur_col; _buffer_bline_replace(cur_line, cur_col, data_cursor, data_linelen, &del_data); nchars_ins += cur_line->char_count - cur_col; insert_rem -= data_linelen; data_cursor += data_linelen; cur_col = cur_line->char_count; if (data_newline && insert_rem >= 2 && delete_rem >= 2 && cur_line->next) { data_cursor += 1; insert_rem -= 1; delete_rem -= 1; cur_line = cur_line->next; cur_col = 0; nchars_ins += 1; str_append_len(&del_data, "\n", 1); } else { break; } nlines += 1; MLBUF_BLINE_ENSURE_CHARS(cur_line); } // Add delete baction if (del_data.len > 0) { action = calloc(1, sizeof(baction_t)); action->type = MLBUF_BACTION_TYPE_DELETE; action->buffer = self; action->start_line = start_line; action->start_line_index = start_line->line_index; action->start_col = start_col; action->byte_delta = -1 * (bint_t)del_data.len; action->char_delta = -1 * (del_chars - delete_rem); action->line_delta = -1 * nlines; action->data = del_data.data; action->data_len = (bint_t)del_data.len; _buffer_update(self, action); } // Add insert baction if (data_len - insert_rem > 0) { action = calloc(1, sizeof(baction_t)); action->type = MLBUF_BACTION_TYPE_INSERT; action->buffer = self; action->start_line = start_line; action->start_line_index = start_line->line_index; action->start_col = start_col; action->maybe_end_line = cur_line; action->maybe_end_line_index = action->start_line_index + nlines; action->maybe_end_col = cur_col; action->byte_delta = data_len - insert_rem; action->char_delta = nchars_ins; action->line_delta = nlines; action->data = strndup(data, action->byte_delta); action->data_len = action->byte_delta; _buffer_update(self, action); } // Delete left over data if (delete_rem > 0) { buffer_delete_w_bline(self, cur_line, cur_col, delete_rem); } // Insert left over data if (insert_rem > 0) { buffer_insert_w_bline(self, cur_line, cur_col, data_cursor, insert_rem, NULL); } return MLBUF_OK; } // Return a line given a line_index int buffer_get_bline(buffer_t *self, bint_t line_index, bline_t **ret_bline) { return buffer_get_bline_w_hint(self, line_index, self->first_line, ret_bline); } // Return a line given a line_index, starting at hint and iterating outward int buffer_get_bline_w_hint(buffer_t *self, bint_t line_index, bline_t *opt_hint, bline_t **ret_bline) { bline_t *fwd, *rev, *found; MLBUF_MAKE_GT_EQ0(line_index); if (!opt_hint) { opt_hint = self->first_line; } fwd = opt_hint; rev = opt_hint->prev; found = NULL; while (fwd || rev) { if (fwd) { if (fwd->line_index == line_index) { found = fwd; break; } fwd = fwd != fwd->next ? fwd->next : NULL; } if (rev) { if (rev->line_index == line_index) { found = rev; break; } rev = rev != rev->prev ? rev->prev : NULL; } } if (found) { *ret_bline = found; return MLBUF_OK; } *ret_bline = self->last_line; return MLBUF_ERR; } // Return a line and col for the given offset int buffer_get_bline_col(buffer_t *self, bint_t offset, bline_t **ret_bline, bint_t *ret_col) { bline_t *tmp_line; bline_t *good_line = NULL; bint_t remaining_chars; MLBUF_MAKE_GT_EQ0(offset); remaining_chars = offset; for (tmp_line = self->first_line; tmp_line != NULL; tmp_line = tmp_line->next) { MLBUF_BLINE_ENSURE_CHARS(tmp_line); if (tmp_line->char_count >= remaining_chars) { *ret_bline = tmp_line; *ret_col = remaining_chars; return MLBUF_OK; } else { remaining_chars -= (tmp_line->char_count + 1); // Plus 1 for newline } good_line = tmp_line; } if (!good_line) good_line = self->first_line; *ret_bline = good_line; *ret_col = good_line->char_count; return MLBUF_OK; } // Return an offset given a line and col int buffer_get_offset(buffer_t *self, bline_t *bline, bint_t col, bint_t *ret_offset) { bline_t *tmp_line; bint_t offset; MLBUF_MAKE_GT_EQ0(col); offset = 0; for (tmp_line = self->first_line; tmp_line != bline->next; tmp_line = tmp_line->next) { MLBUF_BLINE_ENSURE_CHARS(tmp_line); if (tmp_line == bline) { offset += MLBUF_MIN(tmp_line->char_count, col); break; } else { offset += tmp_line->char_count + 1; // Plus 1 for newline } } *ret_offset = offset; return MLBUF_OK; } // Add a style rule to the buffer int buffer_add_srule(buffer_t *self, srule_t *srule) { srule_node_t *node; node = calloc(1, sizeof(srule_node_t)); node->srule = srule; if (srule->type == MLBUF_SRULE_TYPE_SINGLE || srule->type == MLBUF_SRULE_TYPE_MULTI) { DL_APPEND(self->srules, node); } else if (srule->type == MLBUF_SRULE_TYPE_RANGE) { srule->range_a->range_srule = srule; srule->range_b->range_srule = srule; DL_APPEND(self->range_srules, node); return MLBUF_OK; // range styles get applied in bview_draw } else { free(node); return MLBUF_ERR; } return buffer_apply_styles(self, self->first_line, self->line_count - 1); } // Remove a style rule from the buffer int buffer_remove_srule(buffer_t *self, srule_t *srule) { int found; srule_node_t **head; srule_node_t *node; srule_node_t *node_tmp; if (srule->type == MLBUF_SRULE_TYPE_SINGLE || srule->type == MLBUF_SRULE_TYPE_MULTI) { head = &self->srules; } else if (srule->type == MLBUF_SRULE_TYPE_RANGE) { head = &self->range_srules; } else { return MLBUF_ERR; } found = 0; DL_FOREACH_SAFE(*head, node, node_tmp) { if (node->srule != srule) continue; if (srule->type == MLBUF_SRULE_TYPE_RANGE) { srule->range_a->range_srule = NULL; srule->range_b->range_srule = NULL; } DL_DELETE(*head, node); free(node); found = 1; break; } if (!found) return MLBUF_ERR; if (srule->type == MLBUF_SRULE_TYPE_RANGE) { return MLBUF_OK; // range styles get applied in bview_draw } return buffer_apply_styles(self, self->first_line, self->line_count - 1); } // Set callback to cb. Pass in NULL to unset callback. int buffer_set_callback(buffer_t *self, buffer_callback_t cb, void *udata) { if (cb) { self->callback = cb; self->callback_udata = udata; } else { self->callback = NULL; self->callback_udata = NULL; } return MLBUF_OK; } // Set tab_width and recalculate all line char vwidths int buffer_set_tab_width(buffer_t *self, int tab_width) { bline_t *tmp_line; if (tab_width < 1) { return MLBUF_ERR; } self->tab_width = tab_width; for (tmp_line = self->first_line; tmp_line; tmp_line = tmp_line->next) { bline_count_chars(tmp_line); } return MLBUF_OK; } // Return data from start_line:start_col thru end_line:end_col int buffer_substr(buffer_t *self, bline_t *start_line, bint_t start_col, bline_t *end_line, bint_t end_col, char **ret_data, bint_t *ret_data_len, bint_t *ret_data_nchars) { char *data; bint_t data_len; bint_t data_size; bline_t *tmp_line; bint_t copy_len; bint_t copy_index; bint_t add_len; bint_t nchars; MLBUF_MAKE_GT_EQ0(start_col); MLBUF_MAKE_GT_EQ0(end_col); data = calloc(2, sizeof(char)); data_len = 0; data_size = 2; nchars = 0; for (tmp_line = start_line; tmp_line != end_line->next; tmp_line = tmp_line->next) { // Get copy_index + copy_len // Also increment nchars if (start_line == end_line) { copy_index = _buffer_bline_col_to_index(start_line, start_col); copy_len = _buffer_bline_col_to_index(start_line, end_col) - copy_index; nchars += end_col - start_col; } else if (tmp_line == start_line) { copy_index = _buffer_bline_col_to_index(start_line, start_col); copy_len = tmp_line->data_len - copy_index; MLBUF_BLINE_ENSURE_CHARS(start_line); nchars += start_line->char_count - start_col; } else if (tmp_line == end_line) { copy_index = 0; copy_len = _buffer_bline_col_to_index(end_line, end_col); nchars += end_col; } else { copy_index = 0; copy_len = tmp_line->data_len; MLBUF_BLINE_ENSURE_CHARS(tmp_line); nchars += tmp_line->char_count; } // Add 1 for newline if not on end_line add_len = copy_len + (tmp_line != end_line ? 1 : 0); // Plus 1 for newline nchars += (tmp_line != end_line ? 1 : 0); // Copy add_len bytes from copy_index into data if (add_len > 0) { if (data_len + add_len + 1 > data_size) { data_size = data_len + add_len + 1; // Plus 1 for nullchar data = realloc(data, data_size); } if (copy_len > 0) { memcpy(data + data_len, tmp_line->data + copy_index, copy_len); data_len += copy_len; } if (tmp_line != end_line) { *(data + data_len) = '\n'; data_len += 1; } } } *(data + data_len) = '\0'; *ret_data = data; *ret_data_len = data_len; *ret_data_nchars = nchars; return MLBUF_OK; } // Undo an action int buffer_undo(buffer_t *self) { return _buffer_undo(self, 0); } // Undo actions in last action_group int buffer_undo_action_group(buffer_t *self) { return _buffer_undo(self, 1); } // Redo an action int buffer_redo(buffer_t *self) { return _buffer_redo(self, 0); } // Redo actions in last action_group int buffer_redo_action_group(buffer_t *self) { return _buffer_redo(self, 1); } // Toggle is_style_disabled int buffer_set_styles_enabled(buffer_t *self, int is_enabled) { if (!self->is_style_disabled && !is_enabled) { self->is_style_disabled = 1; } else if (self->is_style_disabled && is_enabled) { self->is_style_disabled = 0; buffer_apply_styles(self, self->first_line, self->line_count); } return MLBUF_OK; } // Set buffer action_group pointer int buffer_set_action_group_ptr(buffer_t *self, int *action_group) { self->action_group = action_group; return MLBUF_OK; } // Apply styles from start_line int buffer_apply_styles(buffer_t *self, bline_t *start_line, bint_t line_delta) { bint_t min_nlines; srule_node_t *srule_node; int count_tmp; int srule_count; if (self->is_style_disabled) { return MLBUF_OK; } // min_nlines, minimum number of lines to style // line_delta < 0: 2 (start_line + 1) // line_delta == 0: 1 (start_line) // line_delta > 0: 1 + line_delta (start_line + added lines) min_nlines = 1 + (line_delta < 0 ? 1 : line_delta); // Count current srules srule_count = 0; DL_COUNT(self->srules, srule_node, count_tmp); srule_count += count_tmp; // Apply rules if there are any, or if the number of rules changed if (srule_count > 0 || self->num_applied_srules != srule_count) { _buffer_apply_styles_all(start_line, min_nlines); self->num_applied_srules = srule_count; } return MLBUF_OK; } // Set register int buffer_register_set(buffer_t *self, char reg, char *data, size_t data_len) { MLBUF_ENSURE_AZ(reg); str_set_len(MLBUF_REG_PTR(self, reg), data, data_len); return MLBUF_OK; } // Append to register int buffer_register_append(buffer_t *self, char reg, char *data, size_t data_len) { MLBUF_ENSURE_AZ(reg); str_append_len(MLBUF_REG_PTR(self, reg), data, data_len); return MLBUF_OK; } // Prepend to register int buffer_register_prepend(buffer_t *self, char reg, char *data, size_t data_len) { MLBUF_ENSURE_AZ(reg); str_prepend_len(MLBUF_REG_PTR(self, reg), data, data_len); return MLBUF_OK; } // Clear register int buffer_register_clear(buffer_t *self, char reg) { MLBUF_ENSURE_AZ(reg); str_free(MLBUF_REG_PTR(self, reg)); return MLBUF_OK; } // Get register, optionally allocating duplicate int buffer_register_get(buffer_t *self, char reg, int dup, char **ret_data, size_t *ret_data_len) { str_t *sreg; MLBUF_ENSURE_AZ(reg); sreg = MLBUF_REG_PTR(self, reg); if (dup) { *ret_data = strndup(sreg->data, sreg->len); *ret_data_len = strlen(*ret_data); } else { *ret_data = sreg->len > 0 ? sreg->data : ""; *ret_data_len = sreg->len; } return MLBUF_OK; } // Count multi-byte characters and character widths of this line int bline_count_chars(bline_t *bline) { char *c; int char_len; uint32_t ch; int char_w; bint_t i; int is_tabless_ascii; // Unmark dirty if (bline->is_chars_dirty) bline->is_chars_dirty = 0; // Return early if there is no data if (bline->data_len < 1) { bline->char_count = 0; bline->char_vwidth = 0; return MLBUF_OK; } // Ensure space for chars // It should have data_len elements at most if (!bline->chars) { bline->chars = calloc(bline->data_len, sizeof(bline_char_t)); bline->chars_cap = bline->data_len; } else if (!bline->is_data_slabbed && bline->data_len > bline->chars_cap) { bline->chars = recalloc(bline->chars, bline->chars_cap, bline->data_len, sizeof(bline_char_t)); bline->chars_cap = bline->data_len; } // Attempt shortcut for lines with all ascii and no tabs is_tabless_ascii = 1; c = bline->data; i = 0; while (c < MLBUF_BLINE_DATA_STOP(bline)) { if ((*c & 0x80) || *c == '\t') { is_tabless_ascii = 0; break; } bline->chars[i].ch = *c; bline->chars[i].len = 1; bline->chars[i].index = i; bline->chars[i].vcol = i; bline->chars[i].index_to_vcol = i; i++; c++; } bline->char_count = i; bline->char_vwidth = i; if (!is_tabless_ascii) { // We encountered either non-ascii or a tab above, so we have to do a // little more work. while (c < MLBUF_BLINE_DATA_STOP(bline)) { ch = 0; char_len = utf8_char_to_unicode(&ch, c, MLBUF_BLINE_DATA_STOP(bline)); if (ch == '\t') { // Special case for tabs char_w = bline->buffer->tab_width - (bline->char_vwidth % bline->buffer->tab_width); } else { char_w = wcwidth(ch); } // Let null occupy 1 column if (char_w < 0 || ch == '\0') char_w = 1; if (char_len < 1) char_len = 1; bline->chars[bline->char_count].ch = ch; bline->chars[bline->char_count].len = char_len; bline->chars[bline->char_count].index = (bint_t)(c - bline->data); bline->chars[bline->char_count].vcol = bline->char_vwidth; for (i = 0; i < char_len; i++) if ((c - bline->data) + i < bline->data_len) { bline->chars[(c - bline->data) + i].index_to_vcol = bline->char_count; } bline->char_count += 1; bline->char_vwidth += char_w; c += char_len; } } return MLBUF_OK; } // Insert data on a line int bline_insert(bline_t *self, bint_t col, char *data, bint_t data_len, bint_t *ret_num_chars) { _bline_advance_col(&self, &col); return buffer_insert_w_bline(self->buffer, self, col, data, data_len, ret_num_chars); } // Delete data from a line int bline_delete(bline_t *self, bint_t col, bint_t num_chars) { _bline_advance_col(&self, &col); return buffer_delete_w_bline(self->buffer, self, col, num_chars); } // Replace data on a line int bline_replace(bline_t *self, bint_t col, bint_t num_chars, char *data, bint_t data_len) { _bline_advance_col(&self, &col); return buffer_replace_w_bline(self->buffer, self, col, num_chars, data, data_len); } // Return a col given a byte index int bline_get_col(bline_t *self, bint_t index, bint_t *ret_col) { bint_t col; MLBUF_MAKE_GT_EQ0(index); MLBUF_BLINE_ENSURE_CHARS(self); if (index == 0 || self->char_count == 0) { *ret_col = 0; return MLBUF_OK; } else if (index >= self->data_len) { *ret_col = self->char_count; return MLBUF_OK; } for (col = 1; col < self->char_count; col++) { if (self->chars[col].index > index) { *ret_col = col - 1; return MLBUF_OK; } else if (self->chars[col].index == index) { *ret_col = col; return MLBUF_OK; } } *ret_col = self->char_count - (index < self->data_len ? 1 : 0); return MLBUF_OK; } // Convert a vcol to a col int bline_get_col_from_vcol(bline_t *bline, bint_t vcol, bint_t *ret_col) { bint_t i; MLBUF_BLINE_ENSURE_CHARS(bline); for (i = 0; i < bline->char_count; i++) { if (vcol <= bline->chars[i].vcol) { *ret_col = i; return MLBUF_OK; } } *ret_col = bline->char_count; return MLBUF_OK; } int bline_index_to_col(bline_t *bline, bint_t index, bint_t *ret_col) { *ret_col = _buffer_bline_index_to_col(bline, index); return MLBUF_OK; } static int _buffer_open_mmap(buffer_t *self, int fd, size_t size) { char tmppath[16]; int tmpfd; char readbuf[1024]; ssize_t nread; char *mmap_buf; // Copy fd to tmp file sprintf(tmppath, "%s", "/tmp/mle-XXXXXX"); tmpfd = mkstemp(tmppath); if (tmpfd < 0) { return MLBUF_ERR; } unlink(tmppath); while (1) { nread = read(fd, &readbuf, 1024); if (nread == 0) { break; } else if (nread < 0) { close(tmpfd); return MLBUF_ERR; } if (write(tmpfd, readbuf, nread) != nread) { close(tmpfd); return MLBUF_ERR; } } // Now mmap tmp file mmap_buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, tmpfd, 0); if (mmap_buf == MAP_FAILED) { return MLBUF_ERR; } else if (buffer_set_mmapped(self, mmap_buf, (bint_t)size) != MLBUF_OK) { return MLBUF_ERR; } _buffer_munmap(self); self->mmap = mmap_buf; self->mmap_len = size; self->mmap_fd = tmpfd; return MLBUF_OK; } static int _buffer_open_read(buffer_t *self, int fd, size_t size) { int rc; char *buf; if (size <= PTRDIFF_MAX) { buf = malloc(size); } else { return MLBUF_ERR; } rc = MLBUF_OK; if (size != (size_t)read(fd, buf, size)) { rc = MLBUF_ERR; } else if (buffer_set(self, buf, (bint_t)size) != MLBUF_OK) { rc = MLBUF_ERR; } free(buf); return rc; } static int _buffer_bline_unslab(bline_t *self) { char *data; bline_char_t *chars; if (!self->is_data_slabbed) { return MLBUF_ERR; } data = malloc(self->data_len); chars = malloc(self->data_len * sizeof(bline_char_t)); memcpy(data, self->data, self->data_len); memcpy(chars, self->chars, self->data_len * sizeof(bline_char_t)); self->data = data; self->data_cap = self->data_len; self->chars = chars; self->chars_cap = self->data_len; self->is_data_slabbed = 0; return bline_count_chars(self); } static void _buffer_stat(buffer_t *self) { if (!self->path) { return; } stat(self->path, &self->st); // TODO err? } static int _buffer_baction_do(buffer_t *self, bline_t *bline, baction_t *action, int is_redo, bint_t *opt_repeat_offset) { int rc; bint_t col; bint_t offset; self->_is_in_undo = 1; col = opt_repeat_offset ? *opt_repeat_offset : action->start_col; buffer_get_offset(self, bline, col, &offset); if ((action->type == MLBUF_BACTION_TYPE_DELETE && is_redo) || (action->type == MLBUF_BACTION_TYPE_INSERT && !is_redo) ) { rc = buffer_delete(self, offset, (bint_t)((is_redo ? -1 : 1) * action->char_delta)); } else { rc = buffer_insert(self, offset, action->data, action->data_len, NULL); } self->_is_in_undo = 0; return rc; } static int _buffer_update(buffer_t *self, baction_t *action) { bline_t *tmp_line; bline_t *last_line; bint_t new_line_index; // Adjust counts self->byte_count += action->byte_delta; self->line_count += action->line_delta; self->is_data_dirty = 1; // Set unsaved self->is_unsaved = 1; // Renumber lines if (action->line_delta != 0) { last_line = NULL; new_line_index = action->start_line->line_index; for (tmp_line = action->start_line->next; tmp_line != NULL; tmp_line = tmp_line->next) { tmp_line->line_index = ++new_line_index; last_line = tmp_line; } self->last_line = last_line ? last_line : action->start_line; } // Restyle from start_line buffer_apply_styles(self, action->start_line, action->line_delta); // Raise event on listener if (self->callback && !self->is_in_callback) { self->is_in_callback = 1; self->callback(self, action, self->callback_udata); self->is_in_callback = 0; } // Handle undo stack if (self->_is_in_undo) { _baction_destroy(action); } else { _buffer_add_to_undo_stack(self, action); } return MLBUF_OK; } static int _buffer_undo(buffer_t *self, int by_group) { baction_t *action_to_undo; baction_t *first_action; bline_t *bline; int group_to_undo; // Find action to undo if (self->action_undone) { if (self->action_undone == self->actions) { return MLBUF_ERR; } else if (!self->action_undone->prev) { return MLBUF_ERR; } action_to_undo = self->action_undone->prev; } else if (self->action_tail) { action_to_undo = self->action_tail; } else { return MLBUF_ERR; } first_action = action_to_undo; // Remember action group for coarse undo (by_group) group_to_undo = action_to_undo->action_group; while (1) { // Get line to perform undo on bline = NULL; buffer_get_bline(self, action_to_undo->start_line_index, &bline); MLBUF_BLINE_ENSURE_CHARS(bline); if (!bline) { return MLBUF_ERR; } else if (action_to_undo->start_col > bline->char_count) { return MLBUF_ERR; } // Perform action if (_buffer_baction_do(self, bline, action_to_undo, 0, NULL) != MLBUF_OK) { return MLBUF_ERR; } self->action_undone = action_to_undo; // If by_group, undo next action in same group or break if (by_group && action_to_undo->prev && action_to_undo->prev != first_action && action_to_undo->prev->action_group == group_to_undo ) { action_to_undo = action_to_undo->prev; } else { break; } } return MLBUF_OK; } static int _buffer_redo(buffer_t *self, int by_group) { baction_t *action_to_redo; bline_t *bline; int *group_to_redo; // Find action to undo if (!self->action_undone) { return MLBUF_ERR; } action_to_redo = self->action_undone; // Set action group group_to_redo = by_group ? &action_to_redo->action_group : NULL; while (1) { // Get line to perform undo on bline = NULL; buffer_get_bline(self, action_to_redo->start_line_index, &bline); MLBUF_BLINE_ENSURE_CHARS(bline); if (!bline) { return MLBUF_ERR; } else if (action_to_redo->start_col > bline->char_count) { return MLBUF_ERR; } // Perform action if (_buffer_baction_do(self, bline, action_to_redo, 1, NULL) != MLBUF_OK) { return MLBUF_ERR; } self->action_undone = action_to_redo->next; // Redo next action in same group or break if (group_to_redo && action_to_redo->next && action_to_redo->next != action_to_redo && action_to_redo->next->action_group == *group_to_redo ) { action_to_redo = action_to_redo->next; } else { break; } } return MLBUF_OK; } static int _buffer_truncate_undo_stack(buffer_t *self, baction_t *action_from) { baction_t *action_target; baction_t *action_tmp; int do_delete; self->action_tail = action_from->prev != action_from ? action_from->prev : NULL; do_delete = 0; DL_FOREACH_SAFE(self->actions, action_target, action_tmp) { if (!do_delete && action_target == action_from) { do_delete = 1; } if (do_delete) { DL_DELETE(self->actions, action_target); _baction_destroy(action_target); } } return MLBUF_OK; } static int _buffer_add_to_undo_stack(buffer_t *self, baction_t *action) { if (self->action_undone) { // We are recording an action after an undo has been performed, so we // need to chop off the tail of the baction list before recording the // new one. // TODO could implement multilevel undo here instead _buffer_truncate_undo_stack(self, self->action_undone); self->action_undone = NULL; } // Append action to list DL_APPEND(self->actions, action); if (self->action_group) action->action_group = *self->action_group; self->action_tail = action; return MLBUF_OK; } static int _buffer_apply_styles_all(bline_t *bline, bint_t min_nlines) { buffer_t *buffer; srule_node_t *srule_node; srule_t *open_rule, *found_rule, *eol_rule_orig; bint_t col, styled_nlines, start, stop; int eol_rule_changed; buffer = bline->buffer; open_rule = bline->prev ? bline->prev->eol_rule : NULL; styled_nlines = 0; col = 0; eol_rule_changed = 0; _buffer_bline_reset_styles(bline); while (1) { found_rule = NULL; // Reset style cache at beginning of each line if (col == 0) { DL_FOREACH(buffer->srules, srule_node) { memset(&srule_node->srule->memo, 0, sizeof(srule_node->srule->memo)); memset(&srule_node->srule->memo_end, 0, sizeof(srule_node->srule->memo_end)); } } if (bline->char_count <= 0) { // Nothing to do on empty line } else if (open_rule) { // Look for end of open_rule if (_buffer_match_srule(bline, col, open_rule, 1, &start, &stop)) { // End of open_rule found; close rule found_rule = open_rule; open_rule = NULL; } else { // End of open_rule NOT found; style entire line found_rule = open_rule; start = col; stop = bline->char_count; } } else { // Look for any rule DL_FOREACH(buffer->srules, srule_node) { if (_buffer_match_srule(bline, col, srule_node->srule, 0, &start, &stop) && start == col) { found_rule = srule_node->srule; if (srule_node->srule->cre_end) open_rule = srule_node->srule; break; } } } if (found_rule) { // Set style of found_rule _buffer_bline_style(bline, MLBUF_MIN(start, col), stop, &found_rule->style); col = MLBUF_MAX(stop, col + 1); } else { // Nothing found; advance one char col += 1; } if (col >= bline->char_count) { // At end of line // Set eol_rule and already_open eol_rule_orig = bline->eol_rule; bline->eol_rule = open_rule; // can be NULL eol_rule_changed = eol_rule_orig != bline->eol_rule ? 1 : 0; // Advance to next line styled_nlines += 1; bline = bline->next; col = 0; // Check stop conditions if (!bline || (styled_nlines >= min_nlines && !eol_rule_changed)) { break; } // Clear style of next line _buffer_bline_reset_styles(bline); } } return MLBUF_OK; } static int _buffer_match_srule(bline_t *bline, bint_t look_offset, srule_t *srule, int end_rule, bint_t *ret_start, bint_t *ret_stop) { int rc; PCRE2_SIZE substrs[3]; pcre2_code *cre; smemo_t *memo; cre = end_rule ? srule->cre_end : srule->cre; memo = end_rule ? &srule->memo_end : &srule->memo; if (memo->looked && look_offset >= memo->look_offset) { if (memo->found) { if (look_offset <= memo->start) { *ret_start = memo->start; *ret_stop = memo->stop; return 1; } } else { return 0; } } rc = pcre2_match(cre, (PCRE2_SPTR)bline->data, (PCRE2_SIZE)bline->data_len, (PCRE2_SIZE)look_offset, 0, pcre2_md, NULL); memo->looked = 1; memo->look_offset = look_offset; memo->found = 0; if (rc < 0) return 0; memcpy(substrs, pcre2_get_ovector_pointer(pcre2_md), 3 * sizeof(PCRE2_SIZE)); if (substrs[1] == PCRE2_UNSET) return 0; *ret_start = _buffer_bline_index_to_col(bline, substrs[0]); *ret_stop = _buffer_bline_index_to_col(bline, substrs[1]); memo->found = 1; memo->start = *ret_start; memo->stop = *ret_stop; return 1; } static void _buffer_bline_reset_styles(bline_t *bline) { sblock_t reset = {0}; _buffer_bline_style(bline, 0, bline->char_count, &reset); } static void _buffer_bline_style(bline_t *bline, bint_t start, bint_t stop, sblock_t *style) { bint_t i; for (i = start; i < stop && i < bline->char_count; i++) { bline->chars[i].style = *style; } } static bline_t *_buffer_bline_new(buffer_t *self) { bline_t *bline; bline = calloc(1, sizeof(bline_t)); bline->buffer = self; return bline; } static int _buffer_bline_free(bline_t *bline, bline_t *maybe_mark_line, bint_t col_delta) { mark_t *mark; mark_t *mark_tmp; if (!bline->is_data_slabbed) { if (bline->data) free(bline->data); if (bline->chars) free(bline->chars); } if (bline->marks) { DL_FOREACH_SAFE(bline->marks, mark, mark_tmp) { if (maybe_mark_line) { _mark_mark_move_inner(mark, maybe_mark_line, mark->col + col_delta, 1); } else { buffer_destroy_mark(bline->buffer, mark); } } } if (!bline->is_slabbed) { free(bline); } return MLBUF_OK; } static bline_t *_buffer_bline_break(bline_t *bline, bint_t col) { bint_t index; bint_t len; bline_t *new_line; bline_t *tmp_line; mark_t *mark; mark_t *mark_tmp; // Unslab if needed if (bline->is_data_slabbed) _buffer_bline_unslab(bline); // Make new_line new_line = _buffer_bline_new(bline->buffer); // Find byte index to break on index = _buffer_bline_col_to_index(bline, col); len = bline->data_len - index; if (len > 0) { // Move data to new line new_line->data = malloc(len); memcpy(new_line->data, bline->data + index, len); new_line->data_len = len; new_line->data_cap = len; bline_count_chars(new_line); // Update char widths // Truncate orig line bline->data_len -= len; bline_count_chars(bline); // Update char widths } // Insert new_line in linked list tmp_line = bline->next; bline->next = new_line; new_line->next = tmp_line; new_line->prev = bline; if (tmp_line) tmp_line->prev = new_line; // Move marks at or past col to new_line DL_FOREACH_SAFE(bline->marks, mark, mark_tmp) { if (mark_is_after_col_minus_lefties(mark, col)) { _mark_mark_move_inner(mark, new_line, mark->col - col, 1); } } return new_line; } // Given start_line:start_col + num_chars, find end_line:end_col static void _buffer_find_end_pos(bline_t *start_line, bint_t start_col, bint_t num_chars, bline_t **ret_end_line, bint_t *ret_end_col, bint_t *ret_safe_num_chars) { bline_t *end_line; bint_t end_col; bint_t num_chars_rem; end_line = start_line; end_col = start_col; num_chars_rem = num_chars; while (num_chars_rem > 0) { MLBUF_BLINE_ENSURE_CHARS(end_line); if (end_line->char_count - end_col >= num_chars_rem) { end_col += num_chars_rem; num_chars_rem = 0; } else { num_chars_rem -= (end_line->char_count - end_col) + 1; if (end_line->next) { end_line = end_line->next; end_col = 0; } else { end_col = end_line->char_count; break; } } } num_chars -= num_chars_rem; *ret_end_line = end_line; *ret_end_col = end_col; *ret_safe_num_chars = num_chars; } static void _buffer_bline_replace(bline_t *bline, bint_t start_col, char *data, bint_t data_len, str_t *del_data) { bint_t start_index; mark_t *mark; // Unslab if needed if (bline->is_data_slabbed) _buffer_bline_unslab(bline); // Realloc if needed MLBUF_BLINE_ENSURE_CHARS(bline); if (start_col <= 0) { start_index = 0; } else if (start_col >= bline->char_count) { start_index = bline->data_len; } else { start_index = bline->chars[start_col].index; } if (start_index + data_len >= bline->data_cap) { bline->data = realloc(bline->data, start_index + data_len); bline->data_cap = start_index + data_len; } // Store del_data str_append_len(del_data, bline->data + start_index, bline->data_len - start_index); // Copy data into slot and update chars memmove(bline->data + start_index, data, (size_t)data_len); bline->data_len = start_index + data_len; bline_count_chars(bline); // Fix marks DL_FOREACH(bline->marks, mark) { if (mark->col > bline->char_count) { mark->col = bline->char_count; } } } static bint_t _buffer_bline_insert(bline_t *bline, bint_t col, char *data, bint_t data_len, int move_marks) { bint_t index; mark_t *mark; mark_t *mark_tmp; bint_t orig_char_count; bint_t num_chars_added; // Unslab if needed if (bline->is_data_slabbed) _buffer_bline_unslab(bline); // Get orig char_count MLBUF_BLINE_ENSURE_CHARS(bline); orig_char_count = bline->char_count; // Ensure space for data if (!bline->data) { bline->data = malloc(data_len); bline->data_cap = data_len; } else if (bline->data_len + data_len > bline->data_cap) { bline->data = realloc(bline->data, bline->data_len + data_len); bline->data_cap = bline->data_len + data_len; } // Find insert point index = _buffer_bline_col_to_index(bline, col); // Make room for insert data if (index < bline->data_len) { memmove(bline->data + index + data_len, bline->data + index, bline->data_len - index); } bline->data_len += data_len; // Insert data memcpy(bline->data + index, data, data_len); // Update chars bline_count_chars(bline); num_chars_added = bline->char_count - orig_char_count; // Move marks after col right by num_chars_added if (move_marks) { DL_FOREACH_SAFE(bline->marks, mark, mark_tmp) { if (mark_is_after_col_minus_lefties(mark, col)) { mark->col += num_chars_added; } } } return num_chars_added; } static bint_t _buffer_bline_delete(bline_t *bline, bint_t col, bint_t num_chars) { bint_t safe_num_chars; bint_t index; bint_t index_end; bint_t move_len; mark_t *mark; mark_t *mark_tmp; bint_t orig_char_count; bint_t num_chars_deleted; // Unslab if needed if (bline->is_data_slabbed) _buffer_bline_unslab(bline); // Get orig char_count MLBUF_BLINE_ENSURE_CHARS(bline); orig_char_count = bline->char_count; // Clamp num_chars safe_num_chars = MLBUF_MIN(bline->char_count - col, num_chars); if (safe_num_chars != num_chars) { MLBUF_DEBUG_PRINTF("num_chars=%" PRIdMAX " does not match safe_num_chars=%" PRIdMAX "\n", num_chars, safe_num_chars); } // Nothing to do if safe_num_chars is 0 if (safe_num_chars < 1) { MLBUF_DEBUG_PRINTF("safe_num_chars=%" PRIdMAX " lt 1\n", safe_num_chars); return MLBUF_OK; } // Find delete bounds index = _buffer_bline_col_to_index(bline, col); index_end = _buffer_bline_col_to_index(bline, col + safe_num_chars); move_len = (bint_t)(bline->data_len - index_end); // Shift data if (move_len > 0) { memmove(bline->data + index, bline->data + index_end, move_len); } bline->data_len -= index_end - index; // Update chars bline_count_chars(bline); num_chars_deleted = orig_char_count - bline->char_count; // Move marks after col left by num_chars_deleted DL_FOREACH_SAFE(bline->marks, mark, mark_tmp) { if (mark->col > col) { mark->col = MLBUF_MAX(0, mark->col - num_chars_deleted); } } return num_chars_deleted; } static bint_t _buffer_bline_col_to_index(bline_t *bline, bint_t col) { bint_t index; MLBUF_BLINE_ENSURE_CHARS(bline); if (!bline->chars) { return 0; } if (col >= bline->char_count) { index = bline->data_len; } else { index = bline->chars[col].index; } return index; } static bint_t _buffer_bline_index_to_col(bline_t *bline, bint_t index) { MLBUF_BLINE_ENSURE_CHARS(bline); if (index < 1) { return 0; } else if (index >= bline->data_len) { return bline->char_count; } return bline->chars[index].index_to_vcol; } // Close self->fd and self->mmap if needed static int _buffer_munmap(buffer_t *self) { if (self->mmap) { munmap(self->mmap, self->mmap_len); close(self->mmap_fd); self->mmap = NULL; self->mmap_len = 0; self->mmap_fd = -1; } return MLBUF_OK; } // Make a new single-line style rule srule_t *srule_new_single(char *re, bint_t re_len, int caseless, uint16_t fg, uint16_t bg) { srule_t *rule; int re_errcode; PCRE2_SIZE re_erroffset; rule = calloc(1, sizeof(srule_t)); rule->type = MLBUF_SRULE_TYPE_SINGLE; rule->style.fg = fg; rule->style.bg = bg; rule->re = malloc((re_len + 1) * sizeof(char)); snprintf(rule->re, re_len + 1, "%.*s", (int)re_len, re); rule->cre = pcre2_compile((PCRE2_SPTR)rule->re, (PCRE2_SIZE)strlen(rule->re), PCRE2_NO_AUTO_CAPTURE | (caseless ? PCRE2_CASELESS : 0), &re_errcode, &re_erroffset, NULL); if (!rule->cre) { // TODO log error srule_destroy(rule); return NULL; } return rule; } // Make a new multi-line style rule srule_t *srule_new_multi(char *re, bint_t re_len, char *re_end, bint_t re_end_len, uint16_t fg, uint16_t bg) { srule_t *rule; int re_errcode;; PCRE2_SIZE re_erroffset; rule = calloc(1, sizeof(srule_t)); rule->type = MLBUF_SRULE_TYPE_MULTI; rule->style.fg = fg; rule->style.bg = bg; rule->re = malloc((re_len + 1) * sizeof(char)); rule->re_end = malloc((re_end_len + 1) * sizeof(char)); snprintf(rule->re, re_len + 1, "%.*s", (int)re_len, re); snprintf(rule->re_end, re_end_len + 1, "%.*s", (int)re_end_len, re_end); rule->cre = pcre2_compile((PCRE2_SPTR)rule->re, (PCRE2_SIZE)strlen(rule->re), PCRE2_NO_AUTO_CAPTURE, &re_errcode, &re_erroffset, NULL); rule->cre_end = pcre2_compile((PCRE2_SPTR)rule->re_end, (PCRE2_SIZE)strlen(rule->re_end), PCRE2_NO_AUTO_CAPTURE, &re_errcode, &re_erroffset, NULL); if (!rule->cre || !rule->cre_end) { // TODO log error srule_destroy(rule); return NULL; } return rule; } // Make a new range style rule srule_t *srule_new_range(mark_t *range_a, mark_t *range_b, uint16_t fg, uint16_t bg) { srule_t *rule; rule = calloc(1, sizeof(srule_t)); rule->type = MLBUF_SRULE_TYPE_RANGE; rule->style.fg = fg; rule->style.bg = bg; rule->range_a = range_a; rule->range_b = range_b; return rule; } // Free an srule int srule_destroy(srule_t *srule) { if (srule->re) free(srule->re); if (srule->re_end) free(srule->re_end); if (srule->cre) pcre2_code_free(srule->cre); if (srule->cre_end) pcre2_code_free(srule->cre_end); free(srule); return MLBUF_OK; } static int _baction_destroy(baction_t *action) { if (action->data) free(action->data); free(action); return MLBUF_OK; } // Move self/col forward until col fits on current line static void _bline_advance_col(bline_t **self, bint_t *col) { while (1) { MLBUF_BLINE_ENSURE_CHARS(*self); if (*col > (*self)->char_count) { if ((*self)->next) { *col -= (*self)->char_count + 1; *self = (*self)->next; } else { *col = (*self)->char_count; break; } } else { break; } } } mle-1.7.2/bview.c000066400000000000000000001301351443351614700135710ustar00rootroot00000000000000#include #include #include #include "mle.h" static int _bview_pop_kmap(bview_t *bview, kmap_t **optret_kmap, int allow_pop_root); static int _bview_rectify_viewport_dim(bview_t *self, bline_t *bline, bint_t vpos, int dim_scope, int dim_size, bint_t *view_vpos); static void _bview_init(bview_t *self, buffer_t *buffer); static void _bview_init_resized(bview_t *self); static kmap_t *_bview_get_init_kmap(editor_t *editor); static void _bview_buffer_callback(buffer_t *buffer, baction_t *action, void *udata); static int _bview_set_linenum_width(bview_t *self); static void _bview_deinit(bview_t *self); static void _bview_set_tab_width(bview_t *self, int tab_width); static void _bview_fix_path(bview_t *self, char *path, int path_len, char **ret_path, int *ret_path_len, bint_t *ret_line_num); static buffer_t *_bview_open_buffer(bview_t *self, char *opt_path, int opt_path_len); static void _bview_draw_prompt(bview_t *self); static void _bview_draw_status(bview_t *self); static void _bview_draw_edit(bview_t *self, int x, int y, int w, int h); static void _bview_draw_bline(bview_t *self, bline_t *bline, int rect_y, bline_t **optret_bline, int *optret_rect_y); static void _bview_highlight_bracket_pair(bview_t *self, mark_t *mark); static bint_t _bview_get_viewport_x(bview_t *self, bline_t *bline); static int _bview_is_cursor_line(bview_t *self, bline_t *bline); static int _bview_is_soft_wrapped(bview_t *self, bline_t *bline); static int _bview_is_in_range(bline_t *bline, bint_t col, int is_block, srule_t **ret_srule); static int _bview_is_in_isearch(bview_t *self, bint_t col, srule_t **ret_srule); static int _bview_populate_isearch_ranges(bview_t *self, bline_t *bline); // Create a new bview bview_t *bview_new(editor_t *editor, int type, char *opt_path, int opt_path_len, buffer_t *opt_buffer) { static int id = 0; bview_t *self; buffer_t *buffer; // Allocate and init bview self = calloc(1, sizeof(bview_t)); self->editor = editor; self->type = type; self->path = strndup(opt_path, opt_path_len); self->rect_caption.fg = TB_WHITE; self->rect_caption.bg = TB_BLACK; self->rect_lines.fg = TB_YELLOW; self->rect_lines.bg = TB_BLACK; self->rect_margin_left.fg = TB_RED; self->rect_margin_right.fg = TB_RED; self->rect_buffer.h = 10; // TODO hack to fix _bview_set_linenum_width before bview_resize self->tab_width = editor->tab_width; self->tab_to_space = editor->tab_to_space; self->soft_wrap = editor->soft_wrap; self->viewport_scope_x = editor->viewport_scope_x; self->viewport_scope_y = editor->viewport_scope_y; self->id = (id++); // Open buffer if (opt_buffer) { buffer = opt_buffer; } else { buffer = _bview_open_buffer(self, opt_path, opt_path_len); } _bview_init(self, buffer); return self; } // Open a buffer in an existing bview int bview_open(bview_t *self, char *path, int path_len) { buffer_t *buffer; buffer = _bview_open_buffer(self, path, path_len); if (self->path) free(self->path); self->path = strndup(path, path_len); _bview_init(self, buffer); bview_resize(self, self->x, self->y, self->w, self->h); return MLE_OK; } // Free a bview int bview_destroy(bview_t *self) { _bview_deinit(self); if (self->path) free(self->path); // TODO ensure everything freed free(self); return MLE_OK; } // Move and resize a bview to the given position and dimensions int bview_resize(bview_t *self, int x, int y, int w, int h) { int aw, ah; self->x = x; self->y = y; self->w = w; self->h = h; aw = w; ah = h; if (self->split_child) { if (self->split_is_vertical) { aw = MLE_MAX(1, (int)((float)aw * self->split_factor)); } else { ah = MLE_MAX(1, (int)((float)ah * self->split_factor)); } } if (MLE_BVIEW_IS_EDIT(self)) { self->rect_caption.x = x; self->rect_caption.y = y; self->rect_caption.w = aw; self->rect_caption.h = 1; self->rect_lines.x = x; self->rect_lines.y = y + 1; self->rect_lines.w = self->linenum_width; self->rect_lines.h = ah - 1; self->rect_margin_left.x = x + self->linenum_width; self->rect_margin_left.y = y + 1; self->rect_margin_left.w = 1; self->rect_margin_left.h = ah - 1; self->rect_buffer.x = x + self->linenum_width + 1; self->rect_buffer.y = y + 1; self->rect_buffer.w = MLE_MAX(1, aw - (self->linenum_width + 1 + 1)); self->rect_buffer.h = ah - 1; self->rect_margin_right.x = x + (aw - 1); self->rect_margin_right.y = y + 1; self->rect_margin_right.w = 1; self->rect_margin_right.h = ah - 1; } else { self->rect_buffer.x = x; self->rect_buffer.y = y; self->rect_buffer.w = aw; self->rect_buffer.h = ah; } if (self->split_child) { bview_resize( self->split_child, x + (self->split_is_vertical ? aw : 0), y + (self->split_is_vertical ? 0 : ah), w - (self->split_is_vertical ? aw : 0), h - (self->split_is_vertical ? 0 : ah) ); } if (!self->is_resized) { _bview_init_resized(self); self->is_resized = 1; } bview_rectify_viewport(self); return MLE_OK; } // Return top-most split_parent of a bview bview_t *bview_get_split_root(bview_t *self) { bview_t *root; root = self; while (root->split_parent) { root = root->split_parent; } return root; } // Draw bview to screen int bview_draw(bview_t *self) { if (MLE_BVIEW_IS_PROMPT(self)) { _bview_draw_prompt(self); } else if (MLE_BVIEW_IS_STATUS(self)) { _bview_draw_status(self); } _bview_draw_edit(self, self->x, self->y, self->w, self->h); return MLE_OK; } // Set cursor to screen int bview_draw_cursor(bview_t *self, int set_real_cursor) { cursor_t *cursor; mark_t *mark; int screen_x; int screen_y; struct tb_cell *cell; DL_FOREACH(self->cursors, cursor) { mark = cursor->mark; if (bview_get_screen_coords(self, mark, &screen_x, &screen_y, &cell) != MLE_OK) { // Out of bounds continue; } if (set_real_cursor && cursor == self->active_cursor) { // Set terminal cursor tb_set_cursor(screen_x, screen_y); } else { // Set fake cursor tb_change_cell(screen_x, screen_y, cell->ch, cell->fg, cell->bg | (cursor->is_asleep ? TB_RED : TB_CYAN)); // TODO configurable } if (self->editor->highlight_bracket_pairs) { _bview_highlight_bracket_pair(self, mark); } } return MLE_OK; } // Push a kmap int bview_push_kmap(bview_t *bview, kmap_t *kmap) { kmap_node_t *node; node = calloc(1, sizeof(kmap_node_t)); node->kmap = kmap; node->bview = bview; DL_APPEND(bview->kmap_stack, node); bview->kmap_tail = node; return MLE_OK; } // Pop a kmap int bview_pop_kmap(bview_t *bview, kmap_t **optret_kmap) { return _bview_pop_kmap(bview, optret_kmap, 0); } // Split a bview int bview_split(bview_t *self, int is_vertical, float factor, bview_t **optret_bview) { bview_t *child; if (self->split_child) { MLE_RETURN_ERR(self->editor, "bview %p is already split", (void*)self); } else if (!MLE_BVIEW_IS_EDIT(self)) { MLE_RETURN_ERR(self->editor, "bview %p is not an edit bview", (void*)self); } // Make child editor_open_bview(self->editor, self, self->type, NULL, 0, 1, 0, 1, self->buffer, &child); child->split_parent = self; self->split_child = child; self->split_factor = factor; self->split_is_vertical = is_vertical; // Move cursor to same position mark_move_to(child->active_cursor->mark, self->active_cursor->mark->bline->line_index, self->active_cursor->mark->col); bview_center_viewport_y(child); // Resize self bview_resize(self, self->x, self->y, self->w, self->h); if (optret_bview) { *optret_bview = child; } return MLE_OK; } // Return number of active cursors int bview_get_active_cursor_count(bview_t *self) { int count; cursor_t *cursor; count = 0; DL_FOREACH(self->cursors, cursor) { if (!cursor->is_asleep) { count += 1; } } return count; } // Add a cursor to a bview int bview_add_cursor(bview_t *self, bline_t *opt_bline, bint_t opt_col, cursor_t **optret_cursor) { cursor_t *cursor; cursor = calloc(1, sizeof(cursor_t)); cursor->bview = self; if (!opt_bline) opt_bline = self->buffer->first_line; if (opt_col < 0) opt_col = 0; cursor->mark = buffer_add_mark(self->buffer, opt_bline, opt_col); DL_APPEND(self->cursors, cursor); if (!self->active_cursor) { self->active_cursor = cursor; } cursor->is_block = self->active_cursor->is_block; if (optret_cursor) { *optret_cursor = cursor; } return MLE_OK; } // Add sleeping cursor int bview_add_cursor_asleep(bview_t *self, bline_t *opt_bline, bint_t opt_col, cursor_t **optret_cursor) { cursor_t *cursor; bview_add_cursor(self, opt_bline, opt_col, &cursor); cursor->is_asleep = 1; if (optret_cursor) *optret_cursor = cursor; return MLE_OK; } // Wake all sleeping cursors int bview_wake_sleeping_cursors(bview_t *self) { cursor_t *cursor; DL_FOREACH(self->cursors, cursor) { if (cursor->is_asleep) { cursor->is_asleep = 0; } } return MLE_OK; } // Remove all cursors except one int bview_remove_cursors_except(bview_t *self, cursor_t *one) { cursor_t *cursor; cursor_t *cursor_tmp; DL_FOREACH_SAFE(self->cursors, cursor, cursor_tmp) { if (cursor != one) { bview_remove_cursor(self, cursor); } } return MLE_OK; } // Remove a cursor from a bview int bview_remove_cursor(bview_t *self, cursor_t *cursor) { cursor_t *el; cursor_t *tmp; DL_FOREACH_SAFE(self->cursors, el, tmp) { if (el == cursor) { self->active_cursor = el->prev && el->prev != el ? el->prev : el->next; DL_DELETE(self->cursors, el); if (el->sel_rule) { buffer_remove_srule(el->bview->buffer, el->sel_rule); srule_destroy(el->sel_rule); el->sel_rule = NULL; } if (el->cut_buffer) free(el->cut_buffer); free(el); return MLE_OK; } } return MLE_ERR; } // Set viewport y safely int bview_set_viewport_y(bview_t *self, bint_t y, int do_rectify) { if (y < 0) { y = 0; } else if (y >= self->buffer->line_count) { y = self->buffer->line_count - 1; } self->viewport_y = y; if (do_rectify) bview_rectify_viewport(self); mark_move_to(self->viewport_mark, self->viewport_y, 0); return MLE_OK; } // Center the viewport vertically int bview_center_viewport_y(bview_t *self) { bint_t center; center = self->active_cursor->mark->bline->line_index - self->rect_buffer.h/2; if (center < 0) center = 0; return bview_set_viewport_y(self, center, 1); } // Zero the viewport vertically int bview_zero_viewport_y(bview_t *self) { return bview_set_viewport_y(self, self->active_cursor->mark->bline->line_index, 1); } // Maximize the viewport vertically int bview_max_viewport_y(bview_t *self) { bint_t max; max = self->active_cursor->mark->bline->line_index - self->rect_buffer.h; if (max < 0) max = 0; return bview_set_viewport_y(self, max, 1); } // Rectify the viewport int bview_rectify_viewport(bview_t *self) { mark_t *mark; mark = self->active_cursor->mark; // Rectify each dimension of the viewport MLBUF_BLINE_ENSURE_CHARS(mark->bline); _bview_rectify_viewport_dim(self, mark->bline, MLE_MARK_COL_TO_VCOL(mark), self->viewport_scope_x, self->rect_buffer.w, &self->viewport_x_vcol); bline_get_col_from_vcol(mark->bline, self->viewport_x_vcol, &(self->viewport_x)); if (_bview_rectify_viewport_dim(self, mark->bline, mark->bline->line_index, self->viewport_scope_y, self->rect_buffer.h, &self->viewport_y)) { // TODO viewport_y_vrow (soft-wrapped lines, code folding, etc) // Adjust viewport_mark mark_move_to(self->viewport_mark, self->viewport_y, 0); } return MLE_OK; } // Add a listener int bview_add_listener(bview_t *self, bview_listener_cb_t callback, void *udata) { bview_listener_t *listener; listener = calloc(1, sizeof(bview_listener_t)); listener->callback = callback; listener->udata = udata; DL_APPEND(self->listeners, listener); return MLE_OK; } // Remove and free a listener int bview_destroy_listener(bview_t *self, bview_listener_t *listener) { DL_DELETE(self->listeners, listener); free(listener); return MLE_OK; } // Pop a kmap static int _bview_pop_kmap(bview_t *bview, kmap_t **optret_kmap, int allow_pop_root) { kmap_node_t *node_to_pop; node_to_pop = bview->kmap_tail; if (!node_to_pop || (!allow_pop_root && node_to_pop == bview->kmap_stack)) { return MLE_ERR; } if (optret_kmap) { *optret_kmap = node_to_pop->kmap; } bview->kmap_tail = node_to_pop->prev != node_to_pop ? node_to_pop->prev : NULL; DL_DELETE(bview->kmap_stack, node_to_pop); free(node_to_pop); return MLE_OK; } // Rectify a viewport dimension. Return 1 if changed, else 0. static int _bview_rectify_viewport_dim(bview_t *self, bline_t *bline, bint_t vpos, int dim_scope, int dim_size, bint_t *view_vpos) { int rc; bint_t vpos_start; bint_t vpos_stop; (void)self; (void)bline; // Find bounds if (dim_scope < 0) { // Keep cursor at least `dim_scope` cells away from edge // Remember dim_scope is negative here dim_scope = MLE_MAX(dim_scope, ((dim_size / 2) * -1)); vpos_start = *view_vpos - dim_scope; // N in from left edge vpos_stop = (*view_vpos + dim_size) + dim_scope; // N in from right edge } else { // Keep cursor within `dim_scope/2` cells of midpoint dim_scope = MLE_MIN(dim_scope, dim_size); vpos_start = (*view_vpos + (dim_size / 2)) - (int)floorf((float)dim_scope * 0.5); // -N/2 from midpoint vpos_stop = (*view_vpos + (dim_size / 2)) + (int)ceilf((float)dim_scope * 0.5); // +N/2 from midpoint } // Rectify rc = 1; if (vpos < vpos_start) { *view_vpos -= MLE_MIN(*view_vpos, vpos_start - vpos); } else if (vpos >= vpos_stop) { *view_vpos += ((vpos - vpos_stop) + 1); } else { rc = 0; } return rc; } // Init a bview with a buffer static void _bview_init(bview_t *self, buffer_t *buffer) { cursor_t *cursor_tmp; kmap_t *kmap_init; _bview_deinit(self); // Reference buffer self->buffer = buffer; self->buffer->ref_count += 1; _bview_set_linenum_width(self); // Push normal mode kmap_init = _bview_get_init_kmap(self->editor); if (kmap_init != self->editor->kmap_normal) { // Make kmap_normal the bottom if init kmap isn't kmap_normal bview_push_kmap(self, self->editor->kmap_normal); } bview_push_kmap(self, kmap_init); // Set syntax bview_set_syntax(self, NULL); // Add a cursor bview_add_cursor(self, self->buffer->first_line, 0, &cursor_tmp); // Add viewport_mark self->viewport_mark = buffer_add_mark(self->buffer, NULL, 0); self->viewport_mark->lefty = 1; // Stay put at col 0 } // Invoked once after a bview has been resized for the first time static void _bview_init_resized(bview_t *self) { // Move cursor to startup line if present if (self->startup_linenum > 0) { mark_move_to(self->active_cursor->mark, self->startup_linenum, 0); bview_center_viewport_y(self); } } // Return initial kmap to use static kmap_t *_bview_get_init_kmap(editor_t *editor) { if (!editor->kmap_init) { if (editor->kmap_init_name) { HASH_FIND_STR(editor->kmap_map, editor->kmap_init_name, editor->kmap_init); } if (!editor->kmap_init) { editor->kmap_init = editor->kmap_normal; } } return editor->kmap_init; } // Called by mlbuf after edits static void _bview_buffer_callback(buffer_t *buffer, baction_t *action, void *udata) { editor_t *editor; bview_t *self; bview_t *active; bview_listener_t *listener; self = (bview_t*)udata; editor = self->editor; active = editor->active; // Rectify viewport if edit was on active bview if (active->buffer == buffer) { bview_rectify_viewport(active); } if (action && action->line_delta != 0) { bview_t *bview; bview_t *tmp1; bview_t *tmp2; CDL_FOREACH_SAFE2(editor->all_bviews, bview, tmp1, tmp2, all_prev, all_next) { if (bview->buffer == buffer) { // Adjust linenum_width if (_bview_set_linenum_width(bview)) { bview_resize(bview, bview->x, bview->y, bview->w, bview->h); } } } } // Call bview listeners DL_FOREACH(self->listeners, listener) { listener->callback(self, action, listener->udata); } // Notify event observers editor_notify_observers(editor, "buffer:baction", (void*)action); } // Set linenum_width and return 1 if changed static int _bview_set_linenum_width(bview_t *self) { int orig; orig = self->linenum_width; self->abs_linenum_width = MLE_MAX(1, (int)(floor(log10((double)self->buffer->line_count))) + 1); if (self->editor->linenum_type != MLE_LINENUM_TYPE_ABS) { self->rel_linenum_width = MLE_MAX( self->editor->linenum_type == MLE_LINENUM_TYPE_BOTH ? 1 : self->abs_linenum_width, (int)(floor(log10((double)self->rect_buffer.h))) + 1 ); } else { self->rel_linenum_width = 0; } if (self->editor->linenum_type == MLE_LINENUM_TYPE_ABS) { self->linenum_width = self->abs_linenum_width; } else if (self->editor->linenum_type == MLE_LINENUM_TYPE_REL) { self->linenum_width = self->abs_linenum_width > self->rel_linenum_width ? self->abs_linenum_width : self->rel_linenum_width; } else if (self->editor->linenum_type == MLE_LINENUM_TYPE_BOTH) { self->linenum_width = self->abs_linenum_width + 1 + self->rel_linenum_width; } return orig == self->linenum_width ? 0 : 1; } // Deinit a bview static void _bview_deinit(bview_t *self) { bview_listener_t *listener; bview_listener_t *listener_tmp; // Remove all kmaps while (self->kmap_tail) { _bview_pop_kmap(self, NULL, 1); } // Remove all syntax rules if (self->syntax) { srule_node_t *srule_node; buffer_set_styles_enabled(self->buffer, 0); DL_FOREACH(self->syntax->srules, srule_node) { buffer_remove_srule(self->buffer, srule_node->srule); } buffer_set_styles_enabled(self->buffer, 1); } // Remove all cursors while (self->active_cursor) { bview_remove_cursor(self, self->active_cursor); } // Destroy async proc if (self->aproc) { aproc_destroy(self->aproc, 1); self->aproc = NULL; } // Remove all listeners DL_FOREACH_SAFE(self->listeners, listener, listener_tmp) { bview_destroy_listener(self, listener); } // Dereference/free buffer if (self->buffer) { self->buffer->ref_count -= 1; if (self->buffer->ref_count < 1) { buffer_destroy(self->buffer); } self->buffer = NULL; } // Unset viewport_mark as it may point to a freed mark at this point if // we are re-using a bview, e.g., after cmd_open_replace_file. self->viewport_mark = NULL; // Free last_search if (self->last_search) { free(self->last_search); self->last_search = NULL; } // Free isearch_ranges if (self->isearch_ranges) { free(self->isearch_ranges); self->isearch_ranges = NULL; self->isearch_ranges_len = 0; self->isearch_ranges_cap = 0; } } // Set syntax on bview buffer int bview_set_syntax(bview_t *self, char *opt_syntax) { syntax_t *syntax; syntax_t *use_syntax; srule_node_t *srule_node; // Only set syntax on edit bviews if (!MLE_BVIEW_IS_EDIT(self)) { return MLE_ERR; } use_syntax = NULL; if (opt_syntax) { // Set by opt_syntax HASH_FIND_STR(self->editor->syntax_map, opt_syntax, use_syntax); } else if (self->editor->is_in_init && self->editor->syntax_override) { // Set by override at init HASH_FIND_STR(self->editor->syntax_map, self->editor->syntax_override, use_syntax); } else if (self->buffer->path) { // Set by path for (syntax = self->editor->syntax_last; syntax != NULL; syntax = syntax->hh.prev) { if (util_pcre_match(syntax->path_pattern, self->buffer->path, strlen(self->buffer->path), NULL, NULL)) { use_syntax = syntax; break; } } } buffer_set_styles_enabled(self->buffer, 0); // Remove current syntax if (self->syntax) { DL_FOREACH(self->syntax->srules, srule_node) { buffer_remove_srule(self->buffer, srule_node->srule); self->syntax = NULL; } } // Set syntax if found if (use_syntax) { DL_FOREACH(use_syntax->srules, srule_node) { buffer_add_srule(self->buffer, srule_node->srule); } self->syntax = use_syntax; } // Set tab settings self->tab_to_space = (use_syntax && use_syntax->tab_to_space >= 0) ? use_syntax->tab_to_space : self->editor->tab_to_space; _bview_set_tab_width(self, (use_syntax && use_syntax->tab_width >= 1) ? use_syntax->tab_width : self->editor->tab_width ); buffer_set_styles_enabled(self->buffer, 1); return use_syntax ? MLE_OK : MLE_ERR; } static void _bview_set_tab_width(bview_t *self, int tab_width) { self->tab_width = tab_width; if (self->buffer && self->buffer->tab_width != self->tab_width) { buffer_set_tab_width(self->buffer, self->tab_width); } } // Attempt to fix path by stripping away git-style diff prefixes ([ab/]) and/or // by extracting a trailing line number after a colon (:) static void _bview_fix_path(bview_t *self, char *path, int path_len, char **ret_path, int *ret_path_len, bint_t *ret_line_num) { char *tmp; int tmp_len; char *colon; int is_valid; int fix_nudge; int fix_len; bint_t line_num; (void)self; fix_nudge = 0; fix_len = path_len; line_num = 0; // Path already valid? if (util_is_file(path, NULL, NULL) || util_is_dir(path)) { goto _bview_fix_path_ret; } // Path valid if we strip "[ab]/" prefix? if (path_len >= 3 && (strncmp(path, "a/", 2) == 0 || strncmp(path, "b/", 2) == 0) && (util_is_file(path+2, NULL, NULL) || util_is_dir(path+2)) ) { fix_nudge = 2; fix_len -= 2; goto _bview_fix_path_ret; } // Path valid if we extract line num after colon? if ((colon = strrchr(path, ':')) != NULL) { tmp_len = colon - path; tmp = strndup(path, tmp_len); is_valid = util_is_file(tmp, NULL, NULL) ? 1 : 0; free(tmp); if (is_valid) { fix_len = tmp_len; line_num = strtoul(colon + 1, NULL, 10); goto _bview_fix_path_ret; } } // Path valid if we strip "[ab]/" prefix and extract line num? if (path_len >= 3 && (strncmp(path, "a/", 2) == 0 || strncmp(path, "b/", 2) == 0) && (colon = strrchr(path, ':')) != NULL ) { tmp_len = (colon - path) - 2; tmp = strndup(path+2, tmp_len); is_valid = util_is_file(tmp, NULL, NULL) ? 1 : 0; free(tmp); if (is_valid) { fix_nudge = 2; fix_len = tmp_len; line_num = strtoul(colon + 1, NULL, 10); goto _bview_fix_path_ret; } } _bview_fix_path_ret: *ret_path = strndup(path + fix_nudge, fix_len); *ret_path_len = strlen(*ret_path); *ret_line_num = line_num > 0 ? line_num - 1 : 0; } // Open a buffer with an optional path to load, otherwise empty static buffer_t *_bview_open_buffer(bview_t *self, char *opt_path, int opt_path_len) { buffer_t *buffer; int has_path; char *fix_path; char *exp_path; int fix_path_len; int exp_path_len; bint_t startup_line_num; buffer = NULL; has_path = opt_path && opt_path_len > 0 ? 1 : 0; if (has_path) { util_expand_tilde(opt_path, opt_path_len, &exp_path, &exp_path_len); _bview_fix_path(self, exp_path, exp_path_len, &fix_path, &fix_path_len, &startup_line_num); buffer = buffer_new_open(fix_path); if (buffer) self->startup_linenum = startup_line_num; free(fix_path); free(exp_path); } if (!buffer) { buffer = buffer_new(); if (has_path) { buffer->path = strndup(opt_path, opt_path_len); } } buffer_set_callback(buffer, _bview_buffer_callback, self); buffer_set_action_group_ptr(buffer, &self->editor->user_input_count); _bview_set_tab_width(self, self->tab_width); return buffer; } static void _bview_draw_prompt(bview_t *self) { _bview_draw_bline(self, self->buffer->first_line, 0, NULL, NULL); } static void _bview_draw_status(bview_t *self) { editor_t *editor; bview_t *active; bview_t *active_edit; mark_t *mark; editor = self->editor; active = editor->active; active_edit = editor->active_edit; mark = active_edit->active_cursor->mark; // Prompt if (active == editor->prompt) { tb_printf_rect(editor->rect_status, 0, 0, TB_GREEN | TB_BOLD, TB_BLACK, "%-*.*s", editor->rect_status.w, editor->rect_status.w, self->editor->prompt->prompt_str); goto _bview_draw_status_end; } // Macro indicator int i_macro_fg, i_macro_bg; char *i_macro; if (editor->is_recording_macro) { i_macro_fg = TB_RED | TB_BOLD; i_macro_bg = TB_BLACK; i_macro = "r"; } else if (editor->macro_apply) { i_macro_fg = TB_GREEN | TB_BOLD; i_macro_bg = TB_BLACK; i_macro = "p"; } else { i_macro_fg = 0; i_macro_bg = 0; i_macro = "."; } // Anchor indicator int i_anchor_fg, i_anchor_bg; bint_t anchor_len, anchor_nlines; char *i_anchor; cursor_t *cursor; if (active_edit->active_cursor->is_anchored) { i_anchor_fg = TB_WHITE | TB_BOLD; i_anchor_bg = TB_BLACK; i_anchor = "a"; cursor = active_edit->active_cursor; mark_get_nchars_between(cursor->anchor, cursor->mark, &anchor_len); if (mark_is_gt(cursor->anchor, cursor->mark)) anchor_len *= -1; anchor_nlines = cursor->anchor->bline->line_index - cursor->mark->bline->line_index; if (anchor_nlines < 0) anchor_nlines *= -1; anchor_nlines += 1; } else { i_anchor_fg = 0; i_anchor_bg = 0; i_anchor = "."; anchor_len = 0; anchor_nlines = 0; } // Block indicator int i_block_fg, i_block_bg; char *i_block; if (active_edit->active_cursor->is_block) { i_block_fg = TB_WHITE | TB_BOLD; i_block_bg = TB_BLACK; i_block = "b"; } else { i_block_fg = 0; i_block_bg = 0; i_block = "."; } // Async indicator int i_async_fg, i_async_bg; char *i_async; if (editor->aprocs) { i_async_fg = TB_YELLOW | TB_BOLD; i_async_bg = TB_BLACK; i_async = "x"; } else { i_async_fg = 0; i_async_bg = 0; i_async = "."; } // Need-more-input icon int i_needinput_fg; int i_needinput_bg; char *i_needinput; if (editor->loop_ctx->need_more_input) { i_needinput_fg = TB_BLUE | TB_BOLD; i_needinput_bg = TB_BLACK; i_needinput = "n"; } else { i_needinput_fg = 0; i_needinput_bg = 0; i_needinput = "."; } // Bview num TODO pre-compute this bview_t *bview_tmp; int bview_count = 0; int bview_num = 0; CDL_FOREACH2(editor->all_bviews, bview_tmp, all_next) { if (!MLE_BVIEW_IS_EDIT(bview_tmp)) continue; bview_count += 1; if (bview_tmp == active_edit) bview_num = bview_count; } // Render status line MLBUF_BLINE_ENSURE_CHARS(mark->bline); tb_printf_rect(editor->rect_status, 0, 0, 0, 0, "%*.*s", editor->rect_status.w, editor->rect_status.w, " "); tb_printf_attr(editor->rect_status, 0, 0, "@%d,%d;%s@%d,%d;" // mle_normal mode "[@%d,%d;%s@%d,%d;%s@%d,%d;%s@%d,%d;%s@%d,%d;%s@%d,%d;] " // [.....] need_input,anchor,block,macro,async "buf:@%d,%d;%d@%d,%d;/@%d,%d;%d@%d,%d; " // buf:1/2 bview num "<@%d,%d;%s@%d,%d;> " // syntax "line:@%d,%d;%llu@%d,%d;/@%d,%d;%llu@%d,%d; " // line:1/100 line "col:@%d,%d;%llu@%d,%d;/@%d,%d;%llu@%d,%d; " // col:0/80 col "%s@%d,%d;%lld@%d,%d;,@%d,%d;%llu@%d,%d; ", // sel:10,1 sel len, nlines TB_MAGENTA | TB_BOLD, 0, active->kmap_tail->kmap->name, 0, 0, i_needinput_fg, i_needinput_bg, i_needinput, i_anchor_fg, i_anchor_bg, i_anchor, i_block_fg, i_block_bg, i_block, i_macro_fg, i_macro_bg, i_macro, i_async_fg, i_async_bg, i_async, 0, 0, TB_BLUE | TB_BOLD, 0, bview_num, 0, 0, TB_BLUE, 0, bview_count, 0, 0, TB_CYAN | TB_BOLD, 0, active_edit->syntax ? active_edit->syntax->name : "none", 0, 0, TB_YELLOW | TB_BOLD, 0, mark->bline->line_index + 1, 0, 0, TB_YELLOW, 0, active_edit->buffer->line_count, 0, 0, TB_YELLOW | TB_BOLD, 0, mark->col, 0, 0, TB_YELLOW, 0, mark->bline->char_count, 0, 0, anchor_nlines == 0 ? "" : "sel:", anchor_nlines == 0 ? TB_BLACK : TB_YELLOW | TB_BOLD, 0, anchor_len, anchor_nlines == 0 ? TB_BLACK : 0, 0, anchor_nlines == 0 ? TB_BLACK : TB_YELLOW, 0, anchor_nlines, 0, 0 ); // Overlay errstr if present _bview_draw_status_end: if (editor->errstr[0] != '\0') { int errstrlen = strlen(editor->errstr) + 5; // Add 5 for "err! " tb_printf_rect(editor->rect_status, editor->rect_status.w - errstrlen, 0, TB_WHITE | TB_BOLD, TB_RED, "err! %s", editor->errstr); editor->errstr[0] = '\0'; // Clear errstr } else if (editor->infostr[0] != '\0') { int infostrlen = strlen(editor->infostr); tb_printf_rect(editor->rect_status, editor->rect_status.w - infostrlen, 0, TB_WHITE, 0, "%s", editor->infostr); editor->infostr[0] = '\0'; // Clear errstr } } static void _bview_draw_edit(bview_t *self, int x, int y, int w, int h) { int split_w; int split_h; int min_w; int min_h; int rect_y; int fg_attr; int bg_attr; bline_t *bline; // Handle split if (self->split_child) { // Calc split dimensions if (self->split_is_vertical) { split_w = w - (int)((float)w * self->split_factor); split_h = h; } else { split_w = w; split_h = h - (int)((float)h * self->split_factor); } // Draw child _bview_draw_edit(self->split_child, x + (w - split_w), y + (h - split_h), split_w, split_h); // Continue drawing self minus split dimensions w -= (w - split_w); h -= (h - split_h); } // Calc min dimensions min_w = self->linenum_width + 3; min_h = 2; // Ensure renderable if (w < min_w || h < min_h || x + w > self->editor->w || y + h > self->editor->h ) { return; } // Render caption fg_attr = self->editor->active_edit == self ? TB_BOLD : 0; bg_attr = self->editor->active_edit == self ? TB_BLUE : 0; tb_printf_rect(self->rect_caption, 0, 0, fg_attr, bg_attr, "%*.*s", self->rect_caption.w, self->rect_caption.w, " "); if (self->buffer->path) { tb_printf_rect(self->rect_caption, 0, 0, fg_attr, bg_attr, "%*.s%s %c", self->linenum_width, " ", self->buffer->path, self->buffer->is_unsaved ? '*' : ' '); } else { tb_printf_rect(self->rect_caption, 0, 0, fg_attr, bg_attr, "%*.s %c", self->linenum_width, " ", self->buffer, self->buffer->is_unsaved ? '*' : ' '); } // Render lines and margins bline = self->viewport_mark->bline; for (rect_y = 0; rect_y < self->rect_buffer.h; rect_y++) { if (self->viewport_y + rect_y < 0 || self->viewport_y + rect_y >= self->buffer->line_count || !bline) { // "|| !bline" See TODOs below // Draw pre/post blank tb_printf_rect(self->rect_lines, 0, rect_y, 0, 0, "%*c", self->linenum_width, '~'); tb_printf_rect(self->rect_margin_left, 0, rect_y, 0, 0, "%c", ' '); tb_printf_rect(self->rect_margin_right, 0, rect_y, 0, 0, "%c", ' '); tb_printf_rect(self->rect_buffer, 0, rect_y, 0, 0, "%-*.*s", self->rect_buffer.w, self->rect_buffer.w, " "); } else { // Draw bline at self->rect_buffer self->viewport_y + rect_y _bview_draw_bline(self, bline, rect_y, &bline, &rect_y); bline = bline->next; } } } static void _bview_draw_bline(bview_t *self, bline_t *bline, int rect_y, bline_t **optret_bline, int *optret_rect_y) { int rect_x; bint_t char_col; int fg; int bg, tbg; uint32_t ch; int char_w; bint_t viewport_x; bint_t viewport_x_vcol; int i, j; int is_cursor_line; int is_soft_wrapped; int orig_rect_y; srule_t *srule; MLBUF_BLINE_ENSURE_CHARS(bline); is_cursor_line = _bview_is_cursor_line(self, bline); is_soft_wrapped = _bview_is_soft_wrapped(self, bline); viewport_x = _bview_get_viewport_x(self, bline); viewport_x_vcol = MLE_COL_TO_VCOL(bline, viewport_x); // Draw linenums and margins if (MLE_BVIEW_IS_EDIT(self)) { int linenum_fg = is_cursor_line ? TB_BOLD : 0; if (self->editor->linenum_type == MLE_LINENUM_TYPE_ABS || self->editor->linenum_type == MLE_LINENUM_TYPE_BOTH || (self->editor->linenum_type == MLE_LINENUM_TYPE_REL && is_cursor_line) ) { tb_printf_rect(self->rect_lines, 0, rect_y, linenum_fg, 0, "%*d", self->abs_linenum_width, (int)(bline->line_index + 1) % (int)pow(10, self->linenum_width)); if (self->editor->linenum_type == MLE_LINENUM_TYPE_BOTH) { tb_printf_rect(self->rect_lines, self->abs_linenum_width, rect_y, linenum_fg, 0, " %*d", self->rel_linenum_width, (int)labs(bline->line_index - self->active_cursor->mark->bline->line_index)); } } else if (self->editor->linenum_type == MLE_LINENUM_TYPE_REL) { tb_printf_rect(self->rect_lines, 0, rect_y, linenum_fg, 0, "%*d", self->rel_linenum_width, (int)labs(bline->line_index - self->active_cursor->mark->bline->line_index)); } tb_printf_rect(self->rect_margin_left, 0, rect_y, 0, 0, "%c", viewport_x > 0 && bline->char_count > 0 ? '^' : ' '); if (!is_soft_wrapped && bline->char_vwidth - viewport_x_vcol > self->rect_buffer.w) { tb_printf_rect(self->rect_margin_right, 0, rect_y, 0, 0, "%c", '$'); } } // Render 0 thru rect_buffer.w cell by cell orig_rect_y = rect_y; rect_x = 0; char_col = viewport_x; _bview_populate_isearch_ranges(self, bline); while (char_col < bline->char_count) { ch = bline->chars[char_col].ch; fg = bline->chars[char_col].style.fg; bg = bline->chars[char_col].style.bg; char_w = char_col == bline->char_count - 1 ? bline->char_vwidth - bline->chars[char_col].vcol : bline->chars[char_col + 1].vcol - bline->chars[char_col].vcol; if (ch == '\t') { ch = ' '; } else if (!iswprint(ch)) { ch = '?'; } if (MLE_BVIEW_IS_MENU(self) && is_cursor_line) { // Highlight menu line bg |= TB_REVERSE; } if (_bview_is_in_isearch(self, char_col, &srule)) { } else if (_bview_is_in_range(bline, char_col, self->active_cursor->is_block, &srule)) { } else { srule = NULL; } if (srule) { fg = srule->style.fg; bg = srule->style.bg; } // Draw char_w chars of ch for (i = 0; i < char_w && rect_x + i < self->rect_buffer.w; i++) { if (self->editor->color_col == rect_x + i && MLE_BVIEW_IS_EDIT(self)) { // Apply color col style tbg = bg | TB_RED; } else { tbg = bg; } tb_change_cell(self->rect_buffer.x + rect_x + i, self->rect_buffer.y + rect_y, ch, fg, tbg); } if (i < char_w) { // There was not enough width to draw if (is_soft_wrapped && rect_y + 1 < self->rect_buffer.h) { // Soft wrap to next line rect_x = 0; rect_y += 1; char_w -= i; // Draw remaining ch on next line for (j = 0; j < char_w && rect_x + j < self->rect_buffer.w; j++) { tb_change_cell(self->rect_buffer.x + j, self->rect_buffer.y + rect_y, ch, fg, bg); } // Draw ellipsis for line num to indicate soft wrap for (j = 0; j < self->linenum_width; j++) { tb_printf_rect(self->rect_lines, j, rect_y, 0, 0, "%c", '.'); } } else { // We are off-screen and soft-wrap is not enabled or there is not // enough height to soft wrap break; } } rect_x += char_w; char_col += 1; } for (i = orig_rect_y; i < rect_y && bline->next; i++) { bline = bline->next; } if (optret_bline) *optret_bline = bline; if (optret_rect_y) *optret_rect_y = rect_y; } // Highlight matching bracket pair under mark static void _bview_highlight_bracket_pair(bview_t *self, mark_t *mark) { bline_t *line; bint_t brkt; bint_t col; mark_t pair; int screen_x; int screen_y; struct tb_cell *cell; MLBUF_BLINE_ENSURE_CHARS(mark->bline); if (mark_is_at_eol(mark) || !util_get_bracket_pair(mark->bline->chars[mark->col].ch, NULL)) { // Not a bracket return; } if (mark_find_bracket_pair(mark, MLE_BRACKET_PAIR_MAX_SEARCH, &line, &col, &brkt) != MLBUF_OK) { // No pair found return; } if (mark->bline == line && (mark->col == col - 1 || mark->col == col + 1)) { // One char away, do not highlight (looks confusing in UI) return; } pair.bline = line; pair.col = col; if (bview_get_screen_coords(self, &pair, &screen_x, &screen_y, &cell) != MLE_OK) { // Out of bounds return; } tb_change_cell(screen_x, screen_y, cell->ch, cell->fg | TB_UNDERLINE, cell->bg); // TODO configurable } // Find screen coordinates for a mark int bview_get_screen_coords(bview_t *self, mark_t *mark, int *ret_x, int *ret_y, struct tb_cell **optret_cell) { int screen_x; int screen_y; int is_soft_wrapped; bint_t viewport_x; MLBUF_BLINE_ENSURE_CHARS(mark->bline); is_soft_wrapped = _bview_is_soft_wrapped(self, mark->bline); if (is_soft_wrapped) { screen_x = self->rect_buffer.x + MLE_MARK_COL_TO_VCOL(mark) % self->rect_buffer.w; screen_y = self->rect_buffer.y + (mark->bline->line_index - self->viewport_mark->bline->line_index) + (MLE_MARK_COL_TO_VCOL(mark) / self->rect_buffer.w); } else { viewport_x = _bview_get_viewport_x(self, mark->bline); screen_x = self->rect_buffer.x + MLE_MARK_COL_TO_VCOL(mark) - MLE_COL_TO_VCOL(mark->bline, viewport_x); screen_y = self->rect_buffer.y + (mark->bline->line_index - self->viewport_mark->bline->line_index); } if (screen_x < self->rect_buffer.x || screen_x >= self->rect_buffer.x + self->rect_buffer.w || screen_y < self->rect_buffer.y || screen_y >= self->rect_buffer.y + self->rect_buffer.h ) { // Out of bounds return MLE_ERR; } *ret_x = screen_x; *ret_y = screen_y; if (optret_cell) { *optret_cell = tb_cell_buffer() + (ptrdiff_t)(tb_width() * screen_y + screen_x); } return MLE_OK; } // Find bline col given a screen coordinate int bview_screen_to_bline_col(bview_t *self, int x, int y, bview_t **ret_bview, bline_t **ret_bline, bint_t *ret_col) { bint_t line_index, vcol; *ret_bview = NULL; *ret_bline = NULL; *ret_col = 0; if ( x >= self->rect_buffer.x && x < self->rect_buffer.x + self->rect_buffer.w && y >= self->rect_buffer.y && y < self->rect_buffer.y + self->rect_buffer.h ) { line_index = self->viewport_y + (y - self->rect_buffer.y); buffer_get_bline_w_hint(self->buffer, line_index, self->viewport_mark->bline, ret_bline); if (*ret_bline) { vcol = _bview_get_viewport_x(self, *ret_bline) + (x - self->rect_buffer.x); bline_get_col_from_vcol(*ret_bline, vcol, ret_col); *ret_bview = self; return MLE_OK; } } if (self->split_child) { return bview_screen_to_bline_col(self->split_child, x, y, ret_bview, ret_bline, ret_col); } return MLE_ERR; } static bint_t _bview_get_viewport_x(bview_t *self, bline_t *bline) { // Use viewport_x only when // - vwidth is longer than buffer width // - rendering current line // - not soft wrapping return bline->char_vwidth > self->rect_buffer.w && _bview_is_cursor_line(self, bline) && !_bview_is_soft_wrapped(self, bline) ? self->viewport_x : 0; } static int _bview_is_cursor_line(bview_t *self, bline_t *bline) { return self->active_cursor->mark->bline == bline ? 1 : 0; } static int _bview_is_soft_wrapped(bview_t *self, bline_t *bline) { return self->soft_wrap && MLE_BVIEW_IS_EDIT(self) && _bview_is_cursor_line(self, bline) ? 1 : 0; } static int _bview_is_in_range(bline_t *bline, bint_t col, int is_block, srule_t **ret_srule) { mark_t mark = {0}; srule_node_t *node; mark.bline = bline; mark.col = col; int rv; DL_FOREACH(bline->buffer->range_srules, node) { rv = is_block ? mark_block_is_between(&mark, node->srule->range_a, node->srule->range_b) : mark_is_between(&mark, node->srule->range_a, node->srule->range_b); if (rv) { *ret_srule = node->srule; return 1; } } return 0; } static int _bview_is_in_isearch(bview_t *self, bint_t col, srule_t **ret_srule) { size_t lo, hi, i; lo = 0; hi = self->isearch_ranges_len / 2; while (lo < hi) { i = (lo + hi) / 2; if (col < self->isearch_ranges[i * 2]) { hi = i; } else if (col >= self->isearch_ranges[i * 2 + 1]) { lo = i + 1; } else { *ret_srule = self->isearch_rule; return 1; } } return 0; } static int _bview_populate_isearch_ranges(bview_t *self, bline_t *bline) { int rc; PCRE2_SIZE substrs[3]; pcre2_code *cre; bint_t look_offset, start, stop; if (!self->isearch_rule) return MLBUF_OK; cre = self->isearch_rule->cre; self->isearch_ranges_len = 0; look_offset = 0; while (look_offset < bline->data_len) { rc = pcre2_match(cre, (PCRE2_SPTR)bline->data, (PCRE2_SIZE)bline->data_len, (PCRE2_SIZE)look_offset, 0, pcre2_md, NULL); if (rc < 0) break; memcpy(substrs, pcre2_get_ovector_pointer(pcre2_md), 3 * sizeof(PCRE2_SIZE)); if (substrs[1] == PCRE2_UNSET) break; self->isearch_ranges_len += 2; if (self->isearch_ranges_len > self->isearch_ranges_cap) { while (self->isearch_ranges_len > self->isearch_ranges_cap) { self->isearch_ranges_cap = 2 * MLE_MAX(self->isearch_ranges_cap, 2); } self->isearch_ranges = realloc(self->isearch_ranges, sizeof(bint_t) * self->isearch_ranges_cap); } bline_index_to_col(bline, substrs[0], &start); bline_index_to_col(bline, substrs[1], &stop); self->isearch_ranges[self->isearch_ranges_len - 2] = start; self->isearch_ranges[self->isearch_ranges_len - 1] = stop; look_offset = MLBUF_MAX(stop, look_offset + 1); } return MLBUF_OK; } mle-1.7.2/cmd.c000066400000000000000000002023631443351614700132230ustar00rootroot00000000000000#include #include #include #include #include #include #include "mle.h" #define MLE_MULTI_CURSOR_MARK_FN(pcursor, pfn, ...) do { \ cursor_t *cursor; \ DL_FOREACH((pcursor)->bview->cursors, cursor) { \ if (cursor->is_asleep) continue; \ pfn(cursor->mark, ##__VA_ARGS__); \ } \ } while(0) #define MLE_MULTI_CURSOR_MARK_FN_NO_ARGS(pcursor, pfn) do { \ cursor_t *cursor; \ DL_FOREACH((pcursor)->bview->cursors, cursor) { \ if (cursor->is_asleep) continue; \ pfn(cursor->mark); \ } \ } while(0) #define MLE_MULTI_CURSOR_CODE(pcursor, pcode) do { \ cursor_t *cursor; \ DL_FOREACH((pcursor)->bview->cursors, cursor) { \ if (cursor->is_asleep) continue; \ pcode \ } \ } while(0) static int _cmd_indent(cmd_context_t *ctx, int outdent); static void _cmd_help_inner(char *buf, kbinding_t *trie, str_t *h); static int _cmd_indent_line(bline_t *bline, int use_tabs, int outdent); static int _cmd_quit_inner(editor_t *editor, bview_t *bview); static int _cmd_pre_close(editor_t *editor, bview_t *bview); static int _cmd_save(editor_t *editor, bview_t *bview, int save_as); static int _cmd_search_ex(cmd_context_t *ctx, int is_prev); static int _cmd_search_next_ex(cmd_context_t *ctx, int is_prev); static int _cmd_search_next(bview_t *bview, cursor_t *cursor, mark_t *search_mark, char *regex, int regex_len, int is_prev); static int _cmd_find_word_ex(cmd_context_t *ctx, int is_prev); static void _cmd_aproc_bview_passthru_cb(aproc_t *aproc, char *buf, size_t buf_len); static void _cmd_isearch_prompt_cb(bview_t *bview_prompt, baction_t *action, void *udata); static int _cmd_menu_grep_cb(cmd_context_t *ctx); static int _cmd_menu_ctag_cb(cmd_context_t *ctx); static int _cmd_menu_browse_cb(cmd_context_t *ctx); static int _cmd_menu_blist_cb(cmd_context_t *ctx); static void _cmd_insert_auto_indent_newline(cmd_context_t *ctx); static void _cmd_insert_auto_indent_closing_bracket(cmd_context_t *ctx); static void _cmd_shell_apply_cmd(cmd_context_t *ctx, char *cmd); static void _cmd_get_input(cmd_context_t *ctx, kinput_t *ret_input); static int _cmd_fsearch_inner(cmd_context_t *ctx, char *shell_cmd); static int _cmd_get_char_param(cmd_context_t *ctx, char *ret_ch); // Insert data int cmd_insert_data(cmd_context_t *ctx) { bint_t insertbuf_len; char *insertbuf_cur; bint_t len; size_t insert_size; kinput_t *input; int i; int (*mark_insert_func)(mark_t *, char *, bint_t); // Ensure space in insertbuf insert_size = MLE_MAX(6, ctx->bview->buffer->tab_width) * (ctx->pastebuf_len + 1); if (ctx->editor->insertbuf_size < insert_size) { ctx->editor->insertbuf = realloc(ctx->editor->insertbuf, insert_size); memset(ctx->editor->insertbuf, 0, insert_size); ctx->editor->insertbuf_size = insert_size; } // Fill insertbuf... i=-1: ctx->input, i>=0: ctx->pastebuf insertbuf_len = 0; insertbuf_cur = ctx->editor->insertbuf; for (i = -1; i == -1 || (size_t)i < ctx->pastebuf_len; i++) { input = i == -1 ? &ctx->input : &ctx->pastebuf[i]; if (input->ch) { len = utf8_unicode_to_char(insertbuf_cur, input->ch); } else if (input->key == TB_KEY_ENTER || input->key == TB_KEY_CTRL_J || input->key == TB_KEY_CTRL_M) { len = sprintf(insertbuf_cur, "\n"); } else if (input->key >= 0x20 && input->key <= 0x7e) { len = sprintf(insertbuf_cur, "%c", input->key); } else if (input->key == 0x09) { if (ctx->bview->tab_to_space) { len = ctx->bview->buffer->tab_width - (ctx->cursor->mark->col % ctx->bview->buffer->tab_width); len = sprintf(insertbuf_cur, "%*c", (int)len, ' '); } else { len = sprintf(insertbuf_cur, "\t"); } } else { len = 0; // TODO verbatim input } insertbuf_cur += len; insertbuf_len += len; } // Write insertbuf to buffer if (insertbuf_len < 1) { return MLE_ERR; } ctx->editor->insertbuf[insertbuf_len] = '\0'; // Insert mark_insert_func = ctx->cursor->is_block ? mark_block_insert_before : mark_insert_before; if (insertbuf_len > 1 && ctx->editor->trim_paste && memchr(ctx->editor->insertbuf, '\n', insertbuf_len) != NULL ) { // Insert with trim char *trimmed = NULL; int trimmed_len = 0; util_pcre_replace("(?m) +$", ctx->editor->insertbuf, "", &trimmed, &trimmed_len); MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_insert_func, trimmed, trimmed_len); free(trimmed); } else if (ctx->editor->auto_indent && !ctx->cursor->next && !ctx->cursor->is_block && insertbuf_len == 1 && ctx->editor->insertbuf[0] == '\n') { _cmd_insert_auto_indent_newline(ctx); } else if (ctx->editor->auto_indent && !ctx->cursor->next && !ctx->cursor->is_block && insertbuf_len == 1 && ctx->editor->insertbuf[0] == '}') { _cmd_insert_auto_indent_closing_bracket(ctx); } else { // Insert without trim MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_insert_func, ctx->editor->insertbuf, insertbuf_len); } // Remember last insert data str_set_len(&ctx->loop_ctx->last_insert, ctx->editor->insertbuf, insertbuf_len); return MLE_OK; } // Insert newline above current line int cmd_insert_newline_above(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, mark_move_bol(cursor->mark); mark_insert_after(cursor->mark, "\n", 1); ); return MLE_OK; } // Insert newline below current line int cmd_insert_newline_below(cmd_context_t *ctx) { mark_t *mark; MLE_MULTI_CURSOR_CODE(ctx->cursor, mark_clone(cursor->mark, &mark); mark_move_eol(mark); cursor->mark->lefty += 1; // Prevent cursor from moving if at eol mark_insert_after(mark, "\n", 1); cursor->mark->lefty -= 1; mark_destroy(mark); ); return MLE_OK; } // Delete char before cursor mark int cmd_delete_before(cmd_context_t *ctx) { bint_t offset; mark_get_offset(ctx->cursor->mark, &offset); if (offset < 1) return MLE_OK; MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_delete_before, 1); return MLE_OK; } // Delete char after cursor mark int cmd_delete_after(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_delete_after, 1); return MLE_OK; } // Move cursor to beginning of line int cmd_move_bol(cmd_context_t *ctx) { uint32_t ch; mark_t *mark; MLE_MULTI_CURSOR_CODE(ctx->cursor, mark_clone(cursor->mark, &mark); mark_move_bol(mark); mark_get_char_after(mark, &ch); if (isspace((char)ch)) { mark_move_next_re(mark, "\\S", 2); } if (mark->col < cursor->mark->col) { mark_join(cursor->mark, mark); } else { mark_move_bol(cursor->mark); } mark_destroy(mark); ); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move cursor to end of line int cmd_move_eol(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN_NO_ARGS(ctx->cursor, mark_move_eol); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move cursor to beginning of buffer int cmd_move_beginning(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN_NO_ARGS(ctx->cursor, mark_move_beginning); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move cursor to end of buffer int cmd_move_end(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN_NO_ARGS(ctx->cursor, mark_move_end); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move cursor left one char int cmd_move_left(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_by, -1); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move cursor right one char int cmd_move_right(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_by, 1); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move cursor up one line int cmd_move_up(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, -1); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move cursor down one line int cmd_move_down(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, 1); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move cursor one page up int cmd_move_page_up(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, -1 * ctx->bview->rect_buffer.h); bview_zero_viewport_y(ctx->bview); return MLE_OK; } // Move cursor one page down int cmd_move_page_down(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, ctx->bview->rect_buffer.h); bview_zero_viewport_y(ctx->bview); return MLE_OK; } // Move to specific line int cmd_move_to_line(cmd_context_t *ctx) { char *linestr; bint_t line; editor_prompt(ctx->editor, "move_to_line: Line num?", NULL, &linestr); if (!linestr) return MLE_OK; line = strtoll(linestr, NULL, 10); free(linestr); if (line < 1) line = 1; MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_to, line - 1, 0); bview_center_viewport_y(ctx->bview); return MLE_OK; } // Move to specific offset int cmd_move_to_offset(cmd_context_t *ctx) { char *offsetstr; bint_t offset; editor_prompt(ctx->editor, "move_to_offset: Offset?", NULL, &offsetstr); if (!offsetstr) return MLE_OK; offset = strtoll(offsetstr, NULL, 10); free(offsetstr); if (offset < 0) offset = 0; MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_offset, offset); bview_center_viewport_y(ctx->bview); return MLE_OK; } // Move vertically relative to current line int cmd_move_relative(cmd_context_t *ctx) { int delta; if (ctx->numeric_params_len < 1) { return MLE_ERR; } if (strcmp(ctx->static_param, "up") == 0) { delta = -1; } else if (strcmp(ctx->static_param, "down") == 0) { delta = 1; } else { return MLE_ERR; } delta *= ctx->numeric_params[0]; MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, delta); return MLE_OK; } // Move one word forward int cmd_move_word_forward(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_next_re_nudge, MLE_RE_WORD_FORWARD, sizeof(MLE_RE_WORD_FORWARD)-1); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move one word back int cmd_move_word_back(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_prev_re, MLE_RE_WORD_BACK, sizeof(MLE_RE_WORD_BACK)-1); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move to next open bracket int cmd_move_bracket_forward(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_next_str_nudge, "{", 1); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move to prev open bracket int cmd_move_bracket_back(cmd_context_t *ctx) { MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_prev_str, "{", 1); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move to matching bracket, or prev bracket if not on a bracket int cmd_move_bracket_toggle(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, if (mark_move_bracket_pair(cursor->mark, ctx->buffer->byte_count) == MLBUF_ERR) { mark_move_prev_re(cursor->mark, "[\\[\\(\\{]", strlen("[\\[\\(\\{]")); } ); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Delete word back int cmd_delete_word_before(cmd_context_t *ctx) { mark_t *tmark; MLE_MULTI_CURSOR_CODE(ctx->cursor, mark_clone(cursor->mark, &tmark); mark_move_prev_re(tmark, MLE_RE_WORD_BACK, sizeof(MLE_RE_WORD_BACK)-1); mark_delete_between(cursor->mark, tmark); mark_destroy(tmark); ); return MLE_OK; } // Delete word ahead int cmd_delete_word_after(cmd_context_t *ctx) { mark_t *tmark; MLE_MULTI_CURSOR_CODE(ctx->cursor, mark_clone(cursor->mark, &tmark); mark_move_next_re(tmark, MLE_RE_WORD_FORWARD, sizeof(MLE_RE_WORD_FORWARD)-1); mark_delete_between(cursor->mark, tmark); mark_destroy(tmark); ); return MLE_OK; } // Toggle anchor on cursors int cmd_toggle_anchor(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, cursor_toggle_anchor(cursor, 1); ); return MLE_OK; } // Toggle block mode on cursors int cmd_toggle_block(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, cursor->is_block = 1 - cursor->is_block; ); return MLE_OK; } // Swap anchor with mark on cursors int cmd_swap_anchor(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, if (cursor->is_anchored) { mark_swap(cursor->mark, cursor->anchor); } ); return MLE_OK; } // Align cursors by padding with whitespace int cmd_align_cursors(cmd_context_t *ctx) { bint_t max_col; max_col = 0; MLE_MULTI_CURSOR_CODE(ctx->cursor, if (cursor->mark->col > max_col) { max_col = cursor->mark->col; } ); MLE_MULTI_CURSOR_CODE(ctx->cursor, while (cursor->mark->col < max_col) { mark_insert_before(cursor->mark, " ", 1); } ); return MLE_OK; } // Drop an is_asleep=1 cursor int cmd_drop_sleeping_cursor(cmd_context_t *ctx) { return bview_add_cursor_asleep(ctx->bview, ctx->cursor->mark->bline, ctx->cursor->mark->col, NULL); } // Awake all is_asleep=1 cursors int cmd_wake_sleeping_cursors(cmd_context_t *ctx) { return bview_wake_sleeping_cursors(ctx->bview); } // Remove all cursors except the active one int cmd_remove_extra_cursors(cmd_context_t *ctx) { return bview_remove_cursors_except(ctx->bview, ctx->cursor); } // Drop column of cursors in selection int cmd_drop_cursor_column(cmd_context_t *ctx) { bline_t *bline; bint_t col; mark_t *lo; mark_t *hi; MLE_MULTI_CURSOR_CODE(ctx->cursor, if (!cursor->is_anchored) continue; col = cursor->mark->col; cursor_get_lo_hi(cursor, &lo, &hi); for (bline = lo->bline; bline != hi->bline->next; bline = bline->next) { MLBUF_BLINE_ENSURE_CHARS(bline); if ((bline == lo->bline && col < lo->col) || (bline == hi->bline && col > hi->col) || col > bline->char_count ) { continue; } if (bline != cursor->mark->bline || col != cursor->mark->col) { bview_add_cursor(ctx->bview, bline, col, NULL); } } cursor_toggle_anchor(cursor, 1); ); return MLE_OK; } // Drop lettered mark int cmd_drop_lettered_mark(cmd_context_t *ctx) { mark_t *mark; char letter; _cmd_get_char_param(ctx, &letter); mark_clone_w_letter(ctx->cursor->mark, letter, &mark); return MLE_OK; } // Jump back and forth to a lettered mark // - If letter exists and we're not at it, drop rot13(letter) and goto letter // - If letter exists and we're already at it, jump to rot13(letter) // - If letter doesn't exist, drop it in place int cmd_goto_lettered_mark(cmd_context_t *ctx) { mark_t *mark, *mark_tmp; char letter, other; _cmd_get_char_param(ctx, &letter); other = 'a' + (((letter - 'a') + 13) % 26); mark = NULL; buffer_get_lettered_mark(ctx->buffer, letter, &mark); if (mark) { if (mark_is_eq(mark, ctx->cursor->mark)) { mark = NULL; buffer_get_lettered_mark(ctx->buffer, other, &mark); if (mark) mark_join(ctx->cursor->mark, mark); } else { mark_clone_w_letter(ctx->cursor->mark, other, &mark_tmp); mark_join(ctx->cursor->mark, mark); } bview_rectify_viewport(ctx->bview); } else { mark_clone_w_letter(ctx->cursor->mark, letter, &mark); } return MLE_OK; } // Search for a regex int cmd_search(cmd_context_t *ctx) { return _cmd_search_ex(ctx, 0); } // Reverse search for a regex int cmd_rsearch(cmd_context_t *ctx) { return _cmd_search_ex(ctx, 1); } // Search for next instance of last search regex int cmd_search_next(cmd_context_t *ctx) { return _cmd_search_next_ex(ctx, 0); } // Search for prev instance of last search regex int cmd_search_prev(cmd_context_t *ctx) { return _cmd_search_next_ex(ctx, 1); } // Interactive search and replace int cmd_replace(cmd_context_t *ctx) { return cursor_replace(ctx->cursor, 1, NULL, NULL); } // Repeat last cmd int cmd_repeat(cmd_context_t *ctx) { // This is a special case in _editor_loop return MLE_OK; } // Redraw screen int cmd_redraw(cmd_context_t *ctx) { bview_center_viewport_y(ctx->bview); editor_force_redraw(ctx->editor); return MLE_OK; } // Zero viewport y int cmd_viewport_top(cmd_context_t *ctx) { bview_zero_viewport_y(ctx->bview); return MLE_OK; } // Center viewport y int cmd_viewport_mid(cmd_context_t *ctx) { bview_center_viewport_y(ctx->bview); return MLE_OK; } // Max viewport y int cmd_viewport_bot(cmd_context_t *ctx) { bview_max_viewport_y(ctx->bview); return MLE_OK; } // Toggle between top and mid viewport y int cmd_viewport_toggle(cmd_context_t *ctx) { bline_t *orig; bline_t *mid; bline_t *top; orig = ctx->bview->viewport_mark->bline; cmd_viewport_mid(ctx); mid = ctx->bview->viewport_mark->bline; cmd_viewport_top(ctx); top = ctx->bview->viewport_mark->bline; if (mid == orig) { cmd_viewport_top(ctx); } else if (top == orig) { cmd_viewport_bot(ctx); } else { cmd_viewport_mid(ctx); } return MLE_OK; } // Find next occurence of word under cursor int cmd_find_word(cmd_context_t *ctx) { return _cmd_find_word_ex(ctx, 0); } // Find prev occurence of word under cursor int cmd_rfind_word(cmd_context_t *ctx) { return _cmd_find_word_ex(ctx, 1); } // Incremental search int cmd_isearch(cmd_context_t *ctx) { editor_prompt(ctx->editor, "isearch: Regex?", &(editor_prompt_params_t) { .kmap = ctx->editor->kmap_prompt_isearch, .prompt_cb = _cmd_isearch_prompt_cb }, NULL); if (ctx->bview->isearch_rule) { srule_destroy(ctx->bview->isearch_rule); ctx->bview->isearch_rule = NULL; } return MLE_OK; } // Cut text int cmd_cut(cmd_context_t *ctx) { int append; append = ctx->loop_ctx->last_cmd_ctx.cmd && ctx->loop_ctx->last_cmd_ctx.cmd->func == cmd_cut ? 1 : 0; MLE_MULTI_CURSOR_CODE(ctx->cursor, cursor_cut_copy(cursor, 1, 1, append); ); return MLE_OK; } // Copy text int cmd_copy(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, cursor_cut_copy(cursor, 0, 1, 0); ); return MLE_OK; } // Paste text (from cursor cut_buffer) int cmd_uncut(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, cursor_uncut(cursor); ); return MLE_OK; } // Paste text (from editor-wide cut_buffer) int cmd_uncut_last(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, cursor_uncut_last(cursor); ); return MLE_OK; } // Copy in between chars int cmd_copy_by(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, if (cursor_select_by(cursor, ctx->static_param, 0) == MLE_OK) { cursor_cut_copy(cursor, 0, 0, 0); } ); return MLE_OK; } // Cut in between chars int cmd_cut_by(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, if (cursor_select_by(cursor, ctx->static_param, 0) == MLE_OK) { cursor_cut_copy(cursor, 1, 0, 0); } ); return MLE_OK; } // Anchor between chars int cmd_anchor_by(cmd_context_t *ctx) { MLE_MULTI_CURSOR_CODE(ctx->cursor, cursor_select_by(cursor, ctx->static_param, 1); ); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Go to next bview int cmd_next(cmd_context_t *ctx) { editor_set_active(ctx->editor, ctx->bview->all_next); return MLE_OK; } // Go to prev bview int cmd_prev(cmd_context_t *ctx) { editor_set_active(ctx->editor, ctx->bview->all_prev); return MLE_OK; } // Go to last bview int cmd_last(cmd_context_t *ctx) { if (!ctx->editor->active_edit_last) return MLE_OK; return editor_set_active(ctx->editor, ctx->editor->active_edit_last); } // Go to nth bview int cmd_goto(cmd_context_t *ctx) { bview_t *bview; int bview_num, num_bviews, bview_i; char *bview_num_str; char prompt[64]; num_bviews = editor_bview_edit_count(ctx->editor); snprintf(prompt, sizeof(prompt), "goto: Buffer #? (1-%d)", num_bviews); // Get bview_num if (ctx->static_param) { bview_num_str = strdup(ctx->static_param); } else { editor_prompt(ctx->editor, prompt, NULL, &bview_num_str); if (!bview_num_str) return MLE_OK; } bview_num = atoi(bview_num_str); free(bview_num_str); // Check if (bview_num < 1 || bview_num > num_bviews) return MLE_OK; bview_i = 1; CDL_FOREACH2(ctx->editor->all_bviews, bview, all_next) { if (!MLE_BVIEW_IS_EDIT(bview)) continue; if (bview_i == bview_num) return editor_set_active(ctx->editor, bview); bview_i += 1; } return MLE_OK; } // Split a bview vertically int cmd_split_vertical(cmd_context_t *ctx) { bview_t *child; if (bview_split(ctx->bview, 1, 0.5, &child) == MLE_OK) { editor_set_active(ctx->editor, child); } return MLE_OK; } // Split a bview horizontally int cmd_split_horizontal(cmd_context_t *ctx) { bview_t *child; if (bview_split(ctx->bview, 0, 0.5, &child) == MLE_OK) { editor_set_active(ctx->editor, child); } return MLE_OK; } // Fuzzy path search via fzf int cmd_fsearch(cmd_context_t *ctx) { return _cmd_fsearch_inner(ctx, "fzf"); } // Fuzzy path search via fzy int cmd_fsearch_fzy(cmd_context_t *ctx) { char shell_cmd[32]; int nlines; nlines = tb_height(); nlines = MLE_MAX(MLE_MIN(nlines, 9999), 3); sprintf(shell_cmd, "find . -type f | fzy -l %d", nlines); return _cmd_fsearch_inner(ctx, shell_cmd); } // Grep for pattern in cwd int cmd_grep(cmd_context_t *ctx) { aproc_t *aproc; char *path; char *path_arg; char *cmd; char *grep_fmt; editor_prompt(ctx->editor, "grep: Pattern?", NULL, &path); if (!path) return MLE_OK; if (ctx->static_param) { grep_fmt = ctx->static_param; } else { #ifdef __APPLE__ grep_fmt = "grep --color=never -E -i -I -n -r %s . 2>/dev/null"; #else grep_fmt = "grep --color=never -P -i -I -n -r %s . 2>/dev/null"; #endif } path_arg = util_escape_shell_arg(path, strlen(path)); free(path); asprintf(&cmd, grep_fmt, path_arg); free(path_arg); if (!cmd) { MLE_RETURN_ERR(ctx->editor, "Failed to format grep cmd: %s", grep_fmt); } aproc = aproc_new(ctx->editor, ctx->bview, &(ctx->bview->aproc), cmd, 0, _cmd_aproc_bview_passthru_cb); free(cmd); if (!aproc) return MLE_ERR; editor_menu(ctx->editor, _cmd_menu_grep_cb, NULL, 0, aproc, NULL); return MLE_OK; } // Invoke ctag search int cmd_ctag(cmd_context_t *ctx) { aproc_t *aproc; char *word; char *word_arg; char *cmd; bint_t word_len; if (cursor_select_by(ctx->cursor, "word", 0) != MLE_OK) { MLE_RETURN_ERR(ctx->editor, "%s", "Failed to select word under cursor"); } mark_get_between(ctx->cursor->mark, ctx->cursor->anchor, &word, &word_len); cursor_toggle_anchor(ctx->cursor, 0); word_arg = util_escape_shell_arg(word, word_len); free(word); asprintf(&cmd, "readtags -e - %s 2>/dev/null", word_arg); free(word_arg); if (!cmd) { MLE_RETURN_ERR(ctx->editor, "%s", "Failed to format readtags cmd"); } aproc = aproc_new(ctx->editor, ctx->bview, &(ctx->bview->aproc), cmd, 0, _cmd_aproc_bview_passthru_cb); free(cmd); if (!aproc) return MLE_ERR; editor_menu(ctx->editor, _cmd_menu_ctag_cb, NULL, 0, aproc, NULL); return MLE_OK; } // Browse directory via tree int cmd_browse(cmd_context_t *ctx) { bview_t *menu; aproc_t *aproc; char *cmd; char *browse_path; browse_path = ctx->static_param ? ctx->static_param : "./"; asprintf(&cmd, "cd %s && tree --charset C -n -f -L 2 2>/dev/null", browse_path); aproc = aproc_new(ctx->editor, ctx->bview, &(ctx->bview->aproc), cmd, 0, _cmd_aproc_bview_passthru_cb); free(cmd); if (!aproc) return MLE_ERR; editor_menu(ctx->editor, _cmd_menu_browse_cb, ".", 1, aproc, &menu); mark_move_beginning(menu->active_cursor->mark); bview_zero_viewport_y(menu); if (strncmp(browse_path, "./", 2) == 0) { browse_path += 2; } else if (strcmp(browse_path, ".") == 0) { browse_path = ""; } snprintf(menu->browse_path, sizeof(menu->browse_path), "%s", browse_path); return MLE_OK; } // Buffer list int cmd_blist(cmd_context_t *ctx) { bview_t *bview; bview_t *menu; int path_max; char *path; int bview_count; str_t blist = {0}; int active_lineno; path_max = 1; CDL_FOREACH2(ctx->editor->all_bviews, bview, all_next) { if (!MLE_BVIEW_IS_EDIT(bview)) continue; if (bview->buffer->path && (int)strlen(bview->buffer->path) > path_max) { path_max = (int)strlen(bview->buffer->path); } } bview_count = 0; active_lineno = -1; CDL_FOREACH2(ctx->editor->all_bviews, bview, all_next) { if (!MLE_BVIEW_IS_EDIT(bview)) continue; if (bview == ctx->editor->active) active_lineno = bview_count; bview_count += 1; path = bview->buffer->path ? bview->buffer->path : bview->path; if (!path || strlen(path) < 1) path = "-"; str_sprintf( &blist, " #%-4d %-*s (unsaved=%s len=%" PRIdMAX " buffer=%p bview=%p id=%d)\n", bview_count, path_max, path, bview->buffer ? (bview->buffer->is_unsaved ? "y" : "n") : "y", bview->buffer->byte_count, bview->buffer, bview, bview->id ); } editor_menu(ctx->editor, _cmd_menu_blist_cb, blist.data, blist.len, NULL, &menu); if (active_lineno >= 0) mark_move_to(menu->active_cursor->mark, active_lineno, 0); bview_set_viewport_y(menu, 0, 1); str_free(&blist); return MLE_OK; } // Save-as file int cmd_save_as(cmd_context_t *ctx) { _cmd_save(ctx->editor, ctx->bview, 1); return MLE_OK; } // Save file int cmd_save(cmd_context_t *ctx) { _cmd_save(ctx->editor, ctx->bview, 0); return MLE_OK; } // Open file in a new bview int cmd_open_file(cmd_context_t *ctx) { char *path; editor_prompt(ctx->editor, "new_open: File?", NULL, &path); if (!path) return MLE_OK; editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, path, strlen(path), 1, 0, 0, NULL, NULL); free(path); return MLE_OK; } // Open empty buffer in a new bview int cmd_open_new(cmd_context_t *ctx) { editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, NULL); return MLE_OK; } // Open file into current bview int cmd_open_replace_file(cmd_context_t *ctx) { char *path; if (_cmd_pre_close(ctx->editor, ctx->bview) == MLE_ERR) return MLE_OK; path = NULL; editor_prompt(ctx->editor, "replace_open: Path?", NULL, &path); if (!path) return MLE_OK; bview_open(ctx->bview, path, strlen(path)); free(path); return MLE_OK; } // Open empty buffer into current bview int cmd_open_replace_new(cmd_context_t *ctx) { if (_cmd_pre_close(ctx->editor, ctx->bview) == MLE_ERR) return MLE_OK; bview_open(ctx->bview, NULL, 0); return MLE_OK; } // Close bview int cmd_close(cmd_context_t *ctx) { int num_open; int num_closed; if (_cmd_pre_close(ctx->editor, ctx->bview) == MLE_ERR) return MLE_OK; num_open = editor_bview_edit_count(ctx->editor); editor_close_bview(ctx->editor, ctx->bview, &num_closed); ctx->loop_ctx->should_exit = num_closed == num_open ? 1 : 0; return MLE_OK; } // Quit editor int cmd_quit(cmd_context_t *ctx) { bview_t *bview; bview_t *tmp; if (ctx->editor->loop_depth > 1) return MLE_OK; DL_FOREACH_SAFE2(ctx->editor->top_bviews, bview, tmp, top_next) { if (!MLE_BVIEW_IS_EDIT(bview)) { continue; } else if (_cmd_quit_inner(ctx->editor, bview) == MLE_ERR) { return MLE_OK; } } ctx->loop_ctx->should_exit = 1; return MLE_OK; } // Quit editor without saving int cmd_quit_without_saving(cmd_context_t *ctx) { ctx->loop_ctx->should_exit = 1; return MLE_OK; } // Apply a macro with single-char name int cmd_apply_macro_by(cmd_context_t *ctx) { kmacro_t *macro; uint32_t ch; char name[6] = { 0 }; if (ctx->editor->macro_apply) MLE_RETURN_ERR(ctx->editor, "Cannot nest macros%s", ""); ch = MLE_PARAM_WILDCARD(ctx, 0); if (!ch) return MLE_OK; utf8_unicode_to_char(name, ch); HASH_FIND_STR(ctx->editor->macro_map, name, macro); if (!macro) MLE_RETURN_ERR(ctx->editor, "Macro not found with name '%s'", name); ctx->editor->macro_apply = macro; ctx->editor->macro_apply_input_index = 0; return MLE_OK; } // Apply a macro int cmd_apply_macro(cmd_context_t *ctx) { char *name; kmacro_t *macro; if (ctx->editor->macro_apply) MLE_RETURN_ERR(ctx->editor, "Cannot nest macros%s", ""); editor_prompt(ctx->editor, "apply_macro: Name?", NULL, &name); if (!name) return MLE_OK; HASH_FIND_STR(ctx->editor->macro_map, name, macro); free(name); if (!macro) MLE_RETURN_ERR(ctx->editor, "Macro not found%s", ""); ctx->editor->macro_apply = macro; ctx->editor->macro_apply_input_index = 0; return MLE_OK; } // No-op int cmd_noop(cmd_context_t *ctx) { (void)ctx; return MLE_OK; } // Move forward til a certain char int cmd_move_until_forward(cmd_context_t *ctx) { uint32_t ch; char str[6] = { 0 }; ch = MLE_PARAM_WILDCARD(ctx, 0); if (!ch) return MLE_OK; utf8_unicode_to_char(str, ch); MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_next_str_nudge, str, strlen(str)); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Move back til a certain char int cmd_move_until_back(cmd_context_t *ctx) { uint32_t ch; char str[6] = { 0 }; ch = MLE_PARAM_WILDCARD(ctx, 0); if (!ch) return MLE_OK; utf8_unicode_to_char(str, ch); MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_prev_str, str, strlen(str)); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Temporarily drop anchor and move (shift-arrow behavior in MS Notepad) int cmd_move_temp_anchor(cmd_context_t *ctx) { cmd_func_t fn; if (!ctx->static_param) { return MLE_ERR; } if (strcmp(ctx->static_param, "up") == 0) { fn = cmd_move_up; } else if (strcmp(ctx->static_param, "down") == 0) { fn = cmd_move_down; } else if (strcmp(ctx->static_param, "left") == 0) { fn = cmd_move_left; } else if (strcmp(ctx->static_param, "right") == 0) { fn = cmd_move_right; } else if (strcmp(ctx->static_param, "bol") == 0) { fn = cmd_move_bol; } else if (strcmp(ctx->static_param, "eol") == 0) { fn = cmd_move_eol; } else if (strcmp(ctx->static_param, "page_up") == 0) { fn = cmd_move_page_up; } else if (strcmp(ctx->static_param, "page_down") == 0) { fn = cmd_move_page_down; } else if (strcmp(ctx->static_param, "word_forward") == 0) { fn = cmd_move_word_forward; } else if (strcmp(ctx->static_param, "word_back") == 0) { fn = cmd_move_word_back; } else { return MLE_ERR; } MLE_MULTI_CURSOR_CODE(ctx->cursor, cursor_drop_anchor(cursor, 1); cursor->is_temp_anchored = 1; ); return fn(ctx); } // Undo int cmd_undo(cmd_context_t *ctx) { if (ctx->editor->coarse_undo) { buffer_undo_action_group(ctx->bview->buffer); } else { buffer_undo(ctx->bview->buffer); } return MLE_OK; } // Redo int cmd_redo(cmd_context_t *ctx) { if (ctx->editor->coarse_undo) { buffer_redo_action_group(ctx->bview->buffer); } else { buffer_redo(ctx->bview->buffer); } return MLE_OK; } // Indent line(s) int cmd_indent(cmd_context_t *ctx) { return _cmd_indent(ctx, 0); } // Outdent line(s) int cmd_outdent(cmd_context_t *ctx) { return _cmd_indent(ctx, 1); } // Set option int cmd_set_opt(cmd_context_t *ctx) { char *prompt; char *val; int vali; if (!ctx->static_param) return MLE_ERR; asprintf(&prompt, "set_opt: %s?", ctx->static_param); editor_prompt(ctx->editor, prompt, NULL, &val); free(prompt); if (!val) return MLE_OK; vali = atoi(val); if (strcmp(ctx->static_param, "tab_to_space") == 0) { ctx->bview->tab_to_space = vali ? 1 : 0; } else if (strcmp(ctx->static_param, "tab_width") == 0) { ctx->bview->tab_width = MLE_MAX(vali, 1); buffer_set_tab_width(ctx->bview->buffer, ctx->bview->tab_width); } else if (strcmp(ctx->static_param, "syntax") == 0) { bview_set_syntax(ctx->bview, val); buffer_apply_styles(ctx->bview->buffer, ctx->bview->buffer->first_line, ctx->bview->buffer->line_count); } else if (strcmp(ctx->static_param, "soft_wrap") == 0) { ctx->bview->soft_wrap = vali ? 1 : 0; } else if (strcmp(ctx->static_param, "coarse_undo") == 0) { ctx->editor->coarse_undo = vali ? 1 : 0; } else if (strcmp(ctx->static_param, "mouse_support") == 0) { ctx->editor->mouse_support = vali ? 1 : 0; editor_set_input_mode(ctx->editor); } return MLE_OK; } // Shell int cmd_shell(cmd_context_t *ctx) { char *cmd; // Get shell cmd if (ctx->static_param) { cmd = strdup(ctx->static_param); } else { editor_prompt(ctx->editor, "shell: Cmd?", NULL, &cmd); if (!cmd) return MLE_OK; } // Apply shell cmd _cmd_shell_apply_cmd(ctx, cmd); free(cmd); return MLE_OK; } // Perl int cmd_perl(cmd_context_t *ctx) { char *code; char *code_escaped; char *cmd; // Get perl code if (ctx->static_param) { code = strdup(ctx->static_param); } else { editor_prompt(ctx->editor, "perl: Code?", NULL, &code); if (!code) return MLE_OK; } // Shell escape code code_escaped = util_escape_shell_arg(code, strlen(code)); free(code); // Format cmd asprintf(&cmd, "perl -lp -E 'BEGIN{$i=0}' -E %s 2>/dev/null", code_escaped); free(code_escaped); // Apply perl cmd _cmd_shell_apply_cmd(ctx, cmd); free(cmd); return MLE_OK; } // Jump to a `\S{2,}` on screen via `[a-z][a-z]` int cmd_jump(cmd_context_t *ctx) { bline_t *bline; bint_t col; bint_t nchars; bint_t stop_line_index; mark_t *mark; mark_t *jumps; int jumpi, jumpt, screen_x, screen_y; char jumpa[3]; kinput_t ev[2]; int headless; // Check if headless headless = ctx->editor->headless_mode ? 1 : 0; // Set boundaries mark_clone(ctx->cursor->mark, &mark); if (headless) { mark_move_bol(mark); stop_line_index = mark->bline->line_index + 1; } else { mark_move_to_w_bline(mark, ctx->bview->viewport_mark->bline, 0); stop_line_index = ctx->bview->viewport_mark->bline->line_index + ctx->bview->rect_buffer.h; } // Make jump map jumps = calloc(26*26, sizeof(mark_t)); jumpi = 0; do { // Loop for words while (jumpi < 26*26 && mark_move_next_re_ex(mark, "\\S{2,}", strlen("\\S{2,}"), &bline, &col, &nchars) == MLBUF_OK) { if (bline->line_index >= stop_line_index) break; jumps[jumpi].bline = bline; jumps[jumpi].col = col; mark_move_by(mark, MLE_MAX(0, nchars - 2)); if (!headless) { bview_get_screen_coords(ctx->bview, mark, &screen_x, &screen_y, NULL); sprintf(jumpa, "%c%c", 'a' + (jumpi / 26), 'a' + (jumpi % 26)); tb_print(screen_x, screen_y, TB_WHITE | TB_BOLD, TB_MAGENTA, jumpa); } jumpi += 1; mark_move_by(mark, 2); } if (jumpi < 1) break; if (!headless) tb_present(); // Get 2 inputs _cmd_get_input(ctx, &ev[0]); if (ev[0].ch < 'a' || ev[0].ch > 'z') break; _cmd_get_input(ctx, &ev[1]); // Jump jumpt = ((ev[0].ch - 'a') * 26) + (ev[1].ch - 'a'); if (jumpt >= 0 && jumpt < jumpi) { mark_move_to_w_bline(ctx->cursor->mark, jumps[jumpt].bline, jumps[jumpt].col); } } while(0); // Cleanup free(jumps); mark_destroy(mark); return MLE_OK; } // Raise SIGTSTP int cmd_suspend(cmd_context_t *ctx) { (void)ctx; tb_shutdown(); raise(SIGTSTP); return MLE_OK; } // Push kmap int cmd_push_kmap(cmd_context_t *ctx) { kmap_t *kmap; char *kmap_name; int rv; // Get kmap name kmap_name = NULL; if (ctx->static_param) { kmap_name = strdup(ctx->static_param); } else { editor_prompt(ctx->editor, "push_kmap: Kmap name?", NULL, &kmap_name); if (!kmap_name) return MLE_OK; } // Find kmap by name kmap = NULL; HASH_FIND_STR(ctx->editor->kmap_map, kmap_name, kmap); if (!kmap) { // Not found MLE_SET_ERR(ctx->editor, "push_kmap: No kmap defined '%s'", kmap_name); rv = MLE_ERR; } else { // Found, push bview_push_kmap(ctx->bview, kmap); rv = MLE_OK; } free(kmap_name); return rv; } // Pop kmap int cmd_pop_kmap(cmd_context_t *ctx) { bview_pop_kmap(ctx->bview, NULL); return MLE_OK; } // Hacky as hell less integration int cmd_less(cmd_context_t *ctx) { int rc; char *sh_fmt; char *sh; char out[32]; int screen_x; int screen_y; ssize_t out_len; bint_t line_top; char tmp_buf[32]; char tmp_linenum[32]; int tmp_buf_fd; int tmp_linenum_fd; rc = MLE_OK; sh = NULL; tmp_buf_fd = -1; tmp_linenum_fd = -1; do { if (MLE_ERR == bview_get_screen_coords(ctx->bview, ctx->cursor->mark, &screen_x, &screen_y, NULL)) { rc = MLE_ERR; break; } sprintf(tmp_linenum, "/tmp/mle-less-XXXXXX"); if ((tmp_linenum_fd = mkstemp(tmp_linenum)) < 0) { rc = MLE_ERR; break; } sprintf(tmp_buf, "/tmp/mle-less-XXXXXX"); if ((tmp_buf_fd = mkstemp(tmp_buf)) < 0) { rc = MLE_ERR; break; } if (MLBUF_ERR == buffer_write_to_fd(ctx->buffer, tmp_buf_fd, NULL)) { rc = MLE_ERR; break; } sh_fmt = "tmp_lesskey=$(mktemp -q /tmp/mle-less-XXXXXX);" "echo -e \"#command\\nq visual\\nQ visual\\n:q visual\\n:Q visual\\nZZ visual\\n#env\\n" "LESSEDIT=echo %%lt >%s; kill 0\" | lesskey -o $tmp_lesskey -- -;" "less +%ld -j%ld -k $tmp_lesskey -S %s;" "rm -f $tmp_lesskey"; asprintf(&sh, sh_fmt, tmp_linenum, ctx->cursor->mark->bline->line_index+1, screen_y+1, tmp_buf); tb_shutdown(); if (MLE_ERR == util_shell_exec(ctx->editor, sh, -1, NULL, 0, 1, "bash", NULL, NULL, NULL)) { rc = MLE_ERR; break; } out_len = read(tmp_linenum_fd, &out, sizeof(out)-1); out[out_len >= 0 ? out_len : 0] = '\0'; line_top = (bint_t)strtoull(out, NULL, 10); if (line_top <= 0) { rc = MLE_ERR; break; } mark_move_to(ctx->cursor->mark, (line_top) + ctx->bview->rect_buffer.h/2, 0); bview_center_viewport_y(ctx->bview); } while(0); if (tmp_buf_fd >= 0) { close(tmp_buf_fd); unlink(tmp_buf); } if (tmp_linenum_fd >= 0) { close(tmp_linenum_fd); unlink(tmp_linenum); } if (sh) free(sh); return rc; } // Show help int cmd_show_help(cmd_context_t *ctx) { str_t h = {0}; // help kmap_t *kmap; kmap_t *kmap_tmp; kmap_node_t *kmap_node; int kmap_node_depth; int i; bview_t *bview; char buf[1024]; str_append(&h, "# mle command help\n\n" " notes\n" " C- means Ctrl\n" " M- means Alt\n" " S- means Shift\n" " cmd_x=(default) means unmatched input is handled by cmd_x\n" " (allow_fallthru)=yes means unmatched input is handled by the parent kmap\n\n" ); // Build current kmap stack str_append(&h, " mode stack\n"); kmap_node_depth = 0; DL_FOREACH(ctx->bview->kmap_stack, kmap_node) { str_append(&h, " "); for (i = 0; i < kmap_node_depth; i++) { str_append(&h, " "); } if (i > 0) { str_append(&h, "\\_ "); } str_append(&h, kmap_node->kmap->name); if (kmap_node == ctx->bview->kmap_tail) { str_append(&h, " (current)"); } str_append(&h, "\n"); kmap_node_depth += 1; } str_append(&h, "\n"); // Build kmap bindings HASH_ITER(hh, ctx->editor->kmap_map, kmap, kmap_tmp) { str_append(&h, " mode "); str_append(&h, kmap->name); str_append(&h, "\n"); snprintf(buf, sizeof(buf), " %-40s %-16s\n", "(allow_fallthru)", kmap->allow_fallthru ? "yes" : "no"); str_append(&h, buf); if (kmap->default_cmd_name) { snprintf(buf, sizeof(buf), " %-40s %-16s\n", kmap->default_cmd_name, "(default)"); str_append(&h, buf); } _cmd_help_inner(buf, kmap->bindings->children, &h); str_append(&h, "\n"); } // Show help in new bview editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, &bview); buffer_insert(bview->buffer, 0, h.data, (bint_t)h.len, NULL); bview->buffer->is_unsaved = 0; mark_move_beginning(bview->active_cursor->mark); bview_zero_viewport_y(bview); str_free(&h); return MLE_OK; } // Indent or outdent line(s) static int _cmd_indent(cmd_context_t *ctx, int outdent) { bline_t *start; bline_t *end; bline_t *cur; int use_tabs; use_tabs = ctx->bview->tab_to_space ? 0 : 1; MLE_MULTI_CURSOR_CODE(ctx->cursor, start = cursor->mark->bline; if (cursor->is_anchored) { end = cursor->anchor->bline; if (start->line_index > end->line_index) { cur = end; end = start; start = cur; } } else { end = start; } ctx->buffer->is_style_disabled++; for (cur = start; cur != end->next; cur = cur->next) { _cmd_indent_line(cur, use_tabs, outdent); } ctx->buffer->is_style_disabled--; buffer_apply_styles(ctx->buffer, start, end->line_index - start->line_index); ); return MLE_OK; } // Recursively descend into kbinding trie to build help string static void _cmd_help_inner(char *buf, kbinding_t *trie, str_t *h) { kbinding_t *binding; kbinding_t *binding_tmp; HASH_ITER(hh, trie, binding, binding_tmp) { if (binding->children) { _cmd_help_inner(buf, binding->children, h); } else if (binding->is_leaf) { snprintf(buf, 1024, " %-40s %-16s %s\n", binding->cmd_name, binding->key_patt, binding->static_param ? binding->static_param : "" ); str_append(h, buf); } } } // Indent/outdent a line, optionally using tabs static int _cmd_indent_line(bline_t *bline, int use_tabs, int outdent) { char tab_char; int num_chars; int num_to_del; int i; bint_t ig; tab_char = use_tabs ? '\t' : ' '; num_chars = use_tabs ? 1 : bline->buffer->tab_width; MLBUF_BLINE_ENSURE_CHARS(bline); if (outdent) { num_to_del = 0; for (i = 0; i < num_chars; i++) { if (bline->char_count > i && (char)bline->chars[i].ch == tab_char) { num_to_del += 1; } else { break; } } if (num_to_del > 0) bline_delete(bline, 0, num_to_del); } else { if (bline->char_count > 0) { for (i = 0; i < num_chars; i++) { bline_insert(bline, 0, &tab_char, 1, &ig); } } } return MLE_OK; } // Recursively close bviews, prompting to save unsaved changes. Return MLE_OK if // it's OK to continue closing, or MLE_ERR if the action was cancelled. static int _cmd_quit_inner(editor_t *editor, bview_t *bview) { if (bview->split_child && _cmd_quit_inner(editor, bview->split_child) == MLE_ERR) { return MLE_ERR; } else if (_cmd_pre_close(editor, bview) == MLE_ERR) { return MLE_ERR; } editor_close_bview(editor, bview, NULL); return MLE_OK; } // Prompt to save unsaved changes on close. Return MLE_OK if it's OK to continue // closing the bview, or MLE_ERR if the action was cancelled. static int _cmd_pre_close(editor_t *editor, bview_t *bview) { char *yn; if (!MLE_BVIEW_IS_EDIT(bview)) { MLE_RETURN_ERR(editor, "Cannot close non-edit bview %p", (void*)bview); } else if (editor->loop_depth > 1) { MLE_RETURN_ERR(editor, "Cannot close bview %p when loop_depth > 1", (void*)bview); } else if (!bview->buffer->is_unsaved || MLE_BVIEW_IS_MENU(bview) || editor_count_bviews_by_buffer(editor, bview->buffer) > 1 ) { return MLE_OK; } editor_set_active(editor, bview); yn = NULL; editor_prompt(editor, "close: Save modified? (y=yes, n=no, C-c=cancel)", &(editor_prompt_params_t) { .kmap = editor->kmap_prompt_yn }, &yn ); if (!yn) { return MLE_ERR; } else if (0 == strcmp(yn, MLE_PROMPT_NO)) { return MLE_OK; } return _cmd_save(editor, bview, 1); } // Prompt to save changes. Return MLE_OK if file was saved or MLE_ERR if the action // was cancelled. static int _cmd_save(editor_t *editor, bview_t *bview, int save_as) { int rc; char *path, *path_tmp; char *yn; int fname_changed; struct stat st; bint_t nbytes; fname_changed = 0; do { if (!bview->buffer->path || save_as) { // Prompt for name editor_prompt(editor, "save: Save as? (C-c=cancel)", &(editor_prompt_params_t) { .data = bview->buffer->path ? bview->buffer->path : "", .data_len = bview->buffer->path ? strlen(bview->buffer->path) : 0 }, &path_tmp); if (!path_tmp) return MLE_ERR; // Expand tilde util_expand_tilde(path_tmp, strlen(path_tmp), &path, NULL); free(path_tmp); } else { // Re-use existing name path = strdup(bview->buffer->path); } // Remember if fname is changing for refreshing syntax later fname_changed = !bview->buffer->path || strcmp(bview->buffer->path, path) != 0 ? 1 : 0; // Check clobber warning if (stat(path, &st) == 0 && st.st_dev == bview->buffer->st.st_dev && st.st_ino == bview->buffer->st.st_ino && st.st_mtime > bview->buffer->st.st_mtime ) { // File was modified after it was opened/last saved editor_prompt(editor, "save: Clobber detected! Continue? (y=yes, n=no)", &(editor_prompt_params_t) { .kmap = editor->kmap_prompt_yn }, &yn ); if (!yn || 0 == strcmp(yn, MLE_PROMPT_NO)) { free(path); return MLE_OK; } } // Save, check error rc = buffer_save_as(bview->buffer, path, &nbytes); free(path); if (rc == MLBUF_ERR) { MLE_SET_ERR(editor, "save: %s", errno ? strerror(errno) : "failed"); } else { MLE_SET_INFO(editor, "save: Wrote %" PRIdMAX " bytes", nbytes); // Notify event observers editor_notify_observers(editor, "buffer:save", (void*)bview); } } while (rc == MLBUF_ERR && (!bview->buffer->path || save_as)); // Refresh syntax if fname changed if (fname_changed) { bview_set_syntax(bview, NULL); } return rc == MLBUF_OK ? MLE_OK : MLE_ERR; } // Search for a regex static int _cmd_search_ex(cmd_context_t *ctx, int is_prev) { char *regex; int regex_len; mark_t *search_mark; editor_prompt(ctx->editor, is_prev ? "rsearch: Regex?" : "search: Regex?", NULL, ®ex); if (!regex) return MLE_OK; regex_len = strlen(regex); search_mark = buffer_add_mark(ctx->bview->buffer, NULL, 0); MLE_MULTI_CURSOR_CODE(ctx->cursor, _cmd_search_next(ctx->bview, cursor, search_mark, regex, regex_len, is_prev); ); mark_destroy(search_mark); if (ctx->bview->last_search) free(ctx->bview->last_search); ctx->bview->last_search = regex; return MLE_OK; } // Search for next instance of last search regex static int _cmd_search_next_ex(cmd_context_t *ctx, int is_prev) { int regex_len; mark_t *search_mark; if (!ctx->bview->last_search) return MLE_OK; regex_len = strlen(ctx->bview->last_search); search_mark = buffer_add_mark(ctx->bview->buffer, NULL, 0); MLE_MULTI_CURSOR_CODE(ctx->cursor, _cmd_search_next(ctx->bview, cursor, search_mark, ctx->bview->last_search, regex_len, is_prev); ); mark_destroy(search_mark); return MLE_OK; } // Move cursor to next occurrence of term, wrap if necessary. Return MLE_OK if // there was a match, or MLE_ERR if no match. static int _cmd_search_next(bview_t *bview, cursor_t *cursor, mark_t *search_mark, char *regex, int regex_len, int is_prev) { int rc; int (*move_next_prev_re_nudge)(mark_t *, char *, bint_t); int (*move_next_prev_re)(mark_t *, char *, bint_t); int (*move_beginning_end)(mark_t *); rc = MLE_ERR; // Set func pointers if (is_prev) { move_next_prev_re_nudge = mark_move_prev_re; move_next_prev_re = mark_move_prev_re; move_beginning_end = mark_move_end; } else { move_next_prev_re_nudge = mark_move_next_re_nudge; move_next_prev_re = mark_move_next_re; move_beginning_end = mark_move_beginning; } // Move search_mark to cursor mark_join(search_mark, cursor->mark); // Look for match ahead of us if (move_next_prev_re_nudge(search_mark, regex, regex_len) == MLBUF_OK) { // Match! Move there mark_join(cursor->mark, search_mark); rc = MLE_OK; } else { // No match, try from beginning move_beginning_end(search_mark); if (move_next_prev_re(search_mark, regex, regex_len) == MLBUF_OK) { // Match! Move there mark_join(cursor->mark, search_mark); rc = MLE_OK; } } // Rectify viewport if needed if (rc == MLE_OK) bview_rectify_viewport(bview); return rc; } // Find next/prev occurence of word under cursor static int _cmd_find_word_ex(cmd_context_t *ctx, int is_prev) { char *re; char *word; bint_t re_len; bint_t word_len; int (*move_next_prev_re)(mark_t *, char *, bint_t); int (*move_next_prev_re_nudge)(mark_t *, char *, bint_t); int (*move_beginning_end)(mark_t *); if (is_prev) { move_next_prev_re_nudge = mark_move_prev_re; move_next_prev_re = mark_move_prev_re; move_beginning_end = mark_move_end; } else { move_next_prev_re_nudge = mark_move_next_re_nudge; move_next_prev_re = mark_move_next_re; move_beginning_end = mark_move_beginning; } MLE_MULTI_CURSOR_CODE(ctx->cursor, if (cursor_select_by(cursor, "word", 0) == MLE_OK) { mark_swap(cursor->mark, cursor->anchor); mark_get_between(cursor->mark, cursor->anchor, &word, &word_len); re_len = asprintf(&re, "\\b%s\\b", word); free(word); cursor_toggle_anchor(cursor, 0); if (move_next_prev_re_nudge(cursor->mark, re, re_len) == MLBUF_ERR) { move_beginning_end(cursor->mark); move_next_prev_re(cursor->mark, re, re_len); } free(re); } ); bview_rectify_viewport(ctx->bview); return MLE_OK; } // Aproc callback that writes buf to bview buffer static void _cmd_aproc_bview_passthru_cb(aproc_t *aproc, char *buf, size_t buf_len) { mark_t *active_mark; mark_t *ins_mark; int is_cursor_at_zero; bview_t *bview; if (!buf || buf_len < 1) return; // Remeber if cursor is at 0 bview = (bview_t*)aproc->owner; active_mark = bview->active_cursor->mark; is_cursor_at_zero = active_mark->bline->line_index == 0 && active_mark->col == 0 ? 1 : 0; // Append data at end of menu buffer ins_mark = buffer_add_mark(bview->buffer, NULL, 0); mark_move_end(ins_mark); mark_insert_before(ins_mark, buf, buf_len); mark_destroy(ins_mark); bview_rectify_viewport(bview); if (is_cursor_at_zero) mark_move_beginning(active_mark); } // Incremental search prompt callback static void _cmd_isearch_prompt_cb(bview_t *bview_prompt, baction_t *action, void *udata) { bview_t *bview; char *regex; int regex_len; (void)action; (void)udata; bview = bview_prompt->editor->active_edit; if (bview->isearch_rule) { srule_destroy(bview->isearch_rule); bview->isearch_rule = NULL; } regex = bview_prompt->buffer->first_line->data; regex_len = bview_prompt->buffer->first_line->data_len; if (regex_len < 1) return; // isearch_rule is applied in bview.c bview->isearch_rule = srule_new_single(regex, regex_len, 1, TB_BOLD, TB_MAGENTA); if (!bview->isearch_rule) return; mark_move_next_cre(bview->active_cursor->mark, bview->isearch_rule->cre); bview_center_viewport_y(bview); } // Callback from cmd_grep static int _cmd_menu_grep_cb(cmd_context_t *ctx) { bint_t linenum; char *line; char *colon; line = strndup(ctx->bview->active_cursor->mark->bline->data, ctx->bview->active_cursor->mark->bline->data_len); colon = strchr(line, ':'); if (colon == NULL) { free(line); return MLE_OK; } if (*(colon + 1) != '\0' && strchr(colon + 1, ':') != NULL) { linenum = strtoll(colon + 1, NULL, 10); } else { linenum = 0; } editor_close_bview(ctx->editor, ctx->bview, NULL); editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, line, (int)(colon - line), 1, linenum, 0, NULL, NULL); free(line); return MLE_OK; } // Callback from cmd_ctag static int _cmd_menu_ctag_cb(cmd_context_t *ctx) { char *line; char *tok; char *fname; char *re; char *qre; char *qre2; int re_len; int qre_len; int i; bview_t *bview; line = strndup(ctx->bview->active_cursor->mark->bline->data, ctx->bview->active_cursor->mark->bline->data_len); i = 0; fname = NULL; re = NULL; tok = strtok(line, "\t"); while (tok) { if (i == 1) { fname = tok; } else if (i == 2) { re = tok; break; } tok = strtok(NULL, "\t"); i += 1; } re_len = re ? strlen(re) : 0; if (!fname || re_len < 4) { free(line); return MLE_OK; } re += 2; // Skip leading `/^` re_len -= 2; re[re_len-4] = '\0'; // Trunc trailing `$/;"` re_len -= 4; util_pcre_replace("([\\.\\\\\\+\\*\\?\\^\\$\\[\\]\\(\\)\\{\\}\\=\\!\\>\\<\\|\\:\\-])", re, "\\\\$1", &qre, &qre_len); editor_close_bview(ctx->editor, ctx->bview, NULL); editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, fname, strlen(fname), 1, 0, 0, NULL, &bview); asprintf(&qre2, "^%s", qre); mark_move_next_re(bview->active_cursor->mark, qre2, qre_len+1); bview_center_viewport_y(bview); free(line); free(qre); free(qre2); return MLE_OK; } // Callback from cmd_browse static int _cmd_menu_browse_cb(cmd_context_t *ctx) { char *line; char *path, *tmp; char apath[PATH_MAX + 1]; bview_t *new_bview, *self_bview; int rv; rv = MLE_OK; // Get path from tree output line = strndup(ctx->bview->active_cursor->mark->bline->data, ctx->bview->active_cursor->mark->bline->data_len); if ((path = strstr(line, "-- ")) != NULL) { path += 3; if (strncmp(path, "./", 2) == 0) { path += 2; } if ((tmp = strstr(path, " -> ")) != NULL) { *tmp = '\0'; } } else if (strcmp(line, "..") == 0) { path = ".."; } else if (strlen(line) < 1) { goto _cmd_menu_browse_cb_done; } else { MLE_SET_ERR(ctx->editor, "browse: Cannot browse to: '%s'", line); rv = MLE_ERR; goto _cmd_menu_browse_cb_done; } // Derive apath size_t browse_path_len = strlen(ctx->bview->browse_path); if (browse_path_len > 0 && *path != '/') { if (snprintf(apath, sizeof(apath), "%s%s%s", ctx->bview->browse_path, ctx->bview->browse_path[browse_path_len - 1] != '/' ? "/" : "", path ) > PATH_MAX) { MLE_SET_ERR(ctx->editor, "browse: Path longer than PATH_MAX (%d)", PATH_MAX); rv = MLE_ERR; goto _cmd_menu_browse_cb_done; } } else { snprintf(apath, sizeof(apath), "%s", path); } // Open file or browse dir self_bview = ctx->bview; new_bview = NULL; if (util_is_dir(apath)) { ctx->bview = ctx->editor->active_edit; ctx->static_param = apath; cmd_browse(ctx); } else { editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, apath, strlen(apath), 0, 0, 0, NULL, &new_bview); } // Close self editor_close_bview(ctx->editor, self_bview, NULL); // Set new_bview to active if (new_bview) editor_set_active(ctx->editor, new_bview); _cmd_menu_browse_cb_done: free(line); return rv; } // Callback from cmd_blist static int _cmd_menu_blist_cb(cmd_context_t *ctx) { char *sid; int sid_len; int id; bview_t *bview; if (!util_pcre_match( "id=\\d+\\)$", ctx->bview->active_cursor->mark->bline->data, (int)ctx->bview->active_cursor->mark->bline->data_len, &sid, &sid_len )) { return MLE_ERR; } id = strtol(sid + 3, NULL, 10); CDL_FOREACH2(ctx->editor->all_bviews, bview, all_next) { if (bview->id == id) { editor_close_bview(ctx->editor, ctx->bview, NULL); // Close menu editor_set_active(ctx->editor, bview); // Switch to selected bview return MLE_OK; } } MLE_RETURN_ERR(ctx->editor, "Could not switch to buffer #%d", id); } // Insert newline when auto_indent is enabled (preserves or increases indent) static void _cmd_insert_auto_indent_newline(cmd_context_t *ctx) { bline_t *prev_bline; char *prev_line = NULL; bint_t prev_line_len; char *indent; int indent_len; char *tmp; mark_insert_before(ctx->cursor->mark, "\n", 1); prev_bline = ctx->cursor->mark->bline->prev; prev_line = strndup(prev_bline->data, prev_bline->data_len); prev_line_len = strlen(prev_line); if (util_pcre_match("^\\s*", prev_line, prev_line_len, &indent, &indent_len) && indent_len > 0) { // Insert same whitespace from prev_line on this line mark_insert_before(ctx->cursor->mark, indent, indent_len); } if (prev_line_len > 0 && prev_line[prev_line_len-1] == '{') { // Insert extra indent if last line ends with '{' if (ctx->bview->tab_to_space && indent_len % ctx->bview->tab_width == 0 && util_pcre_match("^ *$", indent, indent_len, NULL, NULL) ) { asprintf(&tmp, "%*c", (int)ctx->bview->tab_width, ' '); mark_insert_before(ctx->cursor->mark, tmp, strlen(tmp)); free(tmp); } else if (!ctx->bview->tab_to_space && util_pcre_match("^\\t*$", indent, indent_len, NULL, NULL) ) { mark_insert_before(ctx->cursor->mark, "\t", 1); } } else if (ctx->loop_ctx->last_cmd_ctx.cmd && ctx->loop_ctx->last_cmd_ctx.cmd->func == cmd_insert_data && ctx->loop_ctx->last_insert.len == 1 && ctx->loop_ctx->last_insert.data[0] == '\n' && util_pcre_match("^\\s+$", prev_line, prev_line_len, NULL, NULL) ) { // Clear prev line if it's all whitespace and last cmd was also // inserting a newline MLBUF_BLINE_ENSURE_CHARS(ctx->cursor->mark->bline->prev); bline_delete(ctx->cursor->mark->bline->prev, 0, ctx->cursor->mark->bline->prev->char_count); } free(prev_line); } // Insert closing curly bracket when auto_indent is enabled (decreases indent) static void _cmd_insert_auto_indent_closing_bracket(cmd_context_t *ctx) { char *this_line = NULL; char *this_ws; char *prev_line = NULL; char *prev_ws; int this_line_len; int this_ws_len; int prev_line_len; int prev_ws_len; int prev_open; do { if (!ctx->cursor->mark->bline->prev) break; this_line = strndup(ctx->cursor->mark->bline->data, ctx->cursor->mark->bline->data_len); prev_line = strndup(ctx->cursor->mark->bline->prev->data, ctx->cursor->mark->bline->prev->data_len); this_line_len = strlen(this_line); prev_line_len = strlen(prev_line); prev_open = prev_line_len > 0 && prev_line[prev_line_len-1] == '{' ? 1 : 0; if (!util_pcre_match("^\\s*", this_line, this_line_len, &this_ws, &this_ws_len) || !util_pcre_match("^\\s*", prev_line, prev_line_len, &prev_ws, &prev_ws_len) || (!prev_open && this_ws_len < prev_ws_len) || (prev_open && this_ws_len <= prev_ws_len) ) { // More whitespace on prev line break; } else if (ctx->bview->tab_to_space && ctx->cursor->mark->bline->data_len % ctx->bview->tab_width == 0 && util_pcre_match("^ +$", this_line, this_line_len, NULL, NULL) ) { // Outdent with tab_width worth of spaces bline_delete(ctx->cursor->mark->bline, 0, ctx->bview->tab_width); } else if (!ctx->bview->tab_to_space && util_pcre_match("^\\t+$", this_line, this_line_len, NULL, NULL) ) { // Outdent one tab bline_delete(ctx->cursor->mark->bline, 0, 1); } } while(0); mark_insert_before(ctx->cursor->mark, "}", 1); if (this_line) free(this_line); if (prev_line) free(prev_line); } // Apply cmd for each cursor static void _cmd_shell_apply_cmd(cmd_context_t *ctx, char *cmd) { char *input; bint_t input_len; char *output; size_t output_len; int exit_code; bline_t *block_bline; bint_t block_col; mark_t *block_mark; // Loop for each cursor MLE_MULTI_CURSOR_CODE(ctx->cursor, // Get data to send to stdin if (cursor->is_anchored) { (cursor->is_block ? mark_block_get_between : mark_get_between) (cursor->mark, cursor->anchor, &input, &input_len); } else { input = NULL; input_len = 0; } // Run cmd output = NULL; output_len = 0; exit_code = -1; block_mark = NULL; if (util_shell_exec(ctx->editor, cmd, 1, input, input_len, 0, NULL, &output, &output_len, &exit_code) == MLE_OK) { if (output_len > 0) { // Write output to buffer if (cursor->is_anchored) { if (cursor->is_block) { mark_block_get_top_left(cursor->mark, cursor->anchor, &block_bline, &block_col); mark_clone(cursor->mark, &block_mark); block_mark->lefty = 1; mark_move_to_w_bline(block_mark, block_bline, block_col); mark_block_delete_between(cursor->mark, cursor->anchor); } else { mark_delete_between(cursor->mark, cursor->anchor); } } if (cursor->is_block) { mark_block_insert_before(block_mark ? block_mark : cursor->mark, output, output_len); } else { mark_insert_before(cursor->mark, output, output_len); } } MLE_SET_INFO(ctx->editor, "shell: Exited %d", exit_code); } // Frees if (block_mark) mark_destroy(block_mark); if (input) free(input); if (output) free(output); ); // Loop for next cursor } // Get one kinput_t, saving original input on cmd_context_t static void _cmd_get_input(cmd_context_t *ctx, kinput_t *ret_input) { kinput_t oinput; MLE_KINPUT_COPY(oinput, ctx->input); memset(ret_input, 0, sizeof(kinput_t)); editor_get_input(ctx->editor, ctx->loop_ctx, ctx); MLE_KINPUT_COPY(*ret_input, ctx->input); MLE_KINPUT_COPY(ctx->input, oinput); } // Perform a fuzzy search using fzf/fzy // `shell_cmd` is expected to return a path on stdout static int _cmd_fsearch_inner(cmd_context_t *ctx, char *shell_cmd) { char *path; size_t path_len; path = NULL; if (tb_width() >= 0) tb_shutdown(); if (util_shell_exec(ctx->editor, shell_cmd, -1, NULL, 0, 0, NULL, &path, &path_len, NULL) == MLE_ERR) { return MLE_ERR; } if (path && path_len > 0) { while (path[path_len-1] == '\n') { // Strip trailing newlines path_len -= 1; } if (path_len > 0) { if (ctx->static_param && strcmp(ctx->static_param, "replace") == 0) { if (_cmd_pre_close(ctx->editor, ctx->bview) == MLE_OK) { bview_open(ctx->bview, path, path_len); } } else { editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, path, path_len, 1, 0, 0, NULL, NULL); } } } free(path); return MLE_OK; } // Get char from static param or wildcard param static int _cmd_get_char_param(cmd_context_t *ctx, char *ret_ch) { uint32_t ch; if (ctx->static_param) { *ret_ch = *ctx->static_param; } else if ((ch = MLE_PARAM_WILDCARD(ctx, 0))) { *ret_ch = (char)ch; } else { *ret_ch = '\0'; } return MLE_OK; } mle-1.7.2/cursor.c000066400000000000000000000355321443351614700137770ustar00rootroot00000000000000#include #include #include "mle.h" static int cursor_uncut_ex(cursor_t *cursor, int editor_wide); // Clone cursor int cursor_clone(cursor_t *cursor, int use_srules, cursor_t **ret_clone) { cursor_t *clone; bview_add_cursor(cursor->bview, cursor->mark->bline, cursor->mark->col, &clone); if (cursor->is_anchored) { cursor_toggle_anchor(clone, use_srules); mark_join(clone->anchor, cursor->anchor); } *ret_clone = clone; return MLE_OK; } // Remove cursor int cursor_destroy(cursor_t *cursor) { return bview_remove_cursor(cursor->bview, cursor); } // Select by mark int cursor_select_between(cursor_t *cursor, mark_t *a, mark_t *b, int use_srules) { cursor_drop_anchor(cursor, use_srules); if (mark_is_lt(a, b)) { mark_join(cursor->mark, a); mark_join(cursor->anchor, b); } else { mark_join(cursor->mark, b); mark_join(cursor->anchor, a); } return MLE_OK; } // Toggle cursor anchor int cursor_toggle_anchor(cursor_t *cursor, int use_srules) { if (!cursor->is_anchored && !cursor->is_temp_anchored) { mark_clone(cursor->mark, &(cursor->anchor)); if (use_srules) { cursor->sel_rule = srule_new_range(cursor->mark, cursor->anchor, 0, TB_REVERSE); buffer_add_srule(cursor->bview->buffer, cursor->sel_rule); } cursor->is_anchored = 1; } else { if (use_srules && cursor->sel_rule) { buffer_remove_srule(cursor->bview->buffer, cursor->sel_rule); srule_destroy(cursor->sel_rule); cursor->sel_rule = NULL; } mark_destroy(cursor->anchor); cursor->is_anchored = 0; cursor->is_temp_anchored = 0; } return MLE_OK; } // Drop cursor anchor int cursor_drop_anchor(cursor_t *cursor, int use_srules) { if (cursor->is_anchored) return MLE_OK; return cursor_toggle_anchor(cursor, use_srules); } // Lift cursor anchor int cursor_lift_anchor(cursor_t *cursor) { if (!cursor->is_anchored) return MLE_OK; return cursor_toggle_anchor(cursor, cursor->sel_rule ? 1 : 0); } // Get lo and hi marks in a is_anchored=1 cursor int cursor_get_lo_hi(cursor_t *cursor, mark_t **ret_lo, mark_t **ret_hi) { if (!cursor->is_anchored) { return MLE_ERR; } if (mark_is_gt(cursor->anchor, cursor->mark)) { *ret_lo = cursor->mark; *ret_hi = cursor->anchor; } else { *ret_lo = cursor->anchor; *ret_hi = cursor->mark; } return MLE_OK; } // Get mark int cursor_get_mark(cursor_t *cursor, mark_t **ret_mark) { *ret_mark = cursor->mark; return MLE_OK; } // Get anchor if anchored int cursor_get_anchor(cursor_t *cursor, mark_t **ret_anchor) { *ret_anchor = cursor->is_anchored ? cursor->anchor : NULL; return MLE_OK; } // Make selection by strat int cursor_select_by(cursor_t *cursor, const char *strat, int use_srules) { if (cursor->is_anchored) { return MLE_ERR; } if (strcmp(strat, "bracket") == 0) { return cursor_select_by_bracket(cursor, use_srules); } else if (strcmp(strat, "word") == 0) { return cursor_select_by_word(cursor, use_srules); } else if (strcmp(strat, "word_back") == 0) { return cursor_select_by_word_back(cursor, use_srules); } else if (strcmp(strat, "word_forward") == 0) { return cursor_select_by_word_forward(cursor, use_srules); } else if (strcmp(strat, "eol") == 0) { cursor_toggle_anchor(cursor, use_srules); mark_move_eol(cursor->anchor); } else if (strcmp(strat, "bol") == 0) { cursor_toggle_anchor(cursor, use_srules); mark_move_bol(cursor->anchor); } else if (strcmp(strat, "string") == 0) { return cursor_select_by_string(cursor, use_srules); } else if (strcmp(strat, "all") == 0) { mark_move_beginning(cursor->mark); cursor_toggle_anchor(cursor, use_srules); mark_move_end(cursor->mark); } else { MLE_RETURN_ERR(cursor->bview->editor, "Unrecognized cursor_select_by strat '%s'", strat); } return MLE_OK; } // Select by bracket int cursor_select_by_bracket(cursor_t *cursor, int use_srules) { mark_t *orig; mark_clone(cursor->mark, &orig); if (mark_move_bracket_top(cursor->mark, MLE_BRACKET_PAIR_MAX_SEARCH) != MLBUF_OK) { mark_destroy(orig); return MLE_ERR; } cursor_toggle_anchor(cursor, use_srules); if (mark_move_bracket_pair(cursor->anchor, MLE_BRACKET_PAIR_MAX_SEARCH) != MLBUF_OK) { cursor_toggle_anchor(cursor, use_srules); mark_join(cursor->mark, orig); mark_destroy(orig); return MLE_ERR; } mark_move_by(cursor->mark, 1); mark_destroy(orig); return MLE_OK; } // Select by word-back int cursor_select_by_word_back(cursor_t *cursor, int use_srules) { if (mark_is_at_word_bound(cursor->mark, -1)) return MLE_ERR; cursor_toggle_anchor(cursor, use_srules); mark_move_prev_re(cursor->mark, MLE_RE_WORD_BACK, sizeof(MLE_RE_WORD_BACK)-1); return MLE_OK; } // Select by word-forward int cursor_select_by_word_forward(cursor_t *cursor, int use_srules) { if (mark_is_at_word_bound(cursor->mark, 1)) return MLE_ERR; cursor_toggle_anchor(cursor, use_srules); mark_move_next_re(cursor->mark, MLE_RE_WORD_FORWARD, sizeof(MLE_RE_WORD_FORWARD)-1); return MLE_OK; } // Select by string int cursor_select_by_string(cursor_t *cursor, int use_srules) { mark_t *orig; uint32_t qchar; char *qre1 = "(?mark, &orig); for (left_right = 0; left_right <= 1; left_right++) { mark_join(cursor->mark, orig); // left_right==0 initially look left for quote // left_right==1 initially look right for quote rv = (left_right == 0 ? mark_move_prev_re : mark_move_next_re) (cursor->mark, qre1, strlen(qre1)); if (rv != MLBUF_OK) continue; // Get quote char and make qre2 mark_get_char_after(cursor->mark, &qchar); snprintf(qre2, sizeof(qre2), "(?mark, 1); cursor_drop_anchor(cursor, use_srules); // left_right==0 look right for quote pair // left_right==1 look left for quote pair rv = (left_right == 0 ? mark_move_next_re : mark_move_prev_re) (cursor->anchor, qre2, strlen(qre2)); if (rv != MLBUF_OK) continue; // Selected! if (left_right == 1) mark_move_by(cursor->anchor, 1); mark_destroy(orig); return MLE_OK; } cursor_lift_anchor(cursor); mark_join(cursor->mark, orig); mark_destroy(orig); return MLE_ERR; } // Select by word int cursor_select_by_word(cursor_t *cursor, int use_srules) { uint32_t after; if (mark_is_at_eol(cursor->mark)) return MLE_ERR; mark_get_char_after(cursor->mark, &after); if (!isalnum((char)after) && (char)after != '_') return MLE_ERR; if (!mark_is_at_word_bound(cursor->mark, -1)) { mark_move_prev_re(cursor->mark, MLE_RE_WORD_BACK, sizeof(MLE_RE_WORD_BACK)-1); } cursor_toggle_anchor(cursor, use_srules); mark_move_next_re(cursor->mark, MLE_RE_WORD_FORWARD, sizeof(MLE_RE_WORD_FORWARD)-1); return MLE_OK; } // Cut or copy text int cursor_cut_copy(cursor_t *cursor, int is_cut, int use_srules, int append) { char *cutbuf; bint_t cutbuf_len; bint_t cur_len; if (!append && cursor->cut_buffer) { free(cursor->cut_buffer); cursor->cut_buffer = NULL; } if (!cursor->is_anchored) { use_srules = 0; cursor_toggle_anchor(cursor, use_srules); mark_move_bol(cursor->mark); mark_move_eol(cursor->anchor); if (!cursor->is_block) mark_move_by(cursor->anchor, 1); } if (cursor->is_block) { mark_block_get_between(cursor->mark, cursor->anchor, &cutbuf, &cutbuf_len); } else { mark_get_between(cursor->mark, cursor->anchor, &cutbuf, &cutbuf_len); } if (append && cursor->cut_buffer) { cur_len = strlen(cursor->cut_buffer); cursor->cut_buffer = realloc(cursor->cut_buffer, cur_len + cutbuf_len + 1); strncat(cursor->cut_buffer, cutbuf, cutbuf_len); free(cutbuf); } else { cursor->cut_buffer = cutbuf; } if (cursor->bview->editor->cut_buffer) free(cursor->bview->editor->cut_buffer); cursor->bview->editor->cut_buffer = strdup(cursor->cut_buffer); if (is_cut) { if (cursor->is_block) { mark_block_delete_between(cursor->mark, cursor->anchor); } else { mark_delete_between(cursor->mark, cursor->anchor); } } cursor_toggle_anchor(cursor, use_srules); return MLE_OK; } // Uncut from cursor cut_buffer int cursor_uncut(cursor_t *cursor) { return cursor_uncut_ex(cursor, 0); } // Uncut editor-wide cut_buffer int cursor_uncut_last(cursor_t *cursor) { return cursor_uncut_ex(cursor, 1); } // Regex search and replace int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt_replacement) { char *regex; char *replacement; int wrapped; int all; char *yn; mark_t *lo_mark; mark_t *hi_mark; mark_t *orig_mark; mark_t *search_mark; mark_t *search_mark_end; int anchored_before; srule_t *highlight; bline_t *bline; bint_t col; bint_t char_count; bint_t orig_viewport_y; int pcre_rc; PCRE2_SIZE pcre_ovector[30]; str_t repl_backref = {0}; int num_replacements; if (!interactive && (!opt_regex || !opt_replacement)) { return MLE_ERR; } regex = NULL; replacement = NULL; wrapped = 0; lo_mark = NULL; hi_mark = NULL; orig_mark = NULL; search_mark = NULL; search_mark_end = NULL; anchored_before = 0; all = interactive ? 0 : 1; num_replacements = 0; mark_set_pcre_capture(&pcre_rc, pcre_ovector, 30); orig_viewport_y = -1; do { if (!interactive) { regex = strdup(opt_regex); replacement = strdup(opt_replacement); } else { editor_prompt(cursor->bview->editor, "replace: Search regex?", NULL, ®ex); if (!regex) break; editor_prompt(cursor->bview->editor, "replace: Replacement string?", NULL, &replacement); if (!replacement) break; } orig_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); lo_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); hi_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); search_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); search_mark_end = buffer_add_mark(cursor->bview->buffer, NULL, 0); mark_join(search_mark, cursor->mark); mark_join(orig_mark, cursor->mark); orig_viewport_y = cursor->bview->viewport_y; orig_mark->lefty = 1; // lefty==1 avoids moving when text is inserted at mark lo_mark->lefty = 1; if (cursor->is_anchored) { anchored_before = mark_is_gt(cursor->mark, cursor->anchor); mark_join(lo_mark, !anchored_before ? cursor->mark : cursor->anchor); mark_join(hi_mark, anchored_before ? cursor->mark : cursor->anchor); } else { mark_move_beginning(lo_mark); mark_move_end(hi_mark); } while (1) { pcre_rc = 0; if (mark_find_next_re(search_mark, regex, strlen(regex), &bline, &col, &char_count) == MLBUF_OK && (mark_move_to(search_mark, bline->line_index, col) == MLBUF_OK) && (mark_is_gte(search_mark, lo_mark)) && (mark_is_lt(search_mark, hi_mark)) && (!wrapped || mark_is_lt(search_mark, orig_mark)) ) { mark_move_to(search_mark_end, bline->line_index, col + char_count); mark_join(cursor->mark, search_mark); yn = NULL; if (all) { yn = MLE_PROMPT_YES; } else if (interactive) { highlight = srule_new_range(search_mark, search_mark_end, 0, TB_REVERSE); buffer_add_srule(cursor->bview->buffer, highlight); bview_rectify_viewport(cursor->bview); bview_draw(cursor->bview); editor_prompt(cursor->bview->editor, "replace: OK to replace? (y=yes, n=no, a=all, C-c=stop)", &(editor_prompt_params_t) { .kmap = cursor->bview->editor->kmap_prompt_yna }, &yn ); buffer_remove_srule(cursor->bview->buffer, highlight); srule_destroy(highlight); bview_draw(cursor->bview); } if (!yn) { break; } else if (0 == strcmp(yn, MLE_PROMPT_YES) || 0 == strcmp(yn, MLE_PROMPT_ALL)) { str_append_replace_with_backrefs(&repl_backref, search_mark->bline->data, replacement, pcre_rc, pcre_ovector, 30); mark_replace_between(search_mark, search_mark_end, repl_backref.data, repl_backref.len); str_free(&repl_backref); num_replacements += 1; if (0 == strcmp(yn, MLE_PROMPT_ALL)) all = 1; } else { mark_move_by(search_mark, 1); } } else if (!wrapped) { mark_join(search_mark, lo_mark); wrapped = 1; } else { break; } } } while(0); if (cursor->is_anchored && lo_mark && hi_mark) { mark_join(cursor->mark, anchored_before ? hi_mark : lo_mark); mark_join(cursor->anchor, anchored_before ? lo_mark : hi_mark); } else if (orig_mark) { mark_join(cursor->mark, orig_mark); } mark_set_pcre_capture(NULL, NULL, 0); if (regex) free(regex); if (replacement) free(replacement); if (lo_mark) mark_destroy(lo_mark); if (hi_mark) mark_destroy(hi_mark); if (orig_mark) mark_destroy(orig_mark); if (search_mark) mark_destroy(search_mark); if (search_mark_end) mark_destroy(search_mark_end); if (interactive) { MLE_SET_INFO(cursor->bview->editor, "replace: Replaced %d instance(s)", num_replacements); if (orig_viewport_y >= 0) { bview_set_viewport_y(cursor->bview, orig_viewport_y, 1); } else { bview_rectify_viewport(cursor->bview); } bview_draw(cursor->bview); } return MLE_OK; } // Uncut (paste) text static int cursor_uncut_ex(cursor_t *cursor, int editor_wide) { char *cut_buffer; cut_buffer = cursor->cut_buffer && !editor_wide ? cursor->cut_buffer : cursor->bview->editor->cut_buffer; if (!cut_buffer) return MLE_ERR; if (cursor->is_block) { mark_block_insert_before(cursor->mark, cut_buffer, strlen(cut_buffer)); } else { mark_insert_before(cursor->mark, cut_buffer, strlen(cut_buffer)); } return MLE_OK; } mle-1.7.2/editor.c000066400000000000000000003141351443351614700137470ustar00rootroot00000000000000#include #include #include #include #include #include #define TB_IMPL #include "termbox2.h" #undef TB_IMPL #include "mle.h" #include "mlbuf.h" static int _editor_set_macro_toggle_key(editor_t *editor, char *key); static int _editor_bview_exists(editor_t *editor, bview_t *bview); static int _editor_register_cmd_fn(editor_t *editor, char *name, int (*func)(cmd_context_t *ctx)); static int _editor_should_skip_rc(char **argv); static int _editor_close_bview_inner(editor_t *editor, bview_t *bview, int *optret_num_closed); static int _editor_destroy_cmd(editor_t *editor, cmd_t *cmd); static int _editor_prompt_input_submit(cmd_context_t *ctx); static int _editor_prompt_input_complete(cmd_context_t *ctx); static prompt_history_t *_editor_prompt_find_or_add_history(cmd_context_t *ctx, prompt_hnode_t **optret_prompt_hnode); static int _editor_prompt_history_up(cmd_context_t *ctx); static int _editor_prompt_history_down(cmd_context_t *ctx); static int _editor_prompt_history_append(cmd_context_t *ctx, char *data); static int _editor_prompt_yna_all(cmd_context_t *ctx); static int _editor_prompt_yn_yes(cmd_context_t *ctx); static int _editor_prompt_yn_no(cmd_context_t *ctx); static int _editor_prompt_cancel(cmd_context_t *ctx); static int _editor_menu_submit(cmd_context_t *ctx); static int _editor_menu_cancel(cmd_context_t *ctx); static int _editor_prompt_isearch_next(cmd_context_t *ctx); static int _editor_prompt_isearch_prev(cmd_context_t *ctx); static int _editor_prompt_isearch_viewport_up(cmd_context_t *ctx); static int _editor_prompt_isearch_viewport_down(cmd_context_t *ctx); static int _editor_prompt_isearch_drop_cursors(cmd_context_t *ctx); static void _editor_loop(editor_t *editor, loop_context_t *loop_ctx); static int _editor_debug_key_input(); static void _editor_refresh_cmd_context(editor_t *editor, cmd_context_t *cmd_ctx); static void _editor_notify_cmd_observers(cmd_context_t *ctx, int is_before); static int _editor_maybe_toggle_macro(editor_t *editor, kinput_t *input); static void _editor_resize(editor_t *editor, int w, int h); static void _editor_maybe_lift_temp_anchors(cmd_context_t *ctx); static void _editor_draw_cursors(editor_t *editor, bview_t *bview); static void _editor_get_user_input(editor_t *editor, cmd_context_t *ctx); static void _editor_ingest_paste(editor_t *editor, cmd_context_t *ctx); static void _editor_handle_mouse(editor_t *editor, tb_event_t *ev); static void _editor_append_pastebuf(editor_t *editor, cmd_context_t *ctx, kinput_t *input); static void _editor_record_macro_input(kmacro_t *macro, kinput_t *input); static cmd_t *_editor_get_command(editor_t *editor, cmd_context_t *ctx, kinput_t *opt_paste_input); static kbinding_t *_editor_get_kbinding_node(kbinding_t *node, kinput_t *input, cmd_context_t *ctx, int is_paste, int *ret_is_numeric); static cmd_t *_editor_resolve_cmd(editor_t *editor, cmd_t **rcmd, char *cmd_name); static int _editor_key_to_input(char *key, kinput_t *ret_input); static int _editor_event_to_key(struct tb_event *ev, char *ret_key); static void _editor_init_signal_handlers(editor_t *editor); static void _editor_continue(int signum); static void _editor_graceful_exit(int signum); static void _editor_register_cmds(editor_t *editor); static void _editor_init_kmaps(editor_t *editor); static void _editor_init_kmap(editor_t *editor, kmap_t **ret_kmap, char *name, char *default_cmd_name, int allow_fallthru, kbinding_def_t *defs); static void _editor_init_kmap_add_binding(editor_t *editor, kmap_t *kmap, kbinding_def_t *binding_def); static int _editor_init_kmap_add_binding_to_trie(kbinding_t **trie, char *cmd_name, char *cur_key_patt, char *full_key_patt, char *static_param); static int _editor_init_kmap_by_str(editor_t *editor, kmap_t **ret_kmap, char *str); static int _editor_init_kmap_add_binding_by_str(editor_t *editor, kmap_t *kmap, char *str); static void _editor_destroy_kmap(kmap_t *kmap, kbinding_t *trie); static int _editor_add_macro_by_str(editor_t *editor, char *str); static void _editor_init_syntaxes(editor_t *editor); static void _editor_init_syntax(editor_t *editor, syntax_t **optret_syntax, char *name, char *path_pattern, int tab_width, int tab_to_space, srule_def_t *defs); static int _editor_init_syntax_by_str(editor_t *editor, syntax_t **ret_syntax, char *str); static void _editor_init_syntax_add_rule(syntax_t *syntax, srule_def_t *def); static int _editor_init_syntax_add_rule_by_str(syntax_t *syntax, char *str); static void _editor_destroy_syntax_map(syntax_t *map); static int _editor_init_from_rc_read(editor_t *editor, FILE *rc, char **ret_rc_data, size_t *ret_rc_data_len); static int _editor_init_from_rc_exec(editor_t *editor, char *rc_path, char **ret_rc_data, size_t *ret_rc_data_len); static int _editor_init_from_rc(editor_t *editor, FILE *rc, char *rc_path); static int _editor_init_from_args(editor_t *editor, int argc, char **argv); static void _editor_init_status(editor_t *editor); static void _editor_init_bviews(editor_t *editor, int argc, char **argv); static int _editor_init_headless_mode(editor_t *editor); static int _editor_init_startup_macro(editor_t *editor); static int _editor_init_term(editor_t *editor); static void _editor_display_keys(editor_t *editor); static void _editor_remember_keys(cmd_context_t *ctx); // Init editor from args int editor_init(editor_t *editor, int argc, char **argv) { int rv; FILE *rc; char *home_rc; rv = MLE_OK; do { // Create a resuable PCRE2 match data block. This is hacky / lazy. PCRE // is used all over the place, even in places where there's no easy way // to get at a shared state, e.g., mark.c. Also feels wasteful to keep // reallocating this thing, so let's just create one with 10 match // slots which is the most we ever use. Free in editor_deinit. pcre2_md = pcre2_match_data_create(10, NULL); // Set editor defaults editor->is_in_init = 1; editor->tab_width = MLE_DEFAULT_TAB_WIDTH; editor->tab_to_space = MLE_DEFAULT_TAB_TO_SPACE; editor->trim_paste = MLE_DEFAULT_TRIM_PASTE; editor->auto_indent = MLE_DEFAULT_AUTO_INDENT; editor->highlight_bracket_pairs = MLE_DEFAULT_HILI_BRACKET_PAIRS; editor->read_rc_file = MLE_DEFAULT_READ_RC_FILE; editor->soft_wrap = MLE_DEFAULT_SOFT_WRAP; editor->coarse_undo = MLE_DEFAULT_COARSE_UNDO; editor->viewport_scope_x = -4; editor->viewport_scope_y = -1; editor->color_col = -1; editor->exit_code = EXIT_SUCCESS; editor->headless_mode = isatty(STDIN_FILENO) == 0 ? 1 : 0; _editor_set_macro_toggle_key(editor, MLE_DEFAULT_MACRO_TOGGLE_KEY); // Init signal handlers _editor_init_signal_handlers(editor); // Register commands _editor_register_cmds(editor); // Init kmaps _editor_init_kmaps(editor); // Init syntaxes _editor_init_syntaxes(editor); // Parse rc files if (!_editor_should_skip_rc(argv)) { home_rc = NULL; if (getenv("HOME")) { asprintf(&home_rc, "%s/%s", getenv("HOME"), ".mlerc"); if (util_is_file(home_rc, "rb", &rc)) { rv = _editor_init_from_rc(editor, rc, home_rc); fclose(rc); } free(home_rc); } if (rv != MLE_OK) break; if (util_is_file("/etc/mlerc", "rb", &rc)) { rv = _editor_init_from_rc(editor, rc, "/etc/mlerc"); fclose(rc); } if (rv != MLE_OK) break; } // Parse cli args rv = _editor_init_from_args(editor, argc, argv); if (rv != MLE_OK) break; // Init status bar _editor_init_status(editor); // Init bviews _editor_init_bviews(editor, argc, argv); // Init startup macro _editor_init_headless_mode(editor); // Init headless mode _editor_init_startup_macro(editor); // Init terminal _editor_init_term(editor); } while(0); editor->is_in_init = 0; return rv; } // Run editor int editor_run(editor_t *editor) { loop_context_t loop_ctx; memset(&loop_ctx, 0, sizeof(loop_context_t)); _editor_resize(editor, -1, -1); if (editor->debug_key_input) { return _editor_debug_key_input(); } _editor_loop(editor, &loop_ctx); if (editor->headless_mode && editor->active_edit) { buffer_write_to_fd(editor->active_edit->buffer, STDOUT_FILENO, NULL); } if (editor->debug_dump_state_on_exit) { editor_debug_dump(editor, stderr); } return MLE_OK; } // Deinit editor int editor_deinit(editor_t *editor) { bview_t *bview; bview_t *bview_tmp1; bview_t *bview_tmp2; kmap_t *kmap; kmap_t *kmap_tmp; kmacro_t *macro; kmacro_t *macro_tmp; cmd_t *cmd; cmd_t *cmd_tmp; prompt_history_t *prompt_history; prompt_history_t *prompt_history_tmp; prompt_hnode_t *prompt_hnode; prompt_hnode_t *prompt_hnode_tmp1; prompt_hnode_t *prompt_hnode_tmp2; observer_t *observer; observer_t *observer_tmp; if (editor->status) bview_destroy(editor->status); CDL_FOREACH_SAFE2(editor->all_bviews, bview, bview_tmp1, bview_tmp2, all_prev, all_next) { CDL_DELETE2(editor->all_bviews, bview, all_prev, all_next); bview_destroy(bview); } HASH_ITER(hh, editor->kmap_map, kmap, kmap_tmp) { HASH_DEL(editor->kmap_map, kmap); _editor_destroy_kmap(kmap, kmap->bindings->children); if (kmap->default_cmd_name) free(kmap->default_cmd_name); free(kmap->bindings); free(kmap->name); free(kmap); } HASH_ITER(hh, editor->macro_map, macro, macro_tmp) { HASH_DEL(editor->macro_map, macro); if (macro->inputs) free(macro->inputs); if (macro->name) free(macro->name); free(macro); } HASH_ITER(hh, editor->cmd_map, cmd, cmd_tmp) { HASH_DEL(editor->cmd_map, cmd); _editor_destroy_cmd(editor, cmd); } HASH_ITER(hh, editor->prompt_history, prompt_history, prompt_history_tmp) { HASH_DEL(editor->prompt_history, prompt_history); free(prompt_history->prompt_str); CDL_FOREACH_SAFE(prompt_history->prompt_hlist, prompt_hnode, prompt_hnode_tmp1, prompt_hnode_tmp2) { CDL_DELETE(prompt_history->prompt_hlist, prompt_hnode); free(prompt_hnode->data); free(prompt_hnode); } free(prompt_history); } DL_FOREACH_SAFE(editor->observers, observer, observer_tmp) { editor_destroy_observer(editor, observer); } if (editor->macro_record) { if (editor->macro_record->inputs) free(editor->macro_record->inputs); free(editor->macro_record); } _editor_destroy_syntax_map(editor->syntax_map); if (editor->kmap_init_name) free(editor->kmap_init_name); if (editor->insertbuf) free(editor->insertbuf); if (editor->cut_buffer) free(editor->cut_buffer); if (editor->ttyfd) close(editor->ttyfd); if (editor->startup_macro_name) free(editor->startup_macro_name); pcre2_match_data_free(pcre2_md); if (!editor->headless_mode) { tb_shutdown(); } return MLE_OK; } // Prompt user for input int editor_prompt(editor_t *editor, char *prompt, editor_prompt_params_t *params, char **optret_answer) { bview_t *bview_tmp; loop_context_t loop_ctx; memset(&loop_ctx, 0, sizeof(loop_context_t)); // Disallow nested prompts if (editor->prompt) { if (optret_answer) *optret_answer = NULL; return MLE_ERR; } // Init loop_ctx loop_ctx.invoker = editor->active; loop_ctx.should_exit = 0; loop_ctx.prompt_answer = NULL; // Init prompt editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_PROMPT, NULL, 0, 1, 0, 0, NULL, &editor->prompt); if (params && params->prompt_cb) bview_add_listener(editor->prompt, params->prompt_cb, params->prompt_cb_udata); editor->prompt->prompt_str = prompt; bview_push_kmap(editor->prompt, params && params->kmap ? params->kmap : editor->kmap_prompt_input); // Insert data if present if (params && params->data && params->data_len > 0) { buffer_insert(editor->prompt->buffer, 0, params->data, params->data_len, NULL); mark_move_eol(editor->prompt->active_cursor->mark); } // Loop inside prompt _editor_loop(editor, &loop_ctx); // Set answer if (optret_answer) { *optret_answer = loop_ctx.prompt_answer; } else if (loop_ctx.prompt_answer) { free(loop_ctx.prompt_answer); loop_ctx.prompt_answer = NULL; } // Restore previous focus bview_tmp = editor->prompt; editor->prompt = NULL; editor_close_bview(editor, bview_tmp, NULL); editor_set_active(editor, loop_ctx.invoker); return MLE_OK; } // Open dialog menu int editor_menu(editor_t *editor, cmd_func_t callback, char *opt_buf_data, int opt_buf_data_len, aproc_t *opt_aproc, bview_t **optret_menu) { bview_t *menu; editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, &menu); menu->is_menu = 1; menu->soft_wrap = 0; menu->menu_callback = callback; bview_push_kmap(menu, editor->kmap_menu); if (opt_aproc) { aproc_set_owner(opt_aproc, menu, &(menu->aproc)); } if (opt_buf_data) { mark_insert_before(menu->active_cursor->mark, opt_buf_data, opt_buf_data_len); } if (optret_menu) *optret_menu = menu; return MLE_OK; } // Open a bview int editor_open_bview(editor_t *editor, bview_t *opt_parent, int type, char *opt_path, int opt_path_len, int make_active, bint_t linenum, int skip_resize, buffer_t *opt_buffer, bview_t **optret_bview) { bview_t *bview; bview_rect_t *rect; int found; found = 0; // Check if already open and not dirty if (opt_path) { CDL_FOREACH2(editor->all_bviews, bview, all_next) { if (bview->buffer && !bview->buffer->is_unsaved && bview->buffer->path && strlen(bview->buffer->path) == (size_t)opt_path_len && strncmp(opt_path, bview->buffer->path, opt_path_len) == 0 ) { found = 1; break; } } } // Make new bview if not already open if (!found) { bview = bview_new(editor, type, opt_path, opt_path_len, opt_buffer); CDL_APPEND2(editor->all_bviews, bview, all_prev, all_next); if (!opt_parent) { DL_APPEND2(editor->top_bviews, bview, top_prev, top_next); } else { opt_parent->split_child = bview; } } if (make_active) { editor_set_active(editor, bview); } if (!found && !editor->is_in_init && !skip_resize) { switch (type) { case MLE_BVIEW_TYPE_STATUS: rect = &editor->rect_status; break; case MLE_BVIEW_TYPE_PROMPT: rect = &editor->rect_prompt; break; default: case MLE_BVIEW_TYPE_EDIT: rect = &editor->rect_edit; break; } bview_resize(bview, rect->x, rect->y, rect->w, rect->h); } if (linenum > 0) { mark_move_to(bview->active_cursor->mark, linenum - 1, 0); bview_center_viewport_y(bview); } if (optret_bview) { *optret_bview = bview; } if (!found && opt_path && util_is_dir(opt_path)) { // TODO This is hacky cmd_context_t ctx; memset(&ctx, 0, sizeof(cmd_context_t)); ctx.editor = editor; ctx.static_param = opt_path; ctx.bview = bview; cmd_browse(&ctx); editor_close_bview(editor, bview, NULL); } return MLE_OK; } // Close a bview int editor_close_bview(editor_t *editor, bview_t *bview, int *optret_num_closed) { int rc; if (optret_num_closed) *optret_num_closed = 0; if ((rc = _editor_close_bview_inner(editor, bview, optret_num_closed)) == MLE_OK) { _editor_resize(editor, editor->w, editor->h); } return rc; } // Set the active bview int editor_set_active(editor_t *editor, bview_t *bview) { if (!_editor_bview_exists(editor, bview)) { MLE_RETURN_ERR(editor, "No bview %p in editor->all_bviews", (void*)bview); } else if (editor->prompt && editor->prompt != bview) { MLE_RETURN_ERR(editor, "Cannot abandon prompt for bview %p", (void*)bview); } editor->active = bview; if (MLE_BVIEW_IS_EDIT(bview)) { editor->active_edit_last = editor->active_edit; editor->active_edit = bview; editor->active_edit_root = bview_get_split_root(bview); } bview_rectify_viewport(bview); return MLE_OK; } // Print debug info int editor_debug_dump(editor_t *editor, FILE *fp) { bview_t *bview; cursor_t *cursor; buffer_t *buffer; bline_t *bline; bint_t c; int bview_index; int cursor_index; bview_index = 0; CDL_FOREACH2(editor->all_bviews, bview, all_next) { if (!MLE_BVIEW_IS_EDIT(bview)) continue; cursor_index = 0; DL_FOREACH(bview->cursors, cursor) { fprintf(fp, "bview.%d.cursor.%d.mark.line_index=%" PRIdMAX "\n", bview_index, cursor_index, cursor->mark->bline->line_index); fprintf(fp, "bview.%d.cursor.%d.mark.col=%" PRIdMAX "\n", bview_index, cursor_index, cursor->mark->col); if (cursor->is_anchored) { fprintf(fp, "bview.%d.cursor.%d.anchor.line_index=%" PRIdMAX "\n", bview_index, cursor_index, cursor->anchor->bline->line_index); fprintf(fp, "bview.%d.cursor.%d.anchor.col=%" PRIdMAX "\n", bview_index, cursor_index, cursor->anchor->col); } fprintf(fp, "bview.%d.cursor.%d.sel_rule=%c\n", bview_index, cursor_index, cursor->sel_rule ? 'y' : 'n'); cursor_index += 1; } fprintf(fp, "bview.%d.cursor_count=%d\n", bview_index, cursor_index); buffer = bview->buffer; fprintf(fp, "bview.%d.buffer.byte_count=%" PRIdMAX "\n", bview_index, buffer->byte_count); fprintf(fp, "bview.%d.buffer.line_count=%" PRIdMAX "\n", bview_index, buffer->line_count); fprintf(fp, "bview.%d.buffer.path=%s\n", bview_index, buffer->path ? buffer->path : ""); for (bline = buffer->first_line; bline != NULL; bline = bline->next) { MLBUF_BLINE_ENSURE_CHARS(bline); fprintf(fp, "bview.%d.buffer.blines.%" PRIdMAX ".chars=", bview_index, bline->line_index); for (c = 0; c < bline->char_count; ++c) { fprintf(fp, "", bline->chars[c].ch, bline->chars[c].style.fg, bline->chars[c].style.bg ); } fprintf(fp, "\n"); } fprintf(fp, "bview.%d.buffer.blines_end\n", bview_index); fprintf(fp, "bview.%d.buffer.data_begin\n", bview_index); buffer_write_to_file(buffer, fp, NULL); fprintf(fp, "\nbview.%d.buffer.data_end\n", bview_index); bview_index += 1; } fprintf(fp, "bview_count=%d\n", bview_index); return MLE_OK; } // Invoke tb_select_input_mode appropriately int editor_set_input_mode(editor_t *editor) { int input_mode; if (editor->headless_mode) { return MLE_OK; } input_mode = TB_INPUT_ALT; if (editor->mouse_support) { input_mode |= TB_INPUT_MOUSE; } tb_select_input_mode(input_mode); return MLE_OK; } // Set macro toggle key static int _editor_set_macro_toggle_key(editor_t *editor, char *key) { return _editor_key_to_input(key, &editor->macro_toggle_key); } // Return 1 if bview exists in editor, else return 0 static int _editor_bview_exists(editor_t *editor, bview_t *bview) { bview_t *tmp; CDL_FOREACH2(editor->all_bviews, tmp, all_next) { if (tmp == bview) return 1; } return 0; } // Return number of EDIT bviews open int editor_bview_edit_count(editor_t *editor) { int count; bview_t *bview; count = 0; CDL_FOREACH2(editor->all_bviews, bview, all_next) { if (MLE_BVIEW_IS_EDIT(bview)) count += 1; } return count; } // Return number of bviews displaying buffer int editor_count_bviews_by_buffer(editor_t *editor, buffer_t *buffer) { int count; bview_t *bview; count = 0; CDL_FOREACH2(editor->all_bviews, bview, all_next) { if (bview->buffer == buffer) count += 1; } return count; } // Register a command static int _editor_register_cmd_fn(editor_t *editor, char *name, int (*func)(cmd_context_t *ctx)) { cmd_t cmd = {0}; cmd.name = name; cmd.func = func; return editor_register_cmd(editor, &cmd); } // Register a command (extended) int editor_register_cmd(editor_t *editor, cmd_t *cmd) { cmd_t *existing_cmd; cmd_t *new_cmd; HASH_FIND_STR(editor->cmd_map, cmd->name, existing_cmd); if (existing_cmd) return MLE_ERR; new_cmd = calloc(1, sizeof(cmd_t)); *new_cmd = *cmd; new_cmd->name = strdup(new_cmd->name); HASH_ADD_KEYPTR(hh, editor->cmd_map, new_cmd->name, strlen(new_cmd->name), new_cmd); return MLE_OK; } // Get input from either macro or user int editor_get_input(editor_t *editor, loop_context_t *loop_ctx, cmd_context_t *ctx) { ctx->is_user_input = 0; if (editor->macro_apply && editor->macro_apply_input_index < editor->macro_apply->inputs_len ) { // Get input from macro MLE_KINPUT_COPY(ctx->input, editor->macro_apply->inputs[editor->macro_apply_input_index]); editor->macro_apply_input_index += 1; } else { // Get input from user if (editor->macro_apply) { // Clear macro if present editor->macro_apply = NULL; editor->macro_apply_input_index = 0; } if (editor->headless_mode) { // Bail if in headless mode loop_ctx->should_exit = 1; return MLE_ERR; } else { // Get input from user _editor_get_user_input(editor, ctx); ctx->is_user_input = 1; } } if (editor->is_recording_macro && editor->macro_record) { // Record macro input _editor_record_macro_input(editor->macro_record, &ctx->input); } // Remember keys for display if (editor->debug_display_keys) _editor_remember_keys(ctx); return MLE_OK; } // Display the editor int editor_display(editor_t *editor) { bview_t *bview; if (editor->headless_mode) return MLE_OK; tb_clear(); bview_draw(editor->active_edit_root); bview_draw(editor->status); if (editor->prompt) bview_draw(editor->prompt); DL_FOREACH2(editor->top_bviews, bview, top_next) { _editor_draw_cursors(editor, bview); } if (editor->debug_display_keys) _editor_display_keys(editor); tb_present(); return MLE_OK; } // Register a cmd observer int editor_register_observer(editor_t *editor, char *event_patt, void *udata, observer_func_t fn_callback, observer_t **optret_observer) { observer_t *observer; observer = calloc(1, sizeof(observer_t)); observer->event_patt = strdup(event_patt); observer->callback = fn_callback; observer->udata = udata; DL_APPEND(editor->observers, observer); if (optret_observer) *optret_observer = observer; return MLE_OK; } // Register a cmd observer int editor_destroy_observer(editor_t *editor, observer_t *observer) { DL_DELETE(editor->observers, observer); free(observer->event_patt); free(observer); return MLE_OK; } // Return 1 if we should skip reading rc files static int _editor_should_skip_rc(char **argv) { int skip = 0; while (*argv) { if (strcmp("-h", *argv) == 0 || strcmp("-N", *argv) == 0) { skip = 1; break; } argv++; } return skip; } // Close a bview static int _editor_close_bview_inner(editor_t *editor, bview_t *bview, int *optret_num_closed) { if (!_editor_bview_exists(editor, bview)) { MLE_RETURN_ERR(editor, "No bview %p in editor->all_bviews", (void*)bview); } if (bview->split_child) { _editor_close_bview_inner(editor, bview->split_child, optret_num_closed); } if (bview->split_parent) { bview->split_parent->split_child = NULL; editor_set_active(editor, bview->split_parent); } else if (bview == editor->active_edit) { if (bview->all_prev && bview->all_prev != bview && MLE_BVIEW_IS_EDIT(bview->all_prev)) { editor_set_active(editor, bview->all_prev); } else if (bview->all_next && bview->all_next != bview && MLE_BVIEW_IS_EDIT(bview->all_next)) { editor_set_active(editor, bview->all_next); } else { editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, NULL); } } if (!bview->split_parent) { DL_DELETE2(editor->top_bviews, bview, top_prev, top_next); } CDL_DELETE2(editor->all_bviews, bview, all_prev, all_next); bview_destroy(bview); if (optret_num_closed) *optret_num_closed += 1; return MLE_OK; } // Destroy a command static int _editor_destroy_cmd(editor_t *editor, cmd_t *cmd) { (void)editor; free(cmd->name); free(cmd); return MLE_OK; } // Invoked when user hits enter in a prompt_input static int _editor_prompt_input_submit(cmd_context_t *ctx) { bint_t answer_len; char *answer; buffer_get(ctx->bview->buffer, &answer, &answer_len); ctx->loop_ctx->prompt_answer = strndup(answer, answer_len); _editor_prompt_history_append(ctx, ctx->loop_ctx->prompt_answer); ctx->loop_ctx->should_exit = 1; return MLE_OK; } // Invoke when user hits tab in a prompt_input static int _editor_prompt_input_complete(cmd_context_t *ctx) { loop_context_t *loop_ctx; char *cmd; char *cmd_arg; char *terms; size_t terms_len; int num_terms; char *term; int term_index; loop_ctx = ctx->loop_ctx; // Update tab_complete_term and tab_complete_index if (loop_ctx->last_cmd_ctx.cmd && loop_ctx->last_cmd_ctx.cmd->func == _editor_prompt_input_complete) { loop_ctx->tab_complete_index += 1; } else if (ctx->bview->buffer->first_line->data_len < MLE_LOOP_CTX_MAX_COMPLETE_TERM_SIZE) { snprintf( loop_ctx->tab_complete_term, MLE_LOOP_CTX_MAX_COMPLETE_TERM_SIZE, "%.*s", (int)ctx->bview->buffer->first_line->data_len, ctx->bview->buffer->first_line->data ); loop_ctx->tab_complete_index = 0; } else { return MLE_OK; } // Assemble compgen command cmd_arg = util_escape_shell_arg( loop_ctx->tab_complete_term, strlen(loop_ctx->tab_complete_term) ); asprintf(&cmd, "compgen -f %s 2>/dev/null | sort", cmd_arg); // Run compgen command terms = NULL; terms_len = 0; util_shell_exec(ctx->editor, cmd, 1, NULL, 0, 0, "bash", &terms, &terms_len, NULL); free(cmd); free(cmd_arg); // Get number of terms // TODO valgrind thinks there's an error here num_terms = 0; term = strchr(terms, '\n'); while (term) { num_terms += 1; term = strchr(term + 1, '\n'); } // Bail if no terms if (num_terms < 1) { free(terms); return MLE_OK; } // Determine term index term_index = loop_ctx->tab_complete_index % num_terms; // Set prompt input to term term = strtok(terms, "\n"); while (term != NULL) { if (term_index == 0) { buffer_set(ctx->bview->buffer, term, strlen(term)); mark_move_eol(ctx->cursor->mark); break; } else { term_index -= 1; } term = strtok(NULL, "\n"); } free(terms); return MLE_OK; } // Find or add a prompt history entry for the current prompt static prompt_history_t *_editor_prompt_find_or_add_history(cmd_context_t *ctx, prompt_hnode_t **optret_prompt_hnode) { prompt_history_t *prompt_history; HASH_FIND_STR(ctx->editor->prompt_history, ctx->bview->prompt_str, prompt_history); if (!prompt_history) { prompt_history = calloc(1, sizeof(prompt_history_t)); prompt_history->prompt_str = strdup(ctx->bview->prompt_str); HASH_ADD_KEYPTR(hh, ctx->editor->prompt_history, prompt_history->prompt_str, strlen(prompt_history->prompt_str), prompt_history); } if (!ctx->loop_ctx->prompt_hnode) { ctx->loop_ctx->prompt_hnode = prompt_history->prompt_hlist ? prompt_history->prompt_hlist->prev : NULL; } if (optret_prompt_hnode) { *optret_prompt_hnode = ctx->loop_ctx->prompt_hnode; } return prompt_history; } // Prompt history up static int _editor_prompt_history_up(cmd_context_t *ctx) { prompt_hnode_t *prompt_hnode; _editor_prompt_find_or_add_history(ctx, &prompt_hnode); if (prompt_hnode) { ctx->loop_ctx->prompt_hnode = prompt_hnode->prev; buffer_set(ctx->buffer, prompt_hnode->data, prompt_hnode->data_len); } return MLE_OK; } // Prompt history down static int _editor_prompt_history_down(cmd_context_t *ctx) { prompt_hnode_t *prompt_hnode; _editor_prompt_find_or_add_history(ctx, &prompt_hnode); if (prompt_hnode) { ctx->loop_ctx->prompt_hnode = prompt_hnode->next; buffer_set(ctx->buffer, prompt_hnode->data, prompt_hnode->data_len); } return MLE_OK; } // Prompt history append static int _editor_prompt_history_append(cmd_context_t *ctx, char *data) { prompt_history_t *prompt_history; prompt_hnode_t *prompt_hnode; prompt_history = _editor_prompt_find_or_add_history(ctx, NULL); prompt_hnode = calloc(1, sizeof(prompt_hnode_t)); prompt_hnode->data = strdup(data); prompt_hnode->data_len = (bint_t)strlen(data); CDL_APPEND(prompt_history->prompt_hlist, prompt_hnode); return MLE_OK; } // Invoked when user hits a in a prompt_yna static int _editor_prompt_yna_all(cmd_context_t *ctx) { ctx->loop_ctx->prompt_answer = MLE_PROMPT_ALL; ctx->loop_ctx->should_exit = 1; return MLE_OK; } // Invoked when user hits y in a prompt_yn(a) static int _editor_prompt_yn_yes(cmd_context_t *ctx) { ctx->loop_ctx->prompt_answer = MLE_PROMPT_YES; ctx->loop_ctx->should_exit = 1; return MLE_OK; } // Invoked when user hits n in a prompt_yn(a) static int _editor_prompt_yn_no(cmd_context_t *ctx) { ctx->loop_ctx->prompt_answer = MLE_PROMPT_NO; ctx->loop_ctx->should_exit = 1; return MLE_OK; } // Invoked when user cancels (Ctrl-C) a prompt_(input|yn), or hits any key in a prompt_ok static int _editor_prompt_cancel(cmd_context_t *ctx) { ctx->loop_ctx->prompt_answer = NULL; ctx->loop_ctx->should_exit = 1; return MLE_OK; } // Invoked when user hits enter in a menu static int _editor_menu_submit(cmd_context_t *ctx) { if (ctx->bview->menu_callback) return ctx->bview->menu_callback(ctx); return MLE_OK; } // Invoked when user hits C-c in a menu static int _editor_menu_cancel(cmd_context_t *ctx) { if (ctx->bview->aproc) aproc_destroy(ctx->bview->aproc, 1); return MLE_OK; } // Invoked when user hits down in a prompt_isearch static int _editor_prompt_isearch_next(cmd_context_t *ctx) { if (ctx->editor->active_edit->isearch_rule) { mark_move_next_cre_nudge(ctx->editor->active_edit->active_cursor->mark, ctx->editor->active_edit->isearch_rule->cre); bview_center_viewport_y(ctx->editor->active_edit); } return MLE_OK; } // Invoked when user hits up in a prompt_isearch static int _editor_prompt_isearch_prev(cmd_context_t *ctx) { if (ctx->editor->active_edit->isearch_rule) { mark_move_prev_cre(ctx->editor->active_edit->active_cursor->mark, ctx->editor->active_edit->isearch_rule->cre); bview_center_viewport_y(ctx->editor->active_edit); } return MLE_OK; } // Invoked when user hits up in a prompt_isearch static int _editor_prompt_isearch_viewport_up(cmd_context_t *ctx) { return bview_set_viewport_y(ctx->editor->active_edit, ctx->editor->active_edit->viewport_y - 5, 0); } // Invoked when user hits up in a prompt_isearch static int _editor_prompt_isearch_viewport_down(cmd_context_t *ctx) { return bview_set_viewport_y(ctx->editor->active_edit, ctx->editor->active_edit->viewport_y + 5, 0); } // Drops a cursor on each isearch match static int _editor_prompt_isearch_drop_cursors(cmd_context_t *ctx) { bview_t *bview; mark_t *mark; pcre2_code *cre; cursor_t *orig_cursor; cursor_t *last_cursor; bint_t nchars; bview = ctx->editor->active_edit; if (!bview->isearch_rule) return MLE_OK; orig_cursor = bview->active_cursor; mark = bview->active_cursor->mark; cre = bview->isearch_rule->cre; mark_move_beginning(mark); last_cursor = NULL; while (mark_move_next_cre_ex(mark, cre, NULL, NULL, &nchars) == MLBUF_OK) { bview_add_cursor(bview, mark->bline, mark->col, &last_cursor); mark_move_by(mark, MLE_MAX(1, nchars)); } if (last_cursor) { mark_join(orig_cursor->mark, last_cursor->mark); bview_remove_cursor(bview, last_cursor); } bview->active_cursor = orig_cursor; bview_center_viewport_y(bview); ctx->loop_ctx->prompt_answer = NULL; ctx->loop_ctx->should_exit = 1; return MLE_OK; } // Run editor loop static void _editor_loop(editor_t *editor, loop_context_t *loop_ctx) { cmd_t *cmd; cmd_context_t cmd_ctx; // Increment loop_depth editor->loop_depth += 1; // Init cmd_context memset(&cmd_ctx, 0, sizeof(cmd_context_t)); cmd_ctx.editor = editor; cmd_ctx.loop_ctx = loop_ctx; // Loop until editor should exit while (!loop_ctx->should_exit) { // Set loop_ctx editor->loop_ctx = loop_ctx; // Display editor if (!editor->is_display_disabled) { editor_display(editor); } // Bail if debug_exit_after_startup set if (editor->debug_exit_after_startup) { break; } // Check for async io // aproc_drain_all will bail and return 0 if there's any tty data if (editor->aprocs && aproc_drain_all(editor->aprocs, &editor->ttyfd)) { continue; } // Get input if (editor_get_input(editor, loop_ctx, &cmd_ctx) == MLE_ERR) { break; } // Toggle macro? if (_editor_maybe_toggle_macro(editor, &cmd_ctx.input)) { continue; } if ((cmd = _editor_get_command(editor, &cmd_ctx, NULL)) != NULL) { // Found cmd in kmap trie, now execute cmd_ctx.cmd = cmd; if (cmd_ctx.is_user_input && cmd->func == cmd_insert_data) { // Ingest user paste _editor_ingest_paste(editor, &cmd_ctx); } else if (cmd->func == cmd_repeat && loop_ctx->last_cmd_ctx.cmd) { // Repeat last command memcpy(&cmd_ctx, &loop_ctx->last_cmd_ctx, sizeof(cmd_ctx)); } // Notify cmd:*:before observers _editor_notify_cmd_observers(&cmd_ctx, 1); // Refresh cmd ctx if observers changed anything _editor_refresh_cmd_context(editor, &cmd_ctx); // Execute cmd cmd_ctx.cmd->func(&cmd_ctx); // Lift any temp anchors _editor_maybe_lift_temp_anchors(&cmd_ctx); // Notify cmd:*:after observers _editor_notify_cmd_observers(&cmd_ctx, 0); // Copy cmd_ctx to last_cmd_ctx memcpy(&loop_ctx->last_cmd_ctx, &cmd_ctx, sizeof(cmd_ctx)); loop_ctx->binding_node = NULL; cmd_ctx.wildcard_params_len = 0; cmd_ctx.numeric_params_len = 0; cmd_ctx.pastebuf_len = 0; } else if (loop_ctx->need_more_input) { // Need more input to find } else { // Not found, bad command loop_ctx->binding_node = NULL; } } // Free stuff if (cmd_ctx.pastebuf) free(cmd_ctx.pastebuf); str_free(&loop_ctx->last_insert); if (loop_ctx->input_trail) free(loop_ctx->input_trail); loop_ctx->input_trail_len = 0; loop_ctx->input_trail_cap = 0; // Decrement loop_depth editor->loop_depth -= 1; } // Run debug routine to show key names as they input static int _editor_debug_key_input() { char key[MLE_MAX_KEYNAME_LEN + 1]; struct tb_event ev; int y, h; y = 0; h = tb_height(); tb_print(0, y++, 0, 0, "_editor_debug_key_input: Press q to quit."); tb_present(); y++; while (1) { if (tb_poll_event(&ev) != TB_OK) { if (tb_last_errno() != EINTR) { break; } else { continue; } } if (ev.type == TB_EVENT_RESIZE) { h = tb_height(); } else if (ev.type == TB_EVENT_KEY) { if (ev.ch == 'q' && ev.mod == 0) { break; } if (_editor_event_to_key(&ev, key) != MLE_OK) { sprintf(key, ""); } if (y >= h) { tb_clear(); y = 0; } tb_print(0, y++, 0, 0, key); } tb_present(); } return MLE_OK; } // Set fresh values on cmd_context static void _editor_refresh_cmd_context(editor_t *editor, cmd_context_t *cmd_ctx) { cmd_ctx->cursor = editor->active->active_cursor; cmd_ctx->bview = cmd_ctx->cursor->bview; cmd_ctx->buffer = cmd_ctx->bview->buffer; } // Notify cmd observers static void _editor_notify_cmd_observers(cmd_context_t *ctx, int is_before) { char *event_name; if (!ctx->editor->observers) return; asprintf(&event_name, "cmd:%s:%s", ctx->cmd->name, is_before ? "before" : "after"); _editor_refresh_cmd_context(ctx->editor, ctx); editor_notify_observers(ctx->editor, event_name, (void*)ctx); free(event_name); } // Notify observers int editor_notify_observers(editor_t *editor, char *event_name, void *event_data) { observer_t *observer; DL_FOREACH(editor->observers, observer) { if (fnmatch(observer->event_patt, event_name, 0) == 0) { (observer->callback)(event_name, event_data, observer->udata); } } return MLE_OK; } // Force a redraw of the screen int editor_force_redraw(editor_t *editor) { int w; int h; int x; int y; (void)editor; if (tb_width() >= 0) tb_shutdown(); tb_init(); tb_select_input_mode(TB_INPUT_ALT); tb_set_cursor(-1, -1); w = tb_width(); h = tb_height(); for (x = 0; x < w; x++) { for (y = 0; y < h; y++) { // This used to use char 160 (non-breaking space) but that seems to // leave artifacts on my setup at least. I forget why I didn't use a // normal space to begin with. tb_change_cell(x, y, ' ', 0, 0); } } tb_present(); return MLE_OK; } // If input == editor->macro_toggle_key, toggle macro mode and return 1. Else // return 0. static int _editor_maybe_toggle_macro(editor_t *editor, kinput_t *input) { char *name; if (memcmp(input, &editor->macro_toggle_key, sizeof(kinput_t)) != 0) { return 0; } if (editor->is_recording_macro) { // Stop recording macro and add to map if (editor->macro_record->inputs_len > 0) { // Remove toggle key from macro inputs editor->macro_record->inputs_len -= 1; // TODO This is hacky } HASH_ADD_STR(editor->macro_map, name, editor->macro_record); editor->macro_record = NULL; editor->is_recording_macro = 0; } else { // Get macro name and start recording editor_prompt(editor, "record_macro: Name?", NULL, &name); if (!name) return 1; editor->macro_record = calloc(1, sizeof(kmacro_t)); editor->macro_record->name = name; editor->is_recording_macro = 1; } return 1; } // Resize the editor static void _editor_resize(editor_t *editor, int w, int h) { bview_t *bview; bview_rect_t *bounds; editor->w = w >= 0 ? w : tb_width(); editor->h = h >= 0 ? h : tb_height(); editor->rect_edit.x = 0; editor->rect_edit.y = 0; editor->rect_edit.w = editor->w; editor->rect_edit.h = editor->h - 2; editor->rect_status.x = 0; editor->rect_status.y = editor->h - 2; editor->rect_status.w = editor->w; editor->rect_status.h = 1; editor->rect_prompt.x = 0; editor->rect_prompt.y = editor->h - 1; editor->rect_prompt.w = editor->w; editor->rect_prompt.h = 1; DL_FOREACH2(editor->top_bviews, bview, top_next) { if (MLE_BVIEW_IS_PROMPT(bview)) { bounds = &editor->rect_prompt; } else if (MLE_BVIEW_IS_STATUS(bview)) { bounds = &editor->rect_status; } else { if (bview->split_parent) continue; bounds = &editor->rect_edit; } bview_resize(bview, bounds->x, bounds->y, bounds->w, bounds->h); } } // Lift temporary anchors on cursor if not running cmd_move_temp_anchor static void _editor_maybe_lift_temp_anchors(cmd_context_t *ctx) { cursor_t *cursor; if ( ctx->cmd->func == cmd_move_temp_anchor || ctx->cmd->func == cmd_toggle_block ) { return; } _editor_refresh_cmd_context(ctx->editor, ctx); if (!ctx->cursor || !ctx->cursor->bview || !ctx->cursor->bview->active_cursor) { return; } DL_FOREACH(ctx->cursor->bview->cursors, cursor) { if (!cursor->is_asleep && cursor->is_temp_anchored) { cursor_toggle_anchor(cursor, 1); } } } // Draw bviews cursors recursively static void _editor_draw_cursors(editor_t *editor, bview_t *bview) { if (MLE_BVIEW_IS_EDIT(bview) && bview_get_split_root(bview) != editor->active_edit_root) { return; } bview_draw_cursor(bview, bview == editor->active ? 1 : 0); if (bview->split_child) { _editor_draw_cursors(editor, bview->split_child); } } static void _editor_display_keys(editor_t *editor) { int i; char key[MLE_MAX_KEYNAME_LEN + 1]; char *buf; int keylen; buf = editor->display_keys_buf; for (i = 0; i < MLE_DISPLAY_KEYS_NKEYS; i++) { memset(key, 0, sizeof(key)); memcpy(key, buf + (i * MLE_MAX_KEYNAME_LEN), MLE_MAX_KEYNAME_LEN); keylen = strlen(key); tb_printf( editor->w - keylen, editor->h - i - 3, TB_GREEN | TB_BOLD, 0, key ); } } static void _editor_remember_keys(cmd_context_t *ctx) { char key[MLE_MAX_KEYNAME_LEN + 1]; struct tb_event ev; char *buf; memset(&ev, 0, sizeof(ev)); ev.type = TB_EVENT_KEY; ev.mod = ctx->input.mod; ev.key = ctx->input.key; ev.ch = ctx->input.ch; _editor_event_to_key(&ev, key); buf = ctx->editor->display_keys_buf; memmove(buf + MLE_MAX_KEYNAME_LEN, buf, (MLE_DISPLAY_KEYS_NKEYS - 1) * MLE_MAX_KEYNAME_LEN); memset(buf, 0, MLE_MAX_KEYNAME_LEN); memcpy(buf, key, MLE_MAX_KEYNAME_LEN); } // Get user input static void _editor_get_user_input(editor_t *editor, cmd_context_t *ctx) { int rc; tb_event_t ev; // Use pastebuf_leftover if present if (ctx->has_pastebuf_leftover) { MLE_KINPUT_COPY(ctx->input, ctx->pastebuf_leftover); ctx->has_pastebuf_leftover = 0; return; } // Poll for event while (1) { rc = tb_poll_event(&ev); if (rc != TB_OK) { continue; // Error } else if (ev.type == TB_EVENT_RESIZE) { // Resize _editor_resize(editor, ev.w, ev.h); editor_display(editor); continue; } else if (ev.type == TB_EVENT_MOUSE) { // Mouse cursor _editor_handle_mouse(editor, &ev); editor_display(editor); continue; } MLE_KINPUT_SET(ctx->input, ev.mod, ev.ch, ev.key); editor->user_input_count += 1; break; } } // Ingest available input until non-cmd_insert_data static void _editor_ingest_paste(editor_t *editor, cmd_context_t *ctx) { int rc; tb_event_t ev; kinput_t input; cmd_t *cmd; while (1) { // Peek event rc = tb_peek_event(&ev, 0); if (rc != TB_OK) { break; // Error } else if (ev.type == TB_EVENT_RESIZE) { // Resize _editor_resize(editor, ev.w, ev.h); editor_display(editor); break; } MLE_KINPUT_SET(input, ev.mod, ev.ch, ev.key); // TODO check for macro key cmd = _editor_get_command(editor, ctx, &input); if (cmd && cmd->func == cmd_insert_data) { // Insert data; keep ingesting _editor_append_pastebuf(editor, ctx, &input); } else { // Not insert data; set leftover and stop ingesting ctx->has_pastebuf_leftover = 1; ctx->pastebuf_leftover = input; break; } } } static void _editor_handle_mouse(editor_t *editor, tb_event_t *ev) { bline_t *bline; bint_t col; bview_t *bview; bview_t *root; cursor_t *cursor; mark_t *mark; int drop_anchor; root = bview_get_split_root(editor->active); if (bview_screen_to_bline_col(root, ev->x, ev->y, &bview, &bline, &col) != MLE_OK) { return; } cursor = bview->active_cursor; drop_anchor = 0; if (ev->key == TB_KEY_MOUSE_LEFT) { if (!editor->is_mousedown) { editor->is_mousedown = 1; drop_anchor = 1; } mark = cursor->mark; } else { editor->is_mousedown = 0; if (ev->key == TB_KEY_MOUSE_RELEASE && cursor->is_anchored && mark_is_eq(cursor->mark, cursor->anchor) ) { cursor_lift_anchor(cursor); } return; } mark_move_to_w_bline(mark, bline, col); editor_set_active(editor, bview); // TODO move other cursors? if (drop_anchor) { if (cursor->is_anchored) { cursor_lift_anchor(cursor); } cursor_drop_anchor(cursor, 1); cursor->is_temp_anchored = 1; } } static void _editor_append_pastebuf(editor_t *editor, cmd_context_t *ctx, kinput_t *input) { // Expand pastebuf if needed if (ctx->pastebuf_len + 1 > ctx->pastebuf_size) { ctx->pastebuf_size += MLE_PASTEBUF_INCR; ctx->pastebuf = realloc(ctx->pastebuf, sizeof(kinput_t) * ctx->pastebuf_size); } // Append ctx->pastebuf[ctx->pastebuf_len++] = *input; } // Copy input into macro buffer static void _editor_record_macro_input(kmacro_t *macro, kinput_t *input) { if (!macro->inputs) { macro->inputs = calloc(8, sizeof(kinput_t)); macro->inputs_len = 0; macro->inputs_cap = 8; } else if (macro->inputs_len + 1 > macro->inputs_cap) { macro->inputs_cap = macro->inputs_len + 8; macro->inputs = realloc(macro->inputs, macro->inputs_cap * sizeof(kinput_t)); } memcpy(macro->inputs + macro->inputs_len, input, sizeof(kinput_t)); macro->inputs_len += 1; } // Return command for input static cmd_t *_editor_get_command(editor_t *editor, cmd_context_t *ctx, kinput_t *opt_paste_input) { loop_context_t *loop_ctx; kinput_t *input_cur; kbinding_t *binding_cur, *binding_next; kmap_node_t *kmap_node; int is_paste, is_numeric; size_t i; cmd_t *rv; // Init some vars loop_ctx = ctx->loop_ctx; is_paste = opt_paste_input ? 1 : 0; kmap_node = editor->active->kmap_tail; binding_cur = loop_ctx->binding_node ? loop_ctx->binding_node : kmap_node->kmap->bindings; rv = NULL; // Reset some per-call state loop_ctx->need_more_input = 0; loop_ctx->binding_node = NULL; if (!is_paste) { // Append to input_trail loop_ctx->input_trail_len += 1; if (loop_ctx->input_trail_len > loop_ctx->input_trail_cap) { loop_ctx->input_trail_cap = loop_ctx->input_trail_len; loop_ctx->input_trail = realloc(loop_ctx->input_trail, sizeof(kinput_t) * loop_ctx->input_trail_cap); } MLE_KINPUT_COPY(loop_ctx->input_trail[loop_ctx->input_trail_len - 1], ctx->input); } // Look for key binding while (1) { if (is_paste) { input_cur = opt_paste_input; } else { if (loop_ctx->input_trail_idx >= loop_ctx->input_trail_len) { loop_ctx->need_more_input = 1; loop_ctx->binding_node = binding_cur; break; } input_cur = &loop_ctx->input_trail[loop_ctx->input_trail_idx]; } binding_next = _editor_get_kbinding_node(binding_cur, input_cur, ctx, is_paste, &is_numeric); if (binding_next) { // Binding found if ((is_numeric || binding_next->children) && !is_paste) { // Need more input binding_cur = binding_next; loop_ctx->input_trail_idx += 1; continue; } else if (binding_next->is_leaf) { // Found leaf ctx->static_param = binding_next->static_param; rv = _editor_resolve_cmd(editor, &(binding_next->cmd), binding_next->cmd_name); break; } } else { // Binding not found if (kmap_node->kmap->default_cmd_name) { // Fallback to default cmd rv = _editor_resolve_cmd(editor, &(kmap_node->kmap->default_cmd), kmap_node->kmap->default_cmd_name); break; } else if (kmap_node->kmap->allow_fallthru && kmap_node != kmap_node->prev) { // Fallback to previous kmap on stack kmap_node = kmap_node->prev; binding_cur = kmap_node->kmap->bindings; loop_ctx->input_trail_idx = 0; continue; } } break; } if (rv && !is_paste) { if (rv->func == cmd_insert_data && loop_ctx->input_trail_idx != loop_ctx->input_trail_len - 1) { // Restore lost input_trail for cmd_insert_data MLE_KINPUT_COPY(ctx->input, loop_ctx->input_trail[loop_ctx->input_trail_idx]); for (i = loop_ctx->input_trail_idx + 1; i < loop_ctx->input_trail_len; i++) { _editor_append_pastebuf(editor, ctx, &loop_ctx->input_trail[i]); } } // Reset input_trail loop_ctx->input_trail_idx = 0; loop_ctx->input_trail_len = 0; } if (!rv && !loop_ctx->need_more_input) { // No chance of command being found loop_ctx->input_trail_idx = 0; loop_ctx->input_trail_len = 0; } return rv; } // Find binding by input in trie, taking into account numeric and wildcards patterns static kbinding_t *_editor_get_kbinding_node(kbinding_t *node, kinput_t *input, cmd_context_t *ctx, int is_paste, int *ret_is_numeric) { kbinding_t *binding; kinput_t input_tmp; loop_context_t *loop_ctx; memset(&input_tmp, 0, sizeof(kinput_t)); loop_ctx = ctx->loop_ctx; *ret_is_numeric = 0; if (!is_paste) { // Look for numeric .. TODO can be more efficient about this if (input->ch >= '0' && input->ch <= '9') { if (!loop_ctx->numeric_node) { MLE_KINPUT_SET_NUMERIC(input_tmp); HASH_FIND(hh, node->children, &input_tmp, sizeof(kinput_t), binding); loop_ctx->numeric_node = binding; } if (loop_ctx->numeric_node) { if (loop_ctx->numeric_len < MLE_LOOP_CTX_MAX_NUMERIC_LEN) { loop_ctx->numeric[loop_ctx->numeric_len] = (char)input->ch; loop_ctx->numeric_len += 1; *ret_is_numeric = 1; return node; // Need more input on this node } return NULL; // Ran out of `numeric` buffer .. TODO err } } // Parse/reset numeric buffer if (loop_ctx->numeric_len > 0) { if (ctx->numeric_params_len < MLE_MAX_NUMERIC_PARAMS) { loop_ctx->numeric[loop_ctx->numeric_len] = '\0'; ctx->numeric_params[ctx->numeric_params_len] = strtoul(loop_ctx->numeric, NULL, 10); ctx->numeric_params_len += 1; loop_ctx->numeric_len = 0; node = loop_ctx->numeric_node; // Resume on numeric's children loop_ctx->numeric_node = NULL; } else { loop_ctx->numeric_len = 0; loop_ctx->numeric_node = NULL; return NULL; // Ran out of `numeric_params` space .. TODO err } } } // Look for input HASH_FIND(hh, node->children, input, sizeof(kinput_t), binding); if (binding) { return binding; } if (!is_paste) { // Look for wildcard MLE_KINPUT_SET_WILDCARD(input_tmp); HASH_FIND(hh, node->children, &input_tmp, sizeof(kinput_t), binding); if (binding) { if (ctx->wildcard_params_len < MLE_MAX_WILDCARD_PARAMS) { ctx->wildcard_params[ctx->wildcard_params_len] = input->ch; ctx->wildcard_params_len += 1; } else { return NULL; // Ran out of `wildcard_params` space .. TODO err } return binding; } } return NULL; } // Resolve a potentially unresolved cmd by name static cmd_t *_editor_resolve_cmd(editor_t *editor, cmd_t **rcmd, char *cmd_name) { cmd_t *tcmd; cmd_t *cmd; cmd = NULL; if ((*rcmd)) { cmd = *rcmd; } else if (cmd_name) { HASH_FIND_STR(editor->cmd_map, cmd_name, tcmd); if (tcmd) { *rcmd = tcmd; cmd = tcmd; } } return cmd; } // Return a kinput_t given a key name static int _editor_key_to_input(char *key, kinput_t *ret_input) { int keylen; uint32_t ch; char *dash, *c; keylen = strlen(key); memset(ret_input, 0, sizeof(kinput_t)); // Check for CMS- modifier prefixes dash = keylen >= 2 ? strchr(key + 1, '-') : NULL; if (dash) { for (c = key; c < dash; c++) { switch (*c) { case 'C': ret_input->mod |= TB_MOD_CTRL; break; case 'M': ret_input->mod |= TB_MOD_ALT; break; case 'S': ret_input->mod |= TB_MOD_SHIFT; break; } } key = dash + 1; keylen = strlen(key); } // Check for special key names #define MLE_KEY_DEF(pkname, pmodmin, pmodadd, pch, pkey) \ } else if ( \ ((pmodmin) & ret_input->mod) == (pmodmin) \ && keylen == strlen((pkname)) \ && !strncmp((pkname), key, keylen) \ ) { \ ret_input->ch = (pch); \ ret_input->key = (pkey); \ ret_input->mod |= (pmodadd); \ return MLE_OK; if (keylen < 1) { return MLE_ERR; #include "keys.h" } #undef MLE_KEY_DEF // Check for Unicode code point ch = 0; if (utf8_char_to_unicode(&ch, key, NULL) != keylen || ch < 1) { return MLE_ERR; } ret_input->ch = ch; return MLE_OK; } // Return a key name given a key event static int _editor_event_to_key(struct tb_event *ev, char *ret_keyname) { char key[MLE_MAX_KEYNAME_LEN + 1]; #define MLE_KEY_DEF(pkname, pmodmin, pmodadd, pch, pkey) \ } else if ( \ (((pch) && ev->ch == (pch)) \ || (!(pch) && ev->key == (pkey))) \ && (((pmodmin) & ev->mod) == (pmodmin)) \ ) { \ if ((pmodadd)) ev->mod &= ~(pmodadd); \ sprintf(key, (pkname)); if (0) { return MLE_ERR; #include "keys.h" } else { memset(key, 0, sizeof(key)); utf8_unicode_to_char(key, ev->ch); } #undef MLE_KEY_DEF if (ev->mod & TB_MOD_CTRL) { *ret_keyname++ = 'C'; } if (ev->mod & TB_MOD_ALT) { *ret_keyname++ = 'M'; } if (ev->mod & TB_MOD_SHIFT) { *ret_keyname++ = 'S'; } if (ev->mod & TB_MOD_CTRL || ev->mod & TB_MOD_ALT || ev->mod & TB_MOD_SHIFT) { *ret_keyname++ = '-'; } sprintf(ret_keyname, "%s", key); return MLE_OK; } // Init signal handlers static void _editor_init_signal_handlers(editor_t *editor) { struct sigaction action; (void)editor; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler = _editor_graceful_exit; sigaction(SIGTERM, &action, NULL); sigaction(SIGINT, &action, NULL); sigaction(SIGQUIT, &action, NULL); sigaction(SIGHUP, &action, NULL); action.sa_handler = _editor_continue; sigaction(SIGCONT, &action, NULL); signal(SIGPIPE, SIG_IGN); } // Resume editor after cmd_suspend static void _editor_continue(int signum) { editor_force_redraw(&_editor); } // Gracefully exit static void _editor_graceful_exit(int signum) { bview_t *bview; char path[64]; int bview_num; (void)signum; bview_num = 0; if (tb_width() >= 0) tb_shutdown(); CDL_FOREACH2(_editor.all_bviews, bview, all_next) { if (bview->buffer->is_unsaved) { snprintf((char*)&path, 64, ".mle.bak.%d.%d", getpid(), bview_num); buffer_save_as(bview->buffer, path, NULL); bview_num += 1; } } editor_deinit(&_editor); exit(1); } // Register built-in commands static void _editor_register_cmds(editor_t *editor) { _editor_register_cmd_fn(editor, "cmd_align_cursors", cmd_align_cursors); _editor_register_cmd_fn(editor, "cmd_anchor_by", cmd_anchor_by); _editor_register_cmd_fn(editor, "cmd_apply_macro_by", cmd_apply_macro_by); _editor_register_cmd_fn(editor, "cmd_apply_macro", cmd_apply_macro); _editor_register_cmd_fn(editor, "cmd_blist", cmd_blist); _editor_register_cmd_fn(editor, "cmd_browse", cmd_browse); _editor_register_cmd_fn(editor, "cmd_close", cmd_close); _editor_register_cmd_fn(editor, "cmd_copy_by", cmd_copy_by); _editor_register_cmd_fn(editor, "cmd_copy", cmd_copy); _editor_register_cmd_fn(editor, "cmd_ctag", cmd_ctag); _editor_register_cmd_fn(editor, "cmd_cut_by", cmd_cut_by); _editor_register_cmd_fn(editor, "cmd_cut", cmd_cut); _editor_register_cmd_fn(editor, "cmd_delete_after", cmd_delete_after); _editor_register_cmd_fn(editor, "cmd_delete_before", cmd_delete_before); _editor_register_cmd_fn(editor, "cmd_delete_word_after", cmd_delete_word_after); _editor_register_cmd_fn(editor, "cmd_delete_word_before", cmd_delete_word_before); _editor_register_cmd_fn(editor, "cmd_drop_cursor_column", cmd_drop_cursor_column); _editor_register_cmd_fn(editor, "cmd_drop_lettered_mark", cmd_drop_lettered_mark); _editor_register_cmd_fn(editor, "cmd_drop_sleeping_cursor", cmd_drop_sleeping_cursor); _editor_register_cmd_fn(editor, "cmd_find_word", cmd_find_word); _editor_register_cmd_fn(editor, "cmd_fsearch", cmd_fsearch); _editor_register_cmd_fn(editor, "cmd_fsearch_fzy", cmd_fsearch_fzy); _editor_register_cmd_fn(editor, "cmd_goto", cmd_goto); _editor_register_cmd_fn(editor, "cmd_goto_lettered_mark", cmd_goto_lettered_mark); _editor_register_cmd_fn(editor, "cmd_grep", cmd_grep); _editor_register_cmd_fn(editor, "cmd_indent", cmd_indent); _editor_register_cmd_fn(editor, "cmd_insert_data", cmd_insert_data); _editor_register_cmd_fn(editor, "cmd_insert_newline_above", cmd_insert_newline_above); _editor_register_cmd_fn(editor, "cmd_insert_newline_below", cmd_insert_newline_below); _editor_register_cmd_fn(editor, "cmd_isearch", cmd_isearch); _editor_register_cmd_fn(editor, "cmd_jump", cmd_jump); _editor_register_cmd_fn(editor, "cmd_last", cmd_last); _editor_register_cmd_fn(editor, "cmd_less", cmd_less); _editor_register_cmd_fn(editor, "cmd_move_beginning", cmd_move_beginning); _editor_register_cmd_fn(editor, "cmd_move_bol", cmd_move_bol); _editor_register_cmd_fn(editor, "cmd_move_bracket_back", cmd_move_bracket_back); _editor_register_cmd_fn(editor, "cmd_move_bracket_forward", cmd_move_bracket_forward); _editor_register_cmd_fn(editor, "cmd_move_bracket_toggle", cmd_move_bracket_toggle); _editor_register_cmd_fn(editor, "cmd_move_down", cmd_move_down); _editor_register_cmd_fn(editor, "cmd_move_end", cmd_move_end); _editor_register_cmd_fn(editor, "cmd_move_eol", cmd_move_eol); _editor_register_cmd_fn(editor, "cmd_move_left", cmd_move_left); _editor_register_cmd_fn(editor, "cmd_move_page_down", cmd_move_page_down); _editor_register_cmd_fn(editor, "cmd_move_page_up", cmd_move_page_up); _editor_register_cmd_fn(editor, "cmd_move_relative", cmd_move_relative); _editor_register_cmd_fn(editor, "cmd_move_right", cmd_move_right); _editor_register_cmd_fn(editor, "cmd_move_temp_anchor", cmd_move_temp_anchor); _editor_register_cmd_fn(editor, "cmd_move_to_line", cmd_move_to_line); _editor_register_cmd_fn(editor, "cmd_move_to_offset", cmd_move_to_offset); _editor_register_cmd_fn(editor, "cmd_move_until_back", cmd_move_until_back); _editor_register_cmd_fn(editor, "cmd_move_until_forward", cmd_move_until_forward); _editor_register_cmd_fn(editor, "cmd_move_up", cmd_move_up); _editor_register_cmd_fn(editor, "cmd_move_word_back", cmd_move_word_back); _editor_register_cmd_fn(editor, "cmd_move_word_forward", cmd_move_word_forward); _editor_register_cmd_fn(editor, "cmd_next", cmd_next); _editor_register_cmd_fn(editor, "cmd_open_file", cmd_open_file); _editor_register_cmd_fn(editor, "cmd_open_new", cmd_open_new); _editor_register_cmd_fn(editor, "cmd_open_replace_file", cmd_open_replace_file); _editor_register_cmd_fn(editor, "cmd_open_replace_new", cmd_open_replace_new); _editor_register_cmd_fn(editor, "cmd_outdent", cmd_outdent); _editor_register_cmd_fn(editor, "cmd_perl", cmd_perl); _editor_register_cmd_fn(editor, "cmd_pop_kmap", cmd_pop_kmap); _editor_register_cmd_fn(editor, "cmd_prev", cmd_prev); _editor_register_cmd_fn(editor, "cmd_push_kmap", cmd_push_kmap); _editor_register_cmd_fn(editor, "cmd_quit", cmd_quit); _editor_register_cmd_fn(editor, "cmd_quit_without_saving", cmd_quit_without_saving); _editor_register_cmd_fn(editor, "cmd_redo", cmd_redo); _editor_register_cmd_fn(editor, "cmd_redraw", cmd_redraw); _editor_register_cmd_fn(editor, "cmd_remove_extra_cursors", cmd_remove_extra_cursors); _editor_register_cmd_fn(editor, "cmd_repeat", cmd_repeat); _editor_register_cmd_fn(editor, "cmd_replace", cmd_replace); _editor_register_cmd_fn(editor, "cmd_rfind_word", cmd_rfind_word); _editor_register_cmd_fn(editor, "cmd_rsearch", cmd_rsearch); _editor_register_cmd_fn(editor, "cmd_save_as", cmd_save_as); _editor_register_cmd_fn(editor, "cmd_save", cmd_save); _editor_register_cmd_fn(editor, "cmd_search", cmd_search); _editor_register_cmd_fn(editor, "cmd_search_next", cmd_search_next); _editor_register_cmd_fn(editor, "cmd_search_prev", cmd_search_prev); _editor_register_cmd_fn(editor, "cmd_set_opt", cmd_set_opt); _editor_register_cmd_fn(editor, "cmd_shell", cmd_shell); _editor_register_cmd_fn(editor, "cmd_show_help", cmd_show_help); _editor_register_cmd_fn(editor, "cmd_split_horizontal", cmd_split_horizontal); _editor_register_cmd_fn(editor, "cmd_split_vertical", cmd_split_vertical); _editor_register_cmd_fn(editor, "cmd_suspend", cmd_suspend); _editor_register_cmd_fn(editor, "cmd_swap_anchor", cmd_swap_anchor); _editor_register_cmd_fn(editor, "cmd_toggle_anchor", cmd_toggle_anchor); _editor_register_cmd_fn(editor, "cmd_toggle_block", cmd_toggle_block); _editor_register_cmd_fn(editor, "cmd_uncut", cmd_uncut); _editor_register_cmd_fn(editor, "cmd_uncut_last", cmd_uncut_last); _editor_register_cmd_fn(editor, "cmd_undo", cmd_undo); _editor_register_cmd_fn(editor, "cmd_viewport_bot", cmd_viewport_bot); _editor_register_cmd_fn(editor, "cmd_viewport_mid", cmd_viewport_mid); _editor_register_cmd_fn(editor, "cmd_viewport_toggle", cmd_viewport_toggle); _editor_register_cmd_fn(editor, "cmd_viewport_top", cmd_viewport_top); _editor_register_cmd_fn(editor, "cmd_wake_sleeping_cursors", cmd_wake_sleeping_cursors); _editor_register_cmd_fn(editor, "_editor_menu_cancel", _editor_menu_cancel); _editor_register_cmd_fn(editor, "_editor_menu_submit", _editor_menu_submit); _editor_register_cmd_fn(editor, "_editor_prompt_cancel", _editor_prompt_cancel); _editor_register_cmd_fn(editor, "_editor_prompt_history_down", _editor_prompt_history_down); _editor_register_cmd_fn(editor, "_editor_prompt_history_up", _editor_prompt_history_up); _editor_register_cmd_fn(editor, "_editor_prompt_input_complete", _editor_prompt_input_complete); _editor_register_cmd_fn(editor, "_editor_prompt_input_submit", _editor_prompt_input_submit); _editor_register_cmd_fn(editor, "_editor_prompt_isearch_drop_cursors", _editor_prompt_isearch_drop_cursors); _editor_register_cmd_fn(editor, "_editor_prompt_isearch_next", _editor_prompt_isearch_next); _editor_register_cmd_fn(editor, "_editor_prompt_isearch_prev", _editor_prompt_isearch_prev); _editor_register_cmd_fn(editor, "_editor_prompt_isearch_viewport_down", _editor_prompt_isearch_viewport_down); _editor_register_cmd_fn(editor, "_editor_prompt_isearch_viewport_up", _editor_prompt_isearch_viewport_up); _editor_register_cmd_fn(editor, "_editor_prompt_yna_all", _editor_prompt_yna_all); _editor_register_cmd_fn(editor, "_editor_prompt_yn_no", _editor_prompt_yn_no); _editor_register_cmd_fn(editor, "_editor_prompt_yn_yes", _editor_prompt_yn_yes); } // Init built-in kmaps static void _editor_init_kmaps(editor_t *editor) { _editor_init_kmap(editor, &editor->kmap_normal, "mle_normal", "cmd_insert_data", 0, (kbinding_def_t[]){ MLE_KBINDING_DEF("cmd_show_help", "f2"), MLE_KBINDING_DEF("cmd_delete_before", "backspace"), MLE_KBINDING_DEF("cmd_delete_before", "C-h"), MLE_KBINDING_DEF("cmd_delete_after", "delete"), MLE_KBINDING_DEF("cmd_insert_newline_above", "M-i"), MLE_KBINDING_DEF("cmd_insert_newline_below", "M-u"), MLE_KBINDING_DEF("cmd_move_bol", "C-a"), MLE_KBINDING_DEF("cmd_move_bol", "home"), MLE_KBINDING_DEF("cmd_move_eol", "C-e"), MLE_KBINDING_DEF("cmd_move_eol", "end"), MLE_KBINDING_DEF("cmd_move_beginning", "M-\\"), MLE_KBINDING_DEF("cmd_move_end", "M-/"), MLE_KBINDING_DEF("cmd_move_left", "left"), MLE_KBINDING_DEF("cmd_move_right", "right"), MLE_KBINDING_DEF("cmd_move_up", "up"), MLE_KBINDING_DEF("cmd_move_down", "down"), MLE_KBINDING_DEF("cmd_move_page_up", "pgup"), MLE_KBINDING_DEF("cmd_move_page_down", "pgdn"), MLE_KBINDING_DEF("cmd_move_to_line", "M-g"), MLE_KBINDING_DEF("cmd_move_to_offset", "M-G"), MLE_KBINDING_DEF_EX("cmd_move_relative", "M-y ## u", "up"), MLE_KBINDING_DEF_EX("cmd_move_relative", "M-y ## d", "down"), MLE_KBINDING_DEF("cmd_move_until_forward", "M-' **"), MLE_KBINDING_DEF("cmd_move_until_back", "M-; **"), MLE_KBINDING_DEF("cmd_move_word_forward", "M-f"), MLE_KBINDING_DEF("cmd_move_word_forward", "C-right"), MLE_KBINDING_DEF("cmd_move_word_back", "M-b"), MLE_KBINDING_DEF("cmd_move_word_back", "C-left"), MLE_KBINDING_DEF("cmd_move_bracket_forward", "M-right"), MLE_KBINDING_DEF("cmd_move_bracket_back", "M-left"), MLE_KBINDING_DEF("cmd_move_bracket_toggle", "M-="), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "S-up", "up"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "S-down", "down"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "S-left", "left"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "S-right", "right"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "S-home", "bol"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "S-end", "eol"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "S-pgup", "page_up"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "S-pgdn", "page_down"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "CS-right", "word_forward"), MLE_KBINDING_DEF_EX("cmd_move_temp_anchor", "CS-left", "word_back"), MLE_KBINDING_DEF("cmd_search", "C-f"), MLE_KBINDING_DEF("cmd_rsearch", "CM-f"), MLE_KBINDING_DEF("cmd_search_next", "C-g"), MLE_KBINDING_DEF("cmd_search_prev", "CM-g"), MLE_KBINDING_DEF("cmd_find_word", "C-v"), MLE_KBINDING_DEF("cmd_rfind_word", "CM-v"), MLE_KBINDING_DEF("cmd_isearch", "C-r"), MLE_KBINDING_DEF("cmd_repeat", "f5"), MLE_KBINDING_DEF("cmd_replace", "C-t"), MLE_KBINDING_DEF("cmd_cut", "C-k"), MLE_KBINDING_DEF("cmd_copy", "M-k"), MLE_KBINDING_DEF("cmd_uncut", "C-u"), MLE_KBINDING_DEF("cmd_uncut_last", "CM-u"), MLE_KBINDING_DEF("cmd_redraw", "M-x l"), MLE_KBINDING_DEF("cmd_less", "M-l"), MLE_KBINDING_DEF("cmd_viewport_top", "CM-o"), MLE_KBINDING_DEF("cmd_viewport_toggle", "C-l"), MLE_KBINDING_DEF("cmd_viewport_bot", "CM-p"), MLE_KBINDING_DEF("cmd_push_kmap", "M-x p"), MLE_KBINDING_DEF("cmd_pop_kmap", "M-x P"), MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c d", "bracket"), MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c w", "word"), MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c s", "word_back"), MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c f", "word_forward"), MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c a", "bol"), MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c e", "eol"), MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c c", "string"), MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c q", "all"), MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d d", "bracket"), MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d w", "word"), MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d s", "word_back"), MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d f", "word_forward"), MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d a", "bol"), MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d e", "eol"), MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d c", "string"), MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d q", "all"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 d", "bracket"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 w", "word"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 s", "word_back"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 f", "word_forward"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 a", "bol"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 e", "eol"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 c", "string"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 q", "all"), MLE_KBINDING_DEF_EX("cmd_anchor_by", "CM-a", "all"), MLE_KBINDING_DEF("cmd_delete_word_before", "C-w"), MLE_KBINDING_DEF("cmd_delete_word_after", "M-d"), MLE_KBINDING_DEF("cmd_delete_word_after", "C-delete"), MLE_KBINDING_DEF("cmd_drop_lettered_mark", "CM-z **"), MLE_KBINDING_DEF_EX("cmd_drop_lettered_mark", "CM-m", "a"), MLE_KBINDING_DEF("cmd_goto_lettered_mark", "M-z **"), MLE_KBINDING_DEF_EX("cmd_goto_lettered_mark", "M-m", "a"), MLE_KBINDING_DEF("cmd_toggle_anchor", "M-a"), MLE_KBINDING_DEF("cmd_toggle_block", "insert"), MLE_KBINDING_DEF("cmd_drop_sleeping_cursor", "C-/ ."), MLE_KBINDING_DEF("cmd_wake_sleeping_cursors", "C-/ a"), MLE_KBINDING_DEF("cmd_remove_extra_cursors", "C-/ /"), MLE_KBINDING_DEF("cmd_drop_cursor_column", "C-/ '"), MLE_KBINDING_DEF("cmd_swap_anchor", "C-/ ;"), MLE_KBINDING_DEF("cmd_align_cursors", "C-/ l"), MLE_KBINDING_DEF("cmd_apply_macro", "M-Z"), MLE_KBINDING_DEF("cmd_apply_macro_by", "M-M **"), MLE_KBINDING_DEF("cmd_next", "M-n"), MLE_KBINDING_DEF("cmd_prev", "M-p"), MLE_KBINDING_DEF("cmd_last", "M-0"), MLE_KBINDING_DEF_EX("cmd_goto", "M-1", "1"), MLE_KBINDING_DEF_EX("cmd_goto", "M-2", "2"), MLE_KBINDING_DEF_EX("cmd_goto", "M-3", "3"), MLE_KBINDING_DEF_EX("cmd_goto", "M-4", "4"), MLE_KBINDING_DEF_EX("cmd_goto", "M-5", "5"), MLE_KBINDING_DEF_EX("cmd_goto", "M-6", "6"), MLE_KBINDING_DEF_EX("cmd_goto", "M-7", "7"), MLE_KBINDING_DEF_EX("cmd_goto", "M-8", "8"), MLE_KBINDING_DEF_EX("cmd_goto", "M-9", "9"), MLE_KBINDING_DEF("cmd_split_vertical", "M-v"), MLE_KBINDING_DEF("cmd_split_horizontal", "M-h"), MLE_KBINDING_DEF("cmd_grep", "M-q"), MLE_KBINDING_DEF("cmd_fsearch", "C-p"), MLE_KBINDING_DEF("cmd_browse", "C-b"), MLE_KBINDING_DEF("cmd_blist", "C-\\"), MLE_KBINDING_DEF("cmd_undo", "C-z"), MLE_KBINDING_DEF("cmd_redo", "C-y"), MLE_KBINDING_DEF("cmd_save", "C-s"), MLE_KBINDING_DEF("cmd_save_as", "M-s"), MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o a", "tab_to_space"), MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o t", "tab_width"), MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o y", "syntax"), MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o w", "soft_wrap"), MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o u", "coarse_undo"), MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o m", "mouse_support"), MLE_KBINDING_DEF("cmd_open_new", "C-n"), MLE_KBINDING_DEF("cmd_open_file", "C-o"), MLE_KBINDING_DEF("cmd_open_replace_new", "C-q n"), MLE_KBINDING_DEF("cmd_open_replace_file", "C-q o"), MLE_KBINDING_DEF_EX("cmd_fsearch", "C-q p", "replace"), MLE_KBINDING_DEF("cmd_indent", "M-."), MLE_KBINDING_DEF("cmd_outdent", "M-,"), MLE_KBINDING_DEF("cmd_outdent", "backtab"), MLE_KBINDING_DEF("cmd_ctag", "f3"), MLE_KBINDING_DEF("cmd_shell", "M-e"), MLE_KBINDING_DEF("cmd_perl", "M-w"), MLE_KBINDING_DEF("cmd_jump", "M-j"), MLE_KBINDING_DEF("cmd_close", "M-c"), MLE_KBINDING_DEF("cmd_suspend", "f4"), MLE_KBINDING_DEF("cmd_quit", "C-x"), MLE_KBINDING_DEF(NULL, NULL) }); _editor_init_kmap(editor, &editor->kmap_prompt_input, "mle_prompt_input", NULL, 1, (kbinding_def_t[]){ MLE_KBINDING_DEF("_editor_prompt_input_submit", "enter"), MLE_KBINDING_DEF("_editor_prompt_input_submit", "M-enter"), MLE_KBINDING_DEF("_editor_prompt_input_complete", "tab"), MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"), MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"), MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"), MLE_KBINDING_DEF("_editor_prompt_history_up", "up"), MLE_KBINDING_DEF("_editor_prompt_history_down", "down"), MLE_KBINDING_DEF(NULL, NULL) }); _editor_init_kmap(editor, &editor->kmap_prompt_yn, "mle_prompt_yn", NULL, 0, (kbinding_def_t[]){ MLE_KBINDING_DEF("_editor_prompt_yn_yes", "y"), MLE_KBINDING_DEF("_editor_prompt_yn_no", "n"), MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"), MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"), MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"), MLE_KBINDING_DEF(NULL, NULL) }); _editor_init_kmap(editor, &editor->kmap_prompt_yna, "mle_prompt_yna", NULL, 0, (kbinding_def_t[]){ MLE_KBINDING_DEF("_editor_prompt_yn_yes", "y"), MLE_KBINDING_DEF("_editor_prompt_yn_no", "n"), MLE_KBINDING_DEF("_editor_prompt_yna_all", "a"), MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"), MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"), MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"), MLE_KBINDING_DEF(NULL, NULL) }); _editor_init_kmap(editor, &editor->kmap_prompt_ok, "mle_prompt_ok", "_editor_prompt_cancel", 0, (kbinding_def_t[]){ MLE_KBINDING_DEF(NULL, NULL) }); _editor_init_kmap(editor, &editor->kmap_menu, "mle_menu", NULL, 1, (kbinding_def_t[]){ MLE_KBINDING_DEF("_editor_menu_submit", "enter"), MLE_KBINDING_DEF("_editor_menu_submit", "M-enter"), MLE_KBINDING_DEF("_editor_menu_cancel", "C-c"), MLE_KBINDING_DEF(NULL, NULL) }); _editor_init_kmap(editor, &editor->kmap_prompt_isearch, "mle_prompt_isearch", NULL, 1, (kbinding_def_t[]){ MLE_KBINDING_DEF("_editor_prompt_isearch_prev", "up"), MLE_KBINDING_DEF("_editor_prompt_isearch_next", "down"), MLE_KBINDING_DEF("_editor_prompt_isearch_viewport_up", "pgup"), MLE_KBINDING_DEF("_editor_prompt_isearch_viewport_down", "pgdn"), MLE_KBINDING_DEF("_editor_prompt_isearch_drop_cursors", "C-/"), MLE_KBINDING_DEF("_editor_prompt_cancel", "enter"), MLE_KBINDING_DEF("_editor_prompt_cancel", "M-enter"), MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"), MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"), MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"), MLE_KBINDING_DEF(NULL, NULL) }); } // Init a single kmap static void _editor_init_kmap(editor_t *editor, kmap_t **ret_kmap, char *name, char *default_cmd_name, int allow_fallthru, kbinding_def_t *defs) { kmap_t *kmap; kmap = calloc(1, sizeof(kmap_t)); kmap->name = strdup(name); kmap->allow_fallthru = allow_fallthru; kmap->bindings = calloc(1, sizeof(kbinding_t)); if (default_cmd_name) { kmap->default_cmd_name = strdup(default_cmd_name); } while (defs && defs->cmd_name) { _editor_init_kmap_add_binding(editor, kmap, defs); defs++; } HASH_ADD_KEYPTR(hh, editor->kmap_map, kmap->name, strlen(kmap->name), kmap); *ret_kmap = kmap; } // Add a binding to a kmap static void _editor_init_kmap_add_binding(editor_t *editor, kmap_t *kmap, kbinding_def_t *binding_def) { char *cur_key_patt; cur_key_patt = strdup(binding_def->key_patt); _editor_init_kmap_add_binding_to_trie(&kmap->bindings->children, binding_def->cmd_name, cur_key_patt, binding_def->key_patt, binding_def->static_param); if (strcmp(binding_def->cmd_name, "cmd_show_help") == 0) { // TODO kind of hacky MLE_SET_INFO(editor, "show_help: Press %s", cur_key_patt); } free(cur_key_patt); } // Add a binding to a kmap trie static int _editor_init_kmap_add_binding_to_trie(kbinding_t **trie, char *cmd_name, char *cur_key_patt, char *full_key_patt, char *static_param) { char *next_key_patt; kbinding_t *node; kinput_t input; // Find next_key_patt and add null-char to cur_key_patt next_key_patt = strchr(cur_key_patt, ' '); if (next_key_patt != NULL) { *next_key_patt = '\0'; next_key_patt += 1; } // cur_key_patt points to a null-term cstring now // next_key_patt is either NULL or points to a null-term cstring // Parse cur_key_patt token as input memset(&input, 0, sizeof(kinput_t)); if (strcmp("##", cur_key_patt) == 0) { MLE_KINPUT_SET_NUMERIC(input); } else if (strcmp("**", cur_key_patt) == 0) { MLE_KINPUT_SET_WILDCARD(input); } else if (_editor_key_to_input(cur_key_patt, &input) == MLE_OK) { // Hi mom! } else { return MLE_ERR; } // Add node for input if it doesn't already exist node = NULL; HASH_FIND(hh, *trie, &input, sizeof(kinput_t), node); if (!node) { node = calloc(1, sizeof(kbinding_t)); MLE_KINPUT_COPY(node->input, input); HASH_ADD(hh, *trie, input, sizeof(kinput_t), node); } if (next_key_patt) { // Recurse for next key if (_editor_init_kmap_add_binding_to_trie(&node->children, cmd_name, next_key_patt, full_key_patt, static_param) != MLE_OK) { free(node); return MLE_ERR; } } else { // Leaf node, set cmd node->static_param = static_param ? strdup(static_param) : NULL; node->key_patt = strdup(full_key_patt); node->cmd_name = strdup(cmd_name); node->is_leaf = 1; } return MLE_OK; } // Proxy for _editor_init_kmap with str in format ',,' static int _editor_init_kmap_by_str(editor_t *editor, kmap_t **ret_kmap, char *str) { char *args[3]; args[0] = strtok(str, ","); if (!args[0]) return MLE_ERR; args[1] = strtok(NULL, ","); if (!args[1]) return MLE_ERR; args[2] = strtok(NULL, ","); _editor_init_kmap(editor, ret_kmap, args[0], args[2] ? args[1] : NULL, atoi(args[2] ? args[2] : args[1]), NULL); return MLE_OK; } // Proxy for _editor_init_kmap_add_binding with str in format ',,' static int _editor_init_kmap_add_binding_by_str(editor_t *editor, kmap_t *kmap, char *str) { char *args[3]; args[0] = strtok(str, ","); if (!args[0]) return MLE_ERR; args[1] = strtok(NULL, ","); if (!args[1]) return MLE_ERR; args[2] = strtok(NULL, ","); _editor_init_kmap_add_binding(editor, kmap, &((kbinding_def_t){args[0], args[1], args[2]})); return MLE_OK; } // Destroy a kmap static void _editor_destroy_kmap(kmap_t *kmap, kbinding_t *trie) { kbinding_t *binding; kbinding_t *binding_tmp; int is_top; is_top = (trie == kmap->bindings ? 1 : 0); HASH_ITER(hh, trie, binding, binding_tmp) { if (binding->children) { _editor_destroy_kmap(kmap, binding->children); } HASH_DELETE(hh, trie, binding); if (binding->static_param) free(binding->static_param); if (binding->cmd_name) free(binding->cmd_name); if (binding->key_patt) free(binding->key_patt); free(binding); } if (is_top) { if (kmap->name) free(kmap->name); free(kmap); } } // Add a macro by str with format ' ... ' static int _editor_add_macro_by_str(editor_t *editor, char *str) { int has_input; char *token; kmacro_t *macro; kinput_t input; has_input = 0; macro = NULL; // Tokenize by space token = strtok(str, " "); while (token) { if (!macro) { // Make macro with on first token macro = calloc(1, sizeof(kmacro_t)); macro->name = strdup(token); } else { // Parse token as kinput_t if (_editor_key_to_input(token, &input) != MLE_OK) { free(macro->name); free(macro); return MLE_ERR; } // Add kinput_t to macro _editor_record_macro_input(macro, &input); has_input = 1; } // Get next token token = strtok(NULL, " "); } // Add macro to map if has_input if (has_input) { HASH_ADD_KEYPTR(hh, editor->macro_map, macro->name, strlen(macro->name), macro); return MLE_OK; } // Fail if (macro) { free(macro->name); free(macro); } return MLE_ERR; } // Init built-in syntax map static void _editor_init_syntaxes(editor_t *editor) { _editor_init_syntax(editor, NULL, "syn_generic", "\\.(c|cc|cpp|h|hh|hpp|d|php|py|rb|erb|sh|pl|go|js|java|jsp|lua|rs|zig)$", -1, -1, (srule_def_t[]){ { "'([^'\\\\]|\\\\.)*'", NULL, TB_YELLOW | TB_BOLD, TB_DEFAULT }, { "\"([^\"\\\\]|\\\\.)*\"", NULL, TB_YELLOW | TB_BOLD, TB_DEFAULT }, { "`([^`\\\\]|\\\\.)*`", NULL, TB_YELLOW | TB_BOLD, TB_DEFAULT }, { "/\\*", "\\*/", TB_CYAN, TB_DEFAULT }, { "//.*$", NULL, TB_CYAN, TB_DEFAULT }, { "^\\s*#( .*|)$", NULL, TB_CYAN, TB_DEFAULT }, { "^#!/.*$", NULL, TB_CYAN, TB_DEFAULT }, { "[(){}<>\\[\\].,;:?!+=/\\\\%^*-]", NULL, TB_RED | TB_BOLD, TB_DEFAULT }, { "(?name = strdup(name); syntax->path_pattern = strdup(path_pattern); syntax->tab_width = tab_width >= 1 ? tab_width : -1; // -1 means default syntax->tab_to_space = tab_to_space >= 0 ? (tab_to_space ? 1 : 0) : -1; while (defs && defs->re) { _editor_init_syntax_add_rule(syntax, defs); defs++; } HASH_ADD_KEYPTR(hh, editor->syntax_map, syntax->name, strlen(syntax->name), syntax); editor->syntax_last = syntax; if (optret_syntax) *optret_syntax = syntax; } // Proxy for _editor_init_syntax with str in format ',,,' static int _editor_init_syntax_by_str(editor_t *editor, syntax_t **ret_syntax, char *str) { char *args[4]; args[0] = strtok(str, ","); if (!args[0]) return MLE_ERR; args[1] = strtok(NULL, ","); if (!args[1]) return MLE_ERR; args[2] = strtok(NULL, ","); if (!args[2]) return MLE_ERR; args[3] = strtok(NULL, ","); if (!args[3]) return MLE_ERR; _editor_init_syntax(editor, ret_syntax, args[0], args[1], atoi(args[2]), atoi(args[3]), NULL); return MLE_OK; } // Add rule to syntax static void _editor_init_syntax_add_rule(syntax_t *syntax, srule_def_t *def) { srule_node_t *node; node = calloc(1, sizeof(srule_node_t)); if (def->re_end) { node->srule = srule_new_multi(def->re, strlen(def->re), def->re_end, strlen(def->re_end), def->fg, def->bg); } else { node->srule = srule_new_single(def->re, strlen(def->re), 0, def->fg, def->bg); } if (node->srule) DL_APPEND(syntax->srules, node); } // Proxy for _editor_init_syntax_add_rule with str in format ',,,' or ',,' static int _editor_init_syntax_add_rule_by_str(syntax_t *syntax, char *str) { char *args[4]; int style_i; args[0] = strtok(str, ","); if (!args[0]) return MLE_ERR; args[1] = strtok(NULL, ","); if (!args[1]) return MLE_ERR; args[2] = strtok(NULL, ","); if (!args[2]) return MLE_ERR; args[3] = strtok(NULL, ","); style_i = args[3] ? 2 : 1; _editor_init_syntax_add_rule(syntax, &((srule_def_t){ args[0], style_i == 2 ? args[1] : NULL, atoi(args[style_i]), atoi(args[style_i + 1]) })); return MLE_OK; } // Destroy a syntax static void _editor_destroy_syntax_map(syntax_t *map) { syntax_t *syntax; syntax_t *syntax_tmp; srule_node_t *srule; srule_node_t *srule_tmp; HASH_ITER(hh, map, syntax, syntax_tmp) { HASH_DELETE(hh, map, syntax); DL_FOREACH_SAFE(syntax->srules, srule, srule_tmp) { DL_DELETE(syntax->srules, srule); srule_destroy(srule->srule); free(srule); } free(syntax->name); free(syntax->path_pattern); free(syntax); } } // Read rc file static int _editor_init_from_rc_read(editor_t *editor, FILE *rc, char **ret_rc_data, size_t *ret_rc_data_len) { (void)editor; fseek(rc, 0L, SEEK_END); *ret_rc_data_len = (size_t)ftell(rc); fseek(rc, 0L, SEEK_SET); *ret_rc_data = malloc(*ret_rc_data_len + 1); fread(*ret_rc_data, *ret_rc_data_len, 1, rc); (*ret_rc_data)[*ret_rc_data_len] = '\0'; return MLE_OK; } // Exec rc file, read stdout static int _editor_init_from_rc_exec(editor_t *editor, char *rc_path, char **ret_rc_data, size_t *ret_rc_data_len) { char buf[512]; size_t nbytes; char *data; size_t data_len; size_t data_cap; FILE *fp; // Popen rc file if ((fp = popen(rc_path, "r")) == NULL) { MLE_RETURN_ERR(editor, "Failed to popen rc file %s", rc_path); } // Read output data = NULL; data_len = 0; data_cap = 0; while ((nbytes = fread(buf, 1, sizeof(buf), fp)) != 0) { if (data_len + nbytes >= data_cap) { data_cap += sizeof(buf); data = realloc(data, data_cap); } memmove(data + data_len, buf, nbytes); data_len += nbytes; } // Add null terminator if (data_len + 1 >= data_cap) { data_cap += 1; data = realloc(data, data_cap); } data[data_len] = '\0'; // Return pclose(fp); *ret_rc_data = data; *ret_rc_data_len = data_len; return MLE_OK; } // Parse rc file static int _editor_init_from_rc(editor_t *editor, FILE *rc, char *rc_path) { int rv; size_t rc_data_len; char *rc_data; char *rc_data_stop; char *eol; char *bol; int fargc; char **fargv; struct stat statbuf; rv = MLE_OK; rc_data = NULL; rc_data_len = 0; // Read or exec rc file if (fstat(fileno(rc), &statbuf) == 0 && statbuf.st_mode & S_IXUSR) { _editor_init_from_rc_exec(editor, rc_path, &rc_data, &rc_data_len); } else { _editor_init_from_rc_read(editor, rc, &rc_data, &rc_data_len); } rc_data_stop = rc_data + rc_data_len; // Make fargc, fargv int i; fargv = NULL; for (i = 0; i < 2; i++) { bol = rc_data; fargc = 1; while (bol < rc_data_stop) { eol = strchr(bol, '\n'); if (!eol) eol = rc_data_stop - 1; if (*bol != ';') { // Treat semicolon lines as comments if (fargv) { *eol = '\0'; fargv[fargc] = bol; } fargc += 1; } bol = eol + 1; } if (!fargv) { if (fargc < 2) break; // No options fargv = malloc((fargc + 1) * sizeof(char*)); fargv[0] = "mle"; fargv[fargc] = NULL; } } // Parse args if (fargv) { rv = _editor_init_from_args(editor, fargc, fargv); free(fargv); } free(rc_data); return rv; } // Parse cli args static int _editor_init_from_args(editor_t *editor, int argc, char **argv) { int rv; kmap_t *cur_kmap; syntax_t *cur_syntax; uscript_t *uscript; int c; rv = MLE_OK; cur_kmap = NULL; cur_syntax = NULL; optind = 1; while (rv == MLE_OK && (c = getopt(argc, argv, "ha:b:c:e:H:i:K:k:l:M:m:Nn:p:S:s:t:u:vw:x:y:z:Q:")) != -1) { switch (c) { case 'h': printf("mle version %s\n\n", MLE_VERSION); printf("Usage: mle [options] [file:line]...\n\n"); printf(" -h Show help\n"); printf(" -a <1|0> Enable/disable tab to space (default: %d)\n", MLE_DEFAULT_TAB_TO_SPACE); printf(" -b <1|0> Enable/disable highlight bracket pairs (default: %d)\n", MLE_DEFAULT_HILI_BRACKET_PAIRS); printf(" -c Set color column (default: -1, disabled)\n"); printf(" -e <1|0> Enable/disable mouse support (default: %d)\n", MLE_DEFAULT_MOUSE_SUPPORT); printf(" -H <1|0> Enable/disable headless mode (default: 1 if no tty, else 0)\n"); printf(" -i <1|0> Enable/disable auto indent (default: %d)\n", MLE_DEFAULT_AUTO_INDENT); printf(" -K Make a kmap definition (use with -k)\n"); printf(" -k Add key binding to current kmap definition (use after -K)\n"); printf(" -l Set linenum type (default: 0, absolute)\n"); printf(" -M Add a macro\n"); printf(" -m Set macro toggle key (default: %s)\n", MLE_DEFAULT_MACRO_TOGGLE_KEY); printf(" -N Skip reading of rc file\n"); printf(" -n Set init kmap (default: mle_normal)\n"); printf(" -p Set startup macro\n"); printf(" -S Make a syntax definition (use with -s)\n"); printf(" -s Add syntax rule to current syntax definition (use after -S)\n"); printf(" -t Set tab size (default: %d)\n", MLE_DEFAULT_TAB_WIDTH); printf(" -u <1|0> Enable/disable coarse undo/redo (default: %d)\n", MLE_DEFAULT_COARSE_UNDO); printf(" -v Print version and exit\n"); printf(" -w <1|0> Enable/disable soft word wrap (default: %d)\n", MLE_DEFAULT_SOFT_WRAP); printf(" -x Run a Lua user script\n"); printf(" -y Set override syntax for files opened at start up\n"); printf(" -z <1|0> Enable/disable trimmed paste (default: %d)\n", MLE_DEFAULT_TRIM_PASTE); printf("\n"); printf(" file At start up, open file\n"); printf(" file:line At start up, open file at line\n"); printf(" kdef ',,'\n"); printf(" kbind ',,'\n"); printf(" ltype 0=absolute, 1=relative, 2=both\n"); printf(" macro ' ... '\n"); printf(" syndef ',,,'\n"); printf(" synrule ',,,'\n"); printf(" fg,bg 0=default 1=black 2=red 3=green\n"); printf(" 4=yellow 5=blue 6=magenta 7=cyan\n"); printf(" 8=white 256=bold 512=underline 1024=reverse\n"); rv = MLE_ERR; break; case 'a': editor->tab_to_space = atoi(optarg) ? 1 : 0; break; case 'b': editor->highlight_bracket_pairs = atoi(optarg) ? 1 : 0; break; case 'c': editor->color_col = atoi(optarg); break; case 'e': editor->mouse_support = atoi(optarg) ? 1 : 0; break; case 'H': editor->headless_mode = atoi(optarg) ? 1 : 0; break; case 'i': editor->auto_indent = atoi(optarg) ? 1 : 0; break; case 'K': if (_editor_init_kmap_by_str(editor, &cur_kmap, optarg) != MLE_OK) { MLE_LOG_ERR("Could not init kmap by str: %s\n", optarg); editor->exit_code = EXIT_FAILURE; rv = MLE_ERR; } break; case 'k': if (!cur_kmap || _editor_init_kmap_add_binding_by_str(editor, cur_kmap, optarg) != MLE_OK) { MLE_LOG_ERR("Could not add key binding to kmap %p by str: %s\n", (void*)cur_kmap, optarg); editor->exit_code = EXIT_FAILURE; rv = MLE_ERR; } break; case 'l': editor->linenum_type = atoi(optarg); if (editor->linenum_type < 0 || editor->linenum_type > 2) editor->linenum_type = 0; break; case 'M': if (_editor_add_macro_by_str(editor, optarg) != MLE_OK) { MLE_LOG_ERR("Could not add macro by str: %s\n", optarg); editor->exit_code = EXIT_FAILURE; rv = MLE_ERR; } break; case 'm': if (_editor_set_macro_toggle_key(editor, optarg) != MLE_OK) { MLE_LOG_ERR("Could not set macro key to: %s\n", optarg); editor->exit_code = EXIT_FAILURE; rv = MLE_ERR; } break; case 'N': // See _editor_should_skip_rc break; case 'n': if (editor->kmap_init_name) free(editor->kmap_init_name); editor->kmap_init_name = strdup(optarg); break; case 'p': if (editor->startup_macro_name) free(editor->startup_macro_name); editor->startup_macro_name = strdup(optarg); break; case 'S': if (_editor_init_syntax_by_str(editor, &cur_syntax, optarg) != MLE_OK) { MLE_LOG_ERR("Could not init syntax by str: %s\n", optarg); editor->exit_code = EXIT_FAILURE; rv = MLE_ERR; } break; case 's': if (!cur_syntax || _editor_init_syntax_add_rule_by_str(cur_syntax, optarg) != MLE_OK) { MLE_LOG_ERR("Could not add style rule to syntax %p by str: %s\n", (void*)cur_syntax, optarg); editor->exit_code = EXIT_FAILURE; rv = MLE_ERR; } break; case 't': editor->tab_width = atoi(optarg); break; case 'u': editor->coarse_undo = atoi(optarg); break; case 'v': printf("mle version %s\n", MLE_VERSION); rv = MLE_ERR; break; case 'w': editor->soft_wrap = atoi(optarg); break; case 'x': if (!(uscript = uscript_run(editor, optarg))) { MLE_LOG_ERR("Failed to run uscript: %s\n", optarg); editor->exit_code = EXIT_FAILURE; rv = MLE_ERR; } else { DL_APPEND(editor->uscripts, uscript); } break; case 'y': editor->syntax_override = strcmp(optarg, "-") == 0 ? "syn_generic" : optarg; break; case 'z': editor->trim_paste = atoi(optarg) ? 1 : 0; break; case 'Q': switch (*optarg) { case 'q': editor->debug_exit_after_startup = 1; break; case 'd': editor->debug_dump_state_on_exit = 1; break; case 'k': editor->debug_key_input = 1; break; case 'i': editor->debug_display_keys = 1; break; } break; default: // Unknown option editor->exit_code = EXIT_FAILURE; rv = MLE_ERR; break; } } return rv; } // Init status bar static void _editor_init_status(editor_t *editor) { editor->status = bview_new(editor, MLE_BVIEW_TYPE_STATUS, NULL, 0, NULL); editor->rect_status.fg = TB_WHITE; editor->rect_status.bg = TB_BLACK; } // Init bviews static void _editor_init_bviews(editor_t *editor, int argc, char **argv) { int i; char *path; int path_len; // Open bviews if (optind >= argc) { // Open blank editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, NULL); } else { // Open files for (i = optind; i < argc; i++) { path = argv[i]; path_len = strlen(path); editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, path, path_len, 1, 0, 0, NULL, NULL); } } } // Init headless mode static int _editor_init_headless_mode(editor_t *editor) { fd_set readfds; ssize_t nbytes; char buf[1024]; bview_t *bview; int stdin_is_a_pipe; // Check if stdin is a pipe stdin_is_a_pipe = isatty(STDIN_FILENO) != 1 ? 1 : 0; // Bail if not in headless mode and stdin is not a pipe if (!editor->headless_mode && !stdin_is_a_pipe) return MLE_OK; // Ensure blank bview if (!editor->active_edit->buffer->path && editor->active_edit->buffer->byte_count == 0 ) { bview = editor->active_edit; } else { editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, &bview); } // If stdin is a pipe, read into bview if (!stdin_is_a_pipe) return MLE_OK; do { FD_ZERO(&readfds); FD_SET(STDIN_FILENO, &readfds); select(STDIN_FILENO + 1, &readfds, NULL, NULL, NULL); nbytes = 0; if (FD_ISSET(STDIN_FILENO, &readfds)) { nbytes = read(STDIN_FILENO, &buf, 1024); if (nbytes > 0) { mark_insert_before(bview->active_cursor->mark, buf, nbytes); } } } while (nbytes > 0); mark_move_beginning(bview->active_cursor->mark); return MLE_OK; } // Init startup macro if present static int _editor_init_startup_macro(editor_t *editor) { kmacro_t *macro; if (!editor->startup_macro_name) return MLE_OK; macro = NULL; HASH_FIND_STR(editor->macro_map, editor->startup_macro_name, macro); if (!macro) return MLE_ERR; editor->macro_apply = macro; editor->macro_apply_input_index = 0; return MLE_OK; } // Init terminal via termbox static int _editor_init_term(editor_t *editor) { if (editor->headless_mode) { return MLE_OK; } tb_init(); editor_set_input_mode(editor); return MLE_OK; } mle-1.7.2/keys.h000066400000000000000000000106061443351614700134350ustar00rootroot00000000000000// kname modmin modadd ch key MLE_KEY_DEF("space", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_TILDE) MLE_KEY_DEF("~", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_TILDE) MLE_KEY_DEF("2", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_TILDE) MLE_KEY_DEF("a", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_A) MLE_KEY_DEF("b", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_B) MLE_KEY_DEF("c", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_C) MLE_KEY_DEF("d", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_D) MLE_KEY_DEF("e", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_E) MLE_KEY_DEF("f", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_F) MLE_KEY_DEF("g", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_G) MLE_KEY_DEF("h", TB_MOD_CTRL, 0, 0, TB_KEY_BACKSPACE) MLE_KEY_DEF("tab", 0, TB_MOD_CTRL, 0, TB_KEY_TAB) MLE_KEY_DEF("i", TB_MOD_CTRL, 0, 0, TB_KEY_TAB) MLE_KEY_DEF("j", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_J) MLE_KEY_DEF("k", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_K) MLE_KEY_DEF("l", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_L) MLE_KEY_DEF("enter", 0, TB_MOD_CTRL, 0, TB_KEY_ENTER) MLE_KEY_DEF("m", TB_MOD_CTRL, 0, 0, TB_KEY_ENTER) MLE_KEY_DEF("n", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_N) MLE_KEY_DEF("o", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_O) MLE_KEY_DEF("p", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_P) MLE_KEY_DEF("q", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_Q) MLE_KEY_DEF("r", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_R) MLE_KEY_DEF("s", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_S) MLE_KEY_DEF("t", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_T) MLE_KEY_DEF("u", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_U) MLE_KEY_DEF("v", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_V) MLE_KEY_DEF("w", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_W) MLE_KEY_DEF("x", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_X) MLE_KEY_DEF("y", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_Y) MLE_KEY_DEF("z", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_Z) MLE_KEY_DEF("4", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_4) MLE_KEY_DEF("\\", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_4) MLE_KEY_DEF("5", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_5) MLE_KEY_DEF("6", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_6) MLE_KEY_DEF("7", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_7) MLE_KEY_DEF("/", TB_MOD_CTRL, 0, 0, TB_KEY_CTRL_SLASH) MLE_KEY_DEF("backspace", 0, TB_MOD_CTRL, 0, TB_KEY_CTRL_8) MLE_KEY_DEF("escape", 0, 0, 0, TB_KEY_ESC) MLE_KEY_DEF("space", 0, 0, 32, 0) MLE_KEY_DEF("comma", 0, 0, 44, 0) MLE_KEY_DEF("up", 0, 0, 0, TB_KEY_ARROW_UP) MLE_KEY_DEF("down", 0, 0, 0, TB_KEY_ARROW_DOWN) MLE_KEY_DEF("left", 0, 0, 0, TB_KEY_ARROW_LEFT) MLE_KEY_DEF("right", 0, 0, 0, TB_KEY_ARROW_RIGHT) MLE_KEY_DEF("insert", 0, 0, 0, TB_KEY_INSERT) MLE_KEY_DEF("delete", 0, 0, 0, TB_KEY_DELETE) MLE_KEY_DEF("home", 0, 0, 0, TB_KEY_HOME) MLE_KEY_DEF("end", 0, 0, 0, TB_KEY_END) MLE_KEY_DEF("pgup", 0, 0, 0, TB_KEY_PGUP) MLE_KEY_DEF("pgdn", 0, 0, 0, TB_KEY_PGDN) MLE_KEY_DEF("backtab", 0, 0, 0, TB_KEY_BACK_TAB) MLE_KEY_DEF("f1", 0, 0, 0, TB_KEY_F1) MLE_KEY_DEF("f2", 0, 0, 0, TB_KEY_F2) MLE_KEY_DEF("f3", 0, 0, 0, TB_KEY_F3) MLE_KEY_DEF("f4", 0, 0, 0, TB_KEY_F4) MLE_KEY_DEF("f5", 0, 0, 0, TB_KEY_F5) MLE_KEY_DEF("f6", 0, 0, 0, TB_KEY_F6) MLE_KEY_DEF("f7", 0, 0, 0, TB_KEY_F7) MLE_KEY_DEF("f8", 0, 0, 0, TB_KEY_F8) MLE_KEY_DEF("f9", 0, 0, 0, TB_KEY_F9) MLE_KEY_DEF("f10", 0, 0, 0, TB_KEY_F10) MLE_KEY_DEF("f11", 0, 0, 0, TB_KEY_F11) MLE_KEY_DEF("f12", 0, 0, 0, TB_KEY_F12) mle-1.7.2/main.c000066400000000000000000000006611443351614700134010ustar00rootroot00000000000000#include #include #include #include #include #include "termbox2.h" #include "mlbuf.h" #include "mle.h" editor_t _editor; int main(int argc, char **argv) { memset(&_editor, 0, sizeof(editor_t)); setlocale(LC_ALL, ""); if (editor_init(&_editor, argc, argv) == MLE_OK) { editor_run(&_editor); } editor_deinit(&_editor); return _editor.exit_code; } mle-1.7.2/mark.c000066400000000000000000001026301443351614700134060ustar00rootroot00000000000000#include #include #include #include #include "mlbuf.h" static int mark_block_x_between(mark_t *self, mark_t *other, int del, char **optret_str, bint_t *optret_str_len); typedef char* (*mark_find_match_fn)(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *u1, void *u2, bint_t *ret_needle_len); static int mark_find_match(mark_t *self, mark_find_match_fn matchfn, void *u1, void *u2, int reverse, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars); static int mark_find_re(mark_t *self, char *re, bint_t re_len, int reverse, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars); static char *mark_find_match_prev(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, mark_find_match_fn matchfn, void *u1, void *u2); static char *mark_find_next_str_matchfn(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *needle, void *needle_len, bint_t *ret_needle_len); static char *mark_find_prev_str_matchfn(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *needle, void *needle_len, bint_t *ret_needle_len); static char *mark_find_next_cre_matchfn(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *cre, void *unused, bint_t *ret_needle_len); static char *mark_find_prev_cre_matchfn(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *cre, void *unused, bint_t *ret_needle_len); pcre2_match_data *pcre2_md = NULL; static PCRE2_SIZE *pcre_ovector = NULL; static int pcre_ovector_size = 0; static int *pcre_rc; static char bracket_pairs[8] = { '[', ']', '(', ')', '{', '}' }; // Return a clone (same position) of an existing mark int mark_clone(mark_t *self, mark_t **ret_mark) { *ret_mark = buffer_add_mark(self->bline->buffer, self->bline, self->col); return MLBUF_OK; } // Return a lettered clone (same position) of an existing mark int mark_clone_w_letter(mark_t *self, char letter, mark_t **ret_mark) { *ret_mark = buffer_add_mark_ex(self->bline->buffer, letter, self->bline, self->col); return MLBUF_OK; } // Insert data before mark int mark_insert_before(mark_t *self, char *data, bint_t data_len) { return bline_insert(self->bline, self->col, data, data_len, NULL); } // Insert data after mark int mark_insert_after(mark_t *self, char *data, bint_t data_len) { int rc; bint_t num_chars; if ((rc = bline_insert(self->bline, self->col, data, data_len, &num_chars)) == MLBUF_OK) { rc = mark_move_by(self, -1 * num_chars); } return rc; } // Delete data after mark int mark_delete_after(mark_t *self, bint_t num_chars) { return bline_delete(self->bline, self->col, num_chars); } // Delete data before mark int mark_delete_before(mark_t *self, bint_t num_chars) { int rc; if ((rc = mark_move_by(self, -1 * num_chars)) == MLBUF_OK) { rc = mark_delete_after(self, num_chars); } return rc; } // Replace data int mark_replace(mark_t *self, bint_t num_chars, char *data, bint_t data_len) { return bline_replace(self->bline, self->col, num_chars, data, data_len); } // Replace data between marks int mark_replace_between(mark_t *self, mark_t *other, char *data, bint_t data_len) { bint_t offset_a; bint_t offset_b; // TODO More efficient buffer_replace_w_bline_and_end buffer_get_offset(self->bline->buffer, self->bline, self->col, &offset_a); buffer_get_offset(other->bline->buffer, other->bline, other->col, &offset_b); if (offset_a < offset_b) { return buffer_replace(self->bline->buffer, offset_a, offset_b - offset_a, data, data_len); } return buffer_replace(self->bline->buffer, offset_b, offset_a - offset_b, data, data_len); } // Move mark to bline:col int mark_move_to_w_bline(mark_t *self, bline_t *bline, bint_t col) { _mark_mark_move_inner(self, bline, col, 1); return MLBUF_OK; } // Move mark to line_index:col int mark_move_to(mark_t *self, bint_t line_index, bint_t col) { bline_t *bline; buffer_get_bline_w_hint(self->bline->buffer, line_index, self->bline, &bline); _mark_mark_move_inner(self, bline, col, 1); return MLBUF_OK; } // Move mark by a character delta int mark_move_by(mark_t *self, bint_t char_delta) { bint_t offset; buffer_get_offset(self->bline->buffer, self->bline, self->col, &offset); return mark_move_offset(self, offset + char_delta); } // Get mark offset int mark_get_offset(mark_t *self, bint_t *ret_offset) { return buffer_get_offset(self->bline->buffer, self->bline, self->col, ret_offset); } // Move mark by line delta int mark_move_vert(mark_t *self, bint_t line_delta) { bline_t *cur_line; bline_t *tmp_line; cur_line = self->bline; while (line_delta != 0) { tmp_line = line_delta > 0 ? cur_line->next : cur_line->prev; if (!tmp_line) { break; } cur_line = tmp_line; line_delta = line_delta + (line_delta > 0 ? -1 : 1); } if (cur_line == self->bline) { return MLBUF_OK; } _mark_mark_move_inner(self, cur_line, self->target_col, 0); return MLBUF_OK; } // Move mark to beginning of line int mark_move_bol(mark_t *self) { _mark_mark_move_inner(self, self->bline, 0, 1); return MLBUF_OK; } // Move mark to end of line int mark_move_eol(mark_t *self) { MLBUF_BLINE_ENSURE_CHARS(self->bline); _mark_mark_move_inner(self, self->bline, self->bline->char_count, 1); return MLBUF_OK; } // Move mark to a column on the current line int mark_move_col(mark_t *self, bint_t col) { _mark_mark_move_inner(self, self->bline, col, 1); return MLBUF_OK; } // Move mark to beginning of buffer int mark_move_beginning(mark_t *self) { _mark_mark_move_inner(self, self->bline->buffer->first_line, 0, 1); return MLBUF_OK; } // Move mark to end of buffer int mark_move_end(mark_t *self) { MLBUF_BLINE_ENSURE_CHARS(self->bline->buffer->last_line); _mark_mark_move_inner(self, self->bline->buffer->last_line, self->bline->buffer->last_line->char_count, 1); return MLBUF_OK; } // Move mark to a particular offset int mark_move_offset(mark_t *self, bint_t offset) { bline_t *dest_line; bint_t dest_col; buffer_get_bline_col(self->bline->buffer, offset, &dest_line, &dest_col); _mark_mark_move_inner(self, dest_line, dest_col, 1); return MLBUF_OK; } // Find next occurrence of string from mark int mark_find_next_str(mark_t *self, char *str, bint_t str_len, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars) { return mark_find_match(self, mark_find_next_str_matchfn, (void*)str, (void*)&str_len, 0, ret_line, ret_col, ret_num_chars); } // Find prev occurrence of string from mark int mark_find_prev_str(mark_t *self, char *str, bint_t str_len, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars) { return mark_find_match(self, mark_find_prev_str_matchfn, (void*)str, (void*)&str_len, 1, ret_line, ret_col, ret_num_chars); } // Find next occurence of regex from mark int mark_find_next_cre(mark_t *self, pcre2_code *cre, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars) { return mark_find_match(self, mark_find_next_cre_matchfn, (void*)cre, NULL, 0, ret_line, ret_col, ret_num_chars); } // Find prev occurence of regex from mark int mark_find_prev_cre(mark_t *self, pcre2_code *cre, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars) { return mark_find_match(self, mark_find_prev_cre_matchfn, (void*)cre, NULL, 1, ret_line, ret_col, ret_num_chars); } // Find next occurence of uncompiled regex str from mark int mark_find_next_re(mark_t *self, char *re, bint_t re_len, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars) { return mark_find_re(self, re, re_len, 0, ret_line, ret_col, ret_num_chars); } // Find prev occurence of uncompiled regex str from mark int mark_find_prev_re(mark_t *self, char *re, bint_t re_len, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars) { return mark_find_re(self, re, re_len, 1, ret_line, ret_col, ret_num_chars); } // Return 1 if self is before other, otherwise return 0 int mark_is_lt(mark_t *self, mark_t *other) { if (self->bline->line_index == other->bline->line_index) { return self->col < other->col ? 1 : 0; } else if (self->bline->line_index < other->bline->line_index) { return 1; } return 0; } // Return 1 if self is past other, otherwise return 0 int mark_is_gt(mark_t *self, mark_t *other) { if (self->bline->line_index == other->bline->line_index) { return self->col > other->col ? 1 : 0; } else if (self->bline->line_index > other->bline->line_index) { return 1; } return 0; } // Return 1 if self is at same position as other, otherwise return 0 int mark_is_eq(mark_t *self, mark_t *other) { if (self->bline->line_index == other->bline->line_index) { return self->col == other->col ? 1 : 0; } return 0; } // Return 1 if self >= other int mark_is_gte(mark_t *self, mark_t *other) { return !mark_is_lt(self, other); } // Return 1 if self <= other int mark_is_lte(mark_t *self, mark_t *other) { return !mark_is_gt(self, other); } // Return 1 if self is between a and b int mark_is_between(mark_t *self, mark_t *ma, mark_t *mb) { mark_t *a, *b; mark_cmp(ma, mb, &a, &b); return mark_is_gte(self, a) && mark_is_lt(self, b) ? 1 : 0; } // Find top-level bracket to the left examining no more than max_chars int mark_find_bracket_top(mark_t *self, bint_t max_chars, bline_t **ret_line, bint_t *ret_col, bint_t *ret_brkt) { bline_t *cur_line; bint_t col; int *stacks; int found; int i; int i_left; cur_line = self->bline; col = self->col; stacks = calloc(128, sizeof(int)); found = 0; while (!found && max_chars > 0 && cur_line) { MLBUF_BLINE_ENSURE_CHARS(cur_line); col -= 1; if (col < 0) { cur_line = cur_line->prev; if (cur_line) col = cur_line->char_count; max_chars -= 1; continue; } for (i = 0; i < 8; i++) { i_left = (i % 2 == 0 ? i : i - 1); if (cur_line->chars[col].ch == (uint32_t)bracket_pairs[i]) { stacks[(int)bracket_pairs[i_left]] += (i % 2 == 0 ? -1 : 1); if (stacks[(int)bracket_pairs[i_left]] <= -1) { *ret_line = cur_line; *ret_col = col; *ret_brkt = bracket_pairs[i]; found = 1; } break; } } } free(stacks); return found ? MLBUF_OK : MLBUF_ERR; } // Find the matching bracket character under the mark, examining no more than // max_chars. int mark_find_bracket_pair(mark_t *self, bint_t max_chars, bline_t **ret_line, bint_t *ret_col, bint_t *ret_brkt) { char brkt; char targ; char cur; int dir; int i; int nest; bint_t col; bint_t nchars; bline_t *cur_line; MLBUF_BLINE_ENSURE_CHARS(self->bline); // If we're at eol, there's nothing to match if (self->col >= self->bline->char_count) { return MLBUF_ERR; } // Set brkt to char under mark brkt = *(self->bline->data + self->bline->chars[self->col].index); // Find targ matching bracket char targ = 0; for (i = 0; i < 8; i++) { if (bracket_pairs[i] == brkt) { if (i % 2 == 0) { targ = bracket_pairs[i + 1]; dir = 1; } else { targ = bracket_pairs[i - 1]; dir = -1; } break; } } // If targ is not set, brkt was not a bracket char if (!targ) { return MLBUF_ERR; } // Now look for targ, keeping track of nesting // Break if we look at more than max_chars nest = -1; cur_line = self->bline; col = self->col; nchars = 0; while (cur_line) { MLBUF_BLINE_ENSURE_CHARS(cur_line); for (; col >= 0 && col < cur_line->char_count; col += dir) { cur = *(cur_line->data + cur_line->chars[col].index); if (cur == targ) { if (nest == 0) { // Match! *ret_line = cur_line; *ret_col = col; *ret_brkt = targ; return MLBUF_OK; } else { nest -= 1; } } else if (cur == brkt) { nest += 1; } nchars += 1; if (nchars >= max_chars) { return MLBUF_ERR; } } if (dir > 0) { cur_line = cur_line->next; if (cur_line) col = 0; } else { cur_line = cur_line->prev; if (cur_line) col = MLBUF_MAX(1, cur_line->char_count) - 1; } } // If we got here, targ was not found, or nesting was off return MLBUF_ERR; } // Delete data between self and other int mark_delete_between(mark_t *self, mark_t *other) { bint_t offset_a; bint_t offset_b; // TODO More efficient buffer_replace_w_bline_and_end buffer_get_offset(self->bline->buffer, self->bline, self->col, &offset_a); buffer_get_offset(other->bline->buffer, other->bline, other->col, &offset_b); if (offset_a == offset_b) { return MLBUF_OK; } else if (offset_a > offset_b) { return buffer_delete(self->bline->buffer, offset_b, offset_a - offset_b); } return buffer_delete(self->bline->buffer, offset_a, offset_b - offset_a); } // Return data between self and other int mark_get_between(mark_t *self, mark_t *other, char **ret_str, bint_t *ret_str_len) { bint_t ig; mark_t *a, *b; if (mark_cmp(self, other, &a, &b) == 0) { *ret_str = strdup(""); *ret_str_len = 0; return MLBUF_OK; } return buffer_substr( a->bline->buffer, a->bline, a->col, b->bline, b->col, ret_str, ret_str_len, &ig ); } // Return num chars between two marks int mark_get_nchars_between(mark_t *self, mark_t *other, bint_t *ret_nchars) { mark_t *a, *b; bline_t *bline; bint_t col, nchars; if (mark_cmp(self, other, &a, &b) == 0) { *ret_nchars = 0; return MLBUF_OK; } bline = a->bline; col = a->col; nchars = 0; while (bline != b->bline) { MLBUF_BLINE_ENSURE_CHARS(bline); nchars += (bline->char_count - col) + 1; bline = bline->next; col = 0; if (!bline) { *ret_nchars = 0; return MLBUF_ERR; } } nchars += (b->col - col); *ret_nchars = nchars; return MLBUF_OK; } // Move self to other int mark_join(mark_t *self, mark_t *other) { _mark_mark_move_inner(self, other->bline, other->col, 1); return MLBUF_OK; } // Swap positions of self and other int mark_swap(mark_t *self, mark_t *other) { mark_t tmp_mark; tmp_mark.bline = other->bline; tmp_mark.col = other->col; _mark_mark_move_inner(other, self->bline, self->col, 1); _mark_mark_move_inner(self, tmp_mark.bline, tmp_mark.col, 1); return MLBUF_OK; } // Return 1 if mark is at eol, else return 0 int mark_is_at_eol(mark_t *self) { MLBUF_BLINE_ENSURE_CHARS(self->bline); return self->col >= self->bline->char_count ? 1 : 0; } // Return 1 if mark is at bol, else return 0 int mark_is_at_bol(mark_t *self) { return self->col <= 0; } // Destroy a mark int mark_destroy(mark_t *self) { return buffer_destroy_mark(self->bline->buffer, self); } #define MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(mark, findfn, ...) do { \ int rc; \ bline_t *line = NULL; \ bint_t col = 0; \ bint_t char_count = 0; \ if ((rc = (findfn)((mark), __VA_ARGS__, &line, &col, &char_count)) == MLBUF_OK) { \ _mark_mark_move_inner((mark), line, col, 1); \ if (optret_line) *optret_line = line; \ if (optret_col) *optret_col = col; \ if (optret_char_count) *optret_char_count = char_count; \ return MLBUF_OK; \ } \ return rc; \ } while(0) #define MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(mark, findfn, ...) do { \ int rc; \ bline_t *line = NULL; \ bint_t col = 0; \ bint_t char_count = 0; \ if ((rc = (findfn)((mark), __VA_ARGS__, &line, &col, &char_count)) == MLBUF_OK) { \ _mark_mark_move_inner((mark), line, col, 1); \ } \ return rc; \ } while(0) #define MLBUF_MARK_IMPLEMENT_NUDGE_VIA_FIND(mark, findfn, ...) do { \ int rc; \ bline_t *line = NULL; \ bint_t col = 0; \ bint_t char_count = 0; \ mark_t *tmark = NULL; \ mark_clone((mark), &tmark); \ mark_move_by(tmark, 1); \ if ((rc = (findfn)(tmark, __VA_ARGS__, &line, &col, &char_count)) == MLBUF_OK) { \ _mark_mark_move_inner((mark), line, col, 1); \ } \ mark_destroy(tmark); \ return rc; \ } while(0) int mark_move_next_str(mark_t *self, char *str, bint_t str_len) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(self, mark_find_next_str, str, str_len); } int mark_move_prev_str(mark_t *self, char *str, bint_t str_len) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(self, mark_find_prev_str, str, str_len); } int mark_move_next_cre(mark_t *self, pcre2_code *cre) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(self, mark_find_next_cre, cre); } int mark_move_prev_cre(mark_t *self, pcre2_code *cre) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(self, mark_find_prev_cre, cre); } int mark_move_next_re(mark_t *self, char *re, bint_t re_len) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(self, mark_find_next_re, re, re_len); } int mark_move_prev_re(mark_t *self, char *re, bint_t re_len) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(self, mark_find_prev_re, re, re_len); } int mark_move_next_str_nudge(mark_t *self, char *str, bint_t str_len) { MLBUF_MARK_IMPLEMENT_NUDGE_VIA_FIND(self, mark_find_next_str, str, str_len); } int mark_move_next_cre_nudge(mark_t *self, pcre2_code *cre) { MLBUF_MARK_IMPLEMENT_NUDGE_VIA_FIND(self, mark_find_next_cre, cre); } int mark_move_next_re_nudge(mark_t *self, char *re, bint_t re_len) { MLBUF_MARK_IMPLEMENT_NUDGE_VIA_FIND(self, mark_find_next_re, re, re_len); } int mark_move_bracket_pair(mark_t *self, bint_t max_chars) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(self, mark_find_bracket_pair, max_chars); } int mark_move_bracket_top(mark_t *self, bint_t max_chars) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND(self, mark_find_bracket_top, max_chars); } int mark_move_next_str_ex(mark_t *self, char *str, bint_t str_len, bline_t **optret_line, bint_t *optret_col, bint_t *optret_char_count) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(self, mark_find_next_str, str, str_len); } int mark_move_prev_str_ex(mark_t *self, char *str, bint_t str_len, bline_t **optret_line, bint_t *optret_col, bint_t *optret_char_count) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(self, mark_find_prev_str, str, str_len); } int mark_move_next_cre_ex(mark_t *self, pcre2_code *cre, bline_t **optret_line, bint_t *optret_col, bint_t *optret_char_count) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(self, mark_find_next_cre, cre); } int mark_move_prev_cre_ex(mark_t *self, pcre2_code *cre, bline_t **optret_line, bint_t *optret_col, bint_t *optret_char_count) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(self, mark_find_prev_cre, cre); } int mark_move_next_re_ex(mark_t *self, char *re, bint_t re_len, bline_t **optret_line, bint_t *optret_col, bint_t *optret_char_count) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(self, mark_find_next_re, re, re_len); } int mark_move_prev_re_ex(mark_t *self, char *re, bint_t re_len, bline_t **optret_line, bint_t *optret_col, bint_t *optret_char_count) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(self, mark_find_prev_re, re, re_len); } int mark_move_bracket_pair_ex(mark_t *self, bint_t max_chars, bline_t **optret_line, bint_t *optret_col, bint_t *optret_char_count) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(self, mark_find_bracket_pair, max_chars); } int mark_move_bracket_top_ex(mark_t *self, bint_t max_chars, bline_t **optret_line, bint_t *optret_col, bint_t *optret_char_count) { MLBUF_MARK_IMPLEMENT_MOVE_VIA_FIND_EX(self, mark_find_bracket_top, max_chars); } // Return 1 if mark is at a word boundary. If side <= -1, return 1 only for // left word boundary (i.e., \W\w). If side >= 1, return 1 only for right word // boundary (i.e., \w\W). If side == 0, return 1 for either case. int mark_is_at_word_bound(mark_t *self, int side) { uint32_t before, after; MLBUF_BLINE_ENSURE_CHARS(self->bline); before = self->col > 0 && self->col - 1 < self->bline->char_count ? self->bline->chars[self->col - 1].ch : 0; after = self->col < self->bline->char_count ? self->bline->chars[self->col].ch : 0; if (side <= -1 || side == 0) { // If before is bol or non-word, and after is word if ((before == 0 || !(isalnum(before) || before == '_')) && (isalnum(after) || after == '_') ) { return 1; } } if (side >= 1 || side == 0) { // If after is eol or non-word, and before is word if ((after == 0 || !(isalnum(after) || after == '_')) && (isalnum(before) || before == '_') ) { return 1; } } return 0; } // Set ovector for capturing substrs int mark_set_pcre_capture(int *rc, PCRE2_SIZE *ovector, int ovector_size) { if (rc == NULL || ovector == NULL || ovector_size == 0) { rc = NULL; pcre_ovector = NULL; pcre_ovector_size = 0; return MLBUF_OK; } else if (rc != NULL && ovector != NULL && ovector_size >= 3 && ovector_size % 3 == 0) { pcre_rc = rc; pcre_ovector = ovector; pcre_ovector_size = ovector_size; return MLBUF_OK; } pcre_rc = NULL; pcre_ovector = NULL; pcre_ovector_size = 0; return MLBUF_ERR; } // Return char after mark, or 0 if at eol. int mark_get_char_after(mark_t *self, uint32_t *ret_char) { if (mark_is_at_eol(self)) { *ret_char = 0; } else { MLBUF_BLINE_ENSURE_CHARS(self->bline); *ret_char = self->bline->chars[self->col].ch; } return MLBUF_OK; } // Return char before mark, or 0 if at bol. int mark_get_char_before(mark_t *self, uint32_t *ret_char) { if (mark_is_at_bol(self)) { *ret_char = 0; } else { MLBUF_BLINE_ENSURE_CHARS(self->bline); *ret_char = self->bline->chars[self->col - 1].ch; } return MLBUF_OK; } // Return 1 if mark is after col, else 0. Lefty marks are considered 'after' col // if `mark->col > col`. Righty marks (the default) are considered 'after' col // if `mark->col >= col`. int mark_is_after_col_minus_lefties(mark_t *self, bint_t col) { if (self->lefty) { return self->col > col ? 1 : 0; } return self->col >= col ? 1 : 0; } // Sort marks a and b as optret_first and optret_second // Return -1 if a comes before b // 1 if a comes after b // 0 if a and b are at the same position int mark_cmp(mark_t *a, mark_t *b, mark_t **optret_first, mark_t **optret_second) { int retval; mark_t *first, *second; if (mark_is_gt(b, a)) { first = a; second = b; retval = -1; } else if (mark_is_gt(a, b)) { first = b; second = a; retval = 1; } else { first = a; second = b; retval = 0; } if (optret_first) *optret_first = first; if (optret_second) *optret_second = second; return retval; } int mark_block_insert_before(mark_t *self, char *data, bint_t data_len) { char *data_cur, *data_nl; bint_t data_rem_len, line_len, char_len, del_len, prefix_col; mark_t *m; m = self; prefix_col = m->col; data_cur = data; data_rem_len = data_len; while (1) { // Get line of data data_nl = memchr(data_cur, '\n', data_rem_len); line_len = data_nl ? (bint_t)(data_nl - data_cur) : data_rem_len; char_len = utf8_str_length(data_cur, line_len); // Replace up to char_len-worth of line del_len = MLBUF_MIN(m->bline->char_count - m->col, char_len); buffer_replace_w_bline(m->bline->buffer, m->bline, m->col, del_len, data_cur, line_len); // Advance if (!data_nl) break; data_cur = data_nl + 1; data_rem_len -= line_len + 1; // Ensure next line exists if (m->bline->next) { mark_move_to_w_bline(m, m->bline->next, 0); } else { mark_move_eol(m); mark_insert_before(m, "\n", 1); } // Ensure mark at prefix_col if (m->bline->char_count < prefix_col) { mark_move_eol(m); while (m->bline->char_count < prefix_col) mark_insert_before(m, " ", 1); } _mark_mark_move_inner(m, m->bline, prefix_col, 0); } return MLBUF_OK; } int mark_block_get_between(mark_t *self, mark_t *other, char **ret_str, bint_t *ret_str_len) { return mark_block_x_between(self, other, 0, ret_str, ret_str_len); } int mark_block_delete_between(mark_t *self, mark_t *other) { return mark_block_x_between(self, other, 1, NULL, NULL); } int mark_block_is_between(mark_t *self, mark_t *ma, mark_t *mb) { mark_t *a, *b; bint_t s, e; mark_cmp(ma, mb, &a, &b); s = MLBUF_MIN(a->col, b->col); e = MLBUF_MAX(a->col, b->col); return ( self->bline->line_index >= a->bline->line_index && self->bline->line_index <= b->bline->line_index && self->col >= s && self->col < e ) ? 1 : 0; } int mark_block_get_top_left(mark_t *self, mark_t *other, bline_t **ret_bline, bint_t *ret_col) { mark_t *a, *b; mark_cmp(self, other, &a, &b); *ret_bline = a->bline; *ret_col = MLBUF_MIN(a->col, b->col); return MLBUF_OK; } static int mark_block_x_between(mark_t *self, mark_t *other, int del, char **optret_str, bint_t *optret_str_len) { str_t buf = {0}; char *s; bint_t slen, start_col, end_col, end_col_adj, ig; bline_t *bline; mark_t *a, *b; if (mark_cmp(self, other, &a, &b) == 0 || a->col == b->col) { if (!del) { *optret_str = strdup(""); *optret_str_len = 0; } return MLBUF_OK; } start_col = MLBUF_MIN(a->col, b->col); end_col = MLBUF_MAX(a->col, b->col); for (bline = a->bline; bline != b->bline->next; bline = bline->next) { if (start_col < bline->char_count) { end_col_adj = MLBUF_MIN(end_col, bline->char_count); if (del) { while (end_col_adj - start_col > (bint_t)buf.len) str_append_char(&buf, ' '); buffer_replace_w_bline(bline->buffer, bline, start_col, end_col_adj - start_col, buf.data, end_col_adj - start_col); } else { buffer_substr(bline->buffer, bline, start_col, bline, end_col_adj, &s, &slen, &ig); if (slen > 0) str_append_len(&buf, s, slen); } } if (!del && bline != b->bline->next) { str_append_char(&buf, '\n'); } } if (del) { str_free(&buf); } else { *optret_str = buf.data; *optret_str_len = buf.len; } return MLBUF_OK; } // Find first occurrence of match according to matchfn. Search backwards if // reverse is truthy. static int mark_find_match(mark_t *self, mark_find_match_fn matchfn, void *u1, void *u2, int reverse, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars) { bline_t *search_line = NULL; char *match = NULL; bint_t look_offset = 0; bint_t match_col = 0; bint_t match_col_end = 0; bint_t max_offset = 0; bint_t match_len = 0; search_line = self->bline; *ret_line = NULL; if (reverse) { if (self->col <= 0) { // At bol, so look on prev line search_line = search_line->prev; if (!search_line) return MLBUF_ERR; look_offset = 0; max_offset = search_line->data_len; } else { look_offset = 0; MLBUF_BLINE_ENSURE_CHARS(search_line); max_offset = search_line->chars[self->col - 1].index; } } else { MLBUF_BLINE_ENSURE_CHARS(search_line); if (self->col >= search_line->char_count) { // At eol, so look on next line search_line = search_line->next; if (!search_line) return MLBUF_ERR; look_offset = 0; max_offset = search_line->data_len; } else { look_offset = self->col < search_line->char_count ? search_line->chars[self->col].index : search_line->data_len; max_offset = search_line->data_len; } } while (search_line) { match = matchfn(search_line->data, search_line->data_len, look_offset, max_offset, u1, u2, &match_len); if (match != NULL) { bline_get_col(search_line, (bint_t)(match - search_line->data), &match_col); bline_get_col(search_line, (bint_t)((match + match_len) - search_line->data), &match_col_end); *ret_line = search_line; *ret_col = match_col; *ret_num_chars = match_col_end - match_col; return MLBUF_OK; } search_line = reverse ? search_line->prev : search_line->next; if (search_line) { look_offset = 0; max_offset = search_line->data_len; } } return MLBUF_ERR; } // Move mark to target:col, setting target_col if do_set_target is truthy void _mark_mark_move_inner(mark_t *mark, bline_t *bline_target, bint_t col, int do_set_target) { int is_changing_line; is_changing_line = mark->bline != bline_target ? 1 : 0; if (is_changing_line) { DL_DELETE(mark->bline->marks, mark); mark->bline = bline_target; } MLBUF_BLINE_ENSURE_CHARS(mark->bline); mark->col = MLBUF_MIN(mark->bline->char_count, MLBUF_MAX(0, col)); if (do_set_target) { mark->target_col = mark->col; } if (is_changing_line) { DL_APPEND(bline_target->marks, mark); } } // Return the last occurrence of a match given a forward-searching matchfn static char *mark_find_match_prev(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, mark_find_match_fn matchfn, void *u1, void *u2) { char *match; char *last_match; bint_t match_len; last_match = NULL; while (1) { match = matchfn(haystack, haystack_len, look_offset, max_offset, u1, u2, &match_len); if (match == NULL) { return last_match; } if (match - haystack > max_offset) { return last_match; } // Override match_len to 1. Reasoning: If we have a haystack like // 'banana' and our re is 'ana', using the actual match_len for the // next search offset would skip the 2nd 'ana' match. match_len = 1; look_offset = (bint_t)(match - haystack) + match_len; if (look_offset + match_len > haystack_len) { return match; } last_match = match; } } // Find uncompiled regex from mark. Search backwards if reverse is truthy. static int mark_find_re(mark_t *self, char *re, bint_t re_len, int reverse, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars) { int rc; char *regex; pcre2_code *cre; int errcode; PCRE2_SIZE erroffset; MLBUF_MAKE_GT_EQ0(re_len); regex = malloc(re_len + 1); snprintf(regex, re_len + 1, "%s", re); cre = pcre2_compile((PCRE2_SPTR)regex, (PCRE2_SIZE)strlen(regex), PCRE2_CASELESS, &errcode, &erroffset, NULL); if (cre == NULL) { // TODO log error free(regex); return MLBUF_ERR; } if (reverse) { rc = mark_find_prev_cre(self, cre, ret_line, ret_col, ret_num_chars); } else { rc = mark_find_next_cre(self, cre, ret_line, ret_col, ret_num_chars); } pcre2_code_free(cre); free(regex); return rc; } static char *mark_find_next_str_matchfn(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *needle, void *needle_len, bint_t *ret_needle_len) { if (ret_needle_len) *ret_needle_len = *((bint_t*)needle_len); if (look_offset >= haystack_len) return NULL; return memmem(haystack + look_offset, haystack_len - look_offset, needle, *((bint_t*)needle_len)); } static char *mark_find_prev_str_matchfn(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *needle, void *needle_len, bint_t *ret_needle_len) { return mark_find_match_prev(haystack, haystack_len, look_offset, max_offset, mark_find_next_str_matchfn, needle, needle_len); } static char *mark_find_next_cre_matchfn(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *cre, void *unused, bint_t *ret_needle_len) { int local_rc; PCRE2_SIZE local_ovector[3]; int *rc; PCRE2_SIZE *ovector; int ovector_count; if (!haystack || haystack_len == 0) { haystack = ""; haystack_len = 0; } if (pcre_ovector) { ovector = pcre_ovector; ovector_count = pcre_ovector_size; rc = pcre_rc; } else { ovector = local_ovector; ovector_count = 3; rc = &local_rc; } if ((*rc = pcre2_match((pcre2_code *)cre, (PCRE2_SPTR)haystack, (PCRE2_SIZE)haystack_len, (PCRE2_SIZE)look_offset, 0, pcre2_md, NULL)) >= 0) { ovector_count = MLBUF_MIN((int)(pcre2_get_ovector_count(pcre2_md) * 2), ovector_count); memcpy(ovector, pcre2_get_ovector_pointer(pcre2_md), ovector_count * sizeof(PCRE2_SIZE)); if (ret_needle_len) *ret_needle_len = (bint_t)(ovector[1] - ovector[0]); return haystack + ovector[0]; } return NULL; } static char *mark_find_prev_cre_matchfn(char *haystack, bint_t haystack_len, bint_t look_offset, bint_t max_offset, void *cre, void *unused, bint_t *ret_needle_len) { return mark_find_match_prev(haystack, haystack_len, look_offset, max_offset, mark_find_next_cre_matchfn, cre, unused); } mle-1.7.2/mlbuf.h000066400000000000000000000356521443351614700135770ustar00rootroot00000000000000#ifndef __MLBUF_H #define __MLBUF_H #include #include #include #include #include #include #include // Typedefs typedef struct buffer_s buffer_t; // A buffer of text (stored as a linked list of blines) typedef struct bline_s bline_t; // A line in a buffer typedef struct bline_char_s bline_char_t; // Metadata about a character in a bline typedef struct baction_s baction_t; // An insert or delete action (used for undo) typedef struct mark_s mark_t; // A mark in a buffer typedef struct srule_s srule_t; // A style rule typedef struct srule_node_s srule_node_t; // A node in a list of style rules typedef struct sblock_s sblock_t; // A style of a particular character typedef struct smemo_s smemo_t; // A memoization of pcre2_match typedef struct str_s str_t; // A dynamically resizeable string typedef void (*buffer_callback_t)(buffer_t *buffer, baction_t *action, void *udata); typedef intmax_t bint_t; // str_t struct str_s { char *data; size_t len; size_t cap; ssize_t inc; }; // buffer_t struct buffer_s { bline_t *first_line; bline_t *last_line; bint_t byte_count; bint_t line_count; srule_node_t *srules; srule_node_t *range_srules; baction_t *actions; baction_t *action_tail; baction_t *action_undone; str_t registers[26]; mark_t *lettered_marks[26]; char *path; struct stat st; int is_unsaved; char *data; bint_t data_len; int is_data_dirty; int ref_count; int tab_width; buffer_callback_t callback; void *callback_udata; int mmap_fd; char *mmap; size_t mmap_len; bline_char_t *slabbed_chars; bline_t *slabbed_blines; int *action_group; int num_applied_srules; int is_in_open; int is_in_callback; int is_style_disabled; int _is_in_undo; }; // bline_t struct bline_s { buffer_t *buffer; char *data; bint_t data_len; bint_t data_cap; bint_t line_index; bint_t char_count; bint_t char_vwidth; bline_char_t *chars; bint_t chars_cap; mark_t *marks; srule_t *eol_rule; int is_chars_dirty; int is_slabbed; int is_data_slabbed; bline_t *next; bline_t *prev; }; // sblock_t struct sblock_s { uint16_t fg; uint16_t bg; }; // bline_char_t struct bline_char_s { uint32_t ch; int len; bint_t index; bint_t vcol; bint_t index_to_vcol; // accessed via >chars[index], not >chars[char] sblock_t style; }; // baction_t struct baction_s { int type; // MLBUF_BACTION_TYPE_* buffer_t *buffer; bline_t *start_line; bint_t start_line_index; bint_t start_col; bline_t *maybe_end_line; bint_t maybe_end_line_index; bint_t maybe_end_col; bint_t byte_delta; bint_t char_delta; bint_t line_delta; int action_group; char *data; bint_t data_len; baction_t *next; baction_t *prev; }; // mark_t struct mark_s { bline_t *bline; bint_t col; bint_t target_col; srule_t *range_srule; char letter; mark_t *next; mark_t *prev; int lefty; }; // smemo_t struct smemo_s { int looked; bint_t look_offset; int found; bint_t start; bint_t stop; }; // srule_t struct srule_s { int type; // MLBUF_SRULE_TYPE_* char *re; char *re_end; pcre2_code *cre; pcre2_code *cre_end; mark_t *range_a; mark_t *range_b; sblock_t style; smemo_t memo; smemo_t memo_end; }; // srule_node_t struct srule_node_s { srule_t *srule; srule_node_t *next; srule_node_t *prev; }; // buffer functions buffer_t *buffer_new(); buffer_t *buffer_new_open(char *path); mark_t *buffer_add_mark(buffer_t *self, bline_t *maybe_line, bint_t maybe_col); mark_t *buffer_add_mark_ex(buffer_t *self, char letter, bline_t *maybe_line, bint_t maybe_col); int buffer_get_lettered_mark(buffer_t *self, char letter, mark_t **ret_mark); int buffer_destroy_mark(buffer_t *self, mark_t *mark); int buffer_open(buffer_t *self, char *path); int buffer_save(buffer_t *self); int buffer_save_as(buffer_t *self, char *path, bint_t *optret_nbytes); int buffer_write_to_file(buffer_t *self, FILE *fp, size_t *optret_nbytes); int buffer_write_to_fd(buffer_t *self, int fd, size_t *optret_nbytes); int buffer_get(buffer_t *self, char **ret_data, bint_t *ret_data_len); int buffer_clear(buffer_t *self); int buffer_set(buffer_t *self, char *data, bint_t data_len); int buffer_set_mmapped(buffer_t *self, char *data, bint_t data_len); int buffer_substr(buffer_t *self, bline_t *start_line, bint_t start_col, bline_t *end_line, bint_t end_col, char **ret_data, bint_t *ret_data_len, bint_t *ret_nchars); int buffer_insert(buffer_t *self, bint_t offset, char *data, bint_t data_len, bint_t *optret_num_chars); int buffer_delete(buffer_t *self, bint_t offset, bint_t num_chars); int buffer_replace(buffer_t *self, bint_t offset, bint_t num_chars, char *data, bint_t data_len); int buffer_insert_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, char *data, bint_t data_len, bint_t *optret_num_chars); int buffer_delete_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, bint_t num_chars); int buffer_replace_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, bint_t num_chars, char *data, bint_t data_len); int buffer_get_bline(buffer_t *self, bint_t line_index, bline_t **ret_bline); int buffer_get_bline_w_hint(buffer_t *self, bint_t line_index, bline_t *opt_hint, bline_t **ret_bline); int buffer_get_bline_col(buffer_t *self, bint_t offset, bline_t **ret_bline, bint_t *ret_col); int buffer_get_offset(buffer_t *self, bline_t *bline, bint_t col, bint_t *ret_offset); int buffer_undo(buffer_t *self); int buffer_redo(buffer_t *self); int buffer_undo_action_group(buffer_t *self); int buffer_redo_action_group(buffer_t *self); int buffer_add_srule(buffer_t *self, srule_t *srule); int buffer_remove_srule(buffer_t *self, srule_t *srule); int buffer_set_callback(buffer_t *self, buffer_callback_t fn_cb, void *udata); int buffer_set_action_group_ptr(buffer_t *self, int *action_group); int buffer_set_tab_width(buffer_t *self, int tab_width); int buffer_set_styles_enabled(buffer_t *self, int is_enabled); int buffer_apply_styles(buffer_t *self, bline_t *start_line, bint_t line_delta); int buffer_register_set(buffer_t *self, char reg, char *data, size_t data_len); int buffer_register_append(buffer_t *self, char reg, char *data, size_t data_len); int buffer_register_prepend(buffer_t *self, char reg, char *data, size_t data_len); int buffer_register_clear(buffer_t *self, char reg); int buffer_register_get(buffer_t *self, char reg, int dup, char **ret_data, size_t *ret_data_len); int buffer_destroy(buffer_t *self); // bline functions int bline_insert(bline_t *self, bint_t col, char *data, bint_t data_len, bint_t *ret_num_chars); int bline_delete(bline_t *self, bint_t col, bint_t num_chars); int bline_replace(bline_t *self, bint_t col, bint_t num_chars, char *data, bint_t data_len); int bline_get_col(bline_t *self, bint_t index, bint_t *ret_col); int bline_get_col_from_vcol(bline_t *self, bint_t vcol, bint_t *ret_col); int bline_index_to_col(bline_t *bline, bint_t index, bint_t *ret_col); int bline_count_chars(bline_t *bline); // mark functions int mark_block_delete_between(mark_t *self, mark_t *other); int mark_block_get_between(mark_t *self, mark_t *other, char **ret_str, bint_t *ret_str_len); int mark_block_get_top_left(mark_t *self, mark_t *other, bline_t **ret_bline, bint_t *ret_col); int mark_block_insert_before(mark_t *self, char *data, bint_t data_len); int mark_block_is_between(mark_t *self, mark_t *ma, mark_t *mb); int mark_clone(mark_t *self, mark_t **ret_mark); int mark_clone_w_letter(mark_t *self, char letter, mark_t **ret_mark); int mark_cmp(mark_t *a, mark_t *b, mark_t **optret_first, mark_t **optret_second); int mark_delete_after(mark_t *self, bint_t num_chars); int mark_delete_before(mark_t *self, bint_t num_chars); int mark_delete_between(mark_t *self, mark_t *other); int mark_destroy(mark_t *self); int mark_find_bracket_pair(mark_t *self, bint_t max_chars, bline_t **ret_line, bint_t *ret_col, bint_t *ret_brkt); int mark_find_bracket_top(mark_t *self, bint_t max_chars, bline_t **ret_line, bint_t *ret_col, bint_t *ret_brkt); int mark_find_next_cre(mark_t *self, pcre2_code *cre, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars); int mark_find_next_re(mark_t *self, char *re, bint_t re_len, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars); int mark_find_next_str(mark_t *self, char *str, bint_t str_len, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars); int mark_find_prev_cre(mark_t *self, pcre2_code *cre, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars); int mark_find_prev_re(mark_t *self, char *re, bint_t re_len, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars); int mark_find_prev_str(mark_t *self, char *str, bint_t str_len, bline_t **ret_line, bint_t *ret_col, bint_t *ret_num_chars); int mark_get_between(mark_t *self, mark_t *other, char **ret_str, bint_t *ret_str_len); int mark_get_char_after(mark_t *self, uint32_t *ret_char); int mark_get_char_before(mark_t *self, uint32_t *ret_char); int mark_get_nchars_between(mark_t *self, mark_t *other, bint_t *ret_nchars); int mark_get_offset(mark_t *self, bint_t *ret_offset); int mark_insert_after(mark_t *self, char *data, bint_t data_len); int mark_insert_before(mark_t *self, char *data, bint_t data_len); int mark_is_after_col_minus_lefties(mark_t *self, bint_t col); int mark_is_at_bol(mark_t *self); int mark_is_at_eol(mark_t *self); int mark_is_at_word_bound(mark_t *self, int side); int mark_is_eq(mark_t *self, mark_t *other); int mark_is_gte(mark_t *self, mark_t *other); int mark_is_gt(mark_t *self, mark_t *other); int mark_is_lte(mark_t *self, mark_t *other); int mark_is_lt(mark_t *self, mark_t *other); int mark_is_between(mark_t *self, mark_t *ma, mark_t *mb); int mark_join(mark_t *self, mark_t *other); int mark_move_beginning(mark_t *self); int mark_move_bol(mark_t *self); int mark_move_bracket_pair_ex(mark_t *self, bint_t max_chars, bline_t **optret_line, bint_t *optret_col, bint_t *optret_num_chars); int mark_move_bracket_pair(mark_t *self, bint_t max_chars); int mark_move_bracket_top_ex(mark_t *self, bint_t max_chars, bline_t **optret_line, bint_t *optret_col, bint_t *optret_num_chars); int mark_move_bracket_top(mark_t *self, bint_t max_chars); int mark_move_by(mark_t *self, bint_t char_delta); int mark_move_col(mark_t *self, bint_t col); int mark_move_end(mark_t *self); int mark_move_eol(mark_t *self); int mark_move_next_cre_ex(mark_t *self, pcre2_code *cre, bline_t **optret_line, bint_t *optret_col, bint_t *optret_num_chars); int mark_move_next_cre(mark_t *self, pcre2_code *cre); int mark_move_next_cre_nudge(mark_t *self, pcre2_code *cre); int mark_move_next_re_ex(mark_t *self, char *re, bint_t re_len, bline_t **optret_line, bint_t *optret_col, bint_t *optret_num_chars); int mark_move_next_re(mark_t *self, char *re, bint_t re_len); int mark_move_next_re_nudge(mark_t *self, char *re, bint_t re_len); int mark_move_next_str_ex(mark_t *self, char *str, bint_t str_len, bline_t **optret_line, bint_t *optret_col, bint_t *optret_num_chars); int mark_move_next_str(mark_t *self, char *str, bint_t str_len); int mark_move_next_str_nudge(mark_t *self, char *str, bint_t str_len); int mark_move_offset(mark_t *self, bint_t offset); int mark_move_prev_cre_ex(mark_t *self, pcre2_code *cre, bline_t **optret_line, bint_t *optret_col, bint_t *optret_num_chars); int mark_move_prev_cre(mark_t *self, pcre2_code *cre); int mark_move_prev_re_ex(mark_t *self, char *re, bint_t re_len, bline_t **optret_line, bint_t *optret_col, bint_t *optret_num_chars); int mark_move_prev_re(mark_t *self, char *re, bint_t re_len); int mark_move_prev_str_ex(mark_t *self, char *str, bint_t str_len, bline_t **optret_line, bint_t *optret_col, bint_t *optret_num_chars); int mark_move_prev_str(mark_t *self, char *str, bint_t str_len); int mark_move_to(mark_t *self, bint_t line_index, bint_t col); int mark_move_to_w_bline(mark_t *self, bline_t *bline, bint_t col); int mark_move_vert(mark_t *self, bint_t line_delta); int mark_replace_between(mark_t *self, mark_t *other, char *data, bint_t data_len); int mark_replace(mark_t *self, bint_t num_chars, char *data, bint_t data_len); int mark_set_pcre_capture(int *rc, PCRE2_SIZE *ovector, int ovector_size); int mark_swap(mark_t *self, mark_t *other); // srule functions srule_t *srule_new_single(char *re, bint_t re_len, int caseless, uint16_t fg, uint16_t bg); srule_t *srule_new_multi(char *re, bint_t re_len, char *re_end, bint_t re_end_len, uint16_t fg, uint16_t bg); srule_t *srule_new_range(mark_t *range_a, mark_t *range_b, uint16_t fg, uint16_t bg); int srule_destroy(srule_t *srule); // utf8 functions int utf8_char_length(char c); int utf8_char_to_unicode(uint32_t *out, const char *c, const char *stop); int utf8_unicode_to_char(char *out, uint32_t c); size_t utf8_str_length(char *data, size_t len); // util functions void *recalloc(void *ptr, size_t orig_num, size_t new_num, size_t el_size); void _mark_mark_move_inner(mark_t *mark, bline_t *bline_target, bint_t col, int do_set_target); void str_append_stop(str_t *str, char *data, char *data_stop); void str_append(str_t *str, char *data); void str_append_char(str_t *str, char c); void str_append_len(str_t *str, char *data, size_t data_len); void str_prepend_stop(str_t *str, char *data, char *data_stop); void str_prepend(str_t *str, char *data); void str_prepend_len(str_t *str, char *data, size_t data_len); void str_set(str_t *str, char *data); void str_set_len(str_t *str, char *data, size_t data_len); void str_put_len(str_t *str, char *data, size_t data_len, int is_prepend); void str_ensure_cap(str_t *str, size_t cap); void str_clear(str_t *str); void str_free(str_t *str); void str_sprintf(str_t *str, const char *fmt, ...); void str_append_replace_with_backrefs(str_t *str, char *subj, char *repl, int pcre_rc, PCRE2_SIZE *pcre_ovector, int pcre_ovecsize); // Globals extern pcre2_match_data *pcre2_md; // Macros #define MLBUF_DEBUG 1 // #define MLBUF_LARGE_FILE_SIZE 10485760 #define MLBUF_LARGE_FILE_SIZE 0 #define MLBUF_OK 0 #define MLBUF_ERR 1 #define MLBUF_BACTION_TYPE_INSERT 0 #define MLBUF_BACTION_TYPE_DELETE 1 #define MLBUF_SRULE_TYPE_SINGLE 0 #define MLBUF_SRULE_TYPE_MULTI 1 #define MLBUF_SRULE_TYPE_RANGE 2 #define MLBUF_MIN(a,b) (((a)<(b)) ? (a) : (b)) #define MLBUF_MAX(a,b) (((a)>(b)) ? (a) : (b)) #define MLBUF_BLINE_DATA_STOP(bline) ((bline)->data + ((bline)->data_len)) #define MLBUF_DEBUG_PRINTF(fmt, ...) do { \ if (MLBUF_DEBUG) { \ fprintf(stderr, "%lu ", time(0)); \ fprintf(stderr, (fmt), __VA_ARGS__); \ fflush(stderr); \ } \ } while (0) #define MLBUF_BLINE_ENSURE_CHARS(b) do { \ if ((b)->is_chars_dirty) { \ bline_count_chars(b); \ } \ } while (0) #define MLBUF_MAKE_GT_EQ0(v) if ((v) < 0) v = 0 #define MLBUF_ENSURE_AZ(c) \ if ((c) < 'a' || (c) > 'z') return MLBUF_ERR #define MLBUF_REG_PTR(buf, lett) \ &((buf)->registers[(lett) - 'a']) #define MLBUF_LETT_MARK(buf, lett) \ (buf)->lettered_marks[(lett) - 'a'] #endif mle-1.7.2/mle.1000066400000000000000000000160621443351614700131520ustar00rootroot00000000000000.Dd March 1, 2023 .Dt MLE 1 .Os .Sh NAME .Nm mle .Nd flexible terminal-based text editor .Sh SYNOPSIS .Nm mle .Op Fl abceHhiKklMmNnpSstuvwxyz .Op Ar file[:line] .Li ... .Sh DESCRIPTION .Nm is a small, flexible, terminal-based text editor written in C. It runs on Linux, Windows (Cygwin or WSL), FreeBSD, macOS, and more. .Ss Basic usage .Bd -literal $ mle # Open blank buffer $ mle one.c # Edit one.c $ mle one.c:100 # Edit one.c at line 100 $ mle one.c two.c # Edit one.c and two.c $ mle -h # Show command line help .Ed .Pp The default key bindings are intuitive. Input text as normal, use directional keys to move around, use `Ctrl-S` to save, `Ctrl-O` to open, `Ctrl-X` to exit. .Pp Press `F2` for full help. .Ss Options .Bl -tag -width ".Fl foo barbaz" -offset indent .It Fl h Show help .It Fl a Aq 1|0 Enable/disable tab to space (default: 1) .It Fl b Aq 1|0 Enable/disable highlight bracket pairs (default: 1) .It Fl c Ar column Color column (default: -1, disabled) .It Fl e Aq 1|0 Enable/disable mouse support (default: 0) .It Fl H Aq 1|0 Enable/disable headless mode (default: 1 if no tty, else 0) .It Fl i Aq 1|0 Enable/disable auto indent (default: 0) .It Fl K Ar kdef Make a kmap definition (use with -k). .Pp .Ar kdef is formatted as .Li `,,` , where .Ar name is the name of the kmap, .Ar default_cmd is the default command handler (can be empty), and .Ar allow_fallthru is a 0 or 1 specifying whether unhandled key input should be forwarded to the previous kmap on the stack or not. .It Fl k Ar kbind Add key binding to current kmap definition (use after -K). .Pp .Ar kbind is formatted as .Li `,,` , where .Ar cmd is a command name, .Ar key is a key name, and .Ar param is a static parameter passed to the command (can be empty). .It Fl l Ar ltype Set linenum type (default: 0, absolute). .Pp .Ar ltype can be 0 (absolute), 1 (relative), or 2 (both) .It Fl M Ar macro Add a macro. .Pp .Ar macro is formatted as .Li ` ... ` , where .Ar name is the name of the macro, and .Ar keyN are space-separated key names. .It Fl m Ar key Set macro toggle key (default: M-r). .Ar key is a key name. .It Fl N Skip reading of rc file .It Fl n Ar kmap Set init kmap (default: mle_normal). .Ar kmap is a kmap name. .It Fl p Ar macro Set startup macro. .Ar macro is a macro name. .It Fl S Ar syndef Make a syntax definition (use with -s). .Pp .Ar syndef is formatted as .Li `,,,` , where .Ar name is a syntax name, .Ar path_pattern is a path matching regex (PCRE), .Ar tab_width is the default tab width, .Ar tab_to_space is a 0 or 1 specifying whether to convert tabs to spaces or not. .It Fl s Ar synrule Add syntax rule to current syntax definition (use after -S). .Pp .Ar synrule is formatted as .Li `,,,` , where .Ar start and .Ar end are text matching regexes (PCRE), and .Ar fg and .Ar bg are attributes to apply to matching text. .Pp If both .Ar start and .Ar end are supplied, the rule applies to all text matched in between the regexes, potentially spanning multiple lines. If only .Ar start is specified, the rule applies to text matched by the regex on a single line. .Pp Attributes for .Ar fg and .Ar bg are as follows: .Bl -tag -width "####" -offset indent .It 0 default .It 1 black .It 2 red .It 4 yellow .It 5 blue .It 6 magenta .It 7 cyan .It 8 white .It 256 bold .It 512 underline .It 1024 reverse .It 2048 italic .El .It Fl t Ar size Set tab size (default: 4) .It Fl u Aq 1|0 Enable/disable coarse undo/redo (default: 0) .It Fl v Print version and exit .It Fl w Aq 1|0 Enable/disable soft word wrap (default: 0) .It Fl x Ar uscript Run a Lua user script (experimental) .It Fl y Ar syntax Set override syntax for files opened at start up. If '-' is specified, use the built-in generic syntax. .Ar syntax is any syntax name. .It Fl z Aq 1|0 Enable/disable trimmed paste (default: 1) .El .Sh KEY NAMES Key names for -k, -M, and -m are formatted as `` or `-`. .Pp .Ar key is any character or one of the following: space, tab, enter, backspace, comma, up, down, left, right, insert, delete, home, end, pgup, pgdn, backtab, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12. .Pp .Ar mod is one of .Bl -tag -width "############" -offset indent .It S Shift .It M Alt (Meta) .It MS Alt-Shift .It C Ctrl .It CS Ctrl-Shift .It CM Ctrl-Alt .It CMS Ctrl-Alt-Shift .El .Pp Not all key names are valid or supported by all terminals. Run with `-Qk` to display key names for given input. .Sh ADVANCED USAGE Below are some advanced things you can do with mle. .Ss rc file To customize the editor, make an rc file named .Pa ~/.mlerc or .Pa /etc/mlerc . The contents of the rc file are any number of cli options separated by newlines. Lines that begin with a semi-colon are interpreted as comments. .Pp If the rc file is executable, mle executes it and interprets the resulting stdout as described above. For example, consider the following snippet from an executable .Ar ~/.mlerc .Xr bash 1 script: .Bd -literal ... # Define 'test' kmap echo '-Ktest,,1' # M-q: replace grep with git grep if `.git` exists if [ -d ".git" ]; then echo '-kcmd_grep,M-q,git grep --color=never -P -i -I -n %s 2>/dev/null' fi # Set default kmap echo '-n test' ... .Ed .Pp This overrides the built-in grep command with `git grep` if .Pa .git exists in the current working directory. .Ss Shell command integration The following programs will enable or enhance certain features of mle if they exist in .Em PATH . .Bl -tag -width "############" -offset indent .It Xr bash 1 file tab completion .It Xr fzf 1 fuzzy file search .It Xr grep 1 file grep .It Xr less 1 less integration .It Xr perl 1 perl 1-liners .It Xr readtags 1 ctags integration .It Xr tree 1 file browsing .El .Pp Arbitrary shell commands can also be run via `cmd_shell` (M-e by default). If any text is selected, it is sent to stdin of the command. Any resulting stdout is inserted into the text buffer. .Ss Headless mode mle provides support for non-interactive editing which may be useful for using the editor as a regular command line tool. In headless mode, mle reads stdin into a buffer, applies a startup macro if specified, and then writes the buffer contents to stdout. For example: .Bd -literal $ echo -n hello | mle -M 'test C-e space w o r l d enter' -p test hello world .Ed .Pp If stdin is a pipe, mle goes into headless mode automatically. Headless mode can be explicitly enabled or disabled with the `-H` option. .Pp If stdin is a pipe and headless mode is disabled via -H0, mle reads stdin into a new buffer and then runs as normal in interactive mode. .Ss Scripting (experimental) mle is extensible via the Lua programming language. Scripts are loaded via the `-x` cli option. Commands registered by scripts can be mapped to keys as normal via `-k`. See .Lk https://github.com/adsr/mle for more info. .Sh ACKNOWLEDGEMENTS mle makes extensive use of the following libraries. .Bl -tag -width "############" -offset indent .It Em uthash for hash maps and linked lists .It Em termbox2 for TUI .It Em PCRE2 for syntax highlighting and search .El mle-1.7.2/mle.h000066400000000000000000000603321443351614700132400ustar00rootroot00000000000000#ifndef __MLE_H #define __MLE_H #include #include #include #include #include #include #include "termbox2.h" #include "mlbuf.h" // Typedefs typedef struct editor_s editor_t; // A container for editor-wide globals typedef struct bview_s bview_t; // A view of a buffer typedef struct bview_rect_s bview_rect_t; // A rectangle in bview with a default styling typedef struct bview_listener_s bview_listener_t; // A listener to buffer events in a bview typedef void (*bview_listener_cb_t)(bview_t *bview, baction_t *action, void *udata); // A bview_listener_t callback typedef struct cursor_s cursor_t; // A cursor (insertion mark + anchor mark) in a buffer typedef struct loop_context_s loop_context_t; // Context for a single _editor_loop typedef struct cmd_s cmd_t; // A command definition typedef struct cmd_context_s cmd_context_t; // Context for a single command invocation typedef struct observer_s observer_t; // An observer of a cmd or event typedef struct kinput_s kinput_t; // A single key input (similar to a tb_event from termbox) typedef struct kmacro_s kmacro_t; // A sequence of kinputs and a name typedef struct kmap_s kmap_t; // A map of keychords to functions typedef struct kmap_node_s kmap_node_t; // A node in a list of keymaps typedef struct kbinding_def_s kbinding_def_t; // A definition of a keymap typedef struct kbinding_s kbinding_t; // A single binding in a keymap typedef struct syntax_s syntax_t; // A syntax definition typedef struct syntax_node_s syntax_node_t; // A node in a linked list of syntaxes typedef struct srule_def_s srule_def_t; // A definition of a syntax typedef struct aproc_s aproc_t; // An asynchronous process typedef void (*aproc_cb_t)(aproc_t *self, char *buf, size_t buf_len); // An aproc_t callback typedef struct editor_prompt_params_s editor_prompt_params_t; // Extra params for editor_prompt typedef struct tb_event tb_event_t; // A termbox event typedef struct prompt_history_s prompt_history_t; // A map of prompt histories keyed by prompt_str typedef struct prompt_hnode_s prompt_hnode_t; // A node in a linked list of prompt history typedef int (*cmd_func_t)(cmd_context_t *ctx); // A command function typedef int (*observer_func_t)(char *event_name, void *event_data, void *udata); // An event callback function typedef struct uscript_s uscript_t; // A userscript typedef struct uhandle_s uhandle_t; // A method handle in a uscript // kinput_t struct kinput_s { uint8_t mod; uint32_t ch; uint16_t key; }; // bview_rect_t struct bview_rect_s { int x; int y; int w; int h; uint16_t fg; uint16_t bg; }; // editor_t struct editor_s { int w; int h; bview_t *top_bviews; bview_t *all_bviews; bview_t *active; bview_t *active_edit; bview_t *active_edit_last; bview_t *active_edit_root; bview_t *status; bview_t *prompt; bview_rect_t rect_edit; bview_rect_t rect_status; bview_rect_t rect_prompt; syntax_t *syntax_map; syntax_t *syntax_last; int is_display_disabled; kmacro_t *macro_map; kinput_t macro_toggle_key; kmacro_t *macro_record; kmacro_t *macro_apply; size_t macro_apply_input_index; int is_recording_macro; char *startup_macro_name; cmd_t *cmd_map; kmap_t *kmap_map; kmap_t *kmap_normal; kmap_t *kmap_prompt_input; kmap_t *kmap_prompt_yn; kmap_t *kmap_prompt_yna; kmap_t *kmap_prompt_ok; kmap_t *kmap_prompt_isearch; kmap_t *kmap_menu; prompt_history_t *prompt_history; char *kmap_init_name; kmap_t *kmap_init; aproc_t *aprocs; uscript_t *uscripts; observer_t *observers; FILE *tty; int ttyfd; char *syntax_override; int linenum_type; int tab_width; int tab_to_space; int trim_paste; int auto_indent; int read_rc_file; int highlight_bracket_pairs; int color_col; int soft_wrap; int coarse_undo; int mouse_support; int viewport_scope_x; // TODO cli option int viewport_scope_y; // TODO cli option int headless_mode; loop_context_t *loop_ctx; int loop_depth; int is_in_init; int is_mousedown; char *insertbuf; size_t insertbuf_size; char *cut_buffer; int user_input_count; #define MLE_ERRSTR_SIZE 256 char errstr[MLE_ERRSTR_SIZE]; char infostr[MLE_ERRSTR_SIZE]; int debug_exit_after_startup; int debug_dump_state_on_exit; int debug_key_input; #define MLE_DISPLAY_KEYS_NKEYS 8 #define MLE_MAX_KEYNAME_LEN 16 int debug_display_keys; char display_keys_buf[MLE_DISPLAY_KEYS_NKEYS * MLE_MAX_KEYNAME_LEN]; int exit_code; }; // srule_def_t struct srule_def_s { char *re; char *re_end; uint16_t fg; uint16_t bg; }; // syntax_node_t struct syntax_node_s { srule_t *srule; syntax_node_t *next; syntax_node_t *prev; }; // syntax_t struct syntax_s { char *name; char *path_pattern; int tab_width; int tab_to_space; srule_node_t *srules; UT_hash_handle hh; }; // bview_t struct bview_s { #define MLE_BVIEW_TYPE_EDIT 0 #define MLE_BVIEW_TYPE_STATUS 1 #define MLE_BVIEW_TYPE_PROMPT 2 editor_t *editor; int x; int y; int w; int h; int is_resized; int type; int linenum_width; int abs_linenum_width; int rel_linenum_width; bview_rect_t rect_caption; bview_rect_t rect_lines; bview_rect_t rect_margin_left; bview_rect_t rect_buffer; bview_rect_t rect_margin_right; buffer_t *buffer; bint_t viewport_x; bint_t viewport_x_vcol; bint_t viewport_y; mark_t *viewport_mark; int viewport_scope_x; int viewport_scope_y; bview_t *split_parent; bview_t *split_child; float split_factor; int split_is_vertical; char *prompt_str; char *path; bint_t startup_linenum; kmap_node_t *kmap_stack; kmap_node_t *kmap_tail; cursor_t *cursors; cursor_t *active_cursor; char *last_search; srule_t *isearch_rule; bint_t *isearch_ranges; size_t isearch_ranges_len; size_t isearch_ranges_cap; int tab_width; int tab_to_space; int soft_wrap; syntax_t *syntax; aproc_t *aproc; cmd_func_t menu_callback; int is_menu; #ifndef PATH_MAX #define PATH_MAX 4096 #endif char browse_path[PATH_MAX + 1]; int id; bview_listener_t *listeners; bview_t *top_next; bview_t *top_prev; bview_t *all_next; bview_t *all_prev; }; // bview_listener_t struct bview_listener_s { bview_listener_cb_t callback; void *udata; bview_listener_t *next; bview_listener_t *prev; }; // cursor_t struct cursor_s { bview_t *bview; mark_t *mark; mark_t *anchor; int is_anchored; int is_temp_anchored; int is_block; int is_asleep; srule_t *sel_rule; char *cut_buffer; cursor_t *next; cursor_t *prev; }; // kmacro_t struct kmacro_s { char *name; kinput_t *inputs; size_t inputs_len; size_t inputs_cap; UT_hash_handle hh; }; // cmd_t struct cmd_s { char *name; cmd_func_t func; void *udata; int is_resolved; UT_hash_handle hh; }; // kbinding_def_t struct kbinding_def_s { #define MLE_KBINDING_DEF(pcmdname, pkeypatt) { (pcmdname), (pkeypatt), NULL } #define MLE_KBINDING_DEF_EX(pcmdname, pkeypatt, pstatp) { (pcmdname), (pkeypatt), (pstatp) } char *cmd_name; char *key_patt; char *static_param; }; // kbinding_t struct kbinding_s { kinput_t input; char *cmd_name; cmd_t *cmd; char *static_param; char *key_patt; int is_leaf; kbinding_t *children; UT_hash_handle hh; }; // kmap_node_t struct kmap_node_s { kmap_t *kmap; bview_t *bview; kmap_node_t *next; kmap_node_t *prev; }; // kmap_t struct kmap_s { char *name; kbinding_t *bindings; int allow_fallthru; char *default_cmd_name; cmd_t *default_cmd; UT_hash_handle hh; }; // cmd_context_t struct cmd_context_s { #define MLE_PASTEBUF_INCR 1024 #define MLE_MAX_NUMERIC_PARAMS 8 #define MLE_MAX_WILDCARD_PARAMS 8 editor_t *editor; loop_context_t *loop_ctx; cmd_t *cmd; buffer_t *buffer; bview_t *bview; cursor_t *cursor; kinput_t input; char *static_param; uintmax_t numeric_params[MLE_MAX_NUMERIC_PARAMS]; int numeric_params_len; uint32_t wildcard_params[MLE_MAX_WILDCARD_PARAMS]; int wildcard_params_len; int is_user_input; kinput_t *pastebuf; size_t pastebuf_len; size_t pastebuf_size; int has_pastebuf_leftover; kinput_t pastebuf_leftover; }; // observer_t struct observer_s { char *event_patt; observer_func_t callback; void *udata; observer_t *next; observer_t *prev; }; // loop_context_t struct loop_context_s { #define MLE_LOOP_CTX_MAX_NUMERIC_LEN 20 #define MLE_LOOP_CTX_MAX_COMPLETE_TERM_SIZE 256 bview_t *invoker; char numeric[MLE_LOOP_CTX_MAX_NUMERIC_LEN + 1]; kbinding_t *numeric_node; int numeric_len; kbinding_t *binding_node; int need_more_input; int should_exit; char *prompt_answer; cmd_func_t prompt_callack; prompt_hnode_t *prompt_hnode; int tab_complete_index; char tab_complete_term[MLE_LOOP_CTX_MAX_COMPLETE_TERM_SIZE]; cmd_context_t last_cmd_ctx; str_t last_insert; kinput_t *input_trail; size_t input_trail_len; size_t input_trail_cap; size_t input_trail_idx; }; // aproc_t struct aproc_s { editor_t *editor; void *owner; aproc_t **owner_aproc; FILE *rpipe; FILE *wpipe; pid_t pid; int rfd; int wfd; int is_done; aproc_cb_t callback; aproc_t *next; aproc_t *prev; }; // editor_prompt_params_t struct editor_prompt_params_s { char *data; int data_len; kmap_t *kmap; bview_listener_cb_t prompt_cb; void *prompt_cb_udata; }; // prompt_history_t struct prompt_history_s { char *prompt_str; prompt_hnode_t *prompt_hlist; UT_hash_handle hh; }; // prompt_hnode_t struct prompt_hnode_s { char *data; bint_t data_len; prompt_hnode_t *prev; prompt_hnode_t *next; }; // uscript_t struct uscript_s { editor_t *editor; lua_State *L; uhandle_t *uhandles; uscript_t *prev; uscript_t *next; }; // uhandle_t struct uhandle_s { uscript_t *uscript; int callback_ref; uhandle_t *next; uhandle_t *prev; }; // editor functions int editor_init(editor_t *editor, int argc, char **argv); int editor_run(editor_t *editor); int editor_deinit(editor_t *editor); int editor_prompt(editor_t *editor, char *prompt, editor_prompt_params_t *params, char **optret_answer); int editor_menu(editor_t *editor, cmd_func_t fn_callback, char *opt_buf_data, int opt_buf_data_len, aproc_t *opt_aproc, bview_t **optret_menu); int editor_open_bview(editor_t *editor, bview_t *opt_parent, int type, char *opt_path, int opt_path_len, int make_active, bint_t linenum, int skip_resize, buffer_t *opt_buffer, bview_t **optret_bview); int editor_close_bview(editor_t *editor, bview_t *bview, int *optret_num_closed); int editor_set_active(editor_t *editor, bview_t *bview); int editor_bview_edit_count(editor_t *editor); int editor_count_bviews_by_buffer(editor_t *editor, buffer_t *buffer); int editor_register_cmd(editor_t *editor, cmd_t *cmd); int editor_register_observer(editor_t *editor, char *event_patt, void *udata, observer_func_t fn_callback, observer_t **optret_observer); int editor_notify_observers(editor_t *editor, char *event_name, void *event_data); int editor_destroy_observer(editor_t *editor, observer_t *observer); int editor_get_input(editor_t *editor, loop_context_t *loop_ctx, cmd_context_t *ctx); int editor_display(editor_t *editor); int editor_debug_dump(editor_t *editor, FILE *fp); int editor_force_redraw(editor_t *editor); int editor_set_input_mode(editor_t *editor); // bview functions bview_t *bview_get_split_root(bview_t *self); bview_t *bview_new(editor_t *editor, int type, char *opt_path, int opt_path_len, buffer_t *opt_buffer); int bview_add_cursor_asleep(bview_t *self, bline_t *opt_bline, bint_t opt_col, cursor_t **optret_cursor); int bview_add_cursor(bview_t *self, bline_t *opt_bline, bint_t opt_col, cursor_t **optret_cursor); int bview_add_listener(bview_t *self, bview_listener_cb_t fn_callback, void *udata); int bview_center_viewport_y(bview_t *self); int bview_destroy(bview_t *self); int bview_destroy_listener(bview_t *self, bview_listener_t *listener); int bview_draw(bview_t *self); int bview_draw_cursor(bview_t *self, int set_real_cursor); int bview_get_active_cursor_count(bview_t *self); int bview_get_screen_coords(bview_t *self, mark_t *mark, int *ret_x, int *ret_y, struct tb_cell **optret_cell); int bview_max_viewport_y(bview_t *self); int bview_open(bview_t *self, char *path, int path_len); int bview_pop_kmap(bview_t *bview, kmap_t **optret_kmap); int bview_push_kmap(bview_t *bview, kmap_t *kmap); int bview_rectify_viewport(bview_t *self); int bview_remove_cursor(bview_t *self, cursor_t *cursor); int bview_remove_cursors_except(bview_t *self, cursor_t *one); int bview_resize(bview_t *self, int x, int y, int w, int h); int bview_screen_to_bline_col(bview_t *self, int x, int y, bview_t **ret_bview, bline_t **ret_bline, bint_t *ret_col); int bview_set_syntax(bview_t *self, char *opt_syntax); int bview_set_viewport_y(bview_t *self, bint_t y, int do_rectify); int bview_split(bview_t *self, int is_vertical, float factor, bview_t **optret_bview); int bview_wake_sleeping_cursors(bview_t *self); int bview_zero_viewport_y(bview_t *self); // cursor functions int cursor_clone(cursor_t *cursor, int use_srules, cursor_t **ret_clone); int cursor_cut_copy(cursor_t *cursor, int is_cut, int use_srules, int append); int cursor_destroy(cursor_t *cursor); int cursor_drop_anchor(cursor_t *cursor, int use_srules); int cursor_get_lo_hi(cursor_t *cursor, mark_t **ret_lo, mark_t **ret_hi); int cursor_get_mark(cursor_t *cursor, mark_t **ret_mark); int cursor_get_anchor(cursor_t *cursor, mark_t **ret_anchor); int cursor_lift_anchor(cursor_t *cursor); int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt_replacement); int cursor_select_between(cursor_t *cursor, mark_t *a, mark_t *b, int use_srules); int cursor_select_by(cursor_t *cursor, const char *strat, int use_srules); int cursor_select_by_bracket(cursor_t *cursor, int use_srules); int cursor_select_by_string(cursor_t *cursor, int use_srules); int cursor_select_by_word_back(cursor_t *cursor, int use_srules); int cursor_select_by_word(cursor_t *cursor, int use_srules); int cursor_select_by_word_forward(cursor_t *cursor, int use_srules); int cursor_toggle_anchor(cursor_t *cursor, int use_srules); int cursor_uncut(cursor_t *cursor); int cursor_uncut_last(cursor_t *cursor); // cmd functions int cmd_align_cursors(cmd_context_t *ctx); int cmd_anchor_by(cmd_context_t *ctx); int cmd_apply_macro_by(cmd_context_t *ctx); int cmd_apply_macro(cmd_context_t *ctx); int cmd_blist(cmd_context_t *ctx); int cmd_browse(cmd_context_t *ctx); int cmd_close(cmd_context_t *ctx); int cmd_copy_by(cmd_context_t *ctx); int cmd_copy(cmd_context_t *ctx); int cmd_ctag(cmd_context_t *ctx); int cmd_cut_by(cmd_context_t *ctx); int cmd_cut(cmd_context_t *ctx); int cmd_delete_after(cmd_context_t *ctx); int cmd_delete_before(cmd_context_t *ctx); int cmd_delete_word_after(cmd_context_t *ctx); int cmd_delete_word_before(cmd_context_t *ctx); int cmd_drop_cursor_column(cmd_context_t *ctx); int cmd_drop_lettered_mark(cmd_context_t *ctx); int cmd_drop_sleeping_cursor(cmd_context_t *ctx); int cmd_find_word(cmd_context_t *ctx); int cmd_fsearch(cmd_context_t *ctx); int cmd_fsearch_fzy(cmd_context_t *ctx); int cmd_goto(cmd_context_t *ctx); int cmd_goto_lettered_mark(cmd_context_t *ctx); int cmd_grep(cmd_context_t *ctx); int cmd_indent(cmd_context_t *ctx); int cmd_insert_data(cmd_context_t *ctx); int cmd_insert_newline_above(cmd_context_t *ctx); int cmd_insert_newline_below(cmd_context_t *ctx); int cmd_insert_newline(cmd_context_t *ctx); int cmd_insert_tab(cmd_context_t *ctx); int cmd_isearch(cmd_context_t *ctx); int cmd_jump(cmd_context_t *ctx); int cmd_last(cmd_context_t *ctx); int cmd_less(cmd_context_t *ctx); int cmd_move_beginning(cmd_context_t *ctx); int cmd_move_bol(cmd_context_t *ctx); int cmd_move_bracket_back(cmd_context_t *ctx); int cmd_move_bracket_forward(cmd_context_t *ctx); int cmd_move_bracket_toggle(cmd_context_t *ctx); int cmd_move_down(cmd_context_t *ctx); int cmd_move_end(cmd_context_t *ctx); int cmd_move_eol(cmd_context_t *ctx); int cmd_move_left(cmd_context_t *ctx); int cmd_move_page_down(cmd_context_t *ctx); int cmd_move_page_up(cmd_context_t *ctx); int cmd_move_relative(cmd_context_t *ctx); int cmd_move_right(cmd_context_t *ctx); int cmd_move_temp_anchor(cmd_context_t *ctx); int cmd_move_to_line(cmd_context_t *ctx); int cmd_move_to_offset(cmd_context_t *ctx); int cmd_move_until_back(cmd_context_t *ctx); int cmd_move_until_forward(cmd_context_t *ctx); int cmd_move_up(cmd_context_t *ctx); int cmd_move_word_back(cmd_context_t *ctx); int cmd_move_word_forward(cmd_context_t *ctx); int cmd_next(cmd_context_t *ctx); int cmd_noop(cmd_context_t *ctx); int cmd_open_file(cmd_context_t *ctx); int cmd_open_new(cmd_context_t *ctx); int cmd_open_replace_file(cmd_context_t *ctx); int cmd_open_replace_new(cmd_context_t *ctx); int cmd_outdent(cmd_context_t *ctx); int cmd_perl(cmd_context_t *ctx); int cmd_pop_kmap(cmd_context_t *ctx); int cmd_prev(cmd_context_t *ctx); int cmd_push_kmap(cmd_context_t *ctx); int cmd_quit(cmd_context_t *ctx); int cmd_quit_without_saving(cmd_context_t *ctx); int cmd_redo(cmd_context_t *ctx); int cmd_redraw(cmd_context_t *ctx); int cmd_remove_extra_cursors(cmd_context_t *ctx); int cmd_repeat(cmd_context_t *ctx); int cmd_replace(cmd_context_t *ctx); int cmd_rfind_word(cmd_context_t *ctx); int cmd_rsearch(cmd_context_t *ctx); int cmd_save_as(cmd_context_t *ctx); int cmd_save(cmd_context_t *ctx); int cmd_search(cmd_context_t *ctx); int cmd_search_next(cmd_context_t *ctx); int cmd_search_prev(cmd_context_t *ctx); int cmd_set_opt(cmd_context_t *ctx); int cmd_shell(cmd_context_t *ctx); int cmd_show_help(cmd_context_t *ctx); int cmd_split_horizontal(cmd_context_t *ctx); int cmd_split_vertical(cmd_context_t *ctx); int cmd_suspend(cmd_context_t *ctx); int cmd_swap_anchor(cmd_context_t *ctx); int cmd_toggle_anchor(cmd_context_t *ctx); int cmd_toggle_block(cmd_context_t *ctx); int cmd_uncut(cmd_context_t *ctx); int cmd_uncut_last(cmd_context_t *ctx); int cmd_undo(cmd_context_t *ctx); int cmd_viewport_bot(cmd_context_t *ctx); int cmd_viewport_mid(cmd_context_t *ctx); int cmd_viewport_toggle(cmd_context_t *ctx); int cmd_viewport_top(cmd_context_t *ctx); int cmd_wake_sleeping_cursors(cmd_context_t *ctx); // async functions aproc_t *aproc_new(editor_t *editor, void *owner, aproc_t **owner_aproc, char *shell_cmd, int rw, aproc_cb_t fn_callback); int aproc_set_owner(aproc_t *aproc, void *owner, aproc_t **owner_aproc); int aproc_destroy(aproc_t *aproc, int preempt); int aproc_drain_all(aproc_t *aprocs, int *ttyfd); // uscript functions uscript_t *uscript_run(editor_t *editor, char *path); int uscript_destroy(uscript_t *uscript); // util functions int util_shell_exec(editor_t *editor, char *cmd, long timeout_s, char *input, size_t input_len, int setsid, char *opt_shell, char **optret_output, size_t *optret_output_len, int *optret_exit_code); int util_popen2(char *cmd, int setsid, char *opt_shell, int *optret_fdread, int *optret_fdwrite, pid_t *optret_pid); int util_get_bracket_pair(uint32_t ch, int *optret_is_closing); int util_is_file(char *path, char *opt_mode, FILE **optret_file); int util_is_dir(char *path); int util_pcre_match(char *re, char *subject, int subject_len, char **optret_capture, int *optret_capture_len); int util_pcre_replace(char *re, char *subj, char *repl, char **ret_result, int *ret_result_len); int util_timeval_is_gt(struct timeval *a, struct timeval *b); char *util_escape_shell_arg(char *str, int l); void util_expand_tilde(char *path, int path_len, char **ret_path, int *ret_path_len); int tb_printf_rect(bview_rect_t rect, int x, int y, uint16_t fg, uint16_t bg, const char *fmt, ...); int tb_printf_attr(bview_rect_t rect, int x, int y, const char *fmt, ...); // Globals extern editor_t _editor; // Macros #define MLE_VERSION "1.7.2" #define MLE_OK 0 #define MLE_ERR 1 #define MLE_PROMPT_YES "yes" #define MLE_PROMPT_NO "no" #define MLE_PROMPT_ALL "all" #define MLE_DEFAULT_TAB_WIDTH 4 #define MLE_DEFAULT_TAB_TO_SPACE 1 #define MLE_DEFAULT_TRIM_PASTE 1 #define MLE_DEFAULT_AUTO_INDENT 0 #define MLE_DEFAULT_MACRO_TOGGLE_KEY "M-r" #define MLE_DEFAULT_HILI_BRACKET_PAIRS 1 #define MLE_DEFAULT_READ_RC_FILE 1 #define MLE_DEFAULT_SOFT_WRAP 0 #define MLE_DEFAULT_COARSE_UNDO 0 #define MLE_DEFAULT_MOUSE_SUPPORT 0 #define MLE_LOG_ERR(fmt, ...) do { \ fprintf(stderr, (fmt), __VA_ARGS__); \ } while (0) #define MLE_SET_ERR(editor, fmt, ...) do { \ snprintf((editor)->errstr, MLE_ERRSTR_SIZE, (fmt), __VA_ARGS__); \ } while (0) #define MLE_SET_INFO(editor, fmt, ...) do { \ snprintf((editor)->infostr, MLE_ERRSTR_SIZE, (fmt), __VA_ARGS__); \ } while (0) #define MLE_RETURN_ERR(editor, fmt, ...) do { \ MLE_SET_ERR((editor), (fmt), __VA_ARGS__); \ return MLE_ERR; \ } while (0) #define MLE_MIN(a,b) (((a)<(b)) ? (a) : (b)) #define MLE_MAX(a,b) (((a)>(b)) ? (a) : (b)) #define MLE_BVIEW_IS_EDIT(bview) ((bview)->type == MLE_BVIEW_TYPE_EDIT) #define MLE_BVIEW_IS_MENU(bview) ((bview)->is_menu && MLE_BVIEW_IS_EDIT(bview)) #define MLE_BVIEW_IS_POPUP(bview) ((bview)->type == MLE_BVIEW_TYPE_POPUP) #define MLE_BVIEW_IS_STATUS(bview) ((bview)->type == MLE_BVIEW_TYPE_STATUS) #define MLE_BVIEW_IS_PROMPT(bview) ((bview)->type == MLE_BVIEW_TYPE_PROMPT) #define MLE_MARK_COL_TO_VCOL(pmark) ( \ (pmark)->col >= (pmark)->bline->char_count \ ? (pmark)->bline->char_vwidth \ : ( (pmark)->col <= 0 ? 0 : (pmark)->bline->chars[(pmark)->col].vcol ) \ ) #define MLE_COL_TO_VCOL(pline, pcol) ( \ (pcol) >= (pline)->char_count \ ? ((pline)->char_vwidth) \ : ((pcol) <= 0 ? 0 : (pline)->chars[(pcol)].vcol) \ ) // Setter macros for kinput structs #define MLE_KINPUT_SET_EX(pi, pfill, pmod, pch, pkey) do { \ memset(&(pi), (pfill), sizeof(pi)); \ (pi).mod = (pmod); \ (pi).ch = (pch); \ (pi).key = (pkey); \ } while(0) #define MLE_KINPUT_SET(pi, pmod, pch, pkey) MLE_KINPUT_SET_EX(pi, 0x00, pmod, pch, pkey) #define MLE_KINPUT_SET_SPECIAL(pi, pmod) MLE_KINPUT_SET_EX(pi, 0xff, pmod, 0xffffffff, 0xffff) #define MLE_KINPUT_SET_NUMERIC(pi) MLE_KINPUT_SET_SPECIAL(pi, 0x40) #define MLE_KINPUT_SET_WILDCARD(pi) MLE_KINPUT_SET_SPECIAL(pi, 0x80) #define MLE_KINPUT_COPY(pi, pj) memcpy(&(pi), &(pj), sizeof(pi)) #define MLE_LINENUM_TYPE_ABS 0 #define MLE_LINENUM_TYPE_REL 1 #define MLE_LINENUM_TYPE_BOTH 2 #define MLE_PARAM_WILDCARD(pctx, pn) ( \ (pn) < (pctx)->wildcard_params_len \ ? (pctx)->wildcard_params[(pn)] \ : 0 \ ) #define MLE_BRACKET_PAIR_MAX_SEARCH 10000 #define MLE_RE_WORD_FORWARD "((?<=\\w)\\W|$)" #define MLE_RE_WORD_BACK "((?<=\\W)\\w|^)" // TODO rename these inline #define tb_change_cell tb_set_cell #define tb_put_cell(x, y, c) tb_set_cell((x), (y), (c) #define tb_set_clear_attributes tb_set_clear_attrs #define tb_select_input_mode tb_set_input_mode #define tb_select_output_mode tb_set_output_mode /* TODO major changes [ ] delete lua api [ ] rewrite kmap (complex/unreadable; ** and ## sucks; kinput as hash key sucks; consolidate input_trail + pastebuf) [ ] rewrite/generalize aproc+menu (too tightly coupled; a better solution possibly supersedes dte's errorfmt/compile) [ ] rewrite buffer_set_mmapped to avoid huge mallocs [ ] use wcwidth9 autc instead of relying on locale and wcwidth (tests/unit/test_bline_insert.c) TODO review [ ] review error checking, esp catch ENOSPC, malloc fail [ ] review compiler warnings TODO new features/improvements [ ] add cmd_tabulate [ ] add ## param to page_up/down (by half/third etc) [ ] replace mark_set_pcre_capture with mark local [ ] use editor prompt history when bview prompt history is empty [ ] improve isearch kmap (next/prev history) [ ] add mark stack (push, move around, pop to go back) [ ] add last cmd status indicator [ ] check if buffer exists by inode instead of path [ ] move single-use macros out of mle.h [ ] review use of multi_cursor_code [ ] review use of MLE_RETURN_ERR [ ] try to get rid of use_srules TODO maybe [ ] ?allow uscripts to preempt control, use shared uscriptfd [ ] ?add vim emulation mode [ ] ?make colors, status line, layout configurable [ ] ?add multi-level undo/redo */ #endif mle-1.7.2/snapcraft.yaml000066400000000000000000000015371443351614700151610ustar00rootroot00000000000000name: mle-editor summary: Flexible terminal-based editor. description: | mle is a small, flexible, terminal-based text editor written in C. Notable features include: full Unicode support, syntax highlighting, scriptable rc file, macros, search and replace (PCRE), window splitting, multiple cursors, and integration with various shell commands. version: 1.5.0 license: Apache-2.0 base: core22 grade: stable confinement: strict apps: mle: command: usr/local/bin/mle parts: mle: plugin: make source: https://github.com/adsr/mle.git source-commit: 466febb604027149a341a3c75a7fb8b386b942fa source-type: git make-parameters: ["mle_vendor=1", "mle_static=1"] build-environment: - SNAPCRAFT_PARALLEL_BUILD_COUNT: "1" build-packages: [libtool, automake] stage-packages: [fzf, tree, less, perl, universal-ctags] mle-1.7.2/termbox2.h000066400000000000000000003501511443351614700142260ustar00rootroot00000000000000/* MIT License Copyright (c) 2010-2020 nsf 2015-2023 Adam Saponara Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __TERMBOX_H #define __TERMBOX_H #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif // __ffi_start #define TB_VERSION_STR "2.2.0-dev" #if defined(TB_LIB_OPTS) || 0 // __tb_lib_opts // Ensure consistent compile-time options when using as a library #undef TB_OPT_TRUECOLOR #undef TB_OPT_EGC #undef TB_OPT_PRINTF_BUF #undef TB_OPT_READ_BUF #define TB_OPT_TRUECOLOR #define TB_OPT_EGC #endif /* ASCII key constants (tb_event.key) */ #define TB_KEY_CTRL_TILDE 0x00 #define TB_KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */ #define TB_KEY_CTRL_A 0x01 #define TB_KEY_CTRL_B 0x02 #define TB_KEY_CTRL_C 0x03 #define TB_KEY_CTRL_D 0x04 #define TB_KEY_CTRL_E 0x05 #define TB_KEY_CTRL_F 0x06 #define TB_KEY_CTRL_G 0x07 #define TB_KEY_BACKSPACE 0x08 #define TB_KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */ #define TB_KEY_TAB 0x09 #define TB_KEY_CTRL_I 0x09 /* clash with 'TAB' */ #define TB_KEY_CTRL_J 0x0a #define TB_KEY_CTRL_K 0x0b #define TB_KEY_CTRL_L 0x0c #define TB_KEY_ENTER 0x0d #define TB_KEY_CTRL_M 0x0d /* clash with 'ENTER' */ #define TB_KEY_CTRL_N 0x0e #define TB_KEY_CTRL_O 0x0f #define TB_KEY_CTRL_P 0x10 #define TB_KEY_CTRL_Q 0x11 #define TB_KEY_CTRL_R 0x12 #define TB_KEY_CTRL_S 0x13 #define TB_KEY_CTRL_T 0x14 #define TB_KEY_CTRL_U 0x15 #define TB_KEY_CTRL_V 0x16 #define TB_KEY_CTRL_W 0x17 #define TB_KEY_CTRL_X 0x18 #define TB_KEY_CTRL_Y 0x19 #define TB_KEY_CTRL_Z 0x1a #define TB_KEY_ESC 0x1b #define TB_KEY_CTRL_LSQ_BRACKET 0x1b /* clash with 'ESC' */ #define TB_KEY_CTRL_3 0x1b /* clash with 'ESC' */ #define TB_KEY_CTRL_4 0x1c #define TB_KEY_CTRL_BACKSLASH 0x1c /* clash with 'CTRL_4' */ #define TB_KEY_CTRL_5 0x1d #define TB_KEY_CTRL_RSQ_BRACKET 0x1d /* clash with 'CTRL_5' */ #define TB_KEY_CTRL_6 0x1e #define TB_KEY_CTRL_7 0x1f #define TB_KEY_CTRL_SLASH 0x1f /* clash with 'CTRL_7' */ #define TB_KEY_CTRL_UNDERSCORE 0x1f /* clash with 'CTRL_7' */ #define TB_KEY_SPACE 0x20 #define TB_KEY_BACKSPACE2 0x7f #define TB_KEY_CTRL_8 0x7f /* clash with 'BACKSPACE2' */ #define tb_key_i(i) 0xffff - (i) /* Terminal-dependent key constants (tb_event.key) and terminfo capabilities */ /* BEGIN codegen h */ /* Produced by ./codegen.sh on Sun, 19 Sep 2021 01:02:02 +0000 */ #define TB_KEY_F1 (0xffff - 0) #define TB_KEY_F2 (0xffff - 1) #define TB_KEY_F3 (0xffff - 2) #define TB_KEY_F4 (0xffff - 3) #define TB_KEY_F5 (0xffff - 4) #define TB_KEY_F6 (0xffff - 5) #define TB_KEY_F7 (0xffff - 6) #define TB_KEY_F8 (0xffff - 7) #define TB_KEY_F9 (0xffff - 8) #define TB_KEY_F10 (0xffff - 9) #define TB_KEY_F11 (0xffff - 10) #define TB_KEY_F12 (0xffff - 11) #define TB_KEY_INSERT (0xffff - 12) #define TB_KEY_DELETE (0xffff - 13) #define TB_KEY_HOME (0xffff - 14) #define TB_KEY_END (0xffff - 15) #define TB_KEY_PGUP (0xffff - 16) #define TB_KEY_PGDN (0xffff - 17) #define TB_KEY_ARROW_UP (0xffff - 18) #define TB_KEY_ARROW_DOWN (0xffff - 19) #define TB_KEY_ARROW_LEFT (0xffff - 20) #define TB_KEY_ARROW_RIGHT (0xffff - 21) #define TB_KEY_BACK_TAB (0xffff - 22) #define TB_KEY_MOUSE_LEFT (0xffff - 23) #define TB_KEY_MOUSE_RIGHT (0xffff - 24) #define TB_KEY_MOUSE_MIDDLE (0xffff - 25) #define TB_KEY_MOUSE_RELEASE (0xffff - 26) #define TB_KEY_MOUSE_WHEEL_UP (0xffff - 27) #define TB_KEY_MOUSE_WHEEL_DOWN (0xffff - 28) #define TB_CAP_F1 0 #define TB_CAP_F2 1 #define TB_CAP_F3 2 #define TB_CAP_F4 3 #define TB_CAP_F5 4 #define TB_CAP_F6 5 #define TB_CAP_F7 6 #define TB_CAP_F8 7 #define TB_CAP_F9 8 #define TB_CAP_F10 9 #define TB_CAP_F11 10 #define TB_CAP_F12 11 #define TB_CAP_INSERT 12 #define TB_CAP_DELETE 13 #define TB_CAP_HOME 14 #define TB_CAP_END 15 #define TB_CAP_PGUP 16 #define TB_CAP_PGDN 17 #define TB_CAP_ARROW_UP 18 #define TB_CAP_ARROW_DOWN 19 #define TB_CAP_ARROW_LEFT 20 #define TB_CAP_ARROW_RIGHT 21 #define TB_CAP_BACK_TAB 22 #define TB_CAP__COUNT_KEYS 23 #define TB_CAP_ENTER_CA 23 #define TB_CAP_EXIT_CA 24 #define TB_CAP_SHOW_CURSOR 25 #define TB_CAP_HIDE_CURSOR 26 #define TB_CAP_CLEAR_SCREEN 27 #define TB_CAP_SGR0 28 #define TB_CAP_UNDERLINE 29 #define TB_CAP_BOLD 30 #define TB_CAP_BLINK 31 #define TB_CAP_ITALIC 32 #define TB_CAP_REVERSE 33 #define TB_CAP_ENTER_KEYPAD 34 #define TB_CAP_EXIT_KEYPAD 35 #define TB_CAP__COUNT 36 /* END codegen h */ /* Some hard-coded caps */ #define TB_HARDCAP_ENTER_MOUSE "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" #define TB_HARDCAP_EXIT_MOUSE "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" /* Colors (numeric) and attributes (bitwise) (tb_cell.fg, tb_cell.bg) */ #define TB_DEFAULT 0x0000 #define TB_BLACK 0x0001 #define TB_RED 0x0002 #define TB_GREEN 0x0003 #define TB_YELLOW 0x0004 #define TB_BLUE 0x0005 #define TB_MAGENTA 0x0006 #define TB_CYAN 0x0007 #define TB_WHITE 0x0008 #define TB_BOLD 0x0100 #define TB_UNDERLINE 0x0200 #define TB_REVERSE 0x0400 #define TB_ITALIC 0x0800 #define TB_BLINK 0x1000 #define TB_256_BLACK 0x2000 #ifdef TB_OPT_TRUECOLOR #define TB_TRUECOLOR_BOLD 0x01000000 #define TB_TRUECOLOR_UNDERLINE 0x02000000 #define TB_TRUECOLOR_REVERSE 0x04000000 #define TB_TRUECOLOR_ITALIC 0x08000000 #define TB_TRUECOLOR_BLINK 0x10000000 #define TB_TRUECOLOR_BLACK 0x20000000 #endif /* Event types (tb_event.type) */ #define TB_EVENT_KEY 1 #define TB_EVENT_RESIZE 2 #define TB_EVENT_MOUSE 3 /* Key modifiers (bitwise) (tb_event.mod) */ #define TB_MOD_ALT 1 #define TB_MOD_CTRL 2 #define TB_MOD_SHIFT 4 #define TB_MOD_MOTION 8 /* Input modes (bitwise) (tb_set_input_mode) */ #define TB_INPUT_CURRENT 0 #define TB_INPUT_ESC 1 #define TB_INPUT_ALT 2 #define TB_INPUT_MOUSE 4 /* Output modes (tb_set_output_mode) */ #define TB_OUTPUT_CURRENT 0 #define TB_OUTPUT_NORMAL 1 #define TB_OUTPUT_256 2 #define TB_OUTPUT_216 3 #define TB_OUTPUT_GRAYSCALE 4 #ifdef TB_OPT_TRUECOLOR #define TB_OUTPUT_TRUECOLOR 5 #endif /* Common function return values unless otherwise noted. * * Library behavior is undefined after receiving TB_ERR_MEM. Callers may * attempt reinitializing by freeing memory, invoking tb_shutdown, then * tb_init. */ #define TB_OK 0 #define TB_ERR -1 #define TB_ERR_NEED_MORE -2 #define TB_ERR_INIT_ALREADY -3 #define TB_ERR_INIT_OPEN -4 #define TB_ERR_MEM -5 #define TB_ERR_NO_EVENT -6 #define TB_ERR_NO_TERM -7 #define TB_ERR_NOT_INIT -8 #define TB_ERR_OUT_OF_BOUNDS -9 #define TB_ERR_READ -10 #define TB_ERR_RESIZE_IOCTL -11 #define TB_ERR_RESIZE_PIPE -12 #define TB_ERR_RESIZE_SIGACTION -13 #define TB_ERR_POLL -14 #define TB_ERR_TCGETATTR -15 #define TB_ERR_TCSETATTR -16 #define TB_ERR_UNSUPPORTED_TERM -17 #define TB_ERR_RESIZE_WRITE -18 #define TB_ERR_RESIZE_POLL -19 #define TB_ERR_RESIZE_READ -20 #define TB_ERR_RESIZE_SSCANF -21 #define TB_ERR_CAP_COLLISION -22 #define TB_ERR_SELECT TB_ERR_POLL #define TB_ERR_RESIZE_SELECT TB_ERR_RESIZE_POLL /* Function types to be used with tb_set_func() */ #define TB_FUNC_EXTRACT_PRE 0 #define TB_FUNC_EXTRACT_POST 1 /* Define this to set the size of the buffer used in tb_printf() * and tb_sendf() */ #ifndef TB_OPT_PRINTF_BUF #define TB_OPT_PRINTF_BUF 4096 #endif /* Define this to set the size of the read buffer used when reading * from the tty */ #ifndef TB_OPT_READ_BUF #define TB_OPT_READ_BUF 64 #endif /* Define this for limited back compat with termbox v1 */ #ifdef TB_OPT_V1_COMPAT #define tb_change_cell tb_set_cell #define tb_put_cell(x, y, c) tb_set_cell((x), (y), (c)->ch, (c)->fg, (c)->bg) #define tb_set_clear_attributes tb_set_clear_attrs #define tb_select_input_mode tb_set_input_mode #define tb_select_output_mode tb_set_output_mode #endif /* Define these to swap in a different allocator */ #ifndef tb_malloc #define tb_malloc malloc #define tb_realloc realloc #define tb_free free #endif #ifdef TB_OPT_TRUECOLOR typedef uint32_t uintattr_t; #else typedef uint16_t uintattr_t; #endif /* The terminal screen is represented as 2d array of cells. The structure is * optimized for dealing with single-width (wcwidth()==1) Unicode code points, * however some support for grapheme clusters (e.g., combining diacritical * marks) and wide code points (e.g., Hiragana) is provided through ech, nech, * cech via tb_set_cell_ex(). ech is only valid when nech>0, otherwise ch is * used. * * For non-single-width code points, given N=wcwidth(ch)/wcswidth(ech): * * when N==0: termbox forces a single-width cell. Callers should avoid this * if aiming to render text accurately. * * when N>1: termbox zeroes out the following N-1 cells and skips sending * them to the tty. So, e.g., if the caller sets x=0,y=0 to an N==2 * code point, the caller's next set should be at x=2,y=0. Anything * set at x=1,y=0 will be ignored. If there are not enough columns * remaining on the line to render N width, spaces are sent * instead. * * See tb_present() for implementation. */ struct tb_cell { uint32_t ch; /* a Unicode character */ uintattr_t fg; /* bitwise foreground attributes */ uintattr_t bg; /* bitwise background attributes */ #ifdef TB_OPT_EGC uint32_t *ech; /* a grapheme cluster of Unicode code points */ size_t nech; /* length in bytes of ech, 0 means use ch instead of ech */ size_t cech; /* capacity in bytes of ech */ #endif }; /* An incoming event from the tty. * * Given the event type, the following fields are relevant: * * when TB_EVENT_KEY: (key XOR ch, one will be zero), mod. Note there is * overlap between TB_MOD_CTRL and TB_KEY_CTRL_*. * TB_MOD_CTRL and TB_MOD_SHIFT are only set as * modifiers to TB_KEY_ARROW_*. * * when TB_EVENT_RESIZE: w, h * * when TB_EVENT_MOUSE: key (TB_KEY_MOUSE_*), x, y */ struct tb_event { uint8_t type; /* one of TB_EVENT_* constants */ uint8_t mod; /* bitwise TB_MOD_* constants */ uint16_t key; /* one of TB_KEY_* constants */ uint32_t ch; /* a Unicode code point */ int32_t w; /* resize width */ int32_t h; /* resize height */ int32_t x; /* mouse x */ int32_t y; /* mouse y */ }; /* Initializes the termbox library. This function should be called before any * other functions. tb_init() is equivalent to tb_init_file("/dev/tty"). After * successful initialization, the library must be finalized using the * tb_shutdown() function. */ int tb_init(void); int tb_init_file(const char *path); int tb_init_fd(int ttyfd); int tb_init_rwfd(int rfd, int wfd); int tb_shutdown(void); /* Returns the size of the internal back buffer (which is the same as terminal's * window size in rows and columns). The internal buffer can be resized after * tb_clear() or tb_present() function calls. Both dimensions have an * unspecified negative value when called before tb_init() or after * tb_shutdown(). */ int tb_width(void); int tb_height(void); /* Clears the internal back buffer using TB_DEFAULT color or the * color/attributes set by tb_set_clear_attrs() function. */ int tb_clear(void); int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg); /* Synchronizes the internal back buffer with the terminal by writing to tty. */ int tb_present(void); /* Clears the internal front buffer effectively forcing a complete re-render of * the back buffer to the tty. It is not necessary to call this under normal * circumstances. */ int tb_invalidate(void); /* Sets the position of the cursor. Upper-left character is (0, 0). */ int tb_set_cursor(int cx, int cy); int tb_hide_cursor(void); /* Set cell contents in the internal back buffer at the specified position. * * Use tb_set_cell_ex() for rendering grapheme clusters (e.g., combining * diacritical marks). * * Function tb_set_cell(x, y, ch, fg, bg) is equivalent to * tb_set_cell_ex(x, y, &ch, 1, fg, bg). * * Function tb_extend_cell() is a shortcut for appending 1 code point to * cell->ech. */ int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg); int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, uintattr_t bg); int tb_extend_cell(int x, int y, uint32_t ch); /* Sets the input mode. Termbox has two input modes: * * 1. TB_INPUT_ESC * When escape (\x1b) is in the buffer and there's no match for an escape * sequence, a key event for TB_KEY_ESC is returned. * * 2. TB_INPUT_ALT * When escape (\x1b) is in the buffer and there's no match for an escape * sequence, the next keyboard event is returned with a TB_MOD_ALT modifier. * * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the * modes (e.g., TB_INPUT_ESC | TB_INPUT_MOUSE) to receive TB_EVENT_MOUSE events. * If none of the main two modes were set, but the mouse mode was, TB_INPUT_ESC * mode is used. If for some reason you've decided to use * (TB_INPUT_ESC | TB_INPUT_ALT) combination, it will behave as if only * TB_INPUT_ESC was selected. * * If mode is TB_INPUT_CURRENT, the function returns the current input mode. * * The default input mode is TB_INPUT_ESC. */ int tb_set_input_mode(int mode); /* Sets the termbox output mode. Termbox has multiple output modes: * * 1. TB_OUTPUT_NORMAL => [0..8] * * This mode provides 8 different colors: * TB_BLACK, TB_RED, TB_GREEN, TB_YELLOW, * TB_BLUE, TB_MAGENTA, TB_CYAN, TB_WHITE * * Plus TB_DEFAULT which skips sending a color code (i.e., uses the * terminal's default color). * * Colors (including TB_DEFAULT) may be bitwise OR'd with attributes: * TB_BOLD, TB_UNDERLINE, TB_REVERSE, TB_ITALIC, TB_BLINK * * As in all modes, the value 0 is interpreted as TB_DEFAULT for * convenience. * * Some notes: TB_REVERSE can be applied as either fg or bg attributes for * the same effect. TB_BOLD, TB_UNDERLINE, TB_ITALIC, TB_BLINK apply as fg * attributes only, and are ignored as bg attributes. * * Example usage: * tb_set_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); * * 2. TB_OUTPUT_256 => [0..255] + TB_256_BLACK * * In this mode you get 256 distinct colors (plus default): * 0x00 (1): TB_DEFAULT * TB_256_BLACK (1): TB_BLACK in TB_OUTPUT_NORMAL * 0x01..0x07 (7): the next 7 colors as in TB_OUTPUT_NORMAL * 0x08..0x0f (8): bright versions of the above * 0x10..0xe7 (216): 216 different colors * 0xe8..0xff (24): 24 different shades of gray * * Attributes may be bitwise OR'd as in TB_OUTPUT_NORMAL. * * Note TB_256_BLACK must be used for black, as 0x00 represents default. * * 3. TB_OUTPUT_216 => [0..216] * * This mode supports the 216-color range of TB_OUTPUT_256 only, but you * don't need to provide an offset: * 0x00 (1): TB_DEFAULT * 0x01..0xd8 (216): 216 different colors * * 4. TB_OUTPUT_GRAYSCALE => [0..24] * * This mode supports the 24-color range of TB_OUTPUT_256 only, but you * don't need to provide an offset: * 0x00 (1): TB_DEFAULT * 0x01..0x18 (24): 24 different shades of gray * * 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xffffff] + TB_TRUECOLOR_BLACK * * This mode provides 24-bit color on supported terminals. The format is * 0xRRGGBB. Colors may be bitwise OR'd with TB_TRUECOLOR_* attributes. * * Note TB_TRUECOLOR_BLACK must be used for black, as 0x000000 represents * default. * * If mode is TB_OUTPUT_CURRENT, the function returns the current output mode. * * The default output mode is TB_OUTPUT_NORMAL. * * To use the terminal default color (i.e., to not send an escape code), pass * TB_DEFAULT. For convenience, the value 0 is interpreted as TB_DEFAULT in * all modes. * * Note, cell attributes persist after switching output modes. Any translation * between, for example, TB_OUTPUT_NORMAL's TB_RED and TB_OUTPUT_TRUECOLOR's * 0xff0000 must be performed by the caller. Also note that cells previously * rendered in one mode may persist unchanged until the front buffer is cleared * (such as after a resize event) at which point it will be re-interpreted and * flushed according to the current mode. Callers may invoke tb_invalidate if * it is desirable to immediately re-interpret and flush the entire screen * according to the current mode. * * Note, not all terminals support all output modes, especially beyond * TB_OUTPUT_NORMAL. There is also no very reliable way to determine color * support dynamically. If portability is desired, callers are recommended to * use TB_OUTPUT_NORMAL or make output mode end-user configurable. */ int tb_set_output_mode(int mode); /* Wait for an event up to timeout_ms milliseconds and fill the event structure * with it. If no event is available within the timeout period, TB_ERR_NO_EVENT * is returned. On a resize event, the underlying select(2) call may be * interrupted, yielding a return code of TB_ERR_POLL. In this case, you may * check errno via tb_last_errno(). If it's EINTR, you can safely ignore that * and call tb_peek_event() again. */ int tb_peek_event(struct tb_event *event, int timeout_ms); /* Same as tb_peek_event except no timeout. */ int tb_poll_event(struct tb_event *event); /* Internal termbox FDs that can be used with poll() / select(). Must call * tb_poll_event() / tb_peek_event() if activity is detected. */ int tb_get_fds(int *ttyfd, int *resizefd); /* Print and printf functions. Specify param out_w to determine width of printed * string. */ int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str); int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...); int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, const char *str); int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, const char *fmt, ...); /* Send raw bytes to terminal. */ int tb_send(const char *buf, size_t nbuf); int tb_sendf(const char *fmt, ...); /* Set custom functions. fn_type is one of TB_FUNC_* constants, fn is a * compatible function pointer, or NULL to clear. * * TB_FUNC_EXTRACT_PRE: * If specified, invoke this function BEFORE termbox tries to extract any * escape sequences from the input buffer. * * TB_FUNC_EXTRACT_POST: * If specified, invoke this function AFTER termbox tries (and fails) to * extract any escape sequences from the input buffer. */ int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)); /* Utility functions. */ int tb_utf8_char_length(char c); int tb_utf8_char_to_unicode(uint32_t *out, const char *c); int tb_utf8_unicode_to_char(char *out, uint32_t c); int tb_last_errno(void); const char *tb_strerror(int err); struct tb_cell *tb_cell_buffer(void); int tb_has_truecolor(void); int tb_has_egc(void); const char *tb_version(void); #ifdef __cplusplus } #endif #endif /* __TERMBOX_H */ #ifdef TB_IMPL #define if_err_return(rv, expr) \ if (((rv) = (expr)) != TB_OK) \ return (rv) #define if_err_break(rv, expr) \ if (((rv) = (expr)) != TB_OK) \ break #define if_ok_return(rv, expr) \ if (((rv) = (expr)) == TB_OK) \ return (rv) #define if_ok_or_need_more_return(rv, expr) \ if (((rv) = (expr)) == TB_OK || (rv) == TB_ERR_NEED_MORE) \ return (rv) #define send_literal(rv, a) \ if_err_return((rv), bytebuf_nputs(&global.out, (a), sizeof(a) - 1)) #define send_num(rv, nbuf, n) \ if_err_return((rv), \ bytebuf_nputs(&global.out, (nbuf), convert_num((n), (nbuf)))) #define snprintf_or_return(rv, str, sz, fmt, ...) \ do { \ (rv) = snprintf((str), (sz), (fmt), __VA_ARGS__); \ if ((rv) < 0 || (rv) >= (int)(sz)) \ return TB_ERR; \ } while (0) #define if_not_init_return() \ if (!global.initialized) \ return TB_ERR_NOT_INIT struct bytebuf_t { char *buf; size_t len; size_t cap; }; struct cellbuf_t { int width; int height; struct tb_cell *cells; }; struct cap_trie_t { char c; struct cap_trie_t *children; size_t nchildren; int is_leaf; uint16_t key; uint8_t mod; }; struct tb_global_t { int ttyfd; int rfd; int wfd; int ttyfd_open; int resize_pipefd[2]; int width; int height; int cursor_x; int cursor_y; int last_x; int last_y; uintattr_t fg; uintattr_t bg; uintattr_t last_fg; uintattr_t last_bg; int input_mode; int output_mode; char *terminfo; size_t nterminfo; const char *caps[TB_CAP__COUNT]; struct cap_trie_t cap_trie; struct bytebuf_t in; struct bytebuf_t out; struct cellbuf_t back; struct cellbuf_t front; struct termios orig_tios; int has_orig_tios; int last_errno; int initialized; int (*fn_extract_esc_pre)(struct tb_event *, size_t *); int (*fn_extract_esc_post)(struct tb_event *, size_t *); char errbuf[1024]; }; static struct tb_global_t global = {0}; /* BEGIN codegen c */ /* Produced by ./codegen.sh on Sun, 19 Sep 2021 01:02:03 +0000 */ static const int16_t terminfo_cap_indexes[] = { 66, // kf1 (TB_CAP_F1) 68, // kf2 (TB_CAP_F2) 69, // kf3 (TB_CAP_F3) 70, // kf4 (TB_CAP_F4) 71, // kf5 (TB_CAP_F5) 72, // kf6 (TB_CAP_F6) 73, // kf7 (TB_CAP_F7) 74, // kf8 (TB_CAP_F8) 75, // kf9 (TB_CAP_F9) 67, // kf10 (TB_CAP_F10) 216, // kf11 (TB_CAP_F11) 217, // kf12 (TB_CAP_F12) 77, // kich1 (TB_CAP_INSERT) 59, // kdch1 (TB_CAP_DELETE) 76, // khome (TB_CAP_HOME) 164, // kend (TB_CAP_END) 82, // kpp (TB_CAP_PGUP) 81, // knp (TB_CAP_PGDN) 87, // kcuu1 (TB_CAP_ARROW_UP) 61, // kcud1 (TB_CAP_ARROW_DOWN) 79, // kcub1 (TB_CAP_ARROW_LEFT) 83, // kcuf1 (TB_CAP_ARROW_RIGHT) 148, // kcbt (TB_CAP_BACK_TAB) 28, // smcup (TB_CAP_ENTER_CA) 40, // rmcup (TB_CAP_EXIT_CA) 16, // cnorm (TB_CAP_SHOW_CURSOR) 13, // civis (TB_CAP_HIDE_CURSOR) 5, // clear (TB_CAP_CLEAR_SCREEN) 39, // sgr0 (TB_CAP_SGR0) 36, // smul (TB_CAP_UNDERLINE) 27, // bold (TB_CAP_BOLD) 26, // blink (TB_CAP_BLINK) 311, // sitm (TB_CAP_ITALIC) 34, // rev (TB_CAP_REVERSE) 89, // smkx (TB_CAP_ENTER_KEYPAD) 88, // rmkx (TB_CAP_EXIT_KEYPAD) }; // xterm static const char *xterm_caps[] = { "\033OP", // kf1 (TB_CAP_F1) "\033OQ", // kf2 (TB_CAP_F2) "\033OR", // kf3 (TB_CAP_F3) "\033OS", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033OH", // khome (TB_CAP_HOME) "\033OF", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033OA", // kcuu1 (TB_CAP_ARROW_UP) "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "\033[?1049h\033[22;0;0t", // smcup (TB_CAP_ENTER_CA) "\033[?1049l\033[23;0;0t", // rmcup (TB_CAP_EXIT_CA) "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) "\033(B\033[m", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "\033[3m", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) }; // linux static const char *linux_caps[] = { "\033[[A", // kf1 (TB_CAP_F1) "\033[[B", // kf2 (TB_CAP_F2) "\033[[C", // kf3 (TB_CAP_F3) "\033[[D", // kf4 (TB_CAP_F4) "\033[[E", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[1~", // khome (TB_CAP_HOME) "\033[4~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033[A", // kcuu1 (TB_CAP_ARROW_UP) "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "", // smcup (TB_CAP_ENTER_CA) "", // rmcup (TB_CAP_EXIT_CA) "\033[?25h\033[?0c", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l\033[?1c", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\017", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "", // smkx (TB_CAP_ENTER_KEYPAD) "", // rmkx (TB_CAP_EXIT_KEYPAD) }; // screen static const char *screen_caps[] = { "\033OP", // kf1 (TB_CAP_F1) "\033OQ", // kf2 (TB_CAP_F2) "\033OR", // kf3 (TB_CAP_F3) "\033OS", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[1~", // khome (TB_CAP_HOME) "\033[4~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033OA", // kcuu1 (TB_CAP_ARROW_UP) "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "\033[?1049h", // smcup (TB_CAP_ENTER_CA) "\033[?1049l", // rmcup (TB_CAP_EXIT_CA) "\033[34h\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\017", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) }; // rxvt-256color static const char *rxvt_256color_caps[] = { "\033[11~", // kf1 (TB_CAP_F1) "\033[12~", // kf2 (TB_CAP_F2) "\033[13~", // kf3 (TB_CAP_F3) "\033[14~", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[7~", // khome (TB_CAP_HOME) "\033[8~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033[A", // kcuu1 (TB_CAP_ARROW_UP) "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\017", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "\033=", // smkx (TB_CAP_ENTER_KEYPAD) "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) }; // rxvt-unicode static const char *rxvt_unicode_caps[] = { "\033[11~", // kf1 (TB_CAP_F1) "\033[12~", // kf2 (TB_CAP_F2) "\033[13~", // kf3 (TB_CAP_F3) "\033[14~", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[7~", // khome (TB_CAP_HOME) "\033[8~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033[A", // kcuu1 (TB_CAP_ARROW_UP) "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) "\033[Z", // kcbt (TB_CAP_BACK_TAB) "\033[?1049h", // smcup (TB_CAP_ENTER_CA) "\033[r\033[?1049l", // rmcup (TB_CAP_EXIT_CA) "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\033(B", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "\033[3m", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "\033=", // smkx (TB_CAP_ENTER_KEYPAD) "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) }; // Eterm static const char *eterm_caps[] = { "\033[11~", // kf1 (TB_CAP_F1) "\033[12~", // kf2 (TB_CAP_F2) "\033[13~", // kf3 (TB_CAP_F3) "\033[14~", // kf4 (TB_CAP_F4) "\033[15~", // kf5 (TB_CAP_F5) "\033[17~", // kf6 (TB_CAP_F6) "\033[18~", // kf7 (TB_CAP_F7) "\033[19~", // kf8 (TB_CAP_F8) "\033[20~", // kf9 (TB_CAP_F9) "\033[21~", // kf10 (TB_CAP_F10) "\033[23~", // kf11 (TB_CAP_F11) "\033[24~", // kf12 (TB_CAP_F12) "\033[2~", // kich1 (TB_CAP_INSERT) "\033[3~", // kdch1 (TB_CAP_DELETE) "\033[7~", // khome (TB_CAP_HOME) "\033[8~", // kend (TB_CAP_END) "\033[5~", // kpp (TB_CAP_PGUP) "\033[6~", // knp (TB_CAP_PGDN) "\033[A", // kcuu1 (TB_CAP_ARROW_UP) "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) "", // kcbt (TB_CAP_BACK_TAB) "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) "\033[m\017", // sgr0 (TB_CAP_SGR0) "\033[4m", // smul (TB_CAP_UNDERLINE) "\033[1m", // bold (TB_CAP_BOLD) "\033[5m", // blink (TB_CAP_BLINK) "", // sitm (TB_CAP_ITALIC) "\033[7m", // rev (TB_CAP_REVERSE) "", // smkx (TB_CAP_ENTER_KEYPAD) "", // rmkx (TB_CAP_EXIT_KEYPAD) }; static struct { const char *name; const char **caps; const char *alias; } builtin_terms[] = { {"xterm", xterm_caps, "" }, {"linux", linux_caps, "" }, {"screen", screen_caps, "tmux"}, {"rxvt-256color", rxvt_256color_caps, "" }, {"rxvt-unicode", rxvt_unicode_caps, "rxvt"}, {"Eterm", eterm_caps, "" }, {NULL, NULL, NULL }, }; /* END codegen c */ static struct { const char *cap; const uint16_t key; const uint8_t mod; } builtin_mod_caps[] = { // xterm arrows {"\x1b[1;2A", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, {"\x1b[1;3A", TB_KEY_ARROW_UP, TB_MOD_ALT }, {"\x1b[1;4A", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5A", TB_KEY_ARROW_UP, TB_MOD_CTRL }, {"\x1b[1;6A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, {"\x1b[1;3B", TB_KEY_ARROW_DOWN, TB_MOD_ALT }, {"\x1b[1;4B", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, {"\x1b[1;6B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, {"\x1b[1;3C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT }, {"\x1b[1;4C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, {"\x1b[1;6C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, {"\x1b[1;3D", TB_KEY_ARROW_LEFT, TB_MOD_ALT }, {"\x1b[1;4D", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, {"\x1b[1;6D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, // xterm keys {"\x1b[1;2H", TB_KEY_HOME, TB_MOD_SHIFT }, {"\x1b[1;3H", TB_KEY_HOME, TB_MOD_ALT }, {"\x1b[1;4H", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5H", TB_KEY_HOME, TB_MOD_CTRL }, {"\x1b[1;6H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2F", TB_KEY_END, TB_MOD_SHIFT }, {"\x1b[1;3F", TB_KEY_END, TB_MOD_ALT }, {"\x1b[1;4F", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5F", TB_KEY_END, TB_MOD_CTRL }, {"\x1b[1;6F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[2;2~", TB_KEY_INSERT, TB_MOD_SHIFT }, {"\x1b[2;3~", TB_KEY_INSERT, TB_MOD_ALT }, {"\x1b[2;4~", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[2;5~", TB_KEY_INSERT, TB_MOD_CTRL }, {"\x1b[2;6~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[2;7~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[2;8~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[3;2~", TB_KEY_DELETE, TB_MOD_SHIFT }, {"\x1b[3;3~", TB_KEY_DELETE, TB_MOD_ALT }, {"\x1b[3;4~", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[3;5~", TB_KEY_DELETE, TB_MOD_CTRL }, {"\x1b[3;6~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[3;7~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[3;8~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[5;2~", TB_KEY_PGUP, TB_MOD_SHIFT }, {"\x1b[5;3~", TB_KEY_PGUP, TB_MOD_ALT }, {"\x1b[5;4~", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[5;5~", TB_KEY_PGUP, TB_MOD_CTRL }, {"\x1b[5;6~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[5;7~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[5;8~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[6;2~", TB_KEY_PGDN, TB_MOD_SHIFT }, {"\x1b[6;3~", TB_KEY_PGDN, TB_MOD_ALT }, {"\x1b[6;4~", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[6;5~", TB_KEY_PGDN, TB_MOD_CTRL }, {"\x1b[6;6~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[6;7~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[6;8~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2P", TB_KEY_F1, TB_MOD_SHIFT }, {"\x1b[1;3P", TB_KEY_F1, TB_MOD_ALT }, {"\x1b[1;4P", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5P", TB_KEY_F1, TB_MOD_CTRL }, {"\x1b[1;6P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2Q", TB_KEY_F2, TB_MOD_SHIFT }, {"\x1b[1;3Q", TB_KEY_F2, TB_MOD_ALT }, {"\x1b[1;4Q", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5Q", TB_KEY_F2, TB_MOD_CTRL }, {"\x1b[1;6Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2R", TB_KEY_F3, TB_MOD_SHIFT }, {"\x1b[1;3R", TB_KEY_F3, TB_MOD_ALT }, {"\x1b[1;4R", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5R", TB_KEY_F3, TB_MOD_CTRL }, {"\x1b[1;6R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[1;2S", TB_KEY_F4, TB_MOD_SHIFT }, {"\x1b[1;3S", TB_KEY_F4, TB_MOD_ALT }, {"\x1b[1;4S", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[1;5S", TB_KEY_F4, TB_MOD_CTRL }, {"\x1b[1;6S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[1;7S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[1;8S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[15;2~", TB_KEY_F5, TB_MOD_SHIFT }, {"\x1b[15;3~", TB_KEY_F5, TB_MOD_ALT }, {"\x1b[15;4~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[15;5~", TB_KEY_F5, TB_MOD_CTRL }, {"\x1b[15;6~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[15;7~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[15;8~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[17;2~", TB_KEY_F6, TB_MOD_SHIFT }, {"\x1b[17;3~", TB_KEY_F6, TB_MOD_ALT }, {"\x1b[17;4~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[17;5~", TB_KEY_F6, TB_MOD_CTRL }, {"\x1b[17;6~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[17;7~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[17;8~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[18;2~", TB_KEY_F7, TB_MOD_SHIFT }, {"\x1b[18;3~", TB_KEY_F7, TB_MOD_ALT }, {"\x1b[18;4~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[18;5~", TB_KEY_F7, TB_MOD_CTRL }, {"\x1b[18;6~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[18;7~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[18;8~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[19;2~", TB_KEY_F8, TB_MOD_SHIFT }, {"\x1b[19;3~", TB_KEY_F8, TB_MOD_ALT }, {"\x1b[19;4~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[19;5~", TB_KEY_F8, TB_MOD_CTRL }, {"\x1b[19;6~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[19;7~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[19;8~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[20;2~", TB_KEY_F9, TB_MOD_SHIFT }, {"\x1b[20;3~", TB_KEY_F9, TB_MOD_ALT }, {"\x1b[20;4~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[20;5~", TB_KEY_F9, TB_MOD_CTRL }, {"\x1b[20;6~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[20;7~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[20;8~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[21;2~", TB_KEY_F10, TB_MOD_SHIFT }, {"\x1b[21;3~", TB_KEY_F10, TB_MOD_ALT }, {"\x1b[21;4~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[21;5~", TB_KEY_F10, TB_MOD_CTRL }, {"\x1b[21;6~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[21;7~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[21;8~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[23;2~", TB_KEY_F11, TB_MOD_SHIFT }, {"\x1b[23;3~", TB_KEY_F11, TB_MOD_ALT }, {"\x1b[23;4~", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[23;5~", TB_KEY_F11, TB_MOD_CTRL }, {"\x1b[23;6~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[23;7~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[23;8~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[24;2~", TB_KEY_F12, TB_MOD_SHIFT }, {"\x1b[24;3~", TB_KEY_F12, TB_MOD_ALT }, {"\x1b[24;4~", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[24;5~", TB_KEY_F12, TB_MOD_CTRL }, {"\x1b[24;6~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[24;7~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[24;8~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, // rxvt arrows {"\x1b[a", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, {"\x1b\x1b[A", TB_KEY_ARROW_UP, TB_MOD_ALT }, {"\x1b\x1b[a", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL }, {"\x1b\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, {"\x1b\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_ALT }, {"\x1b\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, {"\x1b\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, {"\x1b\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT }, {"\x1b\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, {"\x1b\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, {"\x1b\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_ALT }, {"\x1b\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, {"\x1b\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, // rxvt keys {"\x1b[7$", TB_KEY_HOME, TB_MOD_SHIFT }, {"\x1b\x1b[7~", TB_KEY_HOME, TB_MOD_ALT }, {"\x1b\x1b[7$", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL }, {"\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b\x1b[8~", TB_KEY_END, TB_MOD_ALT }, {"\x1b\x1b[8$", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[8^", TB_KEY_END, TB_MOD_CTRL }, {"\x1b\x1b[8^", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[8$", TB_KEY_END, TB_MOD_SHIFT }, {"\x1b\x1b[2~", TB_KEY_INSERT, TB_MOD_ALT }, {"\x1b\x1b[2$", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL }, {"\x1b\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[2$", TB_KEY_INSERT, TB_MOD_SHIFT }, {"\x1b\x1b[3~", TB_KEY_DELETE, TB_MOD_ALT }, {"\x1b\x1b[3$", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL }, {"\x1b\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[3$", TB_KEY_DELETE, TB_MOD_SHIFT }, {"\x1b\x1b[5~", TB_KEY_PGUP, TB_MOD_ALT }, {"\x1b\x1b[5$", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL }, {"\x1b\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[5$", TB_KEY_PGUP, TB_MOD_SHIFT }, {"\x1b\x1b[6~", TB_KEY_PGDN, TB_MOD_ALT }, {"\x1b\x1b[6$", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL }, {"\x1b\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[6$", TB_KEY_PGDN, TB_MOD_SHIFT }, {"\x1b\x1b[11~", TB_KEY_F1, TB_MOD_ALT }, {"\x1b\x1b[23~", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[11^", TB_KEY_F1, TB_MOD_CTRL }, {"\x1b\x1b[11^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[23~", TB_KEY_F1, TB_MOD_SHIFT }, {"\x1b\x1b[12~", TB_KEY_F2, TB_MOD_ALT }, {"\x1b\x1b[24~", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[12^", TB_KEY_F2, TB_MOD_CTRL }, {"\x1b\x1b[12^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[24~", TB_KEY_F2, TB_MOD_SHIFT }, {"\x1b\x1b[13~", TB_KEY_F3, TB_MOD_ALT }, {"\x1b\x1b[25~", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[13^", TB_KEY_F3, TB_MOD_CTRL }, {"\x1b\x1b[13^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[25~", TB_KEY_F3, TB_MOD_SHIFT }, {"\x1b\x1b[14~", TB_KEY_F4, TB_MOD_ALT }, {"\x1b\x1b[26~", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[14^", TB_KEY_F4, TB_MOD_CTRL }, {"\x1b\x1b[14^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[26~", TB_KEY_F4, TB_MOD_SHIFT }, {"\x1b\x1b[15~", TB_KEY_F5, TB_MOD_ALT }, {"\x1b\x1b[28~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[15^", TB_KEY_F5, TB_MOD_CTRL }, {"\x1b\x1b[15^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[28~", TB_KEY_F5, TB_MOD_SHIFT }, {"\x1b\x1b[17~", TB_KEY_F6, TB_MOD_ALT }, {"\x1b\x1b[29~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[17^", TB_KEY_F6, TB_MOD_CTRL }, {"\x1b\x1b[17^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[29~", TB_KEY_F6, TB_MOD_SHIFT }, {"\x1b\x1b[18~", TB_KEY_F7, TB_MOD_ALT }, {"\x1b\x1b[31~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[18^", TB_KEY_F7, TB_MOD_CTRL }, {"\x1b\x1b[18^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[31~", TB_KEY_F7, TB_MOD_SHIFT }, {"\x1b\x1b[19~", TB_KEY_F8, TB_MOD_ALT }, {"\x1b\x1b[32~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[19^", TB_KEY_F8, TB_MOD_CTRL }, {"\x1b\x1b[19^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[32~", TB_KEY_F8, TB_MOD_SHIFT }, {"\x1b\x1b[20~", TB_KEY_F9, TB_MOD_ALT }, {"\x1b\x1b[33~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[20^", TB_KEY_F9, TB_MOD_CTRL }, {"\x1b\x1b[20^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[33~", TB_KEY_F9, TB_MOD_SHIFT }, {"\x1b\x1b[21~", TB_KEY_F10, TB_MOD_ALT }, {"\x1b\x1b[34~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[21^", TB_KEY_F10, TB_MOD_CTRL }, {"\x1b\x1b[21^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[34~", TB_KEY_F10, TB_MOD_SHIFT }, {"\x1b\x1b[23~", TB_KEY_F11, TB_MOD_ALT }, {"\x1b\x1b[23$", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[23^", TB_KEY_F11, TB_MOD_CTRL }, {"\x1b\x1b[23^", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[23$", TB_KEY_F11, TB_MOD_SHIFT }, {"\x1b\x1b[24~", TB_KEY_F12, TB_MOD_ALT }, {"\x1b\x1b[24$", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT }, {"\x1b[24^", TB_KEY_F12, TB_MOD_CTRL }, {"\x1b\x1b[24^", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1b\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, {"\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT }, {"\x1b[24$", TB_KEY_F12, TB_MOD_SHIFT }, // linux console/putty arrows {"\x1b[A", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, {"\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, {"\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, {"\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, // more putty arrows {"\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL }, {"\x1b\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, {"\x1b\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, {"\x1b\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, {"\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, {"\x1b\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, {NULL, 0, 0 }, }; static const unsigned char utf8_length[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, 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, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1}; static const unsigned char utf8_mask[6] = {0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01}; static int tb_reset(void); static int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, const char *fmt, va_list vl); static int init_term_attrs(void); static int init_term_caps(void); static int init_cap_trie(void); static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod); static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, size_t *depth); static int cap_trie_deinit(struct cap_trie_t *node); static int init_resize_handler(void); static int send_init_escape_codes(void); static int send_clear(void); static int update_term_size(void); static int update_term_size_via_esc(void); static int init_cellbuf(void); static int tb_deinit(void); static int load_terminfo(void); static int load_terminfo_from_path(const char *path, const char *term); static int read_terminfo_path(const char *path); static int parse_terminfo_caps(void); static int load_builtin_caps(void); static const char *get_terminfo_string(int16_t str_offsets_pos, int16_t str_table_pos, int16_t str_table_len, int16_t str_index); static int wait_event(struct tb_event *event, int timeout); static int extract_event(struct tb_event *event); static int extract_esc(struct tb_event *event); static int extract_esc_user(struct tb_event *event, int is_post); static int extract_esc_cap(struct tb_event *event); static int extract_esc_mouse(struct tb_event *event); static int resize_cellbufs(void); static void handle_resize(int sig); static int send_attr(uintattr_t fg, uintattr_t bg); static int send_sgr(uintattr_t fg, uintattr_t bg, int fg_is_default, int bg_is_default); static int send_cursor_if(int x, int y); static int send_char(int x, int y, uint32_t ch); static int send_cluster(int x, int y, uint32_t *ch, size_t nch); static int convert_num(uint32_t num, char *buf); static int cell_cmp(struct tb_cell *a, struct tb_cell *b); static int cell_copy(struct tb_cell *dst, struct tb_cell *src); static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, uintattr_t fg, uintattr_t bg); static int cell_reserve_ech(struct tb_cell *cell, size_t n); static int cell_free(struct tb_cell *cell); static int cellbuf_init(struct cellbuf_t *c, int w, int h); static int cellbuf_free(struct cellbuf_t *c); static int cellbuf_clear(struct cellbuf_t *c); static int cellbuf_get(struct cellbuf_t *c, int x, int y, struct tb_cell **out); static int cellbuf_resize(struct cellbuf_t *c, int w, int h); static int bytebuf_puts(struct bytebuf_t *b, const char *str); static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr); static int bytebuf_shift(struct bytebuf_t *b, size_t n); static int bytebuf_flush(struct bytebuf_t *b, int fd); static int bytebuf_reserve(struct bytebuf_t *b, size_t sz); static int bytebuf_free(struct bytebuf_t *b); int tb_init(void) { return tb_init_file("/dev/tty"); } int tb_init_file(const char *path) { if (global.initialized) { return TB_ERR_INIT_ALREADY; } int ttyfd = open(path, O_RDWR); if (ttyfd < 0) { global.last_errno = errno; return TB_ERR_INIT_OPEN; } global.ttyfd_open = 1; return tb_init_fd(ttyfd); } int tb_init_fd(int ttyfd) { return tb_init_rwfd(ttyfd, ttyfd); } int tb_init_rwfd(int rfd, int wfd) { int rv; tb_reset(); global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1; global.rfd = rfd; global.wfd = wfd; do { if_err_break(rv, init_term_attrs()); if_err_break(rv, init_term_caps()); if_err_break(rv, init_cap_trie()); if_err_break(rv, init_resize_handler()); if_err_break(rv, send_init_escape_codes()); if_err_break(rv, send_clear()); if_err_break(rv, update_term_size()); if_err_break(rv, init_cellbuf()); global.initialized = 1; } while (0); if (rv != TB_OK) { tb_deinit(); } return rv; } int tb_shutdown(void) { if_not_init_return(); tb_deinit(); return TB_OK; } int tb_width(void) { if_not_init_return(); return global.width; } int tb_height(void) { if_not_init_return(); return global.height; } int tb_clear(void) { if_not_init_return(); return cellbuf_clear(&global.back); } int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg) { if_not_init_return(); global.fg = fg; global.bg = bg; return TB_OK; } int tb_present(void) { if_not_init_return(); int rv; // TODO Assert global.back.(width,height) == global.front.(width,height) global.last_x = -1; global.last_y = -1; int x, y, i; for (y = 0; y < global.front.height; y++) { for (x = 0; x < global.front.width;) { struct tb_cell *back, *front; if_err_return(rv, cellbuf_get(&global.back, x, y, &back)); if_err_return(rv, cellbuf_get(&global.front, x, y, &front)); int w; { #ifdef TB_OPT_EGC if (back->nech > 0) w = wcswidth((wchar_t *)back->ech, back->nech); else #endif /* wcwidth() simply returns -1 on overflow of wchar_t */ w = wcwidth((wchar_t)back->ch); } if (w < 1) { w = 1; } if (cell_cmp(back, front) != 0) { cell_copy(front, back); send_attr(back->fg, back->bg); if (w > 1 && x >= global.front.width - (w - 1)) { for (i = x; i < global.front.width; i++) { send_char(i, y, ' '); } } else { { #ifdef TB_OPT_EGC if (back->nech > 0) send_cluster(x, y, back->ech, back->nech); else #endif send_char(x, y, back->ch); } for (i = 1; i < w; i++) { struct tb_cell *front_wide; if_err_return(rv, cellbuf_get(&global.front, x + i, y, &front_wide)); if_err_return(rv, cell_set(front_wide, 0, 1, back->fg, back->bg)); } } } x += w; } } if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y)); if_err_return(rv, bytebuf_flush(&global.out, global.wfd)); return TB_OK; } int tb_invalidate(void) { int rv; if_not_init_return(); if_err_return(rv, resize_cellbufs()); return TB_OK; } int tb_set_cursor(int cx, int cy) { if_not_init_return(); int rv; if (cx < 0) cx = 0; if (cy < 0) cy = 0; if (global.cursor_x == -1) { if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR])); } if_err_return(rv, send_cursor_if(cx, cy)); global.cursor_x = cx; global.cursor_y = cy; return TB_OK; } int tb_hide_cursor(void) { if_not_init_return(); int rv; if (global.cursor_x >= 0) { if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR])); } global.cursor_x = -1; global.cursor_y = -1; return TB_OK; } int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) { if_not_init_return(); return tb_set_cell_ex(x, y, &ch, 1, fg, bg); } int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, uintattr_t bg) { if_not_init_return(); int rv; struct tb_cell *cell; if_err_return(rv, cellbuf_get(&global.back, x, y, &cell)); if_err_return(rv, cell_set(cell, ch, nch, fg, bg)); return TB_OK; } int tb_extend_cell(int x, int y, uint32_t ch) { if_not_init_return(); #ifdef TB_OPT_EGC int rv; struct tb_cell *cell; size_t nech; if_err_return(rv, cellbuf_get(&global.back, x, y, &cell)); if (cell->nech > 0) { // append to ech nech = cell->nech + 1; if_err_return(rv, cell_reserve_ech(cell, nech)); cell->ech[nech - 1] = ch; } else { // make new ech nech = 2; if_err_return(rv, cell_reserve_ech(cell, nech)); cell->ech[0] = cell->ch; cell->ech[1] = ch; } cell->ech[nech] = '\0'; cell->nech = nech; return TB_OK; #else (void)x; (void)y; (void)ch; return TB_ERR; #endif } int tb_set_input_mode(int mode) { if_not_init_return(); if (mode == TB_INPUT_CURRENT) { return global.input_mode; } if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) { mode |= TB_INPUT_ESC; } if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) { mode &= ~TB_INPUT_ALT; } if (mode & TB_INPUT_MOUSE) { bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE); bytebuf_flush(&global.out, global.wfd); } else { bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE); bytebuf_flush(&global.out, global.wfd); } global.input_mode = mode; return TB_OK; } int tb_set_output_mode(int mode) { if_not_init_return(); switch (mode) { case TB_OUTPUT_CURRENT: return global.output_mode; case TB_OUTPUT_NORMAL: case TB_OUTPUT_256: case TB_OUTPUT_216: case TB_OUTPUT_GRAYSCALE: #ifdef TB_OPT_TRUECOLOR case TB_OUTPUT_TRUECOLOR: #endif global.last_fg = ~global.fg; global.last_bg = ~global.bg; global.output_mode = mode; return TB_OK; } return TB_ERR; } int tb_peek_event(struct tb_event *event, int timeout_ms) { if_not_init_return(); return wait_event(event, timeout_ms); } int tb_poll_event(struct tb_event *event) { if_not_init_return(); return wait_event(event, -1); } int tb_get_fds(int *ttyfd, int *resizefd) { if_not_init_return(); *ttyfd = global.rfd; *resizefd = global.resize_pipefd[0]; return TB_OK; } int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str) { return tb_print_ex(x, y, fg, bg, NULL, str); } int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, const char *str) { int rv; uint32_t uni; int w, ix = x; if (out_w) { *out_w = 0; } while (*str) { str += tb_utf8_char_to_unicode(&uni, str); w = wcwidth((wchar_t)uni); if (w < 0) { w = 1; } if (w == 0 && x > ix) { if_err_return(rv, tb_extend_cell(x - 1, y, uni)); } else { if_err_return(rv, tb_set_cell(x, y, uni, fg, bg)); } x += w; if (out_w) { *out_w += w; } } return TB_OK; } int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...) { int rv; va_list vl; va_start(vl, fmt); rv = tb_printf_inner(x, y, fg, bg, NULL, fmt, vl); va_end(vl); return rv; } int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, const char *fmt, ...) { int rv; va_list vl; va_start(vl, fmt); rv = tb_printf_inner(x, y, fg, bg, out_w, fmt, vl); va_end(vl); return rv; } int tb_send(const char *buf, size_t nbuf) { return bytebuf_nputs(&global.out, buf, nbuf); } int tb_sendf(const char *fmt, ...) { int rv; char buf[TB_OPT_PRINTF_BUF]; va_list vl; va_start(vl, fmt); rv = vsnprintf(buf, sizeof(buf), fmt, vl); va_end(vl); if (rv < 0 || rv >= (int)sizeof(buf)) { return TB_ERR; } return tb_send(buf, (size_t)rv); } int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) { switch (fn_type) { case TB_FUNC_EXTRACT_PRE: global.fn_extract_esc_pre = fn; return TB_OK; case TB_FUNC_EXTRACT_POST: global.fn_extract_esc_post = fn; return TB_OK; } return TB_ERR; } struct tb_cell *tb_cell_buffer(void) { if (!global.initialized) return NULL; return global.back.cells; } int tb_utf8_char_length(char c) { return utf8_length[(unsigned char)c]; } int tb_utf8_char_to_unicode(uint32_t *out, const char *c) { if (*c == 0) { return TB_ERR; } int i; unsigned char len = tb_utf8_char_length(*c); unsigned char mask = utf8_mask[len - 1]; uint32_t result = c[0] & mask; for (i = 1; i < len; ++i) { result <<= 6; result |= c[i] & 0x3f; } *out = result; return (int)len; } int tb_utf8_unicode_to_char(char *out, uint32_t c) { int len = 0; int first; int i; if (c < 0x80) { first = 0; len = 1; } else if (c < 0x800) { first = 0xc0; len = 2; } else if (c < 0x10000) { first = 0xe0; len = 3; } else if (c < 0x200000) { first = 0xf0; len = 4; } else if (c < 0x4000000) { first = 0xf8; len = 5; } else { first = 0xfc; len = 6; } for (i = len - 1; i > 0; --i) { out[i] = (c & 0x3f) | 0x80; c >>= 6; } out[0] = c | first; return len; } int tb_last_errno(void) { return global.last_errno; } const char *tb_strerror(int err) { switch (err) { case TB_OK: return "Success"; case TB_ERR_NEED_MORE: return "Not enough input"; case TB_ERR_INIT_ALREADY: return "Termbox initialized already"; case TB_ERR_MEM: return "Out of memory"; case TB_ERR_NO_EVENT: return "No event"; case TB_ERR_NO_TERM: return "No TERM in environment"; case TB_ERR_NOT_INIT: return "Termbox not initialized"; case TB_ERR_OUT_OF_BOUNDS: return "Out of bounds"; case TB_ERR_UNSUPPORTED_TERM: return "Unsupported terminal"; case TB_ERR_CAP_COLLISION: return "Termcaps collision"; case TB_ERR_RESIZE_SSCANF: return "Terminal width/height not received by sscanf() after " "resize"; case TB_ERR: case TB_ERR_INIT_OPEN: case TB_ERR_READ: case TB_ERR_RESIZE_IOCTL: case TB_ERR_RESIZE_PIPE: case TB_ERR_RESIZE_SIGACTION: case TB_ERR_POLL: case TB_ERR_TCGETATTR: case TB_ERR_TCSETATTR: case TB_ERR_RESIZE_WRITE: case TB_ERR_RESIZE_POLL: case TB_ERR_RESIZE_READ: default: strerror_r(global.last_errno, global.errbuf, sizeof(global.errbuf)); return (const char *)global.errbuf; } } int tb_has_truecolor(void) { #ifdef TB_OPT_TRUECOLOR return 1; #else return 0; #endif } int tb_has_egc(void) { #ifdef TB_OPT_EGC return 1; #else return 0; #endif } const char *tb_version(void) { return TB_VERSION_STR; } static int tb_reset(void) { int ttyfd_open = global.ttyfd_open; memset(&global, 0, sizeof(global)); global.ttyfd = -1; global.rfd = -1; global.wfd = -1; global.ttyfd_open = ttyfd_open; global.resize_pipefd[0] = -1; global.resize_pipefd[1] = -1; global.width = -1; global.height = -1; global.cursor_x = -1; global.cursor_y = -1; global.last_x = -1; global.last_y = -1; global.fg = TB_DEFAULT; global.bg = TB_DEFAULT; global.last_fg = ~global.fg; global.last_bg = ~global.bg; global.input_mode = TB_INPUT_ESC; global.output_mode = TB_OUTPUT_NORMAL; return TB_OK; } static int init_term_attrs(void) { if (global.ttyfd < 0) { return TB_OK; } if (tcgetattr(global.ttyfd, &global.orig_tios) != 0) { global.last_errno = errno; return TB_ERR_TCGETATTR; } struct termios tios; memcpy(&tios, &global.orig_tios, sizeof(tios)); global.has_orig_tios = 1; cfmakeraw(&tios); tios.c_cc[VMIN] = 1; tios.c_cc[VTIME] = 0; if (tcsetattr(global.ttyfd, TCSAFLUSH, &tios) != 0) { global.last_errno = errno; return TB_ERR_TCSETATTR; } return TB_OK; } int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, const char *fmt, va_list vl) { int rv; char buf[TB_OPT_PRINTF_BUF]; rv = vsnprintf(buf, sizeof(buf), fmt, vl); if (rv < 0 || rv >= (int)sizeof(buf)) { return TB_ERR; } return tb_print_ex(x, y, fg, bg, out_w, buf); } static int init_term_caps(void) { if (load_terminfo() == TB_OK) { return parse_terminfo_caps(); } return load_builtin_caps(); } static int init_cap_trie(void) { int rv, i; // Add caps from terminfo or built-in for (i = 0; i < TB_CAP__COUNT_KEYS; i++) { if_err_return(rv, cap_trie_add(global.caps[i], tb_key_i(i), 0)); } // Add built-in mod caps for (i = 0; builtin_mod_caps[i].cap != NULL; i++) { rv = cap_trie_add(builtin_mod_caps[i].cap, builtin_mod_caps[i].key, builtin_mod_caps[i].mod); // Collisions are OK. This can happen if global.caps collides with // builtin_mod_caps. It is desirable to give precedence to global.caps // here. if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) { return rv; } } return TB_OK; } static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) { struct cap_trie_t *next, *node = &global.cap_trie; size_t i, j; for (i = 0; cap[i] != '\0'; i++) { char c = cap[i]; next = NULL; // Check if c is already a child of node for (j = 0; j < node->nchildren; j++) { if (node->children[j].c == c) { next = &node->children[j]; break; } } if (!next) { // We need to add a new child to node node->nchildren += 1; node->children = tb_realloc(node->children, sizeof(*node) * node->nchildren); if (!node->children) { return TB_ERR_MEM; } next = &node->children[node->nchildren - 1]; memset(next, 0, sizeof(*next)); next->c = c; } // Continue node = next; } if (node->is_leaf) { // Already a leaf here return TB_ERR_CAP_COLLISION; } node->is_leaf = 1; node->key = key; node->mod = mod; return TB_OK; } static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, size_t *depth) { struct cap_trie_t *next, *node = &global.cap_trie; size_t i, j; *last = node; *depth = 0; for (i = 0; i < nbuf; i++) { char c = buf[i]; next = NULL; // Find c in node.children for (j = 0; j < node->nchildren; j++) { if (node->children[j].c == c) { next = &node->children[j]; break; } } if (!next) { // Not found return TB_OK; } node = next; *last = node; *depth += 1; if (node->is_leaf && node->nchildren < 1) { break; } } return TB_OK; } static int cap_trie_deinit(struct cap_trie_t *node) { size_t j; for (j = 0; j < node->nchildren; j++) { cap_trie_deinit(&node->children[j]); } if (node->children) { tb_free(node->children); } memset(node, 0, sizeof(*node)); return TB_OK; } static int init_resize_handler(void) { if (pipe(global.resize_pipefd) != 0) { global.last_errno = errno; return TB_ERR_RESIZE_PIPE; } struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handle_resize; if (sigaction(SIGWINCH, &sa, NULL) != 0) { global.last_errno = errno; return TB_ERR_RESIZE_SIGACTION; } return TB_OK; } static int send_init_escape_codes(void) { int rv; if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_CA])); if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_KEYPAD])); if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR])); return TB_OK; } static int send_clear(void) { int rv; if_err_return(rv, send_attr(global.fg, global.bg)); if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN])); if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y)); if_err_return(rv, bytebuf_flush(&global.out, global.wfd)); global.last_x = -1; global.last_y = -1; return TB_OK; } static int update_term_size(void) { int rv, ioctl_errno; if (global.ttyfd < 0) { return TB_OK; } struct winsize sz; memset(&sz, 0, sizeof(sz)); // Try ioctl TIOCGWINSZ if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) { global.width = sz.ws_col; global.height = sz.ws_row; return TB_OK; } ioctl_errno = errno; // Try >cursor(9999,9999), >u7, = 0) { bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]); bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]); bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_CA]); bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_KEYPAD]); bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE); bytebuf_flush(&global.out, global.wfd); } if (global.ttyfd >= 0) { if (global.has_orig_tios) { tcsetattr(global.ttyfd, TCSAFLUSH, &global.orig_tios); } if (global.ttyfd_open) { close(global.ttyfd); global.ttyfd_open = 0; } } sigaction(SIGWINCH, &(struct sigaction){.sa_handler = SIG_DFL}, NULL); if (global.resize_pipefd[0] >= 0) close(global.resize_pipefd[0]); if (global.resize_pipefd[1] >= 0) close(global.resize_pipefd[1]); cellbuf_free(&global.back); cellbuf_free(&global.front); bytebuf_free(&global.in); bytebuf_free(&global.out); if (global.terminfo) tb_free(global.terminfo); cap_trie_deinit(&global.cap_trie); tb_reset(); return TB_OK; } static int load_terminfo(void) { int rv; char tmp[PATH_MAX]; // See terminfo(5) "Fetching Compiled Descriptions" for a description of // this behavior. Some of these paths are compile-time ncurses options, so // best guesses are used here. const char *term = getenv("TERM"); if (!term) { return TB_ERR; } // If TERMINFO is set, try that directory and stop const char *terminfo = getenv("TERMINFO"); if (terminfo) { return load_terminfo_from_path(terminfo, term); } // Next try ~/.terminfo const char *home = getenv("HOME"); if (home) { snprintf_or_return(rv, tmp, sizeof(tmp), "%s/.terminfo", home); if_ok_return(rv, load_terminfo_from_path(tmp, term)); } // Next try TERMINFO_DIRS // // Note, empty entries are supposed to be interpretted as the "compiled-in // default", which is of course system-dependent. Previously /etc/terminfo // was used here. Let's skip empty entries altogether rather than give // precedence to a guess, and check common paths after this loop. const char *dirs = getenv("TERMINFO_DIRS"); if (dirs) { snprintf_or_return(rv, tmp, sizeof(tmp), "%s", dirs); char *dir = strtok(tmp, ":"); while (dir) { const char *cdir = dir; if (*cdir != '\0') { if_ok_return(rv, load_terminfo_from_path(cdir, term)); } dir = strtok(NULL, ":"); } } #ifdef TB_TERMINFO_DIR if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term)); #endif if_ok_return(rv, load_terminfo_from_path("/usr/local/etc/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/local/share/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/local/lib/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/etc/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/share/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/lib/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/usr/share/lib/terminfo", term)); if_ok_return(rv, load_terminfo_from_path("/lib/terminfo", term)); return TB_ERR; } static int load_terminfo_from_path(const char *path, const char *term) { int rv; char tmp[PATH_MAX]; // Look for term at this terminfo location, e.g., /x/xterm snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); if_ok_return(rv, read_terminfo_path(tmp)); #ifdef __APPLE__ // Try the Darwin equivalent path, e.g., /78/xterm snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); return read_terminfo_path(tmp); #endif return TB_ERR; } static int read_terminfo_path(const char *path) { FILE *fp = fopen(path, "rb"); if (!fp) { return TB_ERR; } struct stat st; if (fstat(fileno(fp), &st) != 0) { fclose(fp); return TB_ERR; } size_t fsize = st.st_size; char *data = tb_malloc(fsize); if (!data) { fclose(fp); return TB_ERR; } if (fread(data, 1, fsize, fp) != fsize) { fclose(fp); tb_free(data); return TB_ERR; } global.terminfo = data; global.nterminfo = fsize; fclose(fp); return TB_OK; } static int parse_terminfo_caps(void) { // See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT" for a // description of this behavior. // Ensure there's at least a header's worth of data if (global.nterminfo < 6) { return TB_ERR; } int16_t *header = (int16_t *)global.terminfo; // header[0] the magic number (octal 0432 or 01036) // header[1] the size, in bytes, of the names section // header[2] the number of bytes in the boolean section // header[3] the number of short integers in the numbers section // header[4] the number of offsets (short integers) in the strings section // header[5] the size, in bytes, of the string table // Legacy ints are 16-bit, extended ints are 32-bit const int bytes_per_int = header[0] == 01036 ? 4 // 32-bit : 2; // 16-bit // > Between the boolean section and the number section, a null byte will be // > inserted, if necessary, to ensure that the number section begins on an // > even byte const int align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0; const int pos_str_offsets = (6 * sizeof(int16_t)) // header (12 bytes) + header[1] // length of names section + header[2] // length of boolean section + align_offset + (header[3] * bytes_per_int); // length of numbers section const int pos_str_table = pos_str_offsets + (header[4] * sizeof(int16_t)); // length of string offsets table // Load caps int i; for (i = 0; i < TB_CAP__COUNT; i++) { const char *cap = get_terminfo_string(pos_str_offsets, pos_str_table, header[5], terminfo_cap_indexes[i]); if (!cap) { // Something is not right return TB_ERR; } global.caps[i] = cap; } return TB_OK; } static int load_builtin_caps(void) { int i, j; const char *term = getenv("TERM"); if (!term) { return TB_ERR_NO_TERM; } // Check for exact TERM match for (i = 0; builtin_terms[i].name != NULL; i++) { if (strcmp(term, builtin_terms[i].name) == 0) { for (j = 0; j < TB_CAP__COUNT; j++) { global.caps[j] = builtin_terms[i].caps[j]; } return TB_OK; } } // Check for partial TERM or alias match for (i = 0; builtin_terms[i].name != NULL; i++) { if (strstr(term, builtin_terms[i].name) != NULL || (*(builtin_terms[i].alias) != '\0' && strstr(term, builtin_terms[i].alias) != NULL)) { for (j = 0; j < TB_CAP__COUNT; j++) { global.caps[j] = builtin_terms[i].caps[j]; } return TB_OK; } } return TB_ERR_UNSUPPORTED_TERM; } static const char *get_terminfo_string(int16_t str_offsets_pos, int16_t str_table_pos, int16_t str_table_len, int16_t str_index) { const int16_t *str_offset = (int16_t *)(global.terminfo + (int)str_offsets_pos + ((int)str_index * (int)sizeof(int16_t))); if (*str_offset < 0) { // A negative indicates the cap is absent from this terminal return ""; } if (*str_offset >= str_table_len) { // Invalid string offset return NULL; } if (((size_t)((int)str_table_pos + (int)*str_offset)) >= global.nterminfo) { // Truncated/corrupt terminfo? return NULL; } return ( const char *)(global.terminfo + (int)str_table_pos + (int)*str_offset); } static int wait_event(struct tb_event *event, int timeout) { int rv; char buf[TB_OPT_READ_BUF]; memset(event, 0, sizeof(*event)); if_ok_return(rv, extract_event(event)); fd_set fds; struct timeval tv; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; do { FD_ZERO(&fds); FD_SET(global.rfd, &fds); FD_SET(global.resize_pipefd[0], &fds); int maxfd = global.resize_pipefd[0] > global.rfd ? global.resize_pipefd[0] : global.rfd; int select_rv = select(maxfd + 1, &fds, NULL, NULL, (timeout < 0) ? NULL : &tv); if (select_rv < 0) { // Let EINTR/EAGAIN bubble up global.last_errno = errno; return TB_ERR_POLL; } else if (select_rv == 0) { return TB_ERR_NO_EVENT; } int tty_has_events = (FD_ISSET(global.rfd, &fds)); int resize_has_events = (FD_ISSET(global.resize_pipefd[0], &fds)); if (tty_has_events) { ssize_t read_rv = read(global.rfd, buf, sizeof(buf)); if (read_rv < 0) { global.last_errno = errno; return TB_ERR_READ; } else if (read_rv > 0) { bytebuf_nputs(&global.in, buf, read_rv); } } if (resize_has_events) { int ignore = 0; read(global.resize_pipefd[0], &ignore, sizeof(ignore)); // TODO Harden against errors encountered mid-resize if_err_return(rv, update_term_size()); if_err_return(rv, resize_cellbufs()); event->type = TB_EVENT_RESIZE; event->w = global.width; event->h = global.height; return TB_OK; } memset(event, 0, sizeof(*event)); if_ok_return(rv, extract_event(event)); } while (timeout == -1); return rv; } static int extract_event(struct tb_event *event) { int rv; struct bytebuf_t *in = &global.in; if (in->len == 0) { return TB_ERR; } if (in->buf[0] == '\x1b') { // Escape sequence? // In TB_INPUT_ESC, skip if the buffer is a single escape char if (!((global.input_mode & TB_INPUT_ESC) && in->len == 1)) { if_ok_or_need_more_return(rv, extract_esc(event)); } // Escape key? if (global.input_mode & TB_INPUT_ESC) { event->type = TB_EVENT_KEY; event->ch = 0; event->key = TB_KEY_ESC; event->mod = 0; bytebuf_shift(in, 1); return TB_OK; } // Recurse for alt key event->mod |= TB_MOD_ALT; bytebuf_shift(in, 1); return extract_event(event); } // ASCII control key? if ((uint16_t)in->buf[0] < TB_KEY_SPACE || in->buf[0] == TB_KEY_BACKSPACE2) { event->type = TB_EVENT_KEY; event->ch = 0; event->key = (uint16_t)in->buf[0]; event->mod |= TB_MOD_CTRL; bytebuf_shift(in, 1); return TB_OK; } // UTF-8? if (in->len >= (size_t)tb_utf8_char_length(in->buf[0])) { event->type = TB_EVENT_KEY; tb_utf8_char_to_unicode(&event->ch, in->buf); event->key = 0; bytebuf_shift(in, tb_utf8_char_length(in->buf[0])); return TB_OK; } // Need more input return TB_ERR; } static int extract_esc(struct tb_event *event) { int rv; if_ok_or_need_more_return(rv, extract_esc_user(event, 0)); if_ok_or_need_more_return(rv, extract_esc_cap(event)); if_ok_or_need_more_return(rv, extract_esc_mouse(event)); if_ok_or_need_more_return(rv, extract_esc_user(event, 1)); return TB_ERR; } static int extract_esc_user(struct tb_event *event, int is_post) { int rv; size_t consumed = 0; struct bytebuf_t *in = &global.in; int (*fn)(struct tb_event *, size_t *); fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre; if (!fn) { return TB_ERR; } rv = fn(event, &consumed); if (rv == TB_OK) { bytebuf_shift(in, consumed); } if_ok_or_need_more_return(rv, rv); return TB_ERR; } static int extract_esc_cap(struct tb_event *event) { int rv; struct bytebuf_t *in = &global.in; struct cap_trie_t *node; size_t depth; if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth)); if (node->is_leaf) { // Found a leaf node event->type = TB_EVENT_KEY; event->ch = 0; event->key = node->key; event->mod = node->mod; bytebuf_shift(in, depth); return TB_OK; } else if (node->nchildren > 0 && in->len <= depth) { // Found a branch node (not enough input) return TB_ERR_NEED_MORE; } return TB_ERR; } static int extract_esc_mouse(struct tb_event *event) { struct bytebuf_t *in = &global.in; enum type { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX }; char *cmp[TYPE_MAX] = {// // X10 mouse encoding, the simplest one // \x1b [ M Cb Cx Cy [TYPE_VT200] = "\x1b[M", // xterm 1006 extended mode or urxvt 1015 extended mode // xterm: \x1b [ < Cb ; Cx ; Cy (M or m) [TYPE_1006] = "\x1b[<", // urxvt: \x1b [ Cb ; Cx ; Cy M [TYPE_1015] = "\x1b["}; enum type type = 0; int ret = TB_ERR; // Unrolled at compile-time (probably) for (; type < TYPE_MAX; type++) { size_t size = strlen(cmp[type]); if (in->len >= size && (strncmp(cmp[type], in->buf, size)) == 0) { break; } } if (type == TYPE_MAX) { ret = TB_ERR; // No match return ret; } size_t buf_shift = 0; switch (type) { case TYPE_VT200: if (in->len >= 6) { int b = in->buf[3] - 0x20; int fail = 0; switch (b & 3) { case 0: event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP : TB_KEY_MOUSE_LEFT; break; case 1: event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN : TB_KEY_MOUSE_MIDDLE; break; case 2: event->key = TB_KEY_MOUSE_RIGHT; break; case 3: event->key = TB_KEY_MOUSE_RELEASE; break; default: ret = TB_ERR; fail = 1; break; } if (!fail) { if ((b & 32) != 0) { event->mod |= TB_MOD_MOTION; } // the coord is 1,1 for upper left event->x = ((uint8_t)in->buf[4]) - 0x21; event->y = ((uint8_t)in->buf[5]) - 0x21; ret = TB_OK; } buf_shift = 6; } break; case TYPE_1006: // fallthrough case TYPE_1015: { size_t index_fail = (size_t)-1; enum { FIRST_M = 0, FIRST_SEMICOLON, LAST_SEMICOLON, FIRST_LAST_MAX }; size_t indices[FIRST_LAST_MAX] = {index_fail, index_fail, index_fail}; int m_is_capital = 0; for (size_t i = 0; i < in->len; i++) { if (in->buf[i] == ';') { if (indices[FIRST_SEMICOLON] == index_fail) { indices[FIRST_SEMICOLON] = i; } else { indices[LAST_SEMICOLON] = i; } } else if (indices[FIRST_M] == index_fail) { if (in->buf[i] == 'm' || in->buf[i] == 'M') { m_is_capital = (in->buf[i] == 'M'); indices[FIRST_M] = i; } } } if (indices[FIRST_M] == index_fail || indices[FIRST_SEMICOLON] == index_fail || indices[LAST_SEMICOLON] == index_fail) { ret = TB_ERR; } else { int start = (type == TYPE_1015 ? 2 : 3); unsigned n1 = strtoul(&in->buf[start], NULL, 10); unsigned n2 = strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10); unsigned n3 = strtoul(&in->buf[indices[LAST_SEMICOLON] + 1], NULL, 10); if (type == TYPE_1015) { n1 -= 0x20; } int fail = 0; switch (n1 & 3) { case 0: event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP : TB_KEY_MOUSE_LEFT; break; case 1: event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN : TB_KEY_MOUSE_MIDDLE; break; case 2: event->key = TB_KEY_MOUSE_RIGHT; break; case 3: event->key = TB_KEY_MOUSE_RELEASE; break; default: ret = TB_ERR; fail = 1; break; } buf_shift = in->len; if (!fail) { if (!m_is_capital) { // on xterm mouse release is signaled by lowercase m event->key = TB_KEY_MOUSE_RELEASE; } if ((n1 & 32) != 0) { event->mod |= TB_MOD_MOTION; } event->x = ((uint8_t)n2) - 1; event->y = ((uint8_t)n3) - 1; ret = TB_OK; } } } break; case TYPE_MAX: ret = TB_ERR; } if (buf_shift > 0) { bytebuf_shift(in, buf_shift); } if (ret == TB_OK) { event->type = TB_EVENT_MOUSE; } return ret; } static int resize_cellbufs(void) { int rv; if_err_return(rv, cellbuf_resize(&global.back, global.width, global.height)); if_err_return(rv, cellbuf_resize(&global.front, global.width, global.height)); if_err_return(rv, cellbuf_clear(&global.front)); if_err_return(rv, send_clear()); return TB_OK; } static void handle_resize(int sig) { int errno_copy = errno; write(global.resize_pipefd[1], &sig, sizeof(sig)); errno = errno_copy; } static int send_attr(uintattr_t fg, uintattr_t bg) { int rv; if (fg == global.last_fg && bg == global.last_bg) { return TB_OK; } if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0])); uintattr_t cfg, cbg; switch (global.output_mode) { default: case TB_OUTPUT_NORMAL: cfg = fg & 0x0f; cbg = bg & 0x0f; break; case TB_OUTPUT_256: cfg = fg & 0xff; cbg = bg & 0xff; if (fg & TB_256_BLACK) cfg = 0; if (bg & TB_256_BLACK) cbg = 0; break; case TB_OUTPUT_216: cfg = fg & 0xff; cbg = bg & 0xff; if (cfg > 216) cfg = 216; if (cbg > 216) cbg = 216; cfg += 0x0f; cbg += 0x0f; break; case TB_OUTPUT_GRAYSCALE: cfg = fg & 0xff; cbg = bg & 0xff; if (cfg > 24) cfg = 24; if (cbg > 24) cbg = 24; cfg += 0xe7; cbg += 0xe7; break; #ifdef TB_OPT_TRUECOLOR case TB_OUTPUT_TRUECOLOR: cfg = fg & 0xffffff; cbg = bg & 0xffffff; if (fg & TB_TRUECOLOR_BLACK) cfg = 0; if (bg & TB_TRUECOLOR_BLACK) cbg = 0; break; #endif } uintattr_t attr_bold, attr_blink, attr_italic, attr_underline, attr_reverse; #ifdef TB_OPT_TRUECOLOR if (global.output_mode == TB_OUTPUT_TRUECOLOR) { attr_bold = TB_TRUECOLOR_BOLD; attr_blink = TB_TRUECOLOR_BLINK; attr_italic = TB_TRUECOLOR_ITALIC; attr_underline = TB_TRUECOLOR_UNDERLINE; attr_reverse = TB_TRUECOLOR_REVERSE; } else #endif { attr_bold = TB_BOLD; attr_blink = TB_BLINK; attr_italic = TB_ITALIC; attr_underline = TB_UNDERLINE; attr_reverse = TB_REVERSE; } if (fg & attr_bold) if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BOLD])); if (fg & attr_blink) if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BLINK])); if (fg & attr_underline) if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_UNDERLINE])); if (fg & attr_italic) if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_ITALIC])); if ((fg & attr_reverse) || (bg & attr_reverse)) if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_REVERSE])); int fg_is_default = (fg & 0xff) == 0; int bg_is_default = (bg & 0xff) == 0; if (global.output_mode == TB_OUTPUT_256) { if (fg & TB_256_BLACK) fg_is_default = 0; if (bg & TB_256_BLACK) bg_is_default = 0; } #ifdef TB_OPT_TRUECOLOR if (global.output_mode == TB_OUTPUT_TRUECOLOR) { fg_is_default = ((fg & 0xffffff) == 0) && ((fg & TB_TRUECOLOR_BLACK) == 0); bg_is_default = ((bg & 0xffffff) == 0) && ((bg & TB_TRUECOLOR_BLACK) == 0); } #endif if_err_return(rv, send_sgr(cfg, cbg, fg_is_default, bg_is_default)); global.last_fg = fg; global.last_bg = bg; return TB_OK; } static int send_sgr(uintattr_t cfg, uintattr_t cbg, int fg_is_default, int bg_is_default) { int rv; char nbuf[32]; if (fg_is_default && bg_is_default) { return TB_OK; } switch (global.output_mode) { default: case TB_OUTPUT_NORMAL: send_literal(rv, "\x1b["); if (!fg_is_default) { send_literal(rv, "3"); send_num(rv, nbuf, cfg - 1); if (!bg_is_default) { send_literal(rv, ";"); } } if (!bg_is_default) { send_literal(rv, "4"); send_num(rv, nbuf, cbg - 1); } send_literal(rv, "m"); break; case TB_OUTPUT_256: case TB_OUTPUT_216: case TB_OUTPUT_GRAYSCALE: send_literal(rv, "\x1b["); if (!fg_is_default) { send_literal(rv, "38;5;"); send_num(rv, nbuf, cfg); if (!bg_is_default) { send_literal(rv, ";"); } } if (!bg_is_default) { send_literal(rv, "48;5;"); send_num(rv, nbuf, cbg); } send_literal(rv, "m"); break; #ifdef TB_OPT_TRUECOLOR case TB_OUTPUT_TRUECOLOR: send_literal(rv, "\x1b["); if (!fg_is_default) { send_literal(rv, "38;2;"); send_num(rv, nbuf, (cfg >> 16) & 0xff); send_literal(rv, ";"); send_num(rv, nbuf, (cfg >> 8) & 0xff); send_literal(rv, ";"); send_num(rv, nbuf, cfg & 0xff); if (!bg_is_default) { send_literal(rv, ";"); } } if (!bg_is_default) { send_literal(rv, "48;2;"); send_num(rv, nbuf, (cbg >> 16) & 0xff); send_literal(rv, ";"); send_num(rv, nbuf, (cbg >> 8) & 0xff); send_literal(rv, ";"); send_num(rv, nbuf, cbg & 0xff); } send_literal(rv, "m"); break; #endif } return TB_OK; } static int send_cursor_if(int x, int y) { int rv; char nbuf[32]; if (x < 0 || y < 0) { return TB_OK; } send_literal(rv, "\x1b["); send_num(rv, nbuf, y + 1); send_literal(rv, ";"); send_num(rv, nbuf, x + 1); send_literal(rv, "H"); return TB_OK; } static int send_char(int x, int y, uint32_t ch) { return send_cluster(x, y, &ch, 1); } static int send_cluster(int x, int y, uint32_t *ch, size_t nch) { int rv; char abuf[8]; if (global.last_x != x - 1 || global.last_y != y) { if_err_return(rv, send_cursor_if(x, y)); } global.last_x = x; global.last_y = y; int i; for (i = 0; i < (int)nch; i++) { uint32_t ach = *(ch + i); int aw = tb_utf8_unicode_to_char(abuf, ach); if (!ach) { abuf[0] = ' '; } if_err_return(rv, bytebuf_nputs(&global.out, abuf, (size_t)aw)); } return TB_OK; } static int convert_num(uint32_t num, char *buf) { int i, l = 0; char ch; do { /* '0' = 48; 48 + num%10 < 58 < MAX_8bitCHAR */ buf[l++] = (char)('0' + (num % 10)); num /= 10; } while (num); for (i = 0; i < l / 2; i++) { ch = buf[i]; buf[i] = buf[l - 1 - i]; buf[l - 1 - i] = ch; } return l; } static int cell_cmp(struct tb_cell *a, struct tb_cell *b) { if (a->ch != b->ch || a->fg != b->fg || a->bg != b->bg) { return 1; } #ifdef TB_OPT_EGC if (a->nech != b->nech) { return 1; } else if (a->nech > 0) { // a->nech == b->nech return memcmp(a->ech, b->ech, a->nech); } #endif return 0; } static int cell_copy(struct tb_cell *dst, struct tb_cell *src) { #ifdef TB_OPT_EGC if (src->nech > 0) { return cell_set(dst, src->ech, src->nech, src->fg, src->bg); } #endif return cell_set(dst, &src->ch, 1, src->fg, src->bg); } static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, uintattr_t fg, uintattr_t bg) { cell->ch = ch ? *ch : 0; cell->fg = fg; cell->bg = bg; #ifdef TB_OPT_EGC if (nch <= 1) { cell->nech = 0; } else { int rv; if_err_return(rv, cell_reserve_ech(cell, nch + 1)); memcpy(cell->ech, ch, nch); cell->ech[nch] = '\0'; cell->nech = nch; } #else (void)nch; (void)cell_reserve_ech; #endif return TB_OK; } static int cell_reserve_ech(struct tb_cell *cell, size_t n) { #ifdef TB_OPT_EGC if (cell->cech >= n) { return TB_OK; } if (!(cell->ech = tb_realloc(cell->ech, n * sizeof(cell->ch)))) { return TB_ERR_MEM; } cell->cech = n; return TB_OK; #else (void)cell; (void)n; return TB_ERR; #endif } static int cell_free(struct tb_cell *cell) { #ifdef TB_OPT_EGC if (cell->ech) { tb_free(cell->ech); } #endif memset(cell, 0, sizeof(*cell)); return TB_OK; } static int cellbuf_init(struct cellbuf_t *c, int w, int h) { c->cells = tb_malloc(sizeof(struct tb_cell) * w * h); if (!c->cells) { return TB_ERR_MEM; } memset(c->cells, 0, sizeof(struct tb_cell) * w * h); c->width = w; c->height = h; return TB_OK; } static int cellbuf_free(struct cellbuf_t *c) { if (c->cells) { int i; for (i = 0; i < c->width * c->height; i++) { cell_free(&c->cells[i]); } tb_free(c->cells); } memset(c, 0, sizeof(*c)); return TB_OK; } static int cellbuf_clear(struct cellbuf_t *c) { int rv, i; uint32_t space = (uint32_t)' '; for (i = 0; i < c->width * c->height; i++) { if_err_return(rv, cell_set(&c->cells[i], &space, 1, global.fg, global.bg)); } return TB_OK; } static int cellbuf_get(struct cellbuf_t *c, int x, int y, struct tb_cell **out) { if (x < 0 || x >= c->width || y < 0 || y >= c->height) { *out = NULL; return TB_ERR_OUT_OF_BOUNDS; } *out = &c->cells[(y * c->width) + x]; return TB_OK; } static int cellbuf_resize(struct cellbuf_t *c, int w, int h) { int rv; int ow = c->width; int oh = c->height; if (ow == w && oh == h) { return TB_OK; } w = w < 1 ? 1 : w; h = h < 1 ? 1 : h; int minw = (w < ow) ? w : ow; int minh = (h < oh) ? h : oh; struct tb_cell *prev = c->cells; if_err_return(rv, cellbuf_init(c, w, h)); if_err_return(rv, cellbuf_clear(c)); int x, y; for (x = 0; x < minw; x++) { for (y = 0; y < minh; y++) { struct tb_cell *src, *dst; src = &prev[(y * ow) + x]; if_err_return(rv, cellbuf_get(c, x, y, &dst)); if_err_return(rv, cell_copy(dst, src)); } } tb_free(prev); return TB_OK; } static int bytebuf_puts(struct bytebuf_t *b, const char *str) { return bytebuf_nputs(b, str, (size_t)strlen(str)); } static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) { int rv; if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1)); memcpy(b->buf + b->len, str, nstr); b->len += nstr; b->buf[b->len] = '\0'; return TB_OK; } static int bytebuf_shift(struct bytebuf_t *b, size_t n) { if (n > b->len) { n = b->len; } size_t nmove = b->len - n; memmove(b->buf, b->buf + n, nmove); b->len -= n; return TB_OK; } static int bytebuf_flush(struct bytebuf_t *b, int fd) { if (b->len <= 0) { return TB_OK; } ssize_t write_rv = write(fd, b->buf, b->len); if (write_rv < 0 || (size_t)write_rv != b->len) { // Note, errno will be 0 on partial write global.last_errno = errno; return TB_ERR; } b->len = 0; return TB_OK; } static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) { if (b->cap >= sz) { return TB_OK; } size_t newcap = b->cap > 0 ? b->cap : 1; while (newcap < sz) { newcap *= 2; } char *newbuf; if (b->buf) { newbuf = tb_realloc(b->buf, newcap); } else { newbuf = tb_malloc(newcap); } if (!newbuf) { return TB_ERR_MEM; } b->buf = newbuf; b->cap = newcap; return TB_OK; } static int bytebuf_free(struct bytebuf_t *b) { if (b->buf) { tb_free(b->buf); } memset(b, 0, sizeof(*b)); return TB_OK; } #endif /* TB_IMPL */ mle-1.7.2/tests/000077500000000000000000000000001443351614700134505ustar00rootroot00000000000000mle-1.7.2/tests/Makefile000066400000000000000000000000341443351614700151050ustar00rootroot00000000000000all: ./run.sh .PHONY: all mle-1.7.2/tests/func/000077500000000000000000000000001443351614700144035ustar00rootroot00000000000000mle-1.7.2/tests/func/test.sh000066400000000000000000000024361443351614700157230ustar00rootroot00000000000000#!/usr/bin/env bash if [ -n "$skip" ]; then echo -e " \x1b[33mSKIP\x1b[0m $skip" unset skip else actual=$( $MLE \ -N \ -H1 \ -Qd \ -K test_kmap,,1 \ -k cmd_quit_without_saving,f12, \ -n test_kmap \ -M "test_macro $macro f12" \ -p test_macro \ "${extra_opts[@]}" \ 2>&1 >/dev/null ); exit_code=$? if [ "$exit_code" -ne 0 ]; then echo -e " \x1b[31mERR \x1b[0m nonzero_exit_code=$exit_code\n\n$actual" exit 1 fi for assert_name in "${!expected[@]}"; do expected_re="${expected[$assert_name]}" if grep -Eq "$expected_re" <<<"$actual"; then echo -e " \x1b[32mOK \x1b[0m $assert_name" else echo -e " \x1b[31mERR \x1b[0m $assert_name expected=$expected_re\n\n$actual" exit 1 fi done for assert_name in "${!not_expected[@]}"; do not_expected_re="${not_expected[$assert_name]}" if ! grep -Eq "$not_expected_re" <<<"$actual"; then echo -e " \x1b[32mOK \x1b[0m $assert_name" else echo -e " \x1b[31mERR \x1b[0m $assert_name not_expected=$not_expected_re\n\n$actual" exit 1 fi done unset expected unset not_expected fi mle-1.7.2/tests/func/test_blist.sh000077500000000000000000000011031443351614700171110ustar00rootroot00000000000000#!/usr/bin/env bash extra_opts=(file42 file43) macro='C-\ M-\ C-f f i l e 4 3 enter enter M-c' declare -A expected expected[bview_count ]='^bview_count=2' expected[bview_file42 ]='^bview.[[:digit:]]+.buffer.path=file42$' expected[bview_headless]='^bview.[[:digit:]]+.buffer.path=$' source "test.sh" extra_opts=(apple banana carrot) macro='M-2 M-3 M-0 M-c M-1 M-c' declare -A expected expected[carrot_count ]='^bview_count=2' expected[carrot_file ]='^bview.[[:digit:]]+.buffer.path=carrot$' expected[carrot_headless]='^bview.[[:digit:]]+.buffer.path=$' source "test.sh" mle-1.7.2/tests/func/test_block.sh000077500000000000000000000026161443351614700171000ustar00rootroot00000000000000#!/usr/bin/env bash macro='a b c enter d e f enter g h i enter M-\ M-a insert j k down C-k C-u' declare -A expected expected[acut_line1]='^ c$' expected[acut_line2]='^ jk$' expected[acut_line3]='^ghde$' source 'test.sh' macro='a b c enter d e f enter g h i enter M-\ M-a insert right right down M-k C-u' declare -A expected expected[acopy_line1]='^abc$' expected[acopy_line2]='^deab$' expected[acopy_line3]='^ghde$' source 'test.sh' macro='space a b enter enter space c d M-a insert M-; a M-k C-e C-u' declare -A expected expected[aspace_line1]='^ abab$' expected[aspace_line2]='^ cdcd$' source 'test.sh' macro='a b c insert M-k C-u' declare -A expected expected[copy_line1]='^abc$' expected[copy_line2]='^$' expected[copy_count]='\.line_count=2$' source 'test.sh' macro='a b c insert C-k C-u' declare -A expected expected[cut_line1]='^ abc$' expected[cut_line1]='^ $' expected[cut_count]='\.line_count=2$' source 'test.sh' macro="a enter b enter c M-\ M-a insert M-/ C-/ ' x" declare -A expected expected[drop_line1]='^ax$' expected[drop_line2]='^bx$' expected[drop_line3]='^cx$' expected[drop_count]='\.line_count=3$' source 'test.sh' macro="a a enter b b enter c c M-\ M-a insert M-/ C-/ ' M-a C-a C-k C-u" declare -A expected expected[adrop_line1]='^ aa$' expected[adrop_line2]='^ bb$' expected[adrop_line3]='^ cc$' expected[adrop_line3]='^ $' expected[adrop_count]='\.line_count=4$' source 'test.sh' mle-1.7.2/tests/func/test_browse.sh000077500000000000000000000013701443351614700173030ustar00rootroot00000000000000#!/usr/bin/env bash # skip if tree is not available if ! command -v tree &>/dev/null; then skip='tree not in PATH' source 'test.sh' exit 0 elif ! tree --help | grep -q charset; then skip='tree version looks incorrect' source 'test.sh' exit 0 fi # make tmpdir and delete at exit this_dir=$(pwd) tmpdir=$(mktemp -d) cd $tmpdir finish() { cd $this_dir; rm -rf $tmpdir; } trap finish EXIT # setup tmpdir mkdir -p adir/bdir touch adir/bdir/hi # ensure that we can browse into adir, bdir, then select hi # ensure path is relative to where we started macro='C-b C-f a d i r enter enter C-f b d i r enter enter C-f h i enter enter' declare -A expected expected[hi]='^bview.[[:digit:]]+.buffer.path=adir/bdir/hi$' source "$this_dir/test.sh" mle-1.7.2/tests/func/test_coarse_undo.sh000077500000000000000000000013201443351614700202760ustar00rootroot00000000000000#!/usr/bin/env bash MLE_ORIG=$MLE export MLE="timeout -s KILL 2 $MLE_ORIG" macro='a C-z' extra_opts=(-u1) # coarse undo declare -A expected expected[no_inf_loop]='.' source 'test.sh' tmpf=$(mktemp) finish() { rm -f $tmpf; } trap finish EXIT cat >$tmpf <<'EOD' func apple() { // banana (ensure coarse undo works properly on very first user action) } // carrot EOD byte_count=$(cat $tmpf | wc -c) macro='C-e enter C-z' extra_opts=(-u1 -i1 $tmpf) # coarse undo, auto indent declare -A expected expected[coarse_undo_1st_1]="apple" expected[coarse_undo_1st_2]="banana" expected[coarse_undo_1st_3]="carrot" expected[coarse_undo_1st_c]="^bview.0.buffer.byte_count=$byte_count\$" source 'test.sh' export MLE="$MLE_ORIG" mle-1.7.2/tests/func/test_cursor.sh000077500000000000000000000012061443351614700173150ustar00rootroot00000000000000#!/usr/bin/env bash macro='b e g i n space { enter l i n e 1 enter l i n e 2 enter } enter C-f l i n e 1 enter C-2 d' declare -A expected expected[sel_bracket_mark_line]='.mark.line_index=0$' expected[sel_bracket_mark_col]='.mark.col=7$' expected[sel_bracket_anchor_line]='.anchor.line_index=3$' expected[sel_bracket_anchor_col]='.anchor.col=0$' source 'test.sh' macro='a b c enter l i n e 2 enter l i n e 3 C-2 q' declare -A expected expected[sel_all_mark_line]='.mark.line_index=2$' expected[sel_all_mark_col]='.mark.col=5$' expected[sel_all_anchor_line]='.anchor.line_index=0$' expected[sel_all_anchor_col]='.anchor.col=0$' source 'test.sh' mle-1.7.2/tests/func/test_cut_copy.sh000077500000000000000000000042231443351614700176270ustar00rootroot00000000000000#!/usr/bin/env bash macro='f o o C-k' declare -A expected expected[cut_data]='^$' source 'test.sh' macro='f o o C-k C-u' declare -A expected expected[uncut_data]='^foo$' source 'test.sh' macro='f o o M-k C-u' declare -A expected expected[copy_data]='^foofoo$' source 'test.sh' macro='f o o M-a left left C-k' declare -A expected expected[sel_cut_data]='^f$' source 'test.sh' macro='f o o M-a left left C-k C-u' declare -A expected expected[sel_uncut_data]='^foo$' source 'test.sh' macro='f o o M-a left left M-k C-u' declare -A expected expected[sel_copy_data]='^foooo$' source 'test.sh' macro='a b c { x y z } C-f x y z enter C-d d C-e C-u' declare -A expected expected[cut_by_bracket_data]='^abc\{\}xyz$' source 'test.sh' macro='a b c space x y z left C-d w C-a C-u' declare -A expected expected[cut_by_word_data]='^xyzabc $' source 'test.sh' macro='a b c space x y z C-d s C-a C-u' declare -A expected expected[cut_by_word_back_data]='^xyzabc $' source 'test.sh' macro='a b c space x y z C-a C-d f C-e C-u' declare -A expected expected[cut_by_word_forward_data]='^ xyzabc$' source 'test.sh' macro='a b c space x y z C-d a C-u C-u' declare -A expected expected[cut_by_bol_data]='^abc xyzabc xyz$' source 'test.sh' macro='a b c space x y z C-a C-d e C-u C-u' declare -A expected expected[cut_by_eol_data]='^abc xyzabc xyz$' source 'test.sh' macro='a space " q u o t e d " space s t r i n g C-f u o t e enter C-d c' declare -A expected expected[cut_by_str1_data]='^a "" string$' source 'test.sh' macro="\" a ' b \" C-f b enter C-d c" declare -A expected expected[cut_by_str2_data]='^""$' source 'test.sh' macro='o n e - a b c M-a left left left C-k C-n t w o - C-u' declare -A expected expected[global_cut_buffer_data]='^two-abc$' source 'test.sh' macro='a b c C-2 e' declare -A expected expected[mark]='^bview.0.cursor.0.mark.col=3$' expected[anchor]='^bview.0.cursor.0.anchor.col=3$' source 'test.sh' macro='a b c enter d e f M-\ M-a C-f f enter C-/ ; right C-k' declare -A expected expected[swap_anchor]='^af$' source 'test.sh' macro='a C-n b S-left M-k C-u M-p S-left M-k M-n CM-u' declare -A expected expected[buf1_data]='^a$' expected[buf2_data]='^bab$' source 'test.sh' mle-1.7.2/tests/func/test_delete.sh000077500000000000000000000013431443351614700172440ustar00rootroot00000000000000#!/usr/bin/env bash macro='f o o enter b a r backspace backspace backspace backspace' declare -A expected expected[bs_line1]='^foo$' expected[bs_byte_count]='^bview.0.buffer.byte_count=3$' expected[bs_line_count]='^bview.0.buffer.line_count=1$' source 'test.sh' unset expected macro='f o o b a r left left left delete delete delete' declare -A expected expected[del_line1]='^foo$' expected[del_byte_count]='^bview.0.buffer.byte_count=3$' expected[del_line_count]='^bview.0.buffer.line_count=1$' source 'test.sh' macro='f o o space b a r C-w' declare -A expected expected[delete_word_back_data]='^foo $' source 'test.sh' macro='f o o space b a r C-a M-d' declare -A expected expected[delete_word_forward_data]='^ bar$' source 'test.sh' mle-1.7.2/tests/func/test_indent_outdent.sh000077500000000000000000000016431443351614700210300ustar00rootroot00000000000000#!/usr/bin/env bash # cmd_indent macro='h e l l o M-.' declare -A expected expected[indent_data]='^ hello$' source 'test.sh' # cmd_outdent macro='space space space space h e l l o M-,' declare -A expected expected[outdent_data]='^hello$' source 'test.sh' # cmd_indent (sel) macro='h e l l o enter w o r l d M-\ M-a M-/ M-.' declare -A expected expected[sel_indent_data1]='^ hello$' expected[sel_indent_data2]='^ world$' source 'test.sh' # cmd_outdent (sel) macro='space space space space h e l l o enter space space space space w o r l d M-\ M-a M-/ M-,' declare -A expected expected[sel_outdent_data1]='^hello$' expected[sel_outdent_data2]='^world$' source 'test.sh' # _cmd_insert_auto_indent_* macro='M-o a 0 enter f o r space { enter i = 1 enter } enter' extra_opts=(-i 1) declare -A expected expected[auto_indent_1]='^for \{$' expected[auto_indent_2]='^'$'\t''i=1$' expected[auto_indent_3]='^}$' source 'test.sh' mle-1.7.2/tests/func/test_insert.sh000077500000000000000000000013201443351614700173010ustar00rootroot00000000000000#!/usr/bin/env bash macro='h e l l o enter w o r l d enter' declare -A expected expected[a_line1 ]='^hello$' expected[a_line2 ]='^world$' expected[a_byte_count]='^bview.0.buffer.byte_count=12$' expected[a_line_count]='^bview.0.buffer.line_count=3$' source 'test.sh' macro='w o r l d M-i h e l l o' declare -A expected expected[b_line1 ]='^hello$' expected[b_line2 ]='^world$' expected[b_byte_count]='^bview.0.buffer.byte_count=11$' expected[b_line_count]='^bview.0.buffer.line_count=2$' source 'test.sh' macro='h e l l o C-a M-u right M-u C-e M-u' declare -A expected expected[c_line1 ]='^hello$' expected[c_lineb ]='^$' expected[c_line_count]='^bview.0.buffer.line_count=4$' source 'test.sh' mle-1.7.2/tests/func/test_issue_51.sh000077500000000000000000000006201443351614700174340ustar00rootroot00000000000000#!/usr/bin/env bash # make tmpf and delete at exit tmpf=$(mktemp) finish() { rm -f $tmpf; } trap finish EXIT # write >MLE_BRACKET_PAIR_MAX_SEARCH chars to tmpf echo '(' >>$tmpf seq 1 10000 >>$tmpf echo ')' >>$tmpf # ensure cursor.sel_rule closed macro='M-c right C-2 d' extra_opts=($tmpf) declare -A expected expected[srule_should_be_closed]='^bview.0.cursor.0.sel_rule=n$' source 'test.sh' mle-1.7.2/tests/func/test_kmap.sh000077500000000000000000000011461443351614700167330ustar00rootroot00000000000000#!/usr/bin/env bash # ensure binding in prev kmap works with fallthru enabled extra_opts=( -K 'ka,,1' -k 'cmd_shell,a a,echo -n A' -k 'cmd_push_kmap,x,kb' -k 'cmd_quit_without_saving,f12,' -K 'kb,,1' -k 'cmd_shell,a b,echo -n B' -k 'cmd_pop_kmap,x,' -n 'ka' ) macro='a a a b x a b a a x a z' declare -A expected expected[data]='^AabBAaz$' source 'test.sh' # ensure input trail resets on non-matching input w/ fallthru disabled extra_opts=( -K 'ka,,0' -k 'cmd_shell,a,echo -n A' -k 'cmd_quit_without_saving,f12,' -n 'ka' ) macro='b a' declare -A expected expected[data]='^A$' source 'test.sh' mle-1.7.2/tests/func/test_lua.sh000077500000000000000000000054511443351614700165670ustar00rootroot00000000000000#!/usr/bin/env bash on_exit() { [ -n "$lua_script" ] && rm -f $lua_script; } trap on_exit EXIT lua_script=$(mktemp 'mle.test_lua.XXXXXXXXXX') extra_opts=(-x $lua_script -K lua_kmap,,1 -k cmd_lua_test,f11, -k cmd_quit_without_saving,f12, -n lua_kmap) # mle.mark_insert_before macro='f11' cat >$lua_script <<"EOD" mle.editor_register_cmd("cmd_lua_test", function (ctx) mle.mark_insert_before(ctx["mark"], "hello from lua\n", 15) end) EOD declare -A expected expected[simple_data]='^hello from lua$' source 'test.sh' # mle.editor_open_bview macro='f11' cat >$lua_script <<"EOD" mle.editor_register_cmd("cmd_lua_test", function (ctx) print "hi1" mle.editor_open_bview(ctx["editor"], nil, 0, nil, 0, 1, 0, 0, nil) print "hi2" end) EOD declare -A expected expected[open_data1 ]='^hi1$' expected[open_data2 ]='^hi2$' expected[open_bview_count]='^bview_count=2$' source 'test.sh' # mle.editor_prompt macro='f11 t e s t enter . . . f11 C-c' cat >$lua_script <<"EOD" mle.editor_register_cmd("cmd_lua_test", function (ctx) rv = mle.editor_prompt(ctx["editor"], "input?") if rv then str = "hello " .. rv .. " from lua" else str = "you hit ctrl-c" end mle.mark_insert_before(ctx["mark"], str, string.len(str)) end) EOD declare -A expected expected[prompt_data]='^hello test from lua...you hit ctrl-c$' source 'test.sh' # mle.editor_register_observer macro='f11' cat >$lua_script <<"EOD" mle.editor_register_cmd("cmd_lua_test", function (ctx) print "ell" end) mle.editor_register_observer("cmd:cmd_lua_test:before", function (ctx) print "h" end) mle.editor_register_observer("cmd:cmd_lua_test:after", function (ctx) print "o" end) EOD declare -A expected expected[observer_data]='^hello$' source 'test.sh' # mle.editor_register_observer macro='f11 h i enter M-e s e q space 1 space 5 | p a s t e space - s d , space - enter backspace backspace' cat >$lua_script <<"EOD" mark = nil in_callback = false mle.editor_register_cmd("cmd_lua_test", function (ctx) mark = ctx["mark"] bview = mle.editor_open_bview(ctx["editor"], nil, 0, nil, 0, 1, 0, 0, nil) mle.editor_set_active(ctx["editor"], bview["optret_bview"]) end) mle.editor_register_observer("buffer:baction", function (baction) if in_callback or not mark then return end in_callback = true str = "buffer=" .. baction["buffer"] .. " byte_delta=" .. baction["byte_delta"] .. "\n" mle.mark_insert_before(mark, str, string.len(str)) in_callback = false end) EOD declare -A expected expected[observer_data1 ]='^hi$' expected[observer_data2 ]='^1,2,3,4,$' expected[observer_output1]='^buffer=[^ ]+ byte_delta=1$' # typing expected[observer_output1]='^buffer=[^ ]+ byte_delta=10$' # output from `seq 1 5 | paste -sd, -` expected[observer_output1]='^buffer=[^ ]+ byte_delta=-1$' # backspacing source 'test.sh' mle-1.7.2/tests/func/test_move.sh000077500000000000000000000057021443351614700167530ustar00rootroot00000000000000#!/usr/bin/env bash # cmd_move_(left|right|up|down) macro='h e l l o enter w o r l d left up right down' declare -A expected expected[dpad_cursor_line]='^bview.0.cursor.0.mark.line_index=1$' expected[dpad_cursor_col ]='^bview.0.cursor.0.mark.col=5$' source 'test.sh' # cmd_move_(bol) macro='h e l l o enter w o r l d C-a' declare -A expected expected[bol_cursor_line]='^bview.0.cursor.0.mark.line_index=1$' expected[bol_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_move_(eol) macro='h e l l o enter w o r l d M-\ C-e' declare -A expected expected[eol_cursor_line]='^bview.0.cursor.0.mark.line_index=0$' expected[eol_cursor_col ]='^bview.0.cursor.0.mark.col=5$' source 'test.sh' # cmd_move_(beginning,end) macro='h e l l o enter w o r l d M-\ M-/' declare -A expected expected[beg_end_cursor_line]='^bview.0.cursor.0.mark.line_index=1$' expected[beg_end_cursor_col ]='^bview.0.cursor.0.mark.col=5$' source 'test.sh' # cmd_move_(to_line) macro='0 enter 1 enter 2 enter 3 M-g 2 enter' declare -A expected expected[to_line_cursor_col]='^bview.0.cursor.0.mark.line_index=1$' source 'test.sh' # cmd_move_(to_offset) macro='a a enter b b enter c c enter M-G 5 enter' declare -A expected expected[to_offset_cursor_line]='^bview.0.cursor.0.mark.line_index=1$' expected[to_offset_cursor_col ]='^bview.0.cursor.0.mark.col=2$' source 'test.sh' # cmd_move_(to_relative) macro='0 enter 1 enter 2 enter 3 enter M-y 2 u M-y 1 d' declare -A expected expected[relative_cursor_col]='^bview.0.cursor.0.mark.line_index=3$' source 'test.sh' # cmd_move_(until_forward|until_back) macro="b a n a n a M-; b M-' a" declare -A expected expected[until_cursor_col]='^bview.0.cursor.0.mark.col=1$' source 'test.sh' # cmd_move_until_forward (nudge) macro="a a C-a M-' a" declare -A expected expected[nudge_cursor_col]='^bview.0.cursor.0.mark.col=1$' source 'test.sh' # cmd_move_(word_forward|word_back) macro='a p p l e space b a n a n a space c r a n M-b M-b M-f' declare -A expected expected[word_cursor_col]='^bview.0.cursor.0.mark.col=12$' source 'test.sh' # cmd_move_(bracket_forward|bracket_back) macro='t e s t space { space { space { M-left M-left M-left M-right' declare -A expected expected[bracket_cursor_col]='^bview.0.cursor.0.mark.col=7$' source 'test.sh' # cmd_move_bracket_toggle macro='t e s t 1 { enter t e s t 2 [ enter ] enter } M-= a M-= b' declare -A expected expected[bracket_toggle_line]='^bview.0.cursor.0.mark.line_index=2$' expected[bracket_toggle_col ]='^bview.0.cursor.0.mark.col=1$' expected[bracket_toggle_a ]='^test2a\[$' expected[bracket_toggle_b ]='^b\]$' source 'test.sh' # cmd_jump macro='a n t space b a t space c a t space d o g M-j a c' declare -A expected expected[jump_cursor_col]='^bview.0.cursor.0.mark.col=8$' source 'test.sh' # cmd_(drop|goto)_lettered_mark macro='a enter b enter c M-m M-\ a M-m c up b M-z z M-m c M-z z b M-m c' declare -A expected expected[lettmark_a]='^aa$' expected[lettmark_b]='^bbb$' expected[lettmark_c]='^cccc$' source 'test.sh' mle-1.7.2/tests/func/test_multi_cursor.sh000077500000000000000000000026771443351614700205440ustar00rootroot00000000000000#!/usr/bin/env bash macro='1 0 space 2 0 space 3 0 C-r \ d + C-/ y e s C-/ /' declare -A expected expected[isearch_data ]='^yes10 yes20 yes30$' expected[isearch_cursor_count]='^bview.0.cursor_count=1$' source 'test.sh' macro='h i space C-/ . C-a C-/ a X X' declare -A expected expected[drop_wake_data ]='^XXhi XX$' expected[drop_wake_cursor_count]='^bview.0.cursor_count=2$' source 'test.sh' macro="o n e enter t w o enter t h r e e M-a M-\ C-/ ' C-e space a n d" declare -A expected expected[column_data1 ]='^one and$' expected[column_data2 ]='^two and$' expected[column_data3 ]='^three and$' expected[column_cursor_count]='^bview.0.cursor_count=3$' source 'test.sh' macro="a enter space b enter space space c enter C-r \ S + C-/ C-/ l" declare -A expected expected[align_line_1]='^ a$' expected[align_line_2]='^ b$' expected[align_line_3]='^ c$' source 'test.sh' macro="a b enter c d enter e f enter C-r \ S + C-/ M-a C-e C-/ ; x" declare -A expected expected[swap_line_1]='^xab$' expected[swap_line_2]='^xcd$' expected[swap_line_2]='^xef$' source 'test.sh' macro="a b enter c d enter C-r \ S + C-/ M-a C-e M-." declare -A expected expected[indent_line_1]='^ ab$' expected[indent_line_2]='^ cd$' source 'test.sh' macro="a enter b c enter d e f enter C-r \ S + C-/ M-a C-e M-e w c space - c enter" declare -A expected expected[shell_line_1]='^1$' expected[shell_line_2]='^2$' expected[shell_line_2]='^3$' source 'test.sh' mle-1.7.2/tests/func/test_open.sh000077500000000000000000000003621443351614700167430ustar00rootroot00000000000000#!/usr/bin/env bash macro='' extra_opts=(test_file_w_suffix test_file) declare -A expected expected[path_1]='^bview.[[:digit:]]+.buffer.path=test_file_w_suffix$' expected[path_2]='^bview.[[:digit:]]+.buffer.path=test_file$' source 'test.sh' mle-1.7.2/tests/func/test_repeat.sh000077500000000000000000000002201443351614700172530ustar00rootroot00000000000000#!/usr/bin/env bash macro="b a n a n a home M-' a f5 f5" declare -A expected expected[cursor]='^bview.0.cursor.0.mark.col=5$' source 'test.sh' mle-1.7.2/tests/func/test_search_replace.sh000077500000000000000000000057171443351614700207530ustar00rootroot00000000000000#!/usr/bin/env bash # cmd_search macro='h e l l o enter w o r l d C-f h e l l o enter' declare -A expected expected[search_cursor_line]='^bview.0.cursor.0.mark.line_index=0$' expected[search_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_rsearch macro='h i enter h i enter h i enter CM-f h i enter' declare -A expected expected[rsearch_cursor_line]='^bview.0.cursor.0.mark.line_index=2$' expected[rsearch_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_search_next macro='h i enter h i enter h i enter C-f h i enter C-g' declare -A expected expected[next_cursor_line]='^bview.0.cursor.0.mark.line_index=1$' expected[next_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_search_prev (wrap) macro='h i enter h i enter h i enter C-f h i enter CM-g' declare -A expected expected[prev_wrap_cursor_line]='^bview.0.cursor.0.mark.line_index=2$' expected[prev_wrap_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_search_next (wrap) macro='h e l l o enter h e l l o C-f h e l l o enter C-g C-g' declare -A expected expected[next_wrap_cursor_line]='^bview.0.cursor.0.mark.line_index=0$' expected[next_wrap_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_find_word macro='h e l l o enter h e l l o left C-v' declare -A expected expected[find_cursor_line]='^bview.0.cursor.0.mark.line_index=0$' expected[find_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_find_word (wrap) macro='h e l l o enter h e l l o left C-v C-v' declare -A expected expected[find_wrap_cursor_line]='^bview.0.cursor.0.mark.line_index=1$' expected[find_wrap_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_rfind_word macro='h i enter h i enter h i C-a CM-v' declare -A expected expected[rfind_cursor_line]='^bview.0.cursor.0.mark.line_index=1$' expected[rfind_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_isearch macro='a c t o r enter a p p l e enter a p p e t i t e enter a z u r e M-\ C-r a p p e enter' declare -A expected expected[isearch_cursor_line]='^bview.0.cursor.0.mark.line_index=2$' expected[iesarch_cursor_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' # cmd_replace 1 macro='a 1 space b 2 space c 3 space d 4 C-t \ d + enter x enter y n a' declare -A expected expected[replace1_data]='^ax b2 cx dx$' source 'test.sh' # cmd_replace 2 macro='a 1 space b 2 space c 3 space d 4 C-t \ d + enter x enter y n C-c' declare -A expected expected[replace2_data]='^ax b2 c3 d4$' source 'test.sh' # cmd_replace 3 macro='a b c 1 2 3 C-t ( . . . ) ( . . . ) enter A $ 2 $ n B $ 1 $ n $ x 4 3 enter a' declare -A expected expected[replace3_data1]='^A123$' expected[replace3_data2]='^Babc$' expected[replace3_data3]='^C$' source 'test.sh' # cmd_search history macro='h e l l o enter h e l l o C-f h e l l o enter C-f up enter' declare -A expected expected[history_line]='^bview.0.cursor.0.mark.line_index=1$' expected[history_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' mle-1.7.2/tests/func/test_shell.sh000077500000000000000000000002111443351614700171020ustar00rootroot00000000000000#!/usr/bin/env bash macro='M-e e c h o space h e l l o 4 2 enter' declare -A expected expected[shell_data]='^hello42$' source 'test.sh' mle-1.7.2/tests/func/test_show_help.sh000077500000000000000000000001471443351614700177730ustar00rootroot00000000000000#!/usr/bin/env bash macro='f2' declare -A expected expected[help]='mle command help' source 'test.sh' mle-1.7.2/tests/func/test_syntax.sh000077500000000000000000000036321443351614700173330ustar00rootroot00000000000000#!/usr/bin/env bash tmpn=test_syntax_file extra_opts=(-S syn_test,$tmpn,4,0 -s foo,,258,3 $tmpn) macro='M-c f o o' declare -A expected expected[style_fg]='\bfg=258\b' expected[style_bg]='\bbg=3\b' source 'test.sh' tmpn=test_syntax_file extra_opts=(-S syn_test,$tmpn,4,0 -s foo,,1,2 $tmpn) macro='M-c { enter f o o ; enter } enter M-\ M-a M-/ M-. M-a' declare -A expected expected[outdent_style]='' source 'test.sh' on_exit() { [ -n "$tmpf" ] && rm -f $tmpf; } trap on_exit EXIT tmpf=$(mktemp 'mle.test_syntax.XXXXXXXXXX') cat >$tmpf <<"EOD" "aaa" "aaa\\" "aaa \" aaa" "aaa" bbb "aaa" "aaa \" aaa" bbb "aaa \" aaa" "aaa \" aaa\\" bbb "aaa \" aaa\\" "aaa" bbb "aaa\\" bbb "aaa \" aaa" bbb "aaa" bbb "aaa" bbb "aaa \" aaa" bbb "aaa \" aaa" bbb "aaa \" aaa\\" bbb "aaa \" aaa\\" bbb bbb "aaa" bbb bbb "aaa\\" bbb bbb "aaa \" aaa" bbb bbb "aaa" bbb "aaa" bbb bbb "aaa \" aaa" bbb "aaa \" aaa" bbb bbb "aaa \" aaa\\" bbb "aaa \" aaa\\" bbb EOD extra_opts=(-y- $tmpf) macro='' declare -A expected declare -A not_expected # a==97 b==98 expected[style_dq_a]='$tmpf <<"EOD" "aa" /*bb*/ "aa/*aa" /*bb*/ "aa/*aa*/aa" /*bb*/ "aa" /*bb*/ "aa/*aa" /*bb"bb*/ "aa/*aa*/aa" /*bb"bb"bb*/ /*bb*/ "aa" /*bb*/ "aa/*aa" /*bb*/ "aa/*aa*/aa" /*bb*/ "aa" /*bb"bb*/ "aa/*aa" /*bb"bb"bb*/ "aa/*aa*/aa" EOD extra_opts=(-y- $tmpf) macro='' declare -A expected declare -A not_expected # a==97 b==98 expected[style_sc_a]='/dev/null; echo TEST $tshort; tput sgr0 2>/dev/null pushd $tdir &>/dev/null ./$tshort ec=$? popd &>/dev/null echo [ $ec -eq 0 ] && pass=$((pass+1)) total=$((total+1)) done printf "Passed %d out of %d tests\n" $pass $total [ $pass -eq $total ] || exit 1 mle-1.7.2/tests/unit/000077500000000000000000000000001443351614700144275ustar00rootroot00000000000000mle-1.7.2/tests/unit/.gitignore000066400000000000000000000000071443351614700164140ustar00rootroot00000000000000* !*.c mle-1.7.2/tests/unit/test.c000066400000000000000000000021041443351614700155470ustar00rootroot00000000000000#include #include #include #include #include #include #include "mle.h" #include "mlbuf.h" editor_t _editor; // satisfies extern in mle.h int main(int argc, char **argv) { buffer_t *buf; mark_t *cur; void *self; void (*symfn)(buffer_t *, mark_t *); char **symstr; char symbuf[1024]; if (argc < 2) return EXIT_FAILURE; self = dlopen(NULL, RTLD_NOW); if (!self) return EXIT_FAILURE; *(void **)(&symfn) = dlsym(self, argv[1]); sprintf(symbuf, "%s_str", argv[1]); symstr = (char**)dlsym(self, symbuf); if (!symfn || !symstr) return EXIT_FAILURE; setlocale(LC_ALL, ""); pcre2_md = pcre2_match_data_create(10, NULL); // Normally initialized in editor_init memset(&_editor, 0, sizeof(editor_t)); buf = buffer_new(); buffer_insert(buf, 0, *symstr, (bint_t)strlen(*symstr), NULL); cur = buffer_add_mark(buf, NULL, 0); symfn(buf, cur); buffer_destroy(buf); pcre2_match_data_free(pcre2_md); dlclose(self); return EXIT_SUCCESS; } mle-1.7.2/tests/unit/test.h000066400000000000000000000014071443351614700155610ustar00rootroot00000000000000#include #include #include #include #include #include #include "mle.h" #include "mlbuf.h" #define concat1(a, b) a ## b #define concat2(a, b) concat1(a, b) #define str concat2(TEST_NAME, _str) #define test TEST_NAME extern char *str; extern void test(buffer_t *buf, mark_t *cur); // TODO run each test with buffer_set_slabbed + buffer_insert #define ASSERT(testname, expected, observed) do { \ if ((expected) == (observed)) { \ printf(" \x1b[32mOK \x1b[0m %s\n", (testname)); \ } else { \ printf(" \x1b[31mERR \x1b[0m %s expected=%" PRIdPTR " observed=%" PRIdPTR "\n", (testname), (intptr_t)(expected), (intptr_t)(observed)); \ exit(EXIT_FAILURE); \ } \ } while(0); mle-1.7.2/tests/unit/test_bline_delete.c000066400000000000000000000017221443351614700202470ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; bline_delete(buf->first_line, 0, 1); buffer_get(buf, &data, &data_len); ASSERT("bol", 0, strncmp(data, "ello\nworld", data_len)); bline_delete(buf->first_line, 0, 0); buffer_get(buf, &data, &data_len); ASSERT("noop", 0, strncmp(data, "ello\nworld", data_len)); bline_delete(buf->first_line->next, 4, 1); buffer_get(buf, &data, &data_len); ASSERT("eol", 0, strncmp(data, "ello\nworl", data_len)); bline_delete(buf->first_line, 1, 2); buffer_get(buf, &data, &data_len); ASSERT("mid", 0, strncmp(data, "eo\nworl", data_len)); bline_delete(buf->first_line, 4, 1); buffer_get(buf, &data, &data_len); ASSERT("oob", 0, strncmp(data, "eo\nwrl", data_len)); bline_delete(buf->first_line, 2, 3); buffer_get(buf, &data, &data_len); ASSERT("eatnl", 0, strncmp(data, "eol", data_len)); } mle-1.7.2/tests/unit/test_bline_get_col.c000066400000000000000000000017041443351614700204210ustar00rootroot00000000000000#include "test.h" char *str = "h\xc3\x85llo \xe4\xb8\x96\xe7\x95\x8c"; // "hallo ww" void test(buffer_t *buf, mark_t *cur) { // 01 2 34567 8 9 10 11 12 // 01 23456 7 bint_t col; MLBUF_BLINE_ENSURE_CHARS(buf->first_line); ASSERT("len", 8, buf->first_line->char_count); bline_get_col(buf->first_line, 0, &col); ASSERT("0", 0, col); bline_get_col(buf->first_line, 1, &col); ASSERT("1", 1, col); bline_get_col(buf->first_line, 2, &col); ASSERT("2", 1, col); bline_get_col(buf->first_line, 3, &col); ASSERT("3", 2, col); bline_get_col(buf->first_line, 7, &col); ASSERT("7", 6, col); bline_get_col(buf->first_line, 9, &col); ASSERT("9", 6, col); bline_get_col(buf->first_line, 10, &col); ASSERT("10", 7, col); bline_get_col(buf->first_line, 11, &col); ASSERT("11", 7, col); bline_get_col(buf->first_line, 13, &col); ASSERT("13", 8, col); } mle-1.7.2/tests/unit/test_bline_insert.c000066400000000000000000000033771443351614700203210ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; bline_insert(buf->first_line, 5, "\n", 1, NULL); buffer_get(buf, &data, &data_len); ASSERT("nl", 0, strncmp(data, "hello\n\nworld", data_len)); bline_insert(buf->first_line->next, 0, "my", 2, NULL); buffer_get(buf, &data, &data_len); ASSERT("my", 0, strncmp(data, "hello\nmy\nworld", data_len)); bline_insert(buf->first_line->next, 0, "a", 1, NULL); buffer_get(buf, &data, &data_len); ASSERT("bol", 0, strncmp(data, "hello\namy\nworld", data_len)); bline_insert(buf->first_line->next, 3, "'s", 2, NULL); buffer_get(buf, &data, &data_len); ASSERT("eol", 0, strncmp(data, "hello\namy's\nworld", data_len)); bline_insert(buf->first_line->next, 6, " ", 1, NULL); buffer_get(buf, &data, &data_len); ASSERT("oob", 0, strncmp(data, "hello\namy's\n world", data_len)); bline_insert(buf->last_line, buf->last_line->char_count, "\nno\xe2\x80\x8bspace", 11, NULL); // The following assertion relies on a UTF-8 locale. `nl_langinfo` may not // be available, so let's comment it out for now. (In practice, if locale // is, e.g., C, the zero-width space will show up as "?" with vwidth==1.) // // codeset = nl_langinfo(CODESET); // if (codeset && strcmp(codeset, "UTF-8") == 0) { // ASSERT("0wv", 7, buf->last_line->char_vwidth); // } ASSERT("0wc", 8, buf->last_line->char_count); ASSERT("0wd", 10, buf->last_line->data_len); bline_insert(buf->last_line, buf->last_line->char_count, "\nnull\x00", 6, NULL); ASSERT("nulv", 5, buf->last_line->char_vwidth); ASSERT("nulc", 5, buf->last_line->char_count); ASSERT("nuld", 5, buf->last_line->data_len); } mle-1.7.2/tests/unit/test_buffer_add_mark.c000066400000000000000000000006601443351614700207270ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_t *mark; mark = buffer_add_mark(buf, NULL, 0); ASSERT("mark0bline", 1, mark->bline == buf->first_line ? 1 : 0); ASSERT("mark0col", 0, mark->col); mark = buffer_add_mark(buf, buf->first_line->next, 5); ASSERT("mark1bline", 1, mark->bline == buf->first_line->next ? 1 : 0); ASSERT("mark1col", 5, mark->col); } mle-1.7.2/tests/unit/test_buffer_add_srule.c000066400000000000000000000025441443351614700211320ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { bint_t i; srule_t *srule1; srule_t *srule2; srule1 = srule_new_single("world", sizeof("world")-1, 0, 1, 2); srule2 = srule_new_multi("lo", sizeof("lo")-1, "wo", sizeof("wo")-1, 3, 4); buffer_add_srule(buf, srule1); for (i = 0; i < buf->first_line->char_count; i++) { ASSERT("line1fg", 0, buf->first_line->chars[i].style.fg); ASSERT("line1bg", 0, buf->first_line->chars[i].style.bg); } for (i = 0; i < buf->first_line->next->char_count; i++) { ASSERT("line2fg", 1, buf->first_line->next->chars[i].style.fg); ASSERT("line2bg", 2, buf->first_line->next->chars[i].style.bg); } buffer_remove_srule(buf, srule1); buffer_add_srule(buf, srule2); for (i = 0; i < buf->first_line->char_count; i++) { ASSERT("line1fg_m", (i == 3 || i == 4 ? 3 : 0), buf->first_line->chars[i].style.fg); ASSERT("line1bg_m", (i == 3 || i == 4 ? 4 : 0), buf->first_line->chars[i].style.bg); } for (i = 0; i < buf->first_line->next->char_count; i++) { ASSERT("line2fg_m", (i == 0 || i == 1 ? 3 : 0), buf->first_line->next->chars[i].style.fg); ASSERT("line2bg_m", (i == 0 || i == 1 ? 4 : 0), buf->first_line->next->chars[i].style.bg); } srule_destroy(srule1); srule_destroy(srule2); } mle-1.7.2/tests/unit/test_buffer_delete.c000066400000000000000000000011761443351614700204320ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; buffer_delete(buf, 0, 1); buffer_get(buf, &data, &data_len); ASSERT("bol", 0, strncmp(data, "ello\nworld", data_len)); buffer_delete(buf, 4, 1); buffer_get(buf, &data, &data_len); ASSERT("nl", 0, strncmp(data, "elloworld", data_len)); buffer_delete(buf, 7, 3); buffer_get(buf, &data, &data_len); ASSERT("oob", 0, strncmp(data, "ellowor", data_len)); buffer_delete(buf, 0, 7); buffer_get(buf, &data, &data_len); ASSERT("all", 0, strncmp(data, "", data_len)); } mle-1.7.2/tests/unit/test_buffer_destroy.c000066400000000000000000000001641443351614700206550ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { ASSERT("yes", 1, 1); } mle-1.7.2/tests/unit/test_buffer_get.c000066400000000000000000000004071443351614700177430ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; buffer_get(buf, &data, &data_len); ASSERT("len", 11, data_len); ASSERT("get", 0, strncmp(data, "hello\nworld", data_len)); } mle-1.7.2/tests/unit/test_buffer_get_bline.c000066400000000000000000000007571443351614700211240ustar00rootroot00000000000000#include "test.h" char *str = "0\n1\n2\n3\n4\n"; void test(buffer_t *buf, mark_t *cur) { bint_t i; bline_t *line; bline_t *line2; for (line = buf->first_line, i = 0; line; line = line->next, i += 1) { buffer_get_bline(buf, i, &line2); ASSERT("line", line, line2); } for (line = buf->first_line, i = 0; line; line = line->next, i += 1) { buffer_get_bline_w_hint(buf, i, buf->last_line->prev, &line2); ASSERT("line", line, line2); } } mle-1.7.2/tests/unit/test_buffer_get_bline_col.c000066400000000000000000000013471443351614700217550ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { bline_t *line; bint_t col; buffer_get_bline_col(buf, 0, &line, &col); ASSERT("0line", buf->first_line, line); ASSERT("0col", 0, col); buffer_get_bline_col(buf, 1, &line, &col); ASSERT("1line", buf->first_line, line); ASSERT("1col", 1, col); buffer_get_bline_col(buf, 5, &line, &col); ASSERT("5line", buf->first_line, line); ASSERT("5col", 5, col); buffer_get_bline_col(buf, 6, &line, &col); ASSERT("6line", buf->first_line->next, line); ASSERT("6col", 0, col); buffer_get_bline_col(buf, 99, &line, &col); ASSERT("oobline", buf->first_line->next, line); ASSERT("oobcol", 5, col); } mle-1.7.2/tests/unit/test_buffer_get_offset.c000066400000000000000000000010571443351614700213130ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { bint_t offset; buffer_get_offset(buf, buf->first_line, 0, &offset); ASSERT("f0", 0, offset); buffer_get_offset(buf, buf->first_line, 1, &offset); ASSERT("f1", 1, offset); buffer_get_offset(buf, buf->first_line, 5, &offset); ASSERT("f5", 5, offset); buffer_get_offset(buf, buf->first_line->next, 0, &offset); ASSERT("n0", 6, offset); buffer_get_offset(buf, buf->first_line->next, 6, &offset); ASSERT("noob", 11, offset); } mle-1.7.2/tests/unit/test_buffer_insert.c000066400000000000000000000017271443351614700204760ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; bint_t nchars; buffer_insert(buf, 0, "\xe4\xb8\x96\xe7\x95\x8c\n", 7, &nchars); buffer_get(buf, &data, &data_len); ASSERT("bol", 0, strncmp(data, "\xe4\xb8\x96\xe7\x95\x8c\nhello\nworld", data_len)); ASSERT("bolnchars", 3, nchars); buffer_insert(buf, 3, "s", 1, &nchars); buffer_get(buf, &data, &data_len); ASSERT("3", 0, strncmp(data, "\xe4\xb8\x96\xe7\x95\x8c\nshello\nworld", data_len)); ASSERT("3nchars", 1, nchars); buffer_insert(buf, 7, "\n\n", 2, &nchars); buffer_get(buf, &data, &data_len); ASSERT("nlnl", 0, strncmp(data, "\xe4\xb8\x96\xe7\x95\x8c\nshel\n\nlo\nworld", data_len)); ASSERT("nlnlnchars", 2, nchars); buffer_insert(buf, 20, "!", 1, NULL); buffer_get(buf, &data, &data_len); ASSERT("oob", 0, strncmp(data, "\xe4\xb8\x96\xe7\x95\x8c\nshel\n\nlo\nworld!", data_len)); } mle-1.7.2/tests/unit/test_buffer_new.c000066400000000000000000000001641443351614700177550ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { ASSERT("yes", 1, 1); } mle-1.7.2/tests/unit/test_buffer_redo.c000066400000000000000000000015051443351614700201150ustar00rootroot00000000000000#include "test.h" char *str = "hi"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; int action_group; buffer_delete(buf, 0, 1); buffer_undo(buf); buffer_redo(buf); buffer_get(buf, &data, &data_len); ASSERT("del", 0, strncmp(data, "i", data_len)); buffer_insert(buf, 0, "y", 1, NULL); buffer_undo(buf); buffer_redo(buf); buffer_get(buf, &data, &data_len); ASSERT("ins", 0, strncmp(data, "yi", data_len)); action_group = 0; buffer_set_action_group_ptr(buf, &action_group); buffer_insert(buf, 2, "e", 1, NULL); buffer_insert(buf, 3, "ld", 2, NULL); action_group += 1; buffer_undo_action_group(buf); buffer_redo_action_group(buf); buffer_get(buf, &data, &data_len); ASSERT("ins_group", 0, strncmp(data, "yield", data_len)); } mle-1.7.2/tests/unit/test_buffer_register.c000066400000000000000000000013461443351614700210130ustar00rootroot00000000000000#include "test.h" char *str = "hi"; void test(buffer_t *buf, mark_t *cur) { char *reg; size_t reg_len; buffer_register_set(buf, 'a', "yo", 2); buffer_register_get(buf, 'a', 0, ®, ®_len); ASSERT("set", 0, strncmp(reg, "yo", reg_len)) buffer_register_prepend(buf, 'a', "g", 1); buffer_register_get(buf, 'a', 0, ®, ®_len); ASSERT("pre", 0, strncmp(reg, "gyo", reg_len)) buffer_register_append(buf, 'a', "!!!", 3); buffer_register_get(buf, 'a', 0, ®, ®_len); ASSERT("app", 0, strncmp(reg, "gyo!!!", reg_len)) buffer_register_clear(buf, 'a'); buffer_register_get(buf, 'a', 0, ®, ®_len); ASSERT("clr1", 0, reg_len); ASSERT("clr2", 1, reg != NULL ? 1 : 0); } mle-1.7.2/tests/unit/test_buffer_remove_srule.c000066400000000000000000000012661443351614700216770ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { bint_t i; srule_t *srule; srule = srule_new_single("world", sizeof("world")-1, 0, 1, 2); buffer_add_srule(buf, srule); buffer_remove_srule(buf, srule); for (i = 0; i < buf->first_line->char_count; i++) { ASSERT("line1fg", 0, buf->first_line->chars[i].style.fg); ASSERT("line1bg", 0, buf->first_line->chars[i].style.bg); } for (i = 0; i < buf->first_line->next->char_count; i++) { ASSERT("line2fg", 0, buf->first_line->next->chars[i].style.fg); ASSERT("line2bg", 0, buf->first_line->next->chars[i].style.bg); } srule_destroy(srule); } mle-1.7.2/tests/unit/test_buffer_replace.c000066400000000000000000000054641443351614700206070ustar00rootroot00000000000000#include "test.h" char *str = "lineA\n\nline2\nline3\n"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; buffer_replace(buf, 0, 0, "b", 1); buffer_get(buf, &data, &data_len); ASSERT("rpl1", 0, strncmp(data, "blineA\n\nline2\nline3\n", data_len)); ASSERT("r1bc", data_len, buf->byte_count); ASSERT("r1lc", 5, buf->line_count); buffer_replace(buf, 3, 3, "xe0", 3); buffer_get(buf, &data, &data_len); ASSERT("rpl2", 0, strncmp(data, "blixe0\n\nline2\nline3\n", data_len)); ASSERT("r2bc", data_len, buf->byte_count); ASSERT("r2lc", 5, buf->line_count); buffer_replace(buf, 10, 7, "N", 1); buffer_get(buf, &data, &data_len); ASSERT("rpl3", 0, strncmp(data, "blixe0\n\nliNe3\n", data_len)); ASSERT("r3bc", data_len, buf->byte_count); ASSERT("r3lc", 4, buf->line_count); buffer_replace(buf, 5, 4, "jerk\nstuff", 10); buffer_get(buf, &data, &data_len); ASSERT("rpl4", 0, strncmp(data, "blixejerk\nstuffiNe3\n", data_len)); ASSERT("r4bc", data_len, buf->byte_count); ASSERT("r4lc", 3, buf->line_count); buffer_replace(buf, 9, 99, "X", 1); buffer_get(buf, &data, &data_len); ASSERT("rpl5", 0, strncmp(data, "blixejerkX", data_len)); ASSERT("r5bc", data_len, buf->byte_count); ASSERT("r5lc", 1, buf->line_count); buffer_replace(buf, 5, 0, "y\nb", 3); buffer_get(buf, &data, &data_len); ASSERT("rpl6", 0, strncmp(data, "blixey\nbjerkX", data_len)); ASSERT("r6bc", data_len, buf->byte_count); ASSERT("r6lc", 2, buf->line_count); buffer_replace(buf, 0, 0, "\n", 1); buffer_get(buf, &data, &data_len); ASSERT("rpl7", 0, strncmp(data, "\nblixey\nbjerkX", data_len)); ASSERT("r7bc", data_len, buf->byte_count); ASSERT("r7lc", 3, buf->line_count); buffer_replace(buf, 6, 3, NULL, 0); buffer_get(buf, &data, &data_len); ASSERT("rpl8", 0, strncmp(data, "\nblixejerkX", data_len)); ASSERT("r8bc", data_len, buf->byte_count); ASSERT("r8lc", 2, buf->line_count); buffer_replace(buf, 0, 11, "1\n2\n3\n4\n", 8); buffer_get(buf, &data, &data_len); ASSERT("rpl9", 0, strncmp(data, "1\n2\n3\n4\n", data_len)); ASSERT("r9bc", data_len, buf->byte_count); ASSERT("r9lc", 5, buf->line_count); buffer_replace(buf, 2, 6, "five\nsix\nseven\neight\nnine", 25); buffer_get(buf, &data, &data_len); ASSERT("rpla", 0, strncmp(data, "1\nfive\nsix\nseven\neight\nnine", data_len)); ASSERT("rabc", data_len, buf->byte_count); ASSERT("ralc", 6, buf->line_count); buffer_undo(buf); buffer_undo(buf); buffer_undo(buf); buffer_undo(buf); buffer_undo(buf); buffer_undo(buf); buffer_undo(buf); buffer_undo(buf); buffer_undo(buf); buffer_get(buf, &data, &data_len); ASSERT("undo", 0, strncmp(data, "\nblixey\nbjerkX", data_len)); } mle-1.7.2/tests/unit/test_buffer_set.c000066400000000000000000000004201443351614700177520ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; buffer_set(buf, "goodbye\nvoid", 12); buffer_get(buf, &data, &data_len); ASSERT("set", 0, strncmp("goodbye\nvoid", data, data_len)); } mle-1.7.2/tests/unit/test_buffer_set_callback.c000066400000000000000000000022401443351614700215700ustar00rootroot00000000000000#include "test.h" buffer_t *global_buf; int *global_udata; static void callback_fn(buffer_t *buf, baction_t *bac, void *udata) { ASSERT("bufp", global_buf, buf); ASSERT("udata", (void*)global_udata, udata); ASSERT("bac_type", MLBUF_BACTION_TYPE_INSERT, bac->type); ASSERT("bac_buf", buf, bac->buffer); ASSERT("bac_start_line", buf->first_line, bac->start_line); ASSERT("bac_start_line_index", 0, bac->start_line_index); ASSERT("bac_start_col", 0, bac->start_col); ASSERT("bac_maybe_end_line", buf->first_line->next, bac->maybe_end_line); ASSERT("bac_maybe_end_line_index", 1, bac->maybe_end_line_index); ASSERT("bac_maybe_end_col", 2, bac->maybe_end_col); ASSERT("bac_byte_delta", 5, bac->byte_delta); ASSERT("bac_char_delta", 5, bac->char_delta); ASSERT("bac_line_delta", 1, bac->line_delta); ASSERT("bac_data", 0, strncmp("te\nst", bac->data, bac->data_len)); } char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { int udata; udata = 42; global_buf = buf; global_udata = &udata; buffer_set_callback(buf, callback_fn, (void*)global_udata); buffer_insert(buf, 0, "te\nst", 5, NULL); } mle-1.7.2/tests/unit/test_buffer_set_tab_width.c000066400000000000000000000027141443351614700220070ustar00rootroot00000000000000#include "test.h" #define comma , char *str = "he\tllo\t\t"; void test(buffer_t *buf, mark_t *cur) { bint_t i; bint_t char_vcols_4[8] = {0 comma 1 comma 2 comma 4 comma 5 comma 6 comma 7 comma 8}; bint_t char_vcols_2a[8] = {0 comma 1 comma 2 comma 4 comma 5 comma 6 comma 7 comma 8}; bint_t char_vcols_2b[9] = {0 comma 1 comma 2 comma 4 comma 5 comma 6 comma 7 comma 8 comma 10}; buffer_set_tab_width(buf, 4); // [he llo ] // char_vcol // [ t tt ] // tabs ASSERT("count4", 8, buf->first_line->char_count); ASSERT("width4", 12, buf->first_line->char_vwidth); for (i = 0; i < buf->first_line->char_count; i++) { ASSERT("vcol4", char_vcols_4[i], buf->first_line->chars[i].vcol); } buffer_set_tab_width(buf, 2); // [he llo ] // char_vcol // [ t tt ] // tabs ASSERT("count2a", 8, buf->first_line->char_count); ASSERT("width2a", 10, buf->first_line->char_vwidth); for (i = 0; i < buf->first_line->char_count; i++) { ASSERT("vcol2a", char_vcols_2a[i], buf->first_line->chars[i].vcol); } bline_insert(buf->first_line, 4, "\t", 1, NULL); // [he l lo ] // char_vcol // [ t t t t ] // tabs ASSERT("count2b", 9, buf->first_line->char_count); ASSERT("width2b", 12, buf->first_line->char_vwidth); for (i = 0; i < buf->first_line->char_count; i++) { ASSERT("vcol2b", char_vcols_2b[i], buf->first_line->chars[i].vcol); } } mle-1.7.2/tests/unit/test_buffer_srule_overlap.c.todo000066400000000000000000000021601443351614700230100ustar00rootroot00000000000000#include "test.h" char *str = "[\n * comment\n ]/\nsel"; void test(buffer_t *buf, mark_t *cur) { bint_t i; srule_t* multi; srule_t* range; mark_t* ma; mark_t* mb; ma = buffer_add_mark(buf, buf->first_line->next, 0); mb = buffer_add_mark(buf, buf->first_line->next->next->next, 3); multi = srule_new_multi("\\[", 2, "\\]", 2, 1, 2); range = srule_new_range(ma, mb, 3, 4); buffer_add_srule(buf, multi); buffer_add_srule(buf, range); for (i = 0; i < buf->first_line->next->char_count; i++) { ASSERT("line1fg", 1, buf->first_line->next->chars[i].style.fg); ASSERT("line1bg", 2, buf->first_line->next->chars[i].style.bg); } for (i = 0; i < buf->first_line->next->next->next->char_count; i++) { ASSERT("line2fg", 3, buf->first_line->next->next->next->chars[i].style.fg); ASSERT("line2bg", 4, buf->first_line->next->next->next->chars[i].style.bg); } buffer_remove_srule(buf, multi); buffer_remove_srule(buf, range); srule_destroy(multi); srule_destroy(range); buffer_destroy_mark(buf, ma); buffer_destroy_mark(buf, mb); } mle-1.7.2/tests/unit/test_buffer_substr.c000066400000000000000000000006031443351614700205040ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; bint_t nchars; buffer_substr(buf, buf->first_line, 4, buf->first_line->next, 1, &data, &data_len, &nchars); ASSERT("datalen", 3, data_len); ASSERT("nchars", 3, nchars); ASSERT("substr", 0, strncmp("o\nw", data, data_len)); free(data); } mle-1.7.2/tests/unit/test_buffer_undo.c000066400000000000000000000013631443351614700201330ustar00rootroot00000000000000#include "test.h" char *str = "hi"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; int action_group; buffer_delete(buf, 0, 1); buffer_undo(buf); buffer_get(buf, &data, &data_len); ASSERT("del", 0, strncmp(data, "hi", data_len)); buffer_insert(buf, 0, "c", 1, NULL); buffer_undo(buf); buffer_get(buf, &data, &data_len); ASSERT("ins", 0, strncmp(data, "hi", data_len)); action_group = 0; buffer_set_action_group_ptr(buf, &action_group); buffer_insert(buf, 2, "t", 1, NULL); buffer_insert(buf, 3, "!", 1, NULL); action_group += 1; buffer_undo_action_group(buf); buffer_get(buf, &data, &data_len); ASSERT("ins_group", 0, strncmp(data, "hi", data_len)); } mle-1.7.2/tests/unit/test_kinput.c000066400000000000000000000041451443351614700171500ustar00rootroot00000000000000#include "test.h" #define NUM_KINPUTS 256 #define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) char *str = ""; void test(buffer_t *buf, mark_t *cur) { kinput_t weird_input[NUM_KINPUTS]; kinput_t input; int result; size_t i, j; size_t offsetof_mod, offsetof_ch, offsetof_key; offsetof_mod = OFFSETOF(kinput_t, mod); offsetof_ch = OFFSETOF(kinput_t, ch); offsetof_key = OFFSETOF(kinput_t, key); // Fill weird_input values for (i = 0; i < sizeof(kinput_t) * NUM_KINPUTS; i++) { *(((uint8_t*)weird_input) + i) = (uint8_t)i; } // Ensure MLE_KINPUT_COPY *preserves* padding bytes result = 0; for (i = 0; i < NUM_KINPUTS; i++) { MLE_KINPUT_COPY(input, weird_input[i]); result |= memcmp(&input, &weird_input[i], sizeof(kinput_t)); } ASSERT("cmp_kinput_assign", 0, result); // Ensure MLE_KINPUT_SET *zeroes* padding bytes result = 0; for (i = 0; i < NUM_KINPUTS; i++) { // Fill input with non-sense; 42 is a good choice memset(&input, 42, sizeof(input)); // Set input to weird_input[i] MLE_KINPUT_SET(input, weird_input[i].mod, weird_input[i].ch, weird_input[i].key); // Ensure all fields are equal result |= memcmp(&input.mod, &weird_input[i].mod, sizeof(input.mod)); result |= memcmp(&input.ch, &weird_input[i].ch, sizeof(input.ch)); result |= memcmp(&input.key, &weird_input[i].key, sizeof(input.key)); // Ensure bytes between mod and ch are zero for (j = offsetof_mod + sizeof(input.mod); j < offsetof_ch; j++) { result |= *(((uint8_t*)&input) + j) == 0x00 ? 0 : 1; } // Ensure bytes between ch and key are zero for (j = offsetof_ch + sizeof(input.ch); j < offsetof_key; j++) { result |= *(((uint8_t*)&input) + j) == 0x00 ? 0 : 1; } // Ensure bytes between key and end are zero for (j = offsetof_key + sizeof(input.key); j < sizeof(kinput_t); j++) { result |= *(((uint8_t*)&input) + j) == 0x00 ? 0 : 1; } } ASSERT("cmp_kinput_set", 0, result); } mle-1.7.2/tests/unit/test_mark_clone.c000066400000000000000000000005001443351614700177370ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_t *other; mark_clone(cur, &other); ASSERT("neq", 1, other != cur ? 1 : 0); ASSERT("next", 1, other == cur->next ? 1 : 0); ASSERT("line", cur->bline, other->bline); ASSERT("col", cur->col, other->col); } mle-1.7.2/tests/unit/test_mark_cmp.c000066400000000000000000000013461443351614700174270ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_t *other, *first, *second; int cmp; other = buffer_add_mark(buf, NULL, 0); mark_move_beginning(cur); mark_move_end(other); cmp = mark_cmp(cur, other, &first, &second); ASSERT("a_cmp", -1, cmp); ASSERT("a_first", cur, first); ASSERT("a_second", other, second); mark_move_end(cur); mark_move_beginning(other); cmp = mark_cmp(cur, other, &first, &second); ASSERT("b_cmp", 1, cmp); ASSERT("b_first", other, first); ASSERT("b_second", cur, second); mark_move_beginning(cur); mark_move_beginning(other); cmp = mark_cmp(cur, other, &first, &second); ASSERT("c_cmp", 0, cmp); } mle-1.7.2/tests/unit/test_mark_delete_after.c000066400000000000000000000004421443351614700212670ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; mark_move_beginning(cur); mark_delete_after(cur, 1); buffer_get(buf, &data, &data_len); ASSERT("dela", 0, strncmp("ello\nworld", data, data_len)); } mle-1.7.2/tests/unit/test_mark_delete_before.c000066400000000000000000000004351443351614700214320ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; mark_move_end(cur); mark_delete_before(cur, 1); buffer_get(buf, &data, &data_len); ASSERT("delb", 0, strncmp("hello\nworl", data, data_len)); } mle-1.7.2/tests/unit/test_mark_delete_between_mark.c000066400000000000000000000004361443351614700226340ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_t *other; other = buffer_add_mark(buf, NULL, 0); mark_move_beginning(cur); mark_move_end(other); mark_delete_between(cur, other); ASSERT("count", 0, buf->byte_count); } mle-1.7.2/tests/unit/test_mark_delete_between_mark_2.c000066400000000000000000000011611443351614700230510ustar00rootroot00000000000000#include "test.h" char *str = " hello {\n world\n }"; void test(buffer_t *buf, mark_t *cur) { mark_t *other; other = buffer_add_mark(buf, NULL, 0); mark_move_next_str(cur, "{", 1); mark_move_by(cur, 1); mark_move_next_str(other, "}", 1); mark_delete_between(cur, other); ASSERT("lnct", 1, buf->line_count); ASSERT("data", 0, strncmp(" hello {}", buf->first_line->data, buf->first_line->data_len)); ASSERT("clin", 0, cur->bline->line_index); ASSERT("ccol", 9, cur->col); ASSERT("olin", 0, cur->bline->line_index); ASSERT("ocol", 9, cur->col); mark_destroy(other); } mle-1.7.2/tests/unit/test_mark_find_bracket_pair.c000066400000000000000000000004611443351614700222730ustar00rootroot00000000000000#include "test.h" char *str = "[ bracket [ test ] ] xyz"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); mark_move_bracket_pair(cur, 1024); ASSERT("col1", 19, cur->col); mark_move_by(cur, -2); mark_move_bracket_pair(cur, 1024); ASSERT("col2", 10, cur->col); } mle-1.7.2/tests/unit/test_mark_get_between_mark.c000066400000000000000000000006011443351614700221430ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *str; bint_t str_len; mark_t *other; other = buffer_add_mark(buf, NULL, 0); mark_move_beginning(cur); mark_move_end(other); mark_get_between(cur, other, &str, &str_len); ASSERT("len", 11, str_len); ASSERT("eq", 0, strncmp(str, "hello\nworld", str_len)); } mle-1.7.2/tests/unit/test_mark_get_nchars_between.c000066400000000000000000000005071443351614700224740ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_t *other; bint_t nchars; other = buffer_add_mark(buf, NULL, 0); mark_move_beginning(cur); mark_move_end(other); nchars = 0; mark_get_nchars_between(cur, other, &nchars); ASSERT("nchars", 11, nchars); } mle-1.7.2/tests/unit/test_mark_get_offset.c000066400000000000000000000004561443351614700207760ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { bint_t offset; mark_move_end(cur); mark_get_offset(cur, &offset); ASSERT("offset", 11, offset); mark_move_beginning(cur); mark_get_offset(cur, &offset); ASSERT("offset", 0, offset); } mle-1.7.2/tests/unit/test_mark_insert_after.c000066400000000000000000000005111443351614700213260ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; mark_move_beginning(cur); mark_insert_after(cur, "s", 1); buffer_get(buf, &data, &data_len); ASSERT("insa", 0, strncmp("shello\nworld", data, data_len)); ASSERT("col", 0, cur->col); } mle-1.7.2/tests/unit/test_mark_insert_before.c000066400000000000000000000005121443351614700214700ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { char *data; bint_t data_len; mark_move_beginning(cur); mark_insert_before(cur, "s", 1); buffer_get(buf, &data, &data_len); ASSERT("insb", 0, strncmp("shello\nworld", data, data_len)); ASSERT("col", 1, cur->col); } mle-1.7.2/tests/unit/test_mark_is_at_word_bound.c000066400000000000000000000041051443351614700221650ustar00rootroot00000000000000#include "test.h" char *str = "obj->method() yes bob"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); // |obj->method() yes bob ASSERT("l_bol", 1, mark_is_at_word_bound(cur, -1)); ASSERT("x_bol", 1, mark_is_at_word_bound(cur, 0)); ASSERT("r_bol", 0, mark_is_at_word_bound(cur, 1)); mark_move_by(cur, 1); // o|bj->method() yes bob ASSERT("l_mid", 0, mark_is_at_word_bound(cur, -1)); ASSERT("x_mid", 0, mark_is_at_word_bound(cur, 0)); ASSERT("r_mid", 0, mark_is_at_word_bound(cur, 1)); mark_move_by(cur, 2); // obj|->method() yes bob ASSERT("l_eow", 0, mark_is_at_word_bound(cur, -1)); ASSERT("x_eow", 1, mark_is_at_word_bound(cur, 0)); ASSERT("r_eow", 1, mark_is_at_word_bound(cur, 1)); mark_move_by(cur, 1); // obj-|>method() yes bob ASSERT("l_sym", 0, mark_is_at_word_bound(cur, -1)); ASSERT("x_sym", 0, mark_is_at_word_bound(cur, 0)); ASSERT("r_sym", 0, mark_is_at_word_bound(cur, 1)); mark_move_by(cur, 8); // obj->method(|) yes bob ASSERT("l_sym2", 0, mark_is_at_word_bound(cur, -1)); ASSERT("x_sym2", 0, mark_is_at_word_bound(cur, 0)); ASSERT("r_sym2", 0, mark_is_at_word_bound(cur, 1)); mark_move_by(cur, 1); // obj->method()| yes bob ASSERT("l_spa1", 0, mark_is_at_word_bound(cur, -1)); ASSERT("x_spa1", 0, mark_is_at_word_bound(cur, 0)); ASSERT("r_spa1", 0, mark_is_at_word_bound(cur, 1)); mark_move_by(cur, 1); // obj->method() |yes bob ASSERT("l_sow", 1, mark_is_at_word_bound(cur, -1)); ASSERT("x_sow", 1, mark_is_at_word_bound(cur, 0)); ASSERT("r_sow", 0, mark_is_at_word_bound(cur, 1)); mark_move_by(cur, 3); // obj->method() yes| bob ASSERT("l_eow2", 0, mark_is_at_word_bound(cur, -1)); ASSERT("x_eow2", 1, mark_is_at_word_bound(cur, 0)); ASSERT("r_eow2", 1, mark_is_at_word_bound(cur, 1)); mark_move_eol(cur); // obj->method() yes bob| ASSERT("l_eow2", 0, mark_is_at_word_bound(cur, -1)); ASSERT("x_eow2", 1, mark_is_at_word_bound(cur, 0)); ASSERT("r_eow2", 1, mark_is_at_word_bound(cur, 1)); } mle-1.7.2/tests/unit/test_mark_is_gt.c000066400000000000000000000006541443351614700177560ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_t *other; other = buffer_add_mark(buf, NULL, 0); mark_move_beginning(cur); mark_move_end(other); ASSERT("gt", 1, mark_is_gt(other, cur)); ASSERT("ngt1", 0, mark_is_gt(cur, other)); mark_move_beginning(other); ASSERT("ngt2", 0, mark_is_gt(other, cur)); ASSERT("ngt3", 0, mark_is_gt(cur, other)); } mle-1.7.2/tests/unit/test_mark_lettered.c000066400000000000000000000010331443351614700204510ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_t *lett1; mark_t *lett2; mark_t *lett3; mark_clone_w_letter(cur, 'a', &lett1); ASSERT("neq1", 1, lett1 != cur ? 1 : 0); ASSERT("let", '\0', cur->letter); ASSERT("let", 'a', lett1->letter); mark_clone_w_letter(cur, 'a', &lett2); // lett1 destroyed ASSERT("neq2", 1, lett1 != lett2 ? 1 : 0); mark_clone_w_letter(cur, '?', &lett3); ASSERT("null", 1, lett3 == NULL ? 1 : 0); mark_destroy(lett2); } mle-1.7.2/tests/unit/test_mark_move_bol.c000066400000000000000000000003411443351614700204440ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_move_end(cur); mark_move_bol(cur); ASSERT("line", buf->first_line->next, cur->bline); ASSERT("col", 0, cur->col); } mle-1.7.2/tests/unit/test_mark_move_bracket_pair.c000066400000000000000000000004611443351614700223210ustar00rootroot00000000000000#include "test.h" char *str = "[ bracket [ test ] ] xyz"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); mark_move_bracket_pair(cur, 1024); ASSERT("col1", 19, cur->col); mark_move_by(cur, -2); mark_move_bracket_pair(cur, 1024); ASSERT("col2", 10, cur->col); } mle-1.7.2/tests/unit/test_mark_move_by.c000066400000000000000000000007231443351614700203060ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); mark_move_by(cur, 1); ASSERT("col1", 1, cur->col); mark_move_by(cur, 4); ASSERT("col2", 5, cur->col); mark_move_by(cur, 1); ASSERT("col3", 0, cur->col); ASSERT("line3", buf->first_line->next, cur->bline); mark_move_by(cur, -1); ASSERT("col4", 5, cur->col); ASSERT("line4", buf->first_line, cur->bline); } mle-1.7.2/tests/unit/test_mark_move_col.c000066400000000000000000000003621443351614700204500ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); mark_move_col(cur, 5); ASSERT("col1", 5, cur->col); mark_move_col(cur, 6); ASSERT("oob", 5, cur->col); } mle-1.7.2/tests/unit/test_mark_move_eol.c000066400000000000000000000003411443351614700204470ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); mark_move_eol(cur); ASSERT("line", buf->first_line, cur->bline); ASSERT("col", 5, cur->col); } mle-1.7.2/tests/unit/test_mark_move_next_re.c000066400000000000000000000015701443351614700213410ustar00rootroot00000000000000#include "test.h" char *str = "hi\nanna\nbanana"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); mark_move_next_re(cur, "an+a", strlen("an+a")); ASSERT("line1", buf->first_line->next, cur->bline); ASSERT("col1", 0, cur->col); mark_move_next_re(cur, "an+a", strlen("an+a")); ASSERT("line1again", buf->first_line->next, cur->bline); ASSERT("col1again", 0, cur->col); mark_move_next_re_nudge(cur, "an+a", strlen("an+a")); ASSERT("line2", buf->first_line->next->next, cur->bline); ASSERT("col2", 1, cur->col); mark_move_next_re_nudge(cur, "an+a", strlen("an+a")); ASSERT("line3", buf->first_line->next->next, cur->bline); ASSERT("col3", 3, cur->col); mark_move_beginning(cur); mark_move_next_re(cur, "hi", strlen("hi")); ASSERT("line4", buf->first_line, cur->bline); ASSERT("col4", 0, cur->col); } mle-1.7.2/tests/unit/test_mark_move_next_str.c000066400000000000000000000013211443351614700215350ustar00rootroot00000000000000#include "test.h" char *str = "hi\nana\nbanana"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); mark_move_next_str(cur, "ana", strlen("ana")); ASSERT("line1", buf->first_line->next, cur->bline); ASSERT("col1", 0, cur->col); mark_move_next_str(cur, "ana", strlen("ana")); ASSERT("line1again", buf->first_line->next, cur->bline); ASSERT("col1again", 0, cur->col); mark_move_next_str_nudge(cur, "ana", strlen("ana")); ASSERT("line2", buf->first_line->next->next, cur->bline); ASSERT("col2", 1, cur->col); mark_move_next_str_nudge(cur, "ana", strlen("ana")); ASSERT("line3", buf->first_line->next->next, cur->bline); ASSERT("col3", 3, cur->col); } mle-1.7.2/tests/unit/test_mark_move_offset.c000066400000000000000000000007101443351614700211560ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_move_offset(cur, 11); ASSERT("col1", 5, cur->col); ASSERT("line1", buf->first_line->next, cur->bline); mark_move_offset(cur, 6); ASSERT("col2", 0, cur->col); ASSERT("line2", buf->first_line->next, cur->bline); mark_move_offset(cur, 12); ASSERT("oobcol", 5, cur->col); ASSERT("oobline", buf->first_line->next, cur->bline); } mle-1.7.2/tests/unit/test_mark_move_prev_re.c000066400000000000000000000013341443351614700213350ustar00rootroot00000000000000#include "test.h" char *str = "hi\nanna\nbanana"; void test(buffer_t *buf, mark_t *cur) { mark_move_end(cur); mark_move_prev_re(cur, "an+a", strlen("an+a")); ASSERT("line3", buf->first_line->next->next, cur->bline); ASSERT("col3", 3, cur->col); mark_move_prev_re(cur, "an+a", strlen("an+a")); ASSERT("line2", buf->first_line->next->next, cur->bline); ASSERT("col2", 1, cur->col); mark_move_prev_re(cur, "an+a", strlen("an+a")); ASSERT("line1", buf->first_line->next, cur->bline); ASSERT("col1", 0, cur->col); mark_move_end(cur); mark_move_prev_re(cur, "^", strlen("^")); ASSERT("cflex_line", buf->first_line->next->next, cur->bline); ASSERT("cflex_col", 0, cur->col); } mle-1.7.2/tests/unit/test_mark_move_prev_str.c000066400000000000000000000010501443351614700215320ustar00rootroot00000000000000#include "test.h" char *str = "hi\nana\nbanana"; void test(buffer_t *buf, mark_t *cur) { mark_move_end(cur); mark_move_prev_str(cur, "ana", strlen("ana")); ASSERT("line3", buf->first_line->next->next, cur->bline); ASSERT("col3", 3, cur->col); mark_move_prev_str(cur, "ana", strlen("ana")); ASSERT("line2", buf->first_line->next->next, cur->bline); ASSERT("col2", 1, cur->col); mark_move_prev_str(cur, "ana", strlen("ana")); ASSERT("line1", buf->first_line->next, cur->bline); ASSERT("col1", 0, cur->col); } mle-1.7.2/tests/unit/test_mark_move_to.c000066400000000000000000000016701443351614700203200ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_move_beginning(cur); mark_move_to(cur, 0, 0); ASSERT("lin1", buf->first_line, cur->bline); ASSERT("col1", 0, cur->col); mark_move_to(cur, 0, 5); ASSERT("lin2", buf->first_line, cur->bline); ASSERT("col2", 5, cur->col); mark_move_to(cur, 1, 0); ASSERT("lin3", buf->first_line->next, cur->bline); ASSERT("col3", 0, cur->col); mark_move_to(cur, 1, 3); ASSERT("lin4", buf->first_line->next, cur->bline); ASSERT("col4", 3, cur->col); mark_move_to(cur, 1, 6); ASSERT("oob1lin", buf->first_line->next, cur->bline); ASSERT("oob1col", 5, cur->col); mark_move_to(cur, 2, 0); ASSERT("oob2lin", buf->first_line->next, cur->bline); ASSERT("oob2col", 0, cur->col); mark_move_to(cur, 2, 1); ASSERT("oob3lin", buf->first_line->next, cur->bline); ASSERT("oob3col", 1, cur->col); } mle-1.7.2/tests/unit/test_mark_move_vert.c000066400000000000000000000006551443351614700206600ustar00rootroot00000000000000#include "test.h" char *str = "hi\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_move_end(cur); mark_move_vert(cur, 1); ASSERT("oob", buf->first_line->next, cur->bline); mark_move_vert(cur, 0); ASSERT("noop", buf->first_line->next, cur->bline); mark_move_vert(cur, -1); ASSERT("noop", buf->first_line, cur->bline); ASSERT("col", 2, cur->col); ASSERT("tcol", 5, cur->target_col); } mle-1.7.2/tests/unit/test_mark_set_pcre_ovector.c000066400000000000000000000012421443351614700222100ustar00rootroot00000000000000#include "test.h" char *str = "Ervin won gold at age 35!"; void test(buffer_t *buf, mark_t *cur) { PCRE2_SIZE ovector[6] = {0}; int rc = 0; bline_t *bline; bint_t col; bint_t nchars; mark_move_beginning(cur); ASSERT("set", MLBUF_OK, mark_set_pcre_capture(&rc, ovector, 6)); mark_find_next_re(cur, "age ([0-9]+)", strlen("age ([0-9]+)"), &bline, &col, &nchars); ASSERT("bline", cur->bline, bline); ASSERT("col", 18, col); ASSERT("nchars", 6, nchars); ASSERT("rc", 2, rc); ASSERT("vec1a", 18, ovector[0]); ASSERT("vec1b", 24, ovector[1]); ASSERT("vec2a", 22, ovector[2]); ASSERT("vec2b", 24, ovector[3]); } mle-1.7.2/tests/unit/test_mark_swap_with_mark.c000066400000000000000000000006331443351614700216650ustar00rootroot00000000000000#include "test.h" char *str = "hello\nworld"; void test(buffer_t *buf, mark_t *cur) { mark_t *other; other = buffer_add_mark(buf, NULL, 0); mark_move_beginning(cur); mark_move_end(other); mark_swap(cur, other); ASSERT("end", buf->first_line->next, cur->bline); ASSERT("endcol", 5, cur->col); ASSERT("beg", buf->first_line, other->bline); ASSERT("begcol", 0, other->col); } mle-1.7.2/tests/unit/test_recalloc.c000066400000000000000000000011711443351614700174160ustar00rootroot00000000000000#include "test.h" typedef struct { int a; int b; } thing_t; char *str = ""; void test(buffer_t *buf, mark_t *cur) { int i; int is_all_zero; thing_t *things; int nsize = 1024; things = calloc(1, sizeof(thing_t)); things->a = 1; things->b = 2; things = recalloc(things, 1, nsize, sizeof(thing_t)); ASSERT("preserve_a", 1, things->a); ASSERT("preserve_b", 2, things->b); is_all_zero = 1; for (i = 1; i < nsize; i++) { if (things[i].a != 0 || things[i].b != 0) { is_all_zero = 0; break; } } ASSERT("zerod", 1, is_all_zero); } mle-1.7.2/uscript.c000066400000000000000000000304601443351614700141460ustar00rootroot00000000000000#include #include "mle.h" #define MLE_USCRIPT_KEY "_uscript" #define MLE_USCRIPT_GET(pl, pu) do { \ lua_getglobal((pl), MLE_USCRIPT_KEY); \ (pu) = luaL_optpointer((pl), -1, NULL); \ if (!(pu)) return 0; \ } while(0) #define luaL_pushkey(pL, pt, pk, pv) do { \ lua_pushstring((pL), (pk)); \ (lua_push ## pt) ((pL), (pv)); \ lua_settable((pL), -3); \ } while (0) #define luaL_pushkey2(pL, pt, pk, pv1, pv2) do { \ lua_pushstring((pL), (pk)); \ (lua_push ## pt) ((pL), (pv1), (pv2)); \ lua_settable((pL), -3); \ } while (0) static int _uscript_panic(lua_State *L); static int _uscript_cmd_cb(cmd_context_t *ctx); static int _uscript_observer_cb(char *event_name, void *event_data, void *udata); static int _uscript_write(lua_State *L); static void _uscript_push_event_map(uscript_t *uscript, char *event_name, void *event_data); static void _uscript_push_cmd_map(lua_State *L, cmd_context_t *ctx); static void _uscript_push_baction_map(lua_State *L, baction_t *baction); static int _uscript_func_editor_prompt(lua_State *L); static int _uscript_func_editor_register_cmd(lua_State *L); static int _uscript_func_editor_register_observer(lua_State *L); static int _uscript_func_util_escape_shell_arg(lua_State *L); static int _uscript_func_util_shell_exec(lua_State *L); static int _uscript_func_editor_get_input(lua_State *L); static int _uscript_func_editor_menu(lua_State *L); static int _uscript_func_bview_pop_kmap(lua_State *L); static int _uscript_func_bview_push_kmap(lua_State *L); static int _uscript_func_buffer_set_callback(lua_State *L); static int _uscript_func_buffer_add_srule(lua_State *L); static int _uscript_func_buffer_remove_srule(lua_State *L); static int _uscript_func_buffer_write_to_file(lua_State *L); static void *luaL_checkpointer(lua_State *L, int arg); static void *luaL_optpointer(lua_State *L, int arg, void *def); static void lua_pushpointer(lua_State *L, void *ptr); static int luaL_checkfunction(lua_State *L, int arg); static int luaopen_mle(lua_State *L); #include "uscript.inc" // Run uscript uscript_t *uscript_run(editor_t *editor, char *path) { lua_State *L; uscript_t *uscript; L = luaL_newstate(); luaL_openlibs(L); luaL_requiref(L, "mle", luaopen_mle, 1); uscript = calloc(1, sizeof(uscript_t)); uscript->editor = editor; uscript->L = L; lua_pushpointer(L, (void*)uscript); lua_setglobal(L, MLE_USCRIPT_KEY); lua_pop(L, 1); lua_getglobal(L, "_G"); lua_pushcfunction(L, _uscript_write); lua_setfield(L, -2, "print"); lua_pop(L, 1); lua_atpanic(L, _uscript_panic); luaL_loadfile(L, path); // TODO err lua_pcall(L, 0, 0, 0); return uscript; } // Destroy uscript int uscript_destroy(uscript_t *uscript) { lua_close(uscript->L); return MLE_OK; } static int _uscript_panic(lua_State *L) { MLE_SET_ERR(&_editor, "uscript panic: %s", lua_tostring(L, -1)); return 0; } // Invoke cmd in uscript static int _uscript_cmd_cb(cmd_context_t *ctx) { int rv; lua_State *L; uhandle_t *uhandle; int top; uhandle = (uhandle_t*)(ctx->cmd->udata); L = uhandle->uscript->L; top = lua_gettop(L); lua_rawgeti(L, LUA_REGISTRYINDEX, uhandle->callback_ref); _uscript_push_cmd_map(L, ctx); if (lua_pcall(L, 1, 1, 0) != LUA_OK) { printf("err[%s]\n", luaL_checkstring(L, -1)); rv = MLE_ERR; } else if (lua_isboolean(L, -1) && !lua_toboolean(L, -1)) { rv = MLE_ERR; } else { rv = MLE_OK; } lua_settop(L, top); return rv; } static int _uscript_observer_cb(char *event_name, void *event_data, void *udata) { int rv; lua_State *L; uhandle_t *uhandle; uhandle = (uhandle_t*)(udata); L = uhandle->uscript->L; lua_rawgeti(L, LUA_REGISTRYINDEX, uhandle->callback_ref); _uscript_push_event_map(uhandle->uscript, event_name, event_data); if (lua_pcall(L, 1, 1, 0) != LUA_OK) { printf("err[%s]\n", luaL_checkstring(L, -1)); rv = MLE_ERR; } else if (lua_isboolean(L, -1) && !lua_toboolean(L, -1)) { rv = MLE_ERR; } else { rv = MLE_OK; } return rv; } // Handle write from uscript static int _uscript_write(lua_State *L) { uscript_t *uscript; char *str; mark_t *mark; int i, nargs; nargs = lua_gettop(L); MLE_USCRIPT_GET(L, uscript); if (!uscript->editor->active_edit || !uscript->editor->active_edit->active_cursor ) { return 0; } mark = uscript->editor->active_edit->active_cursor->mark; for (i = 1; i <= nargs; i++) { str = (char*)lua_tostring(L, i); mark_insert_before(mark, str, strlen(str)); } return 0; } static void _uscript_push_event_map(uscript_t *uscript, char *event_name, void *event_data) { lua_State *L; L = uscript->L; if (strcmp(event_name, "buffer:baction") == 0) { _uscript_push_baction_map(L, (baction_t*)event_data); return; } else if (strcmp(event_name, "buffer:save") == 0) { lua_createtable(L, 0, 1); luaL_pushkey(L, pointer, "bview", event_data); return; } else if (strncmp(event_name, "cmd:", 4) == 0) { _uscript_push_cmd_map(L, (cmd_context_t*)event_data); return; } lua_pushnil(uscript->L); // TODO } static void _uscript_push_cmd_map(lua_State *L, cmd_context_t *ctx) { lua_createtable(L, 0, 1); luaL_pushkey(L, pointer, "editor", (void*)ctx->editor); luaL_pushkey(L, pointer, "loop_ctx", (void*)ctx->loop_ctx); luaL_pushkey(L, pointer, "cmd", (void*)ctx->cmd); luaL_pushkey(L, pointer, "buffer", (void*)ctx->buffer); luaL_pushkey(L, pointer, "bview", (void*)ctx->bview); luaL_pushkey(L, pointer, "cursor", (void*)ctx->cursor); luaL_pushkey(L, pointer, "mark", (void*)ctx->cursor->mark); luaL_pushkey(L, string, "static_param", (const char*)ctx->static_param); } static void _uscript_push_baction_map(lua_State *L, baction_t *baction) { lua_createtable(L, 0, 1); luaL_pushkey(L, integer, "type", baction->type); luaL_pushkey(L, pointer, "buffer", (void*)baction->buffer); luaL_pushkey(L, integer, "start_line_index", baction->start_line_index); luaL_pushkey(L, integer, "start_col", baction->start_col); luaL_pushkey(L, integer, "maybe_end_line_index", baction->maybe_end_line_index); luaL_pushkey(L, integer, "maybe_end_col", baction->maybe_end_col); luaL_pushkey(L, integer, "byte_delta", baction->byte_delta); luaL_pushkey(L, integer, "char_delta", baction->char_delta); luaL_pushkey(L, integer, "line_delta", baction->line_delta); luaL_pushkey2(L, lstring, "data", (const char*)baction->data, baction->data_len); } // foreign static string _uscript_func_editor_prompt(prompt) static int _uscript_func_editor_prompt(lua_State *L) { uscript_t *uscript; char *prompt; char *answer = NULL; MLE_USCRIPT_GET(L, uscript); prompt = (char*)luaL_checkstring(L, 1); if (editor_prompt(uscript->editor, prompt, NULL, &answer) == MLE_OK && answer) { lua_pushstring(L, answer); free(answer); } else { lua_pushnil(L); } return 1; } int editor_register_cmd(editor_t *editor, cmd_t *cmd); // foreign static int _uscript_func_editor_register_cmd(cmd_name, fn_callback) static int _uscript_func_editor_register_cmd(lua_State *L) { uscript_t *uscript; uhandle_t *uhandle; int rv; char *cmd_name; int fn_callback; cmd_t cmd = {0}; MLE_USCRIPT_GET(L, uscript); cmd_name = (char*)luaL_checkstring(L, 1); // strdup'd by editor_register_cmd fn_callback = luaL_checkfunction(L, 2); uhandle = calloc(1, sizeof(uhandle_t)); uhandle->uscript = uscript; uhandle->callback_ref = fn_callback; DL_APPEND(uscript->uhandles, uhandle); cmd.name = cmd_name; cmd.func = _uscript_cmd_cb; cmd.udata = (void*)uhandle; rv = editor_register_cmd(uscript->editor, &cmd); lua_createtable(L, 0, 1); luaL_pushkey(L, integer, "rv", rv); lua_pushvalue(L, -1); return 1; } // foreign static int _uscript_func_editor_register_observer(event_name, fn_callback) static int _uscript_func_editor_register_observer(lua_State *L) { int rv; char *event_name; int fn_callback; uscript_t *uscript; uhandle_t *uhandle; MLE_USCRIPT_GET(L, uscript); event_name = (char*)luaL_checkstring(L, 1); fn_callback = luaL_checkfunction(L, 2); uhandle = calloc(1, sizeof(uhandle_t)); uhandle->uscript = uscript; uhandle->callback_ref = fn_callback; DL_APPEND(uscript->uhandles, uhandle); rv = editor_register_observer(uscript->editor, event_name, (void*)uhandle, _uscript_observer_cb, NULL); lua_createtable(L, 0, 1); luaL_pushkey(L, integer, "rv", rv); lua_pushvalue(L, -1); return 1; } // foreign static int _uscript_func_util_escape_shell_arg(arg) static int _uscript_func_util_escape_shell_arg(lua_State *L) { char *arg, *arg_escaped; uscript_t *uscript; MLE_USCRIPT_GET(L, uscript); arg = (char*)luaL_checkstring(L, 1); arg_escaped = util_escape_shell_arg(arg, strlen(arg)); lua_createtable(L, 0, 1); luaL_pushkey(L, integer, "rv", 0); luaL_pushkey2(L, lstring, "output", arg_escaped, strlen(arg_escaped)); lua_pushvalue(L, -1); free(arg_escaped); return 1; } // foreign static int _uscript_func_util_shell_exec(cmd, timeout_s) static int _uscript_func_util_shell_exec(lua_State *L) { int rv; char *cmd; long timeout_s; uscript_t *uscript; char *output; size_t output_len; int exit_code; MLE_USCRIPT_GET(L, uscript); cmd = (char*)luaL_checkstring(L, 1); timeout_s = (long)luaL_checkinteger(L, 2); output = NULL; output_len = 0; exit_code = -1; rv = util_shell_exec(uscript->editor, cmd, timeout_s, NULL, 0, 0, NULL, &output, &output_len, &exit_code); lua_createtable(L, 0, 1); luaL_pushkey(L, integer, "rv", rv); luaL_pushkey(L, integer, "exit_code", exit_code); luaL_pushkey2(L, lstring, "output", (output ? output : ""), output_len); lua_pushvalue(L, -1); if (output) free(output); return 1; } // foreign static int _uscript_func_editor_get_input(x, y, z) static int _uscript_func_editor_get_input(lua_State *L) { // TODO (void)L; return 0; } // foreign static int _uscript_func_editor_menu(x, y, z) static int _uscript_func_editor_menu(lua_State *L) { // TODO (void)L; return 0; } // foreign static int _uscript_func_bview_pop_kmap(x, y, z) static int _uscript_func_bview_pop_kmap(lua_State *L) { // TODO (void)L; return 0; } // foreign static int _uscript_func_bview_push_kmap(x, y, z) static int _uscript_func_bview_push_kmap(lua_State *L) { // TODO (void)L; return 0; } // foreign static int _uscript_func_buffer_set_callback(x, y, z) static int _uscript_func_buffer_set_callback(lua_State *L) { // TODO (void)L; return 0; } // foreign static int _uscript_func_buffer_add_srule(x, y, z) static int _uscript_func_buffer_add_srule(lua_State *L) { // TODO (void)L; return 0; } // foreign static int _uscript_func_buffer_remove_srule(x, y, z) static int _uscript_func_buffer_remove_srule(lua_State *L) { // TODO (void)L; return 0; } // foreign static int _uscript_func_buffer_write_to_file(x, y, z) static int _uscript_func_buffer_write_to_file(lua_State *L) { // TODO (void)L; return 0; } static void *luaL_checkpointer(lua_State *L, int arg) { luaL_checktype(L, arg, LUA_TSTRING); return luaL_optpointer(L, arg, NULL); } static void *luaL_optpointer(lua_State *L, int arg, void *def) { const char *ptr; uintptr_t ptr_as_int; ptr = luaL_optstring(L, arg, NULL); if (ptr && strlen(ptr) > 0) { ptr_as_int = (uintptr_t)strtoull(ptr, NULL, 16); return (void*)ptr_as_int; } return def; } static void lua_pushpointer(lua_State *L, void *ptr) { char ptrbuf[32]; if (ptr == NULL) { lua_pushnil(L); } else { snprintf(ptrbuf, 32, "%" PRIxPTR, (uintptr_t)ptr); lua_pushstring(L, ptrbuf); } } static int luaL_checkfunction(lua_State *L, int arg) { luaL_checktype(L, arg, LUA_TFUNCTION); lua_pushvalue(L, arg); return luaL_ref(L, LUA_REGISTRYINDEX); } static int luaopen_mle(lua_State *L) { luaL_newlib(L, mle_lib); return 1; } mle-1.7.2/uscript.inc000066400000000000000000002443601443351614700145030ustar00rootroot00000000000000static int _uscript_func_buffer_add_mark(lua_State *L) { mark_t *rv; buffer_t *self; bline_t *maybe_line; bint_t maybe_col; self = (buffer_t *)luaL_checkpointer(L, 1); maybe_line = (bline_t *)luaL_checkpointer(L, 2); maybe_col = (bint_t)luaL_checkinteger(L, 3); rv = buffer_add_mark(self, maybe_line, maybe_col); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushpointer(L, (void*)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_add_mark_ex(lua_State *L) { mark_t *rv; buffer_t *self; char letter; bline_t *maybe_line; bint_t maybe_col; self = (buffer_t *)luaL_checkpointer(L, 1); letter = (char)luaL_checkinteger(L, 2); maybe_line = (bline_t *)luaL_checkpointer(L, 3); maybe_col = (bint_t)luaL_checkinteger(L, 4); rv = buffer_add_mark_ex(self, letter, maybe_line, maybe_col); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushpointer(L, (void*)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } // static int _uscript_func_buffer_add_srule(lua_State *L) { // } static int _uscript_func_buffer_apply_styles(lua_State *L) { int rv; buffer_t *self; bline_t *start_line; bint_t line_delta; self = (buffer_t *)luaL_checkpointer(L, 1); start_line = (bline_t *)luaL_checkpointer(L, 2); line_delta = (bint_t)luaL_checkinteger(L, 3); rv = buffer_apply_styles(self, start_line, line_delta); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_clear(lua_State *L) { int rv; buffer_t *self; self = (buffer_t *)luaL_checkpointer(L, 1); rv = buffer_clear(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_delete(lua_State *L) { int rv; buffer_t *self; bint_t offset; bint_t num_chars; self = (buffer_t *)luaL_checkpointer(L, 1); offset = (bint_t)luaL_checkinteger(L, 2); num_chars = (bint_t)luaL_checkinteger(L, 3); rv = buffer_delete(self, offset, num_chars); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_delete_w_bline(lua_State *L) { int rv; buffer_t *self; bline_t *start_line; bint_t start_col; bint_t num_chars; self = (buffer_t *)luaL_checkpointer(L, 1); start_line = (bline_t *)luaL_checkpointer(L, 2); start_col = (bint_t)luaL_checkinteger(L, 3); num_chars = (bint_t)luaL_checkinteger(L, 4); rv = buffer_delete_w_bline(self, start_line, start_col, num_chars); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_destroy(lua_State *L) { int rv; buffer_t *self; self = (buffer_t *)luaL_checkpointer(L, 1); rv = buffer_destroy(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_destroy_mark(lua_State *L) { int rv; buffer_t *self; mark_t *mark; self = (buffer_t *)luaL_checkpointer(L, 1); mark = (mark_t *)luaL_checkpointer(L, 2); rv = buffer_destroy_mark(self, mark); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_get(lua_State *L) { int rv; buffer_t *self; char *ret_data = NULL; bint_t ret_data_len = 0; self = (buffer_t *)luaL_checkpointer(L, 1); rv = buffer_get(self, &ret_data, &ret_data_len); lua_createtable(L, 0, 3); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_data"); lua_pushstring(L, (const char*)ret_data); lua_settable(L, -3); lua_pushstring(L, "ret_data_len"); lua_pushinteger(L, (lua_Integer)ret_data_len); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_get_bline(lua_State *L) { int rv; buffer_t *self; bint_t line_index; bline_t *ret_bline = NULL; self = (buffer_t *)luaL_checkpointer(L, 1); line_index = (bint_t)luaL_checkinteger(L, 2); rv = buffer_get_bline(self, line_index, &ret_bline); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_bline"); lua_pushpointer(L, (void*)ret_bline); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_get_bline_col(lua_State *L) { int rv; buffer_t *self; bint_t offset; bline_t *ret_bline = NULL; bint_t ret_col = 0; self = (buffer_t *)luaL_checkpointer(L, 1); offset = (bint_t)luaL_checkinteger(L, 2); rv = buffer_get_bline_col(self, offset, &ret_bline, &ret_col); lua_createtable(L, 0, 3); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_bline"); lua_pushpointer(L, (void*)ret_bline); lua_settable(L, -3); lua_pushstring(L, "ret_col"); lua_pushinteger(L, (lua_Integer)ret_col); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_get_bline_w_hint(lua_State *L) { int rv; buffer_t *self; bint_t line_index; bline_t *opt_hint; bline_t *ret_bline = NULL; self = (buffer_t *)luaL_checkpointer(L, 1); line_index = (bint_t)luaL_checkinteger(L, 2); opt_hint = (bline_t *)luaL_optpointer(L, 3, NULL); rv = buffer_get_bline_w_hint(self, line_index, opt_hint, &ret_bline); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_bline"); lua_pushpointer(L, (void*)ret_bline); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_get_lettered_mark(lua_State *L) { int rv; buffer_t *self; char letter; mark_t *ret_mark = NULL; self = (buffer_t *)luaL_checkpointer(L, 1); letter = (char)luaL_checkinteger(L, 2); rv = buffer_get_lettered_mark(self, letter, &ret_mark); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_mark"); lua_pushpointer(L, (void*)ret_mark); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_get_offset(lua_State *L) { int rv; buffer_t *self; bline_t *bline; bint_t col; bint_t ret_offset = 0; self = (buffer_t *)luaL_checkpointer(L, 1); bline = (bline_t *)luaL_checkpointer(L, 2); col = (bint_t)luaL_checkinteger(L, 3); rv = buffer_get_offset(self, bline, col, &ret_offset); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_offset"); lua_pushinteger(L, (lua_Integer)ret_offset); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_insert(lua_State *L) { int rv; buffer_t *self; bint_t offset; char *data; bint_t data_len; bint_t optret_num_chars = 0; self = (buffer_t *)luaL_checkpointer(L, 1); offset = (bint_t)luaL_checkinteger(L, 2); data = (char *)luaL_checkstring(L, 3); data_len = (bint_t)luaL_checkinteger(L, 4); rv = buffer_insert(self, offset, data, data_len, &optret_num_chars); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_num_chars"); lua_pushinteger(L, (lua_Integer)optret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_insert_w_bline(lua_State *L) { int rv; buffer_t *self; bline_t *start_line; bint_t start_col; char *data; bint_t data_len; bint_t optret_num_chars = 0; self = (buffer_t *)luaL_checkpointer(L, 1); start_line = (bline_t *)luaL_checkpointer(L, 2); start_col = (bint_t)luaL_checkinteger(L, 3); data = (char *)luaL_checkstring(L, 4); data_len = (bint_t)luaL_checkinteger(L, 5); rv = buffer_insert_w_bline(self, start_line, start_col, data, data_len, &optret_num_chars); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_num_chars"); lua_pushinteger(L, (lua_Integer)optret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_new(lua_State *L) { buffer_t *rv; rv = buffer_new(); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushpointer(L, (void*)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_new_open(lua_State *L) { buffer_t *rv; char *path; path = (char *)luaL_checkstring(L, 1); rv = buffer_new_open(path); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushpointer(L, (void*)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_open(lua_State *L) { int rv; buffer_t *self; char *path; self = (buffer_t *)luaL_checkpointer(L, 1); path = (char *)luaL_checkstring(L, 2); rv = buffer_open(self, path); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_redo(lua_State *L) { int rv; buffer_t *self; self = (buffer_t *)luaL_checkpointer(L, 1); rv = buffer_redo(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_redo_action_group(lua_State *L) { int rv; buffer_t *self; self = (buffer_t *)luaL_checkpointer(L, 1); rv = buffer_redo_action_group(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_register_append(lua_State *L) { int rv; buffer_t *self; char reg; char *data; size_t data_len; self = (buffer_t *)luaL_checkpointer(L, 1); reg = (char)luaL_checkinteger(L, 2); data = (char *)luaL_checkstring(L, 3); data_len = (size_t)luaL_checkinteger(L, 4); rv = buffer_register_append(self, reg, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_register_clear(lua_State *L) { int rv; buffer_t *self; char reg; self = (buffer_t *)luaL_checkpointer(L, 1); reg = (char)luaL_checkinteger(L, 2); rv = buffer_register_clear(self, reg); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_register_get(lua_State *L) { int rv; buffer_t *self; char reg; int dup; char *ret_data = NULL; size_t ret_data_len = 0; self = (buffer_t *)luaL_checkpointer(L, 1); reg = (char)luaL_checkinteger(L, 2); dup = (int)luaL_checkinteger(L, 3); rv = buffer_register_get(self, reg, dup, &ret_data, &ret_data_len); lua_createtable(L, 0, 3); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_data"); lua_pushstring(L, (const char*)ret_data); lua_settable(L, -3); lua_pushstring(L, "ret_data_len"); lua_pushpointer(L, (void*)ret_data_len); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_register_prepend(lua_State *L) { int rv; buffer_t *self; char reg; char *data; size_t data_len; self = (buffer_t *)luaL_checkpointer(L, 1); reg = (char)luaL_checkinteger(L, 2); data = (char *)luaL_checkstring(L, 3); data_len = (size_t)luaL_checkinteger(L, 4); rv = buffer_register_prepend(self, reg, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_register_set(lua_State *L) { int rv; buffer_t *self; char reg; char *data; size_t data_len; self = (buffer_t *)luaL_checkpointer(L, 1); reg = (char)luaL_checkinteger(L, 2); data = (char *)luaL_checkstring(L, 3); data_len = (size_t)luaL_checkinteger(L, 4); rv = buffer_register_set(self, reg, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } // static int _uscript_func_buffer_remove_srule(lua_State *L) { // } static int _uscript_func_buffer_replace(lua_State *L) { int rv; buffer_t *self; bint_t offset; bint_t num_chars; char *data; bint_t data_len; self = (buffer_t *)luaL_checkpointer(L, 1); offset = (bint_t)luaL_checkinteger(L, 2); num_chars = (bint_t)luaL_checkinteger(L, 3); data = (char *)luaL_checkstring(L, 4); data_len = (bint_t)luaL_checkinteger(L, 5); rv = buffer_replace(self, offset, num_chars, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_replace_w_bline(lua_State *L) { int rv; buffer_t *self; bline_t *start_line; bint_t start_col; bint_t num_chars; char *data; bint_t data_len; self = (buffer_t *)luaL_checkpointer(L, 1); start_line = (bline_t *)luaL_checkpointer(L, 2); start_col = (bint_t)luaL_checkinteger(L, 3); num_chars = (bint_t)luaL_checkinteger(L, 4); data = (char *)luaL_checkstring(L, 5); data_len = (bint_t)luaL_checkinteger(L, 6); rv = buffer_replace_w_bline(self, start_line, start_col, num_chars, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_save(lua_State *L) { int rv; buffer_t *self; self = (buffer_t *)luaL_checkpointer(L, 1); rv = buffer_save(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_save_as(lua_State *L) { int rv; buffer_t *self; char *path; bint_t optret_nbytes = 0; self = (buffer_t *)luaL_checkpointer(L, 1); path = (char *)luaL_checkstring(L, 2); rv = buffer_save_as(self, path, &optret_nbytes); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_nbytes"); lua_pushinteger(L, (lua_Integer)optret_nbytes); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_set(lua_State *L) { int rv; buffer_t *self; char *data; bint_t data_len; self = (buffer_t *)luaL_checkpointer(L, 1); data = (char *)luaL_checkstring(L, 2); data_len = (bint_t)luaL_checkinteger(L, 3); rv = buffer_set(self, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_set_action_group_ptr(lua_State *L) { int rv; buffer_t *self; int *action_group; self = (buffer_t *)luaL_checkpointer(L, 1); action_group = (int *)luaL_checkinteger(L, 2); rv = buffer_set_action_group_ptr(self, action_group); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } // static int _uscript_func_buffer_set_callback(lua_State *L) { // } static int _uscript_func_buffer_set_mmapped(lua_State *L) { int rv; buffer_t *self; char *data; bint_t data_len; self = (buffer_t *)luaL_checkpointer(L, 1); data = (char *)luaL_checkstring(L, 2); data_len = (bint_t)luaL_checkinteger(L, 3); rv = buffer_set_mmapped(self, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_set_styles_enabled(lua_State *L) { int rv; buffer_t *self; int is_enabled; self = (buffer_t *)luaL_checkpointer(L, 1); is_enabled = (int)luaL_checkinteger(L, 2); rv = buffer_set_styles_enabled(self, is_enabled); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_set_tab_width(lua_State *L) { int rv; buffer_t *self; int tab_width; self = (buffer_t *)luaL_checkpointer(L, 1); tab_width = (int)luaL_checkinteger(L, 2); rv = buffer_set_tab_width(self, tab_width); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_substr(lua_State *L) { int rv; buffer_t *self; bline_t *start_line; bint_t start_col; bline_t *end_line; bint_t end_col; char *ret_data = NULL; bint_t ret_data_len = 0; bint_t ret_nchars = 0; self = (buffer_t *)luaL_checkpointer(L, 1); start_line = (bline_t *)luaL_checkpointer(L, 2); start_col = (bint_t)luaL_checkinteger(L, 3); end_line = (bline_t *)luaL_checkpointer(L, 4); end_col = (bint_t)luaL_checkinteger(L, 5); rv = buffer_substr(self, start_line, start_col, end_line, end_col, &ret_data, &ret_data_len, &ret_nchars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_data"); lua_pushstring(L, (const char*)ret_data); lua_settable(L, -3); lua_pushstring(L, "ret_data_len"); lua_pushinteger(L, (lua_Integer)ret_data_len); lua_settable(L, -3); lua_pushstring(L, "ret_nchars"); lua_pushinteger(L, (lua_Integer)ret_nchars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_undo(lua_State *L) { int rv; buffer_t *self; self = (buffer_t *)luaL_checkpointer(L, 1); rv = buffer_undo(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_undo_action_group(lua_State *L) { int rv; buffer_t *self; self = (buffer_t *)luaL_checkpointer(L, 1); rv = buffer_undo_action_group(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_buffer_write_to_fd(lua_State *L) { int rv; buffer_t *self; int fd; size_t optret_nbytes = 0; self = (buffer_t *)luaL_checkpointer(L, 1); fd = (int)luaL_checkinteger(L, 2); rv = buffer_write_to_fd(self, fd, &optret_nbytes); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_nbytes"); lua_pushpointer(L, (void*)optret_nbytes); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } // static int _uscript_func_buffer_write_to_file(lua_State *L) { // } static int _uscript_func_bview_add_cursor(lua_State *L) { int rv; bview_t *self; bline_t *opt_bline; bint_t opt_col; cursor_t *optret_cursor = NULL; self = (bview_t *)luaL_checkpointer(L, 1); opt_bline = (bline_t *)luaL_optpointer(L, 2, NULL); opt_col = (bint_t)luaL_optinteger(L, 3, 0); rv = bview_add_cursor(self, opt_bline, opt_col, &optret_cursor); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_cursor"); lua_pushpointer(L, (void*)optret_cursor); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_add_cursor_asleep(lua_State *L) { int rv; bview_t *self; bline_t *opt_bline; bint_t opt_col; cursor_t *optret_cursor = NULL; self = (bview_t *)luaL_checkpointer(L, 1); opt_bline = (bline_t *)luaL_optpointer(L, 2, NULL); opt_col = (bint_t)luaL_optinteger(L, 3, 0); rv = bview_add_cursor_asleep(self, opt_bline, opt_col, &optret_cursor); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_cursor"); lua_pushpointer(L, (void*)optret_cursor); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_center_viewport_y(lua_State *L) { int rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_center_viewport_y(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_destroy(lua_State *L) { int rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_destroy(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_draw(lua_State *L) { int rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_draw(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_draw_cursor(lua_State *L) { int rv; bview_t *self; int set_real_cursor; self = (bview_t *)luaL_checkpointer(L, 1); set_real_cursor = (int)luaL_checkinteger(L, 2); rv = bview_draw_cursor(self, set_real_cursor); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_get_active_cursor_count(lua_State *L) { int rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_get_active_cursor_count(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_get_split_root(lua_State *L) { bview_t *rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_get_split_root(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushpointer(L, (void*)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_max_viewport_y(lua_State *L) { int rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_max_viewport_y(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_new(lua_State *L) { bview_t *rv; editor_t *editor; int type; char *opt_path; int opt_path_len; buffer_t *opt_buffer; editor = (editor_t *)luaL_checkpointer(L, 1); type = (int)luaL_checkinteger(L, 2); opt_path = (char *)luaL_optstring(L, 3, NULL); opt_path_len = (int)luaL_optinteger(L, 4, 0); opt_buffer = (buffer_t *)luaL_optpointer(L, 5, NULL); rv = bview_new(editor, type, opt_path, opt_path_len, opt_buffer); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushpointer(L, (void*)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_open(lua_State *L) { int rv; bview_t *self; char *path; int path_len; self = (bview_t *)luaL_checkpointer(L, 1); path = (char *)luaL_checkstring(L, 2); path_len = (int)luaL_checkinteger(L, 3); rv = bview_open(self, path, path_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } // static int _uscript_func_bview_pop_kmap(lua_State *L) { // } // static int _uscript_func_bview_push_kmap(lua_State *L) { // } static int _uscript_func_bview_rectify_viewport(lua_State *L) { int rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_rectify_viewport(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_remove_cursor(lua_State *L) { int rv; bview_t *self; cursor_t *cursor; self = (bview_t *)luaL_checkpointer(L, 1); cursor = (cursor_t *)luaL_checkpointer(L, 2); rv = bview_remove_cursor(self, cursor); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_remove_cursors_except(lua_State *L) { int rv; bview_t *self; cursor_t *one; self = (bview_t *)luaL_checkpointer(L, 1); one = (cursor_t *)luaL_checkpointer(L, 2); rv = bview_remove_cursors_except(self, one); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_resize(lua_State *L) { int rv; bview_t *self; int x; int y; int w; int h; self = (bview_t *)luaL_checkpointer(L, 1); x = (int)luaL_checkinteger(L, 2); y = (int)luaL_checkinteger(L, 3); w = (int)luaL_checkinteger(L, 4); h = (int)luaL_checkinteger(L, 5); rv = bview_resize(self, x, y, w, h); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_set_syntax(lua_State *L) { int rv; bview_t *self; char *opt_syntax; self = (bview_t *)luaL_checkpointer(L, 1); opt_syntax = (char *)luaL_optstring(L, 2, NULL); rv = bview_set_syntax(self, opt_syntax); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_set_viewport_y(lua_State *L) { int rv; bview_t *self; bint_t y; int do_rectify; self = (bview_t *)luaL_checkpointer(L, 1); y = (bint_t)luaL_checkinteger(L, 2); do_rectify = (int)luaL_checkinteger(L, 3); rv = bview_set_viewport_y(self, y, do_rectify); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_split(lua_State *L) { int rv; bview_t *self; int is_vertical; float factor; bview_t *optret_bview = NULL; self = (bview_t *)luaL_checkpointer(L, 1); is_vertical = (int)luaL_checkinteger(L, 2); factor = (float)luaL_checknumber(L, 3); rv = bview_split(self, is_vertical, factor, &optret_bview); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_bview"); lua_pushpointer(L, (void*)optret_bview); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_wake_sleeping_cursors(lua_State *L) { int rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_wake_sleeping_cursors(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_bview_zero_viewport_y(lua_State *L) { int rv; bview_t *self; self = (bview_t *)luaL_checkpointer(L, 1); rv = bview_zero_viewport_y(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_clone(lua_State *L) { int rv; cursor_t *cursor; int use_srules; cursor_t *ret_clone = NULL; cursor = (cursor_t *)luaL_checkpointer(L, 1); use_srules = (int)luaL_checkinteger(L, 2); rv = cursor_clone(cursor, use_srules, &ret_clone); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_clone"); lua_pushpointer(L, (void*)ret_clone); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_cut_copy(lua_State *L) { int rv; cursor_t *cursor; int is_cut; int use_srules; int append; cursor = (cursor_t *)luaL_checkpointer(L, 1); is_cut = (int)luaL_checkinteger(L, 2); use_srules = (int)luaL_checkinteger(L, 3); append = (int)luaL_checkinteger(L, 4); rv = cursor_cut_copy(cursor, is_cut, use_srules, append); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_destroy(lua_State *L) { int rv; cursor_t *cursor; cursor = (cursor_t *)luaL_checkpointer(L, 1); rv = cursor_destroy(cursor); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_drop_anchor(lua_State *L) { int rv; cursor_t *cursor; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); use_srules = (int)luaL_checkinteger(L, 2); rv = cursor_drop_anchor(cursor, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_get_anchor(lua_State *L) { int rv; cursor_t *cursor; mark_t *ret_anchor = NULL; cursor = (cursor_t *)luaL_checkpointer(L, 1); rv = cursor_get_anchor(cursor, &ret_anchor); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_anchor"); lua_pushpointer(L, (void*)ret_anchor); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_get_lo_hi(lua_State *L) { int rv; cursor_t *cursor; mark_t *ret_lo = NULL; mark_t *ret_hi = NULL; cursor = (cursor_t *)luaL_checkpointer(L, 1); rv = cursor_get_lo_hi(cursor, &ret_lo, &ret_hi); lua_createtable(L, 0, 3); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_lo"); lua_pushpointer(L, (void*)ret_lo); lua_settable(L, -3); lua_pushstring(L, "ret_hi"); lua_pushpointer(L, (void*)ret_hi); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_get_mark(lua_State *L) { int rv; cursor_t *cursor; mark_t *ret_mark = NULL; cursor = (cursor_t *)luaL_checkpointer(L, 1); rv = cursor_get_mark(cursor, &ret_mark); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_mark"); lua_pushpointer(L, (void*)ret_mark); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_lift_anchor(lua_State *L) { int rv; cursor_t *cursor; cursor = (cursor_t *)luaL_checkpointer(L, 1); rv = cursor_lift_anchor(cursor); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_replace(lua_State *L) { int rv; cursor_t *cursor; int interactive; char *opt_regex; char *opt_replacement; cursor = (cursor_t *)luaL_checkpointer(L, 1); interactive = (int)luaL_checkinteger(L, 2); opt_regex = (char *)luaL_optstring(L, 3, NULL); opt_replacement = (char *)luaL_optstring(L, 4, NULL); rv = cursor_replace(cursor, interactive, opt_regex, opt_replacement); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_select_between(lua_State *L) { int rv; cursor_t *cursor; mark_t *a; mark_t *b; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); a = (mark_t *)luaL_checkpointer(L, 2); b = (mark_t *)luaL_checkpointer(L, 3); use_srules = (int)luaL_checkinteger(L, 4); rv = cursor_select_between(cursor, a, b, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_select_by(lua_State *L) { int rv; cursor_t *cursor; const char *strat; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); strat = (const char *)luaL_checkstring(L, 2); use_srules = (int)luaL_checkinteger(L, 3); rv = cursor_select_by(cursor, strat, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_select_by_bracket(lua_State *L) { int rv; cursor_t *cursor; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); use_srules = (int)luaL_checkinteger(L, 2); rv = cursor_select_by_bracket(cursor, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_select_by_string(lua_State *L) { int rv; cursor_t *cursor; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); use_srules = (int)luaL_checkinteger(L, 2); rv = cursor_select_by_string(cursor, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_select_by_word(lua_State *L) { int rv; cursor_t *cursor; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); use_srules = (int)luaL_checkinteger(L, 2); rv = cursor_select_by_word(cursor, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_select_by_word_back(lua_State *L) { int rv; cursor_t *cursor; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); use_srules = (int)luaL_checkinteger(L, 2); rv = cursor_select_by_word_back(cursor, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_select_by_word_forward(lua_State *L) { int rv; cursor_t *cursor; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); use_srules = (int)luaL_checkinteger(L, 2); rv = cursor_select_by_word_forward(cursor, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_toggle_anchor(lua_State *L) { int rv; cursor_t *cursor; int use_srules; cursor = (cursor_t *)luaL_checkpointer(L, 1); use_srules = (int)luaL_checkinteger(L, 2); rv = cursor_toggle_anchor(cursor, use_srules); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_cursor_uncut(lua_State *L) { int rv; cursor_t *cursor; cursor = (cursor_t *)luaL_checkpointer(L, 1); rv = cursor_uncut(cursor); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_editor_bview_edit_count(lua_State *L) { int rv; editor_t *editor; editor = (editor_t *)luaL_checkpointer(L, 1); rv = editor_bview_edit_count(editor); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_editor_close_bview(lua_State *L) { int rv; editor_t *editor; bview_t *bview; int optret_num_closed = 0; editor = (editor_t *)luaL_checkpointer(L, 1); bview = (bview_t *)luaL_checkpointer(L, 2); rv = editor_close_bview(editor, bview, &optret_num_closed); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_num_closed"); lua_pushinteger(L, (lua_Integer)optret_num_closed); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_editor_count_bviews_by_buffer(lua_State *L) { int rv; editor_t *editor; buffer_t *buffer; editor = (editor_t *)luaL_checkpointer(L, 1); buffer = (buffer_t *)luaL_checkpointer(L, 2); rv = editor_count_bviews_by_buffer(editor, buffer); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_editor_destroy_observer(lua_State *L) { int rv; editor_t *editor; observer_t *observer; editor = (editor_t *)luaL_checkpointer(L, 1); observer = (observer_t *)luaL_checkpointer(L, 2); rv = editor_destroy_observer(editor, observer); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_editor_display(lua_State *L) { int rv; editor_t *editor; editor = (editor_t *)luaL_checkpointer(L, 1); rv = editor_display(editor); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_editor_force_redraw(lua_State *L) { int rv; editor_t *editor; editor = (editor_t *)luaL_checkpointer(L, 1); rv = editor_force_redraw(editor); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } // static int _uscript_func_editor_get_input(lua_State *L) { // } // static int _uscript_func_editor_menu(lua_State *L) { // } static int _uscript_func_editor_notify_observers(lua_State *L) { int rv; editor_t *editor; char *event_name; void *event_data; editor = (editor_t *)luaL_checkpointer(L, 1); event_name = (char *)luaL_checkstring(L, 2); event_data = (void *)luaL_checkpointer(L, 3); rv = editor_notify_observers(editor, event_name, event_data); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_editor_open_bview(lua_State *L) { int rv; editor_t *editor; bview_t *opt_parent; int type; char *opt_path; int opt_path_len; int make_active; bint_t linenum; int skip_resize; buffer_t *opt_buffer; bview_t *optret_bview = NULL; editor = (editor_t *)luaL_checkpointer(L, 1); opt_parent = (bview_t *)luaL_optpointer(L, 2, NULL); type = (int)luaL_checkinteger(L, 3); opt_path = (char *)luaL_optstring(L, 4, NULL); opt_path_len = (int)luaL_optinteger(L, 5, 0); make_active = (int)luaL_checkinteger(L, 6); linenum = (bint_t)luaL_checkinteger(L, 7); skip_resize = (int)luaL_checkinteger(L, 8); opt_buffer = (buffer_t *)luaL_optpointer(L, 9, NULL); rv = editor_open_bview(editor, opt_parent, type, opt_path, opt_path_len, make_active, linenum, skip_resize, opt_buffer, &optret_bview); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_bview"); lua_pushpointer(L, (void*)optret_bview); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } // static int _uscript_func_editor_prompt(lua_State *L) { // } // static int _uscript_func_editor_register_cmd(lua_State *L) { // } // static int _uscript_func_editor_register_observer(lua_State *L) { // } static int _uscript_func_editor_set_active(lua_State *L) { int rv; editor_t *editor; bview_t *bview; editor = (editor_t *)luaL_checkpointer(L, 1); bview = (bview_t *)luaL_checkpointer(L, 2); rv = editor_set_active(editor, bview); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_clone(lua_State *L) { int rv; mark_t *self; mark_t *ret_mark = NULL; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_clone(self, &ret_mark); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_mark"); lua_pushpointer(L, (void*)ret_mark); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_clone_w_letter(lua_State *L) { int rv; mark_t *self; char letter; mark_t *ret_mark = NULL; self = (mark_t *)luaL_checkpointer(L, 1); letter = (char)luaL_checkinteger(L, 2); rv = mark_clone_w_letter(self, letter, &ret_mark); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_mark"); lua_pushpointer(L, (void*)ret_mark); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_delete_after(lua_State *L) { int rv; mark_t *self; bint_t num_chars; self = (mark_t *)luaL_checkpointer(L, 1); num_chars = (bint_t)luaL_checkinteger(L, 2); rv = mark_delete_after(self, num_chars); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_delete_before(lua_State *L) { int rv; mark_t *self; bint_t num_chars; self = (mark_t *)luaL_checkpointer(L, 1); num_chars = (bint_t)luaL_checkinteger(L, 2); rv = mark_delete_before(self, num_chars); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_delete_between(lua_State *L) { int rv; mark_t *self; mark_t *other; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_delete_between(self, other); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_destroy(lua_State *L) { int rv; mark_t *self; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_destroy(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_find_bracket_pair(lua_State *L) { int rv; mark_t *self; bint_t max_chars; bline_t *ret_line = NULL; bint_t ret_col = 0; bint_t ret_brkt = 0; self = (mark_t *)luaL_checkpointer(L, 1); max_chars = (bint_t)luaL_checkinteger(L, 2); rv = mark_find_bracket_pair(self, max_chars, &ret_line, &ret_col, &ret_brkt); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_line"); lua_pushpointer(L, (void*)ret_line); lua_settable(L, -3); lua_pushstring(L, "ret_col"); lua_pushinteger(L, (lua_Integer)ret_col); lua_settable(L, -3); lua_pushstring(L, "ret_brkt"); lua_pushinteger(L, (lua_Integer)ret_brkt); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_find_bracket_top(lua_State *L) { int rv; mark_t *self; bint_t max_chars; bline_t *ret_line = NULL; bint_t ret_col = 0; bint_t ret_brkt = 0; self = (mark_t *)luaL_checkpointer(L, 1); max_chars = (bint_t)luaL_checkinteger(L, 2); rv = mark_find_bracket_top(self, max_chars, &ret_line, &ret_col, &ret_brkt); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_line"); lua_pushpointer(L, (void*)ret_line); lua_settable(L, -3); lua_pushstring(L, "ret_col"); lua_pushinteger(L, (lua_Integer)ret_col); lua_settable(L, -3); lua_pushstring(L, "ret_brkt"); lua_pushinteger(L, (lua_Integer)ret_brkt); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_find_next_re(lua_State *L) { int rv; mark_t *self; char *re; bint_t re_len; bline_t *ret_line = NULL; bint_t ret_col = 0; bint_t ret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); re = (char *)luaL_checkstring(L, 2); re_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_find_next_re(self, re, re_len, &ret_line, &ret_col, &ret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_line"); lua_pushpointer(L, (void*)ret_line); lua_settable(L, -3); lua_pushstring(L, "ret_col"); lua_pushinteger(L, (lua_Integer)ret_col); lua_settable(L, -3); lua_pushstring(L, "ret_num_chars"); lua_pushinteger(L, (lua_Integer)ret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_find_next_str(lua_State *L) { int rv; mark_t *self; char *str; bint_t str_len; bline_t *ret_line = NULL; bint_t ret_col = 0; bint_t ret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); str = (char *)luaL_checkstring(L, 2); str_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_find_next_str(self, str, str_len, &ret_line, &ret_col, &ret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_line"); lua_pushpointer(L, (void*)ret_line); lua_settable(L, -3); lua_pushstring(L, "ret_col"); lua_pushinteger(L, (lua_Integer)ret_col); lua_settable(L, -3); lua_pushstring(L, "ret_num_chars"); lua_pushinteger(L, (lua_Integer)ret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_find_prev_re(lua_State *L) { int rv; mark_t *self; char *re; bint_t re_len; bline_t *ret_line = NULL; bint_t ret_col = 0; bint_t ret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); re = (char *)luaL_checkstring(L, 2); re_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_find_prev_re(self, re, re_len, &ret_line, &ret_col, &ret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_line"); lua_pushpointer(L, (void*)ret_line); lua_settable(L, -3); lua_pushstring(L, "ret_col"); lua_pushinteger(L, (lua_Integer)ret_col); lua_settable(L, -3); lua_pushstring(L, "ret_num_chars"); lua_pushinteger(L, (lua_Integer)ret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_find_prev_str(lua_State *L) { int rv; mark_t *self; char *str; bint_t str_len; bline_t *ret_line = NULL; bint_t ret_col = 0; bint_t ret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); str = (char *)luaL_checkstring(L, 2); str_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_find_prev_str(self, str, str_len, &ret_line, &ret_col, &ret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_line"); lua_pushpointer(L, (void*)ret_line); lua_settable(L, -3); lua_pushstring(L, "ret_col"); lua_pushinteger(L, (lua_Integer)ret_col); lua_settable(L, -3); lua_pushstring(L, "ret_num_chars"); lua_pushinteger(L, (lua_Integer)ret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_get_between(lua_State *L) { int rv; mark_t *self; mark_t *other; char *ret_str = NULL; bint_t ret_str_len = 0; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_get_between(self, other, &ret_str, &ret_str_len); lua_createtable(L, 0, 3); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_str"); lua_pushstring(L, (const char*)ret_str); lua_settable(L, -3); lua_pushstring(L, "ret_str_len"); lua_pushinteger(L, (lua_Integer)ret_str_len); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_get_char_after(lua_State *L) { int rv; mark_t *self; uint32_t ret_char = 0; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_get_char_after(self, &ret_char); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_char"); lua_pushinteger(L, (lua_Integer)ret_char); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_get_char_before(lua_State *L) { int rv; mark_t *self; uint32_t ret_char = 0; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_get_char_before(self, &ret_char); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_char"); lua_pushinteger(L, (lua_Integer)ret_char); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_get_nchars_between(lua_State *L) { int rv; mark_t *self; mark_t *other; bint_t ret_nchars = 0; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_get_nchars_between(self, other, &ret_nchars); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_nchars"); lua_pushinteger(L, (lua_Integer)ret_nchars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_get_offset(lua_State *L) { int rv; mark_t *self; bint_t ret_offset = 0; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_get_offset(self, &ret_offset); lua_createtable(L, 0, 2); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "ret_offset"); lua_pushinteger(L, (lua_Integer)ret_offset); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_insert_after(lua_State *L) { int rv; mark_t *self; char *data; bint_t data_len; self = (mark_t *)luaL_checkpointer(L, 1); data = (char *)luaL_checkstring(L, 2); data_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_insert_after(self, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_insert_before(lua_State *L) { int rv; mark_t *self; char *data; bint_t data_len; self = (mark_t *)luaL_checkpointer(L, 1); data = (char *)luaL_checkstring(L, 2); data_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_insert_before(self, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_after_col_minus_lefties(lua_State *L) { int rv; mark_t *self; bint_t col; self = (mark_t *)luaL_checkpointer(L, 1); col = (bint_t)luaL_checkinteger(L, 2); rv = mark_is_after_col_minus_lefties(self, col); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_at_bol(lua_State *L) { int rv; mark_t *self; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_is_at_bol(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_at_eol(lua_State *L) { int rv; mark_t *self; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_is_at_eol(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_at_word_bound(lua_State *L) { int rv; mark_t *self; int side; self = (mark_t *)luaL_checkpointer(L, 1); side = (int)luaL_checkinteger(L, 2); rv = mark_is_at_word_bound(self, side); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_between(lua_State *L) { int rv; mark_t *self; mark_t *ma; mark_t *mb; self = (mark_t *)luaL_checkpointer(L, 1); ma = (mark_t *)luaL_checkpointer(L, 2); mb = (mark_t *)luaL_checkpointer(L, 3); rv = mark_is_between(self, ma, mb); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_eq(lua_State *L) { int rv; mark_t *self; mark_t *other; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_is_eq(self, other); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_gt(lua_State *L) { int rv; mark_t *self; mark_t *other; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_is_gt(self, other); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_gte(lua_State *L) { int rv; mark_t *self; mark_t *other; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_is_gte(self, other); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_lt(lua_State *L) { int rv; mark_t *self; mark_t *other; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_is_lt(self, other); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_is_lte(lua_State *L) { int rv; mark_t *self; mark_t *other; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_is_lte(self, other); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_join(lua_State *L) { int rv; mark_t *self; mark_t *other; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_join(self, other); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_beginning(lua_State *L) { int rv; mark_t *self; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_move_beginning(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_bol(lua_State *L) { int rv; mark_t *self; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_move_bol(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_bracket_pair(lua_State *L) { int rv; mark_t *self; bint_t max_chars; self = (mark_t *)luaL_checkpointer(L, 1); max_chars = (bint_t)luaL_checkinteger(L, 2); rv = mark_move_bracket_pair(self, max_chars); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_bracket_pair_ex(lua_State *L) { int rv; mark_t *self; bint_t max_chars; bline_t *optret_line = NULL; bint_t optret_col = 0; bint_t optret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); max_chars = (bint_t)luaL_checkinteger(L, 2); rv = mark_move_bracket_pair_ex(self, max_chars, &optret_line, &optret_col, &optret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_line"); lua_pushpointer(L, (void*)optret_line); lua_settable(L, -3); lua_pushstring(L, "optret_col"); lua_pushinteger(L, (lua_Integer)optret_col); lua_settable(L, -3); lua_pushstring(L, "optret_num_chars"); lua_pushinteger(L, (lua_Integer)optret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_bracket_top(lua_State *L) { int rv; mark_t *self; bint_t max_chars; self = (mark_t *)luaL_checkpointer(L, 1); max_chars = (bint_t)luaL_checkinteger(L, 2); rv = mark_move_bracket_top(self, max_chars); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_bracket_top_ex(lua_State *L) { int rv; mark_t *self; bint_t max_chars; bline_t *optret_line = NULL; bint_t optret_col = 0; bint_t optret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); max_chars = (bint_t)luaL_checkinteger(L, 2); rv = mark_move_bracket_top_ex(self, max_chars, &optret_line, &optret_col, &optret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_line"); lua_pushpointer(L, (void*)optret_line); lua_settable(L, -3); lua_pushstring(L, "optret_col"); lua_pushinteger(L, (lua_Integer)optret_col); lua_settable(L, -3); lua_pushstring(L, "optret_num_chars"); lua_pushinteger(L, (lua_Integer)optret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_by(lua_State *L) { int rv; mark_t *self; bint_t char_delta; self = (mark_t *)luaL_checkpointer(L, 1); char_delta = (bint_t)luaL_checkinteger(L, 2); rv = mark_move_by(self, char_delta); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_col(lua_State *L) { int rv; mark_t *self; bint_t col; self = (mark_t *)luaL_checkpointer(L, 1); col = (bint_t)luaL_checkinteger(L, 2); rv = mark_move_col(self, col); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_end(lua_State *L) { int rv; mark_t *self; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_move_end(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_eol(lua_State *L) { int rv; mark_t *self; self = (mark_t *)luaL_checkpointer(L, 1); rv = mark_move_eol(self); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_next_re(lua_State *L) { int rv; mark_t *self; char *re; bint_t re_len; self = (mark_t *)luaL_checkpointer(L, 1); re = (char *)luaL_checkstring(L, 2); re_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_next_re(self, re, re_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_next_re_ex(lua_State *L) { int rv; mark_t *self; char *re; bint_t re_len; bline_t *optret_line = NULL; bint_t optret_col = 0; bint_t optret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); re = (char *)luaL_checkstring(L, 2); re_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_next_re_ex(self, re, re_len, &optret_line, &optret_col, &optret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_line"); lua_pushpointer(L, (void*)optret_line); lua_settable(L, -3); lua_pushstring(L, "optret_col"); lua_pushinteger(L, (lua_Integer)optret_col); lua_settable(L, -3); lua_pushstring(L, "optret_num_chars"); lua_pushinteger(L, (lua_Integer)optret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_next_re_nudge(lua_State *L) { int rv; mark_t *self; char *re; bint_t re_len; self = (mark_t *)luaL_checkpointer(L, 1); re = (char *)luaL_checkstring(L, 2); re_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_next_re_nudge(self, re, re_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_next_str(lua_State *L) { int rv; mark_t *self; char *str; bint_t str_len; self = (mark_t *)luaL_checkpointer(L, 1); str = (char *)luaL_checkstring(L, 2); str_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_next_str(self, str, str_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_next_str_ex(lua_State *L) { int rv; mark_t *self; char *str; bint_t str_len; bline_t *optret_line = NULL; bint_t optret_col = 0; bint_t optret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); str = (char *)luaL_checkstring(L, 2); str_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_next_str_ex(self, str, str_len, &optret_line, &optret_col, &optret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_line"); lua_pushpointer(L, (void*)optret_line); lua_settable(L, -3); lua_pushstring(L, "optret_col"); lua_pushinteger(L, (lua_Integer)optret_col); lua_settable(L, -3); lua_pushstring(L, "optret_num_chars"); lua_pushinteger(L, (lua_Integer)optret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_next_str_nudge(lua_State *L) { int rv; mark_t *self; char *str; bint_t str_len; self = (mark_t *)luaL_checkpointer(L, 1); str = (char *)luaL_checkstring(L, 2); str_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_next_str_nudge(self, str, str_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_offset(lua_State *L) { int rv; mark_t *self; bint_t offset; self = (mark_t *)luaL_checkpointer(L, 1); offset = (bint_t)luaL_checkinteger(L, 2); rv = mark_move_offset(self, offset); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_prev_re(lua_State *L) { int rv; mark_t *self; char *re; bint_t re_len; self = (mark_t *)luaL_checkpointer(L, 1); re = (char *)luaL_checkstring(L, 2); re_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_prev_re(self, re, re_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_prev_re_ex(lua_State *L) { int rv; mark_t *self; char *re; bint_t re_len; bline_t *optret_line = NULL; bint_t optret_col = 0; bint_t optret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); re = (char *)luaL_checkstring(L, 2); re_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_prev_re_ex(self, re, re_len, &optret_line, &optret_col, &optret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_line"); lua_pushpointer(L, (void*)optret_line); lua_settable(L, -3); lua_pushstring(L, "optret_col"); lua_pushinteger(L, (lua_Integer)optret_col); lua_settable(L, -3); lua_pushstring(L, "optret_num_chars"); lua_pushinteger(L, (lua_Integer)optret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_prev_str(lua_State *L) { int rv; mark_t *self; char *str; bint_t str_len; self = (mark_t *)luaL_checkpointer(L, 1); str = (char *)luaL_checkstring(L, 2); str_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_prev_str(self, str, str_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_prev_str_ex(lua_State *L) { int rv; mark_t *self; char *str; bint_t str_len; bline_t *optret_line = NULL; bint_t optret_col = 0; bint_t optret_num_chars = 0; self = (mark_t *)luaL_checkpointer(L, 1); str = (char *)luaL_checkstring(L, 2); str_len = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_prev_str_ex(self, str, str_len, &optret_line, &optret_col, &optret_num_chars); lua_createtable(L, 0, 4); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushstring(L, "optret_line"); lua_pushpointer(L, (void*)optret_line); lua_settable(L, -3); lua_pushstring(L, "optret_col"); lua_pushinteger(L, (lua_Integer)optret_col); lua_settable(L, -3); lua_pushstring(L, "optret_num_chars"); lua_pushinteger(L, (lua_Integer)optret_num_chars); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_to(lua_State *L) { int rv; mark_t *self; bint_t line_index; bint_t col; self = (mark_t *)luaL_checkpointer(L, 1); line_index = (bint_t)luaL_checkinteger(L, 2); col = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_to(self, line_index, col); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_to_w_bline(lua_State *L) { int rv; mark_t *self; bline_t *bline; bint_t col; self = (mark_t *)luaL_checkpointer(L, 1); bline = (bline_t *)luaL_checkpointer(L, 2); col = (bint_t)luaL_checkinteger(L, 3); rv = mark_move_to_w_bline(self, bline, col); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_move_vert(lua_State *L) { int rv; mark_t *self; bint_t line_delta; self = (mark_t *)luaL_checkpointer(L, 1); line_delta = (bint_t)luaL_checkinteger(L, 2); rv = mark_move_vert(self, line_delta); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_replace(lua_State *L) { int rv; mark_t *self; bint_t num_chars; char *data; bint_t data_len; self = (mark_t *)luaL_checkpointer(L, 1); num_chars = (bint_t)luaL_checkinteger(L, 2); data = (char *)luaL_checkstring(L, 3); data_len = (bint_t)luaL_checkinteger(L, 4); rv = mark_replace(self, num_chars, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_replace_between(lua_State *L) { int rv; mark_t *self; mark_t *other; char *data; bint_t data_len; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); data = (char *)luaL_checkstring(L, 3); data_len = (bint_t)luaL_checkinteger(L, 4); rv = mark_replace_between(self, other, data, data_len); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } static int _uscript_func_mark_swap(lua_State *L) { int rv; mark_t *self; mark_t *other; self = (mark_t *)luaL_checkpointer(L, 1); other = (mark_t *)luaL_checkpointer(L, 2); rv = mark_swap(self, other); lua_createtable(L, 0, 1); lua_pushstring(L, "rv"); lua_pushinteger(L, (lua_Integer)rv); lua_settable(L, -3); lua_pushvalue(L, -1); return 1; } // static int _uscript_func_util_escape_shell_arg(lua_State *L) { // } // static int _uscript_func_util_shell_exec(lua_State *L) { // } static const struct luaL_Reg mle_lib[] = { { "buffer_add_mark", _uscript_func_buffer_add_mark }, { "buffer_add_mark_ex", _uscript_func_buffer_add_mark_ex }, { "buffer_add_srule", _uscript_func_buffer_add_srule }, { "buffer_apply_styles", _uscript_func_buffer_apply_styles }, { "buffer_clear", _uscript_func_buffer_clear }, { "buffer_delete", _uscript_func_buffer_delete }, { "buffer_delete_w_bline", _uscript_func_buffer_delete_w_bline }, { "buffer_destroy", _uscript_func_buffer_destroy }, { "buffer_destroy_mark", _uscript_func_buffer_destroy_mark }, { "buffer_get", _uscript_func_buffer_get }, { "buffer_get_bline", _uscript_func_buffer_get_bline }, { "buffer_get_bline_col", _uscript_func_buffer_get_bline_col }, { "buffer_get_bline_w_hint", _uscript_func_buffer_get_bline_w_hint }, { "buffer_get_lettered_mark", _uscript_func_buffer_get_lettered_mark }, { "buffer_get_offset", _uscript_func_buffer_get_offset }, { "buffer_insert", _uscript_func_buffer_insert }, { "buffer_insert_w_bline", _uscript_func_buffer_insert_w_bline }, { "buffer_new", _uscript_func_buffer_new }, { "buffer_new_open", _uscript_func_buffer_new_open }, { "buffer_open", _uscript_func_buffer_open }, { "buffer_redo", _uscript_func_buffer_redo }, { "buffer_redo_action_group", _uscript_func_buffer_redo_action_group }, { "buffer_register_append", _uscript_func_buffer_register_append }, { "buffer_register_clear", _uscript_func_buffer_register_clear }, { "buffer_register_get", _uscript_func_buffer_register_get }, { "buffer_register_prepend", _uscript_func_buffer_register_prepend }, { "buffer_register_set", _uscript_func_buffer_register_set }, { "buffer_remove_srule", _uscript_func_buffer_remove_srule }, { "buffer_replace", _uscript_func_buffer_replace }, { "buffer_replace_w_bline", _uscript_func_buffer_replace_w_bline }, { "buffer_save", _uscript_func_buffer_save }, { "buffer_save_as", _uscript_func_buffer_save_as }, { "buffer_set", _uscript_func_buffer_set }, { "buffer_set_action_group_ptr", _uscript_func_buffer_set_action_group_ptr }, { "buffer_set_callback", _uscript_func_buffer_set_callback }, { "buffer_set_mmapped", _uscript_func_buffer_set_mmapped }, { "buffer_set_styles_enabled", _uscript_func_buffer_set_styles_enabled }, { "buffer_set_tab_width", _uscript_func_buffer_set_tab_width }, { "buffer_substr", _uscript_func_buffer_substr }, { "buffer_undo", _uscript_func_buffer_undo }, { "buffer_undo_action_group", _uscript_func_buffer_undo_action_group }, { "buffer_write_to_fd", _uscript_func_buffer_write_to_fd }, { "buffer_write_to_file", _uscript_func_buffer_write_to_file }, { "bview_add_cursor", _uscript_func_bview_add_cursor }, { "bview_add_cursor_asleep", _uscript_func_bview_add_cursor_asleep }, { "bview_center_viewport_y", _uscript_func_bview_center_viewport_y }, { "bview_destroy", _uscript_func_bview_destroy }, { "bview_draw", _uscript_func_bview_draw }, { "bview_draw_cursor", _uscript_func_bview_draw_cursor }, { "bview_get_active_cursor_count", _uscript_func_bview_get_active_cursor_count }, { "bview_get_split_root", _uscript_func_bview_get_split_root }, { "bview_max_viewport_y", _uscript_func_bview_max_viewport_y }, { "bview_new", _uscript_func_bview_new }, { "bview_open", _uscript_func_bview_open }, { "bview_pop_kmap", _uscript_func_bview_pop_kmap }, { "bview_push_kmap", _uscript_func_bview_push_kmap }, { "bview_rectify_viewport", _uscript_func_bview_rectify_viewport }, { "bview_remove_cursor", _uscript_func_bview_remove_cursor }, { "bview_remove_cursors_except", _uscript_func_bview_remove_cursors_except }, { "bview_resize", _uscript_func_bview_resize }, { "bview_set_syntax", _uscript_func_bview_set_syntax }, { "bview_set_viewport_y", _uscript_func_bview_set_viewport_y }, { "bview_split", _uscript_func_bview_split }, { "bview_wake_sleeping_cursors", _uscript_func_bview_wake_sleeping_cursors }, { "bview_zero_viewport_y", _uscript_func_bview_zero_viewport_y }, { "cursor_clone", _uscript_func_cursor_clone }, { "cursor_cut_copy", _uscript_func_cursor_cut_copy }, { "cursor_destroy", _uscript_func_cursor_destroy }, { "cursor_drop_anchor", _uscript_func_cursor_drop_anchor }, { "cursor_get_anchor", _uscript_func_cursor_get_anchor }, { "cursor_get_lo_hi", _uscript_func_cursor_get_lo_hi }, { "cursor_get_mark", _uscript_func_cursor_get_mark }, { "cursor_lift_anchor", _uscript_func_cursor_lift_anchor }, { "cursor_replace", _uscript_func_cursor_replace }, { "cursor_select_between", _uscript_func_cursor_select_between }, { "cursor_select_by", _uscript_func_cursor_select_by }, { "cursor_select_by_bracket", _uscript_func_cursor_select_by_bracket }, { "cursor_select_by_string", _uscript_func_cursor_select_by_string }, { "cursor_select_by_word", _uscript_func_cursor_select_by_word }, { "cursor_select_by_word_back", _uscript_func_cursor_select_by_word_back }, { "cursor_select_by_word_forward", _uscript_func_cursor_select_by_word_forward }, { "cursor_toggle_anchor", _uscript_func_cursor_toggle_anchor }, { "cursor_uncut", _uscript_func_cursor_uncut }, { "editor_bview_edit_count", _uscript_func_editor_bview_edit_count }, { "editor_close_bview", _uscript_func_editor_close_bview }, { "editor_count_bviews_by_buffer", _uscript_func_editor_count_bviews_by_buffer }, { "editor_destroy_observer", _uscript_func_editor_destroy_observer }, { "editor_display", _uscript_func_editor_display }, { "editor_force_redraw", _uscript_func_editor_force_redraw }, { "editor_get_input", _uscript_func_editor_get_input }, { "editor_menu", _uscript_func_editor_menu }, { "editor_notify_observers", _uscript_func_editor_notify_observers }, { "editor_open_bview", _uscript_func_editor_open_bview }, { "editor_prompt", _uscript_func_editor_prompt }, { "editor_register_cmd", _uscript_func_editor_register_cmd }, { "editor_register_observer", _uscript_func_editor_register_observer }, { "editor_set_active", _uscript_func_editor_set_active }, { "mark_clone", _uscript_func_mark_clone }, { "mark_clone_w_letter", _uscript_func_mark_clone_w_letter }, { "mark_delete_after", _uscript_func_mark_delete_after }, { "mark_delete_before", _uscript_func_mark_delete_before }, { "mark_delete_between", _uscript_func_mark_delete_between }, { "mark_destroy", _uscript_func_mark_destroy }, { "mark_find_bracket_pair", _uscript_func_mark_find_bracket_pair }, { "mark_find_bracket_top", _uscript_func_mark_find_bracket_top }, { "mark_find_next_re", _uscript_func_mark_find_next_re }, { "mark_find_next_str", _uscript_func_mark_find_next_str }, { "mark_find_prev_re", _uscript_func_mark_find_prev_re }, { "mark_find_prev_str", _uscript_func_mark_find_prev_str }, { "mark_get_between", _uscript_func_mark_get_between }, { "mark_get_char_after", _uscript_func_mark_get_char_after }, { "mark_get_char_before", _uscript_func_mark_get_char_before }, { "mark_get_nchars_between", _uscript_func_mark_get_nchars_between }, { "mark_get_offset", _uscript_func_mark_get_offset }, { "mark_insert_after", _uscript_func_mark_insert_after }, { "mark_insert_before", _uscript_func_mark_insert_before }, { "mark_is_after_col_minus_lefties", _uscript_func_mark_is_after_col_minus_lefties }, { "mark_is_at_bol", _uscript_func_mark_is_at_bol }, { "mark_is_at_eol", _uscript_func_mark_is_at_eol }, { "mark_is_at_word_bound", _uscript_func_mark_is_at_word_bound }, { "mark_is_between", _uscript_func_mark_is_between }, { "mark_is_eq", _uscript_func_mark_is_eq }, { "mark_is_gt", _uscript_func_mark_is_gt }, { "mark_is_gte", _uscript_func_mark_is_gte }, { "mark_is_lt", _uscript_func_mark_is_lt }, { "mark_is_lte", _uscript_func_mark_is_lte }, { "mark_join", _uscript_func_mark_join }, { "mark_move_beginning", _uscript_func_mark_move_beginning }, { "mark_move_bol", _uscript_func_mark_move_bol }, { "mark_move_bracket_pair", _uscript_func_mark_move_bracket_pair }, { "mark_move_bracket_pair_ex", _uscript_func_mark_move_bracket_pair_ex }, { "mark_move_bracket_top", _uscript_func_mark_move_bracket_top }, { "mark_move_bracket_top_ex", _uscript_func_mark_move_bracket_top_ex }, { "mark_move_by", _uscript_func_mark_move_by }, { "mark_move_col", _uscript_func_mark_move_col }, { "mark_move_end", _uscript_func_mark_move_end }, { "mark_move_eol", _uscript_func_mark_move_eol }, { "mark_move_next_re", _uscript_func_mark_move_next_re }, { "mark_move_next_re_ex", _uscript_func_mark_move_next_re_ex }, { "mark_move_next_re_nudge", _uscript_func_mark_move_next_re_nudge }, { "mark_move_next_str", _uscript_func_mark_move_next_str }, { "mark_move_next_str_ex", _uscript_func_mark_move_next_str_ex }, { "mark_move_next_str_nudge", _uscript_func_mark_move_next_str_nudge }, { "mark_move_offset", _uscript_func_mark_move_offset }, { "mark_move_prev_re", _uscript_func_mark_move_prev_re }, { "mark_move_prev_re_ex", _uscript_func_mark_move_prev_re_ex }, { "mark_move_prev_str", _uscript_func_mark_move_prev_str }, { "mark_move_prev_str_ex", _uscript_func_mark_move_prev_str_ex }, { "mark_move_to", _uscript_func_mark_move_to }, { "mark_move_to_w_bline", _uscript_func_mark_move_to_w_bline }, { "mark_move_vert", _uscript_func_mark_move_vert }, { "mark_replace", _uscript_func_mark_replace }, { "mark_replace_between", _uscript_func_mark_replace_between }, { "mark_swap", _uscript_func_mark_swap }, { "util_escape_shell_arg", _uscript_func_util_escape_shell_arg }, { "util_shell_exec", _uscript_func_util_shell_exec }, { NULL, NULL } }; mle-1.7.2/uscript.inc.php000066400000000000000000000225741443351614700152720ustar00rootroot00000000000000getProtoMap(); $hardcoded = $this->getHardcodedProtoMap(); $protos = array_merge($protos, $hardcoded); usort($protos, function ($a, $b) { return strcmp($a->name, $b->name); }); $this->printFuncs($protos); $this->printLibTable($protos); } function getProtoMap() { $mlbuf_h = __DIR__ . '/mlbuf.h'; $mle_h = __DIR__ . '/mle.h'; $grep_re = '^\S+ \*?(editor|bview|buffer|cursor|mark)_.*\);$'; $grep_cmd = sprintf( 'grep -hP %s %s %s', escapeshellarg($grep_re), escapeshellarg($mlbuf_h), escapeshellarg($mle_h) ); $proto_strs = explode("\n", shell_exec($grep_cmd)); $proto_strs = array_filter($proto_strs, function($proto_str) { if (strlen(trim($proto_str)) <= 0) { return false; } if (preg_match($this->blacklist_re, $proto_str)) { return false; } return true; }); $protos = array_map(function($proto_str) { return new Proto($proto_str); }, $proto_strs); return array_combine( array_column($protos, 'name'), $protos ); } function getHardcodedProtoMap() { $uscript_c = __DIR__ . '/uscript.c'; $grep_re = '^// foreign static (?\S+) _uscript_func_(?[^\(]+)\((?[^\)]*)\)$'; $grep_cmd = sprintf( 'grep -hP %s %s', escapeshellarg($grep_re), escapeshellarg($uscript_c) ); $hardcoded_strs = explode("\n", shell_exec($grep_cmd)); $hardcoded_strs = array_filter($hardcoded_strs); $protos = array_map(function($hardcoded_str) use ($grep_re) { $m = null; if (!preg_match("@{$grep_re}@", $hardcoded_str, $m)) { throw new RuntimeException("Failed to parse hardcoded proto: $hardcoded_str"); } $params = preg_split('@\s*,\s*@', $m['params']); $params = array_map(function($param) { return sprintf("void *%s", $param); }, $params); $params_str = implode(', ', $params); $proto_str = sprintf("void %s(%s);", $m['name'], $params_str); $proto = new Proto($proto_str); $proto->is_hardcoded = true; return $proto; }, $hardcoded_strs); return array_combine( array_column($protos, 'name'), $protos ); } function printLibTable($protos) { echo 'static const struct luaL_Reg mle_lib[] = {' . "\n"; foreach ($protos as $proto) { printf(' { "%s", %s },' . "\n", $proto->name, $proto->c_func); } echo " { NULL, NULL }\n"; echo "};\n\n"; } function printFuncs($protos) { foreach ($protos as $proto) { $this->printFunc($proto); } } function printFunc($proto) { $is_hardcoded = $proto->is_hardcoded; printf( "%sstatic int %s(lua_State *L) {\n", $is_hardcoded ? '// ' : '', $proto->c_func ); if (!$is_hardcoded) { printf(" %s%srv;\n", $proto->ret_type, $proto->ret_is_pointer ? '' : ' '); foreach ($proto->params as $param) { if ($param->is_ret) { printf(" %s%s%s = %s;\n", $param->ret_type, $param->ret_is_pointer ? '' : ' ', $param->name, $param->ret_zero_val); } else { printf(" %s%s%s;\n", $param->type, $param->is_pointer ? '' : ' ', $param->name); } } $param_num = 1; foreach ($proto->params as $param) { if ($param->is_ret) continue; $this->fromLua($param, $param_num++); } $call_names = array_map(function($param) { return $param->call_name; }, $proto->params); printf(" rv = %s(%s);\n", $proto->name, implode(', ', $call_names)); printf(" lua_createtable(L, 0, %d);\n", $proto->ret_count); $this->toLua('rv', $proto->ret_type); foreach ($proto->params as $param) { if (!$param->is_ret) continue; $this->toLua($param->name, $param->ret_type); } printf(" lua_pushvalue(L, -1);\n"); printf(" return 1;\n"); } printf("%s}\n\n", $is_hardcoded ? '// ' : ''); } function toLua($name, $type) { printf(' lua_pushstring(L, "%s");' . "\n", $name); if (strpos($type, 'int') !== false || $type === 'char') { printf(" lua_pushinteger(L, (lua_Integer)%s);\n", $name); } else if ($type === 'char *' || $type === 'const char *') { printf(" lua_pushstring(L, (const char*)%s);\n", $name); } else if (preg_match($this->valid_pointer_re, $type)) { printf(" lua_pushpointer(L, (void*)%s);\n", $name); } else { throw new RuntimeException("Unhandled type: $type"); } printf(" lua_settable(L, -3);\n"); } function fromLua($param, $slot) { $type = $param->type; $name = $param->name; if (strpos($type, 'int') !== false || $type === 'char' || $type === 'size_t') { if ($param->is_opt) { printf(" %s = (%s)luaL_optinteger(L, %d, 0);\n", $name, $type, $slot); } else { printf(" %s = (%s)luaL_checkinteger(L, %d);\n", $name, $type, $slot); } } else if ($type === 'float' || $type === 'double') { if ($param->is_opt) { printf(" %s = (%s)luaL_optnumber(L, %d, 0);\n", $name, $type, $slot); } else { printf(" %s = (%s)luaL_checknumber(L, %d);\n", $name, $type, $slot); } } else if ($type === 'char *' || $type === 'const char *') { if ($param->is_opt) { printf(" %s = (%s)luaL_optstring(L, %d, NULL);\n", $name, $type, $slot); } else { printf(" %s = (%s)luaL_checkstring(L, %d);\n", $name, $type, $slot); } } else if (preg_match($this->valid_pointer_re, $type)) { if ($param->is_opt) { printf(" %s = (%s)luaL_optpointer(L, %d, NULL);\n", $name, $type, $slot); } else { printf(" %s = (%s)luaL_checkpointer(L, %d);\n", $name, $type, $slot); } } else { throw new RuntimeException("Unhandled type: $type"); } } } class Proto { function __construct($proto_str) { $parse_re = '@^(?\S+) (?\*?)(?[^\(]+)\((?.*?)\);$@'; $match = []; if (!preg_match($parse_re, $proto_str, $match)) { throw new RuntimeException("Could not parse proto: $proto_str"); } $this->name = $match['name']; $this->c_func = '_uscript_func_' . $this->name; $this->ret_type = trim($match['ret_type'] . ' ' . $match['ret_type_ptr']); $this->ret_is_pointer = substr($this->ret_type, -1) === '*'; $this->params = []; $this->ret_count = 1; $this->has_funcs = false; $this->is_hardcoded = false; if (empty($match['params'])) { return; } $param_strs = preg_split('@\s*,\s*@', $match['params']); foreach ($param_strs as $param_str) { $param = new Param($param_str); $this->params[] = $param; if ($param->is_ret) { $this->ret_count += 1; } if ($param->is_func) { $this->has_funcs = true; } } } } class Param { function __construct($param_str) { $parts = preg_split('@\s+@', $param_str); $name_w_ptr = current(array_slice($parts, -1)); $type_wout_ptr = implode(' ', array_slice($parts, 0, -1)); $ptr_i = 0; while (substr($name_w_ptr, $ptr_i, 1) === '*') $ptr_i += 1; $ptr = substr($name_w_ptr, 0, $ptr_i); $this->type = trim($type_wout_ptr . ' ' . $ptr); $this->name = substr($name_w_ptr, $ptr_i); $this->is_func = preg_match('@^fn_@', $this->name); $this->is_opt = preg_match('@^opt(ret)?_@', $this->name); $this->is_ret = preg_match('@^(opt)?ret_@', $this->name); $this->is_optret = preg_match('@^optret_@', $this->name); $this->is_pointer = strpos($this->type, '*') !== false; $this->is_string = $this->type === 'char *'; if ($this->is_ret) { if (!$this->is_pointer) { throw new RuntimeException("Expected ret param ptr: $param_str"); } $this->ret_type = trim(substr($this->type, 0, -1)); $this->ret_is_pointer = strpos($this->ret_type, '*') !== false; $this->ret_zero_val = $this->ret_is_pointer ? 'NULL' : '0'; } else { $this->ret_type = null; $this->ret_is_pointer = false; $this->ret_zero_val = '0'; } $this->call_name = ($this->is_ret ? '&' : '') . $this->name; } } (new CodeGen())->run(); mle-1.7.2/uscript.lua000066400000000000000000000014211443351614700145000ustar00rootroot00000000000000-- mle -N -x uscript.lua -K lua_kmap,,1 -k cmd_lua_test,f11, -n lua_kmap mle.editor_register_observer("buffer:save", function (bview) r = mle.util_shell_exec("ls", -1) print("ls " .. r["output"]) end) mle.editor_register_cmd("cmd_lua_test", function (ctx) name = mle.editor_prompt("Enter your name") if name then print("hello <" .. name .. "> from lua") else print("you hit cancel") end end) mle.editor_register_observer("cmd:cmd_copy:before", function (ctx) local rv = mle.cursor_get_anchor(ctx["cursor"]) local anchor = rv["ret_anchor"] rv = mle.mark_get_between(ctx["mark"], anchor) rv = mle.util_escape_shell_arg(rv["ret_str"]) mle.util_shell_exec("echo -n " .. rv["output"] .. " | xclip -sel c >/dev/null", 1) end) mle-1.7.2/utf8.c000066400000000000000000000044101443351614700133370ustar00rootroot00000000000000// Adapted from https://github.com/termbox/termbox/blob/a0e450500b3f07ddd172ac64e48a59129a8878fb/src/utf8.c #include #include "mlbuf.h" static const unsigned char utf8_length[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,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, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 }; static const unsigned char utf8_mask[6] = { 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; int utf8_char_length(char c) { return utf8_length[(unsigned char)c]; } int utf8_char_to_unicode(uint32_t *out, const char *c, const char *stop) { if (*c == 0) return -1; int i; unsigned char len = utf8_char_length(*c); unsigned char mask = utf8_mask[len-1]; uint32_t result = c[0] & mask; for (i = 1; i < len; ++i) { if (stop && c + i >= stop) { len -= (len - i); break; } result <<= 6; result |= c[i] & 0x3f; } *out = result; return (int)len; } int utf8_unicode_to_char(char *out, uint32_t c) { int len = 0; int first; int i; if (c < 0x80) { first = 0; len = 1; } else if (c < 0x800) { first = 0xc0; len = 2; } else if (c < 0x10000) { first = 0xe0; len = 3; } else if (c < 0x200000) { first = 0xf0; len = 4; } else if (c < 0x4000000) { first = 0xf8; len = 5; } else { first = 0xfc; len = 6; } for (i = len - 1; i > 0; --i) { out[i] = (c & 0x3f) | 0x80; c >>= 6; } out[0] = c | first; return len; } size_t utf8_str_length(char *data, size_t len) { size_t slen; int clen; char *data_stop, *c; data_stop = data + len; c = data; slen = 0; while (c < data_stop) { clen = utf8_char_length(*c); c += clen; slen += 1; } return slen; } mle-1.7.2/util.c000066400000000000000000000553071443351614700134410ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include "mle.h" // Run a shell command, optionally feeding stdin, collecting stdout // Specify timeout_s=-1 for no timeout int util_shell_exec(editor_t *editor, char *cmd, long timeout_s, char *input, size_t input_len, int setsid, char *opt_shell, char **optret_output, size_t *optret_output_len, int *optret_exit_code) { // TODO clean this crap up int rv; int do_read; int do_write; int readfd; int writefd; ssize_t rc; ssize_t nbytes; fd_set readfds; struct timeval timeout; struct timeval *timeoutptr; pid_t pid; int exit_status; str_t readbuf = {0}; do_read = optret_output != NULL ? 1 : 0; do_write = input && input_len > 0 ? 1 : 0; readfd = -1; writefd = -1; pid = -1; readbuf.inc = -2; // double capacity on each allocation rv = MLE_OK; nbytes = 0; if (do_read) { *optret_output = NULL; *optret_output_len = 0; } // Open cmd if (!util_popen2( cmd, setsid, opt_shell, do_read ? &readfd : NULL, do_write ? &writefd : NULL, &pid )) { MLE_RETURN_ERR(editor, "Failed to exec shell cmd: %s", cmd); } // Read-write loop do { // Write to shell cmd if input is remaining if (do_write && writefd >= 0) { rc = write(writefd, input, input_len); if (rc > 0) { input += rc; input_len -= rc; if (input_len < 1) { close(writefd); writefd = -1; } } else { // write err MLE_SET_ERR(editor, "write error: %s", strerror(errno)); rv = MLE_ERR; break; } } // Read shell cmd, timing out after timeout_sec if (do_read) { if (timeout_s >= 0) { timeout.tv_sec = timeout_s; timeout.tv_usec = 0; timeoutptr = &timeout; } else { timeoutptr = NULL; } FD_ZERO(&readfds); FD_SET(readfd, &readfds); rc = select(readfd + 1, &readfds, NULL, NULL, timeoutptr); if (rc < 0) { // Err on select MLE_SET_ERR(editor, "select error: %s", strerror(errno)); rv = MLE_ERR; break; } else if (rc == 0) { // Timed out rv = MLE_ERR; break; } else { // Read a kilobyte of data str_ensure_cap(&readbuf, readbuf.len + 1024); nbytes = read(readfd, readbuf.data + readbuf.len, 1024); if (nbytes < 0) { // read err or EAGAIN/EWOULDBLOCK MLE_SET_ERR(editor, "read error: %s", strerror(errno)); rv = MLE_ERR; break; } else if (nbytes > 0) { // Got data readbuf.len += nbytes; } } } } while(nbytes > 0); // until EOF // Close pipes and reap child proc if (readfd >= 0) close(readfd); if (writefd >= 0) close(writefd); exit_status = -1; waitpid(pid, &exit_status, do_read ? WNOHANG : 0); if (optret_exit_code) *optret_exit_code = WEXITSTATUS(exit_status); if (do_read) { *optret_output = readbuf.data; *optret_output_len = readbuf.len; } // Force redraw to correct artifacts from child process writing to stderr. // Piping stderr to `/dev/null` in the child process would be cleaner, // however that breaks interactive apps like less(1) which require stdio // ttys to behave properly. if (!_editor.headless_mode) editor_force_redraw(&_editor); return rv; } // Like popen, but more control over pipes. Returns 1 on success, 0 on failure. int util_popen2(char *cmd, int do_setsid, char *opt_shell, int *optret_fdread, int *optret_fdwrite, pid_t *optret_pid) { pid_t pid; int do_read; int do_write; int pout[2]; int pin[2]; // Set r/w do_read = optret_fdread != NULL ? 1 : 0; do_write = optret_fdwrite != NULL ? 1 : 0; // Set shell opt_shell = opt_shell ? opt_shell : "sh"; // Make pipes if (do_read) if (pipe(pout)) return 0; if (do_write) if (pipe(pin)) return 0; // Fork pid = fork(); if (pid < 0) { // Fork failed return 0; } else if (pid == 0) { // Child if (do_read) { close(pout[0]); dup2(pout[1], STDOUT_FILENO); close(pout[1]); } if (do_write) { close(pin[1]); dup2(pin[0], STDIN_FILENO); close(pin[0]); } if (do_setsid) setsid(); execlp(opt_shell, opt_shell, "-c", cmd, NULL); exit(EXIT_FAILURE); } // Parent if (do_read) { close(pout[1]); *optret_fdread = pout[0]; } if (do_write) { close(pin[0]); *optret_fdwrite = pin[1]; } if (optret_pid) *optret_pid = pid; return 1; } // Return paired bracket if ch is a bracket, else return 0 int util_get_bracket_pair(uint32_t ch, int *optret_is_closing) { switch (ch) { case '[': if (optret_is_closing) *optret_is_closing = 0; return ']'; case '(': if (optret_is_closing) *optret_is_closing = 0; return ')'; case '{': if (optret_is_closing) *optret_is_closing = 0; return '}'; case ']': if (optret_is_closing) *optret_is_closing = 1; return '['; case ')': if (optret_is_closing) *optret_is_closing = 1; return '('; case '}': if (optret_is_closing) *optret_is_closing = 1; return '{'; default: return 0; } return 0; } // Return 1 if path is file int util_is_file(char *path, char *opt_mode, FILE **optret_file) { struct stat sb; if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) return 0; if (opt_mode && optret_file) { *optret_file = fopen(path, opt_mode); if (!*optret_file) return 0; } return 1; } // Return 1 if path is dir int util_is_dir(char *path) { struct stat sb; if (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode)) return 0; return 1; } // Return 1 if re matches subject int util_pcre_match(char *re, char *subject, int subject_len, char **optret_capture, int *optret_capture_len) { int rc; pcre2_code *cre; int errcode; PCRE2_SIZE erroffset; PCRE2_SIZE ovector[3]; cre = pcre2_compile((PCRE2_SPTR)re, (PCRE2_SIZE)strlen(re), (optret_capture ? 0 : PCRE2_NO_AUTO_CAPTURE) | PCRE2_CASELESS, &errcode, &erroffset, NULL); if (!cre) return 0; rc = pcre2_match(cre, (PCRE2_SPTR)subject, (PCRE2_SIZE)subject_len, 0, 0, pcre2_md, NULL); memcpy(ovector, pcre2_get_ovector_pointer(pcre2_md), 3 * sizeof(PCRE2_SIZE)); pcre2_code_free(cre); if (optret_capture) { if (rc >= 0) { *optret_capture = subject + ovector[0]; *optret_capture_len = ovector[1] - ovector[0]; } else { *optret_capture = NULL; *optret_capture_len = 0; } } return rc >= 0 ? 1 : 0; } // Perform a regex replace with back-references. Return number of replacements // made. If regex is invalid, `ret_result` is set to NULL, `ret_result_len` is // set to 0 and 0 is returned. int util_pcre_replace(char *re, char *subj, char *repl, char **ret_result, int *ret_result_len) { int rc; pcre2_code *cre; int errcode; PCRE2_SIZE erroffset; int subj_offset; int subj_offset_z; int subj_len; int subj_look_offset; int last_look_offset; PCRE2_SIZE ovector[30]; int num_repls; int got_match = 0; str_t result = {0}; *ret_result = NULL; *ret_result_len = 0; // Compile regex cre = pcre2_compile((PCRE2_SPTR)re, (PCRE2_SIZE)strlen(re), PCRE2_CASELESS, &errcode, &erroffset, NULL); if (!cre) return 0; // Start match-replace loop num_repls = 0; subj_len = strlen(subj); subj_offset = 0; subj_offset_z = 0; subj_look_offset = 0; last_look_offset = 0; while (subj_offset < subj_len) { // Find match rc = pcre2_match(cre, (PCRE2_SPTR)subj, (PCRE2_SIZE)subj_len, (PCRE2_SIZE)subj_look_offset, 0, pcre2_md, NULL); memcpy(ovector, pcre2_get_ovector_pointer(pcre2_md), 30 * sizeof(PCRE2_SIZE)); if (rc < 0 || ovector[0] == PCRE2_UNSET) { got_match = 0; subj_offset_z = subj_len; } else { got_match = 1; subj_offset_z = ovector[0]; } // Append part before match str_append_stop(&result, subj + subj_offset, subj + subj_offset_z); subj_offset = ovector[1]; subj_look_offset = subj_offset + (subj_offset > last_look_offset ? 0 : 1); // Prevent infinite loop last_look_offset = subj_look_offset; // Break if no match if (!got_match) break; // Append replacements with backrefs str_append_replace_with_backrefs(&result, subj, repl, rc, ovector, 30); // Increment num_repls num_repls += 1; } // Free regex pcre2_code_free(cre); // Return result *ret_result = result.data ? result.data : strdup(""); *ret_result_len = result.len; // Return number of replacements return num_repls; } // Return 1 if a > b, else return 0. int util_timeval_is_gt(struct timeval *a, struct timeval *b) { if (a->tv_sec > b->tv_sec) { return 1; } else if (a->tv_sec == b->tv_sec) { return a->tv_usec > b->tv_usec ? 1 : 0; } return 0; } // Ported from php_escape_shell_arg // https://github.com/php/php-src/blob/master/ext/standard/exec.c char *util_escape_shell_arg(char *str, int l) { int x, y = 0; char *cmd; cmd = malloc(4 * l + 3); // worst case cmd[y++] = '\''; for (x = 0; x < l; x++) { int mb_len = tb_utf8_char_length(*(str + x)); // skip non-valid multibyte characters if (mb_len < 0) { continue; } else if (mb_len > 1) { memcpy(cmd + y, str + x, mb_len); y += mb_len; x += mb_len - 1; continue; } switch (str[x]) { case '\'': cmd[y++] = '\''; cmd[y++] = '\\'; cmd[y++] = '\''; // fall-through default: cmd[y++] = str[x]; } } cmd[y++] = '\''; cmd[y] = '\0'; return cmd; } // Attempt to replace leading ~/ with $HOME void util_expand_tilde(char *path, int path_len, char **ret_path, int *optret_path_len) { char *homedir; char *newpath; if (!util_is_file("~", NULL, NULL) && strncmp(path, "~/", 2) == 0 && (homedir = getenv("HOME")) != NULL ) { newpath = malloc(strlen(homedir) + 1 + (path_len - 2) + 1); sprintf(newpath, "%s/%.*s", homedir, path_len-2, path+2); *ret_path = newpath; if (optret_path_len) *optret_path_len = strlen(*ret_path); return; } *ret_path = strndup(path, path_len); if (optret_path_len) *optret_path_len = strlen(*ret_path); } // Adapted from termbox src/demo/keyboard.c int tb_printf_rect(bview_rect_t rect, int x, int y, uint16_t fg, uint16_t bg, const char *fmt, ...) { char buf[4096]; va_list vl; va_start(vl, fmt); vsnprintf(buf, sizeof(buf), fmt, vl); va_end(vl); return tb_print(rect.x + x, rect.y + y, fg ? fg : rect.fg, bg ? bg : rect.bg, buf); } // Like tb_printf, but accepts @fg,bg; attributes inside the string. To print // a literal '@', use '@@' in the format string. Specify fg or bg of 0 to // reset that attribute. int tb_printf_attr(bview_rect_t rect, int x, int y, const char *fmt, ...) { char bufo[4096]; char *buf; int fg; int bg; int tfg; int tbg; int c; uint32_t uni; va_list vl; va_start(vl, fmt); vsnprintf(bufo, sizeof(bufo), fmt, vl); va_end(vl); fg = rect.fg; bg = rect.bg; x = rect.x + x; y = rect.y + y; c = 0; buf = bufo; while (*buf) { buf += utf8_char_to_unicode(&uni, buf, NULL); if (uni == '@') { if (!*buf) break; utf8_char_to_unicode(&uni, buf, NULL); if (uni != '@') { tfg = strtol(buf, &buf, 10); if (!*buf) break; utf8_char_to_unicode(&uni, buf, NULL); if (uni == ',') { buf++; if (!*buf) break; tbg = strtol(buf, &buf, 10); fg = tfg <= 0 ? rect.fg : tfg; bg = tbg <= 0 ? rect.bg : tbg; if (!*buf) break; utf8_char_to_unicode(&uni, buf, NULL); if (uni == ';') buf++; continue; } } } tb_change_cell(x, y, uni, fg, bg); x++; c++; } return c; } // Zero-fill realloc void *recalloc(void *ptr, size_t orig_num, size_t new_num, size_t el_size) { void *newptr; newptr = realloc(ptr, new_num * el_size); if (!newptr) return NULL; if (new_num > orig_num) { memset(newptr + (orig_num * el_size), 0, (new_num - orig_num) * el_size); } return newptr; } // Append from data up until data_stop to str void str_append_stop(str_t *str, char *data, char *data_stop) { size_t data_len; data_len = data_stop >= data ? data_stop - data : 0; str_append_len(str, data, data_len); } // Append data to str void str_append(str_t *str, char *data) { str_append_len(str, data, strlen(data)); } // Append data_len bytes of data to str void str_append_len(str_t *str, char *data, size_t data_len) { str_put_len(str, data, data_len, 0); } // Append char to str void str_append_char(str_t *str, char c) { str_put_len(str, &c, 1, 0); } // Prepend from data up until data_stop to str void str_prepend_stop(str_t *str, char *data, char *data_stop) { size_t data_len; data_len = data_stop >= data ? data_stop - data : 0; str_prepend_len(str, data, data_len); } // Prepend data to str void str_prepend(str_t *str, char *data) { str_prepend_len(str, data, strlen(data)); } // Prepend data_len bytes of data to str void str_prepend_len(str_t *str, char *data, size_t data_len) { str_put_len(str, data, data_len, 1); } // Set str to data void str_set(str_t *str, char *data) { str_set_len(str, data, strlen(data)); } // Set str to data for data_len bytes void str_set_len(str_t *str, char *data, size_t data_len) { str_ensure_cap(str, data_len+1); memcpy(str->data, data, data_len); str->len = data_len; *(str->data + str->len) = '\0'; } // Sprintf to str void str_sprintf(str_t *str, const char *fmt, ...) { va_list va; int len; va_start(va, fmt); len = vsnprintf(NULL, 0, fmt, va); va_end(va); str_ensure_cap(str, str->len + (size_t)len + 1); va_start(va, fmt); vsprintf(str->data + str->len, fmt, va); va_end(va); str->len += (size_t)len; } // Append/prepend data_len bytes of data to str void str_put_len(str_t *str, char *data, size_t data_len, int is_prepend) { size_t req_cap; req_cap = str->len + data_len + 1; if (req_cap > str->cap) { str_ensure_cap(str, req_cap); } if (is_prepend) { memmove(str->data + data_len, str->data, str->len); memcpy(str->data, data, data_len); } else { memcpy(str->data + str->len, data, data_len); } str->len += data_len; *(str->data + str->len) = '\0'; } // Ensure space in str void str_ensure_cap(str_t *str, size_t cap) { if (cap > str->cap) { if (str->inc >= 0) { // If inc is positive, grow linearly cap = MLBUF_MAX(cap, str->cap + (str->inc > 0 ? str->inc : 128)); } else { // If inc is negative, grow geometrically cap = MLBUF_MAX(cap, str->cap * (str->inc <= -2 ? str->inc * -1 : 2)); } str->data = realloc(str->data, cap); str->cap = cap; } } // Clear str void str_clear(str_t *str) { str->len = 0; } // Free str void str_free(str_t *str) { if (str->data) free(str->data); memset(str, 0, sizeof(str_t)); } // Replace `repl` in `subj` and append result to `str`. PCRE style backrefs are // supported. // // str where to append data // subj subject string // repl replacement string with $1 or \1 style backrefs // pcre_rc return code from pcre_exec // pcre_ovector ovector used with pcre_exec // pcre_ovecsize size of pcre_ovector // void str_append_replace_with_backrefs(str_t *str, char *subj, char *repl, int pcre_rc, PCRE2_SIZE *pcre_ovector, int pcre_ovecsize) { char *repl_stop; char *repl_cur; char *repl_z; char *repl_backref; int repl_delta; int ibackref; char *term; char *term_stop; char hex[3]; char byte; repl_stop = repl + strlen(repl); // Start replace loop repl_cur = repl; while (repl_cur < repl_stop) { // Find backref marker (dollar sign or backslash) in replacement str repl_backref = strpbrk(repl_cur, "$\\"); repl_z = repl_backref ? repl_backref : repl_stop; // Append part before backref str_append_stop(str, repl_cur, repl_z); // Break if no backref if (!repl_backref) break; // Append backref term = NULL; repl_delta = 2; // marker + backref symbol if (repl_backref+1 >= repl_stop) { // No data after backref marker; append the marker itself term = repl_backref; term_stop = repl_stop; } else if (*(repl_backref+1) >= '0' && *(repl_backref+1) <= '9') { // $N; append Nth captured substring from match ibackref = *(repl_backref+1) - '0'; if (ibackref < pcre_rc && ibackref < pcre_ovecsize/3) { // Backref exists term = subj + pcre_ovector[ibackref*2]; term_stop = subj + pcre_ovector[ibackref*2 + 1]; } else { // Backref does not exist; append marker + whatever character it was term = repl_backref; term_stop = term + utf8_char_length(*(term+1)); } } else if (*(repl_backref+1) == 'n') { // $n; append newline term = "\n"; term_stop = term + 1; } else if (*(repl_backref+1) == 't') { // $t; append tab term = "\t"; term_stop = term + 1; } else if (*(repl_backref+1) == 'x' && repl_backref+3 < repl_stop) { // $xNN; append byte strncpy(hex, repl_backref+2, 2); hex[2] = '\0'; byte = strtoul(hex, NULL, 16); term = &byte; term_stop = term + 1; repl_delta = 4; // marker + 'x' + d1 + d2 } else { // $* (not number or 'n'); append marker + whatever character it was term = repl_backref; term_stop = term + utf8_char_length(*(term+1)); } str_append_stop(str, term, term_stop); // Advance repl_cur by repl_delta bytes repl_cur = repl_backref + repl_delta; } } // Return a new aproc_t aproc_t *aproc_new(editor_t *editor, void *owner, aproc_t **owner_aproc, char *shell_cmd, int rw, aproc_cb_t callback) { aproc_t *aproc; aproc = calloc(1, sizeof(aproc_t)); aproc->editor = editor; aproc_set_owner(aproc, owner, owner_aproc); if (rw) { if (!util_popen2(shell_cmd, 0, NULL, &aproc->rfd, &aproc->wfd, &aproc->pid)) { goto aproc_new_failure; } aproc->rpipe = fdopen(aproc->rfd, "r"); aproc->wpipe = fdopen(aproc->wfd, "w"); } else { if (!(aproc->rpipe = popen(shell_cmd, "r"))) { goto aproc_new_failure; } aproc->rfd = fileno(aproc->rpipe); } setvbuf(aproc->rpipe, NULL, _IONBF, 0); if (aproc->wpipe) setvbuf(aproc->wpipe, NULL, _IONBF, 0); aproc->callback = callback; DL_APPEND(editor->aprocs, aproc); return aproc; aproc_new_failure: free(aproc); return NULL; } // Set aproc owner int aproc_set_owner(aproc_t *aproc, void *owner, aproc_t **owner_aproc) { if (aproc->owner_aproc) { *aproc->owner_aproc = NULL; } *owner_aproc = aproc; aproc->owner = owner; aproc->owner_aproc = owner_aproc; return MLE_OK; } // Destroy an aproc_t int aproc_destroy(aproc_t *aproc, int preempt) { DL_DELETE(aproc->editor->aprocs, aproc); if (aproc->owner_aproc) *aproc->owner_aproc = NULL; if (preempt) { if (aproc->rfd) close(aproc->rfd); if (aproc->wfd) close(aproc->wfd); if (aproc->pid) kill(aproc->pid, SIGTERM); } if (aproc->rpipe) pclose(aproc->rpipe); if (aproc->wpipe) pclose(aproc->wpipe); free(aproc); return MLE_OK; } // Manage async procs, giving priority to user input. Return 1 if drain should // be called again, else return 0. int aproc_drain_all(aproc_t *aprocs, int *ttyfd) { int maxfd; fd_set readfds; aproc_t *aproc; aproc_t *aproc_tmp; char buf[1024 + 1]; ssize_t nbytes; int rc; // Exit early if no aprocs if (!aprocs) return 0; // Open ttyfd if not already open if (!*ttyfd) { if ((*ttyfd = open("/dev/tty", O_RDONLY)) < 0) { // TODO error return 0; } } // Add tty to readfds FD_ZERO(&readfds); FD_SET(*ttyfd, &readfds); // Add async procs to readfds // Simultaneously check for solo, which takes precedence over everything maxfd = *ttyfd; DL_FOREACH(aprocs, aproc) { FD_SET(aproc->rfd, &readfds); if (aproc->rfd > maxfd) maxfd = aproc->rfd; } // Perform select rc = select(maxfd + 1, &readfds, NULL, NULL, NULL); if (rc < 0) { return 0; // TODO error } else if (rc == 0) { return 1; // Nothing to read, call again } if (FD_ISSET(*ttyfd, &readfds)) { // Immediately give priority to user input return 0; } else { // Read async procs DL_FOREACH_SAFE(aprocs, aproc, aproc_tmp) { // Read and invoke callback if (FD_ISSET(aproc->rfd, &readfds)) { nbytes = read(aproc->rfd, &buf, 1024); buf[nbytes] = '\0'; aproc->callback(aproc, buf, nbytes); if (nbytes == 0) aproc->is_done = 1; } // Destroy on eof. // Not sure if ferror and feof have any effect here given we're not // using fread. if (ferror(aproc->rpipe) || feof(aproc->rpipe) || aproc->is_done) { aproc_destroy(aproc, 0); } } } return 1; } mle-1.7.2/vendor/000077500000000000000000000000001443351614700136035ustar00rootroot00000000000000mle-1.7.2/vendor/Makefile000066400000000000000000000011711443351614700152430ustar00rootroot00000000000000lua_cflags:=-std=c99 -Wall -Wextra -pedantic -g -O3 -DLUA_USE_POSIX $(CFLAGS) lua_objects:=$(patsubst lua/%.c,lua/%.o,$(filter-out lua/lua.c lua/onelua.c, $(wildcard lua/*.c))) all: uthash/src/utlist.h pcre2/.libs/libpcre2-8.a lua/liblua5.4.a uthash/src/utlist.h: command -v git && git submodule update --init pcre2/.libs/libpcre2-8.a: cd pcre2 && ./autogen.sh && ./configure && $(MAKE) lua/liblua5.4.a: $(lua_objects) $(AR) rcs $@ $(lua_objects) $(lua_objects): %.o: %.c $(CC) -c $(lua_cflags) $< -o $@ clean: rm -f lua/liblua5.4.a $(lua_objects) rm -f pcre2/src/config.h.in~ -$(MAKE) -C pcre2 clean .PHONY: all clean mle-1.7.2/vendor/lua/000077500000000000000000000000001443351614700143645ustar00rootroot00000000000000mle-1.7.2/vendor/lua5.4000077700000000000000000000000001443351614700152352luaustar00rootroot00000000000000mle-1.7.2/vendor/pcre2/000077500000000000000000000000001443351614700146165ustar00rootroot00000000000000mle-1.7.2/vendor/uthash/000077500000000000000000000000001443351614700150775ustar00rootroot00000000000000