├── .gitignore ├── .gitmodules ├── .travis.yml ├── 01-mlbuf-patch.diff ├── LICENSE ├── Makefile ├── README.md ├── plugins ├── align │ └── plugin.lua ├── comment-lines │ └── plugin.lua ├── eslint │ ├── eslintrc │ └── plugin.lua ├── git-file-changes │ └── plugin.lua ├── htmltidy │ └── plugin.lua ├── test │ ├── plugin.conf │ ├── plugin.lua │ └── upper.lua └── trailing-spaces │ └── plugin.lua └── src ├── async.c ├── bview.c ├── cmd.c ├── colors.h ├── cursor.c ├── editor.c ├── eon.h ├── keys.h ├── libs ├── jsmn.h ├── uthash.h ├── utlist.h └── vector.h ├── main.c ├── plugin_api.c ├── plugins.c └── util.c /.gitignore: -------------------------------------------------------------------------------- 1 | .gdb_history 2 | *.o 3 | LuaJIT-2.0.5 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "mlbuf"] 2 | path = mlbuf 3 | url = https://github.com/adsr/mlbuf 4 | [submodule "termbox"] 5 | path = termbox 6 | url = https://github.com/tomas/termbox 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | script: 3 | - make test 4 | before_install: 5 | - git submodule update --init --recursive 6 | - sudo apt-get install libpcre3-dev 7 | -------------------------------------------------------------------------------- /01-mlbuf-patch.diff: -------------------------------------------------------------------------------- 1 | diff --git a/buffer.c b/buffer.c 2 | index 949a4dc..ec6aeef 100644 3 | --- a/buffer.c 4 | +++ b/buffer.c 5 | @@ -94,8 +94,11 @@ int buffer_open(buffer_t* self, char* path) { 6 | self->is_in_open = 1; 7 | if (st.st_size >= MLBUF_LARGE_FILE_SIZE) { 8 | if (_buffer_open_mmap(self, fd, st.st_size) != MLBUF_OK) { 9 | - rc = MLBUF_ERR; 10 | - break; 11 | + // if mmap fails (perhaps because /tmp doesn't exist) fallback to read 12 | + if (_buffer_open_read(self, fd, st.st_size) != MLBUF_OK) { 13 | + rc = MLBUF_ERR; 14 | + break; 15 | + } 16 | } 17 | } else { 18 | if (_buffer_open_read(self, fd, st.st_size) != MLBUF_OK) { 19 | @@ -268,7 +271,7 @@ int buffer_destroy_mark(buffer_t* self, mark_t* mark) { 20 | && (node->srule->range_a == mark 21 | || node->srule->range_b == mark) 22 | ) { 23 | - buffer_remove_srule(self, node->srule); 24 | + buffer_remove_srule(self, node->srule, 0, 0); 25 | } 26 | } 27 | free(mark); 28 | @@ -356,13 +359,29 @@ int buffer_set_mmapped(buffer_t* self, char* data, bint_t data_len) { 29 | line_num = 0; 30 | data_cursor = data; 31 | data_remaining_len = data_len; 32 | + char str[1]; 33 | while (1) { 34 | - data_newline = data_remaining_len > 0 35 | - ? memchr(data_cursor, '\n', data_remaining_len) 36 | - : NULL; 37 | - line_len = data_newline ? 38 | - (bint_t)(data_newline - data_cursor) 39 | - : data_remaining_len; 40 | + data_newline = data_remaining_len > 0 ? memchr(data_cursor, '\n', data_remaining_len) : NULL; 41 | + if (data_newline) { 42 | + 43 | + line_len = (bint_t)(data_newline - data_cursor); 44 | + 45 | + // if the first char isn't a newline 46 | + if (data_newline != data_cursor) { 47 | + // copy the previous char to see whether it's a \r\n sequence 48 | + strncpy(str, data_newline-1, 1); 49 | + if (strcmp(str, "\r") == 0) { // found! we have a DOS linebreak 50 | + line_len--; 51 | + data_len--; 52 | + data_remaining_len--; 53 | + } 54 | + } 55 | + 56 | + } else { 57 | + line_len = data_remaining_len; 58 | + } 59 | + 60 | + 61 | blines[line_num] = (bline_t){ 62 | .buffer = self, 63 | .data = data_cursor, 64 | @@ -750,10 +769,21 @@ int buffer_get_offset(buffer_t* self, bline_t* bline, bint_t col, bint_t* ret_of 65 | } 66 | 67 | // Add a style rule to the buffer 68 | -int buffer_add_srule(buffer_t* self, srule_t* srule) { 69 | +int buffer_add_srule(buffer_t* self, srule_t* srule, bint_t start_line_index, bint_t num_lines) { 70 | + 71 | srule_node_t* node; 72 | node = calloc(1, sizeof(srule_node_t)); 73 | node->srule = srule; 74 | + 75 | + bline_t * start_line; 76 | + if (start_line_index > 0) 77 | + buffer_get_bline(self, start_line_index, &start_line); 78 | + else 79 | + start_line = self->first_line; 80 | + 81 | + if (num_lines <= 0) 82 | + num_lines = self->line_count - start_line->line_index; 83 | + 84 | if (srule->type == MLBUF_SRULE_TYPE_SINGLE) { 85 | DL_APPEND(self->single_srules, node); 86 | } else { 87 | @@ -763,15 +793,26 @@ int buffer_add_srule(buffer_t* self, srule_t* srule) { 88 | srule->range_a->range_srule = srule; 89 | srule->range_b->range_srule = srule; 90 | } 91 | - return buffer_apply_styles(self, self->first_line, self->line_count - 1); 92 | + 93 | + return buffer_apply_styles(self, start_line, num_lines); 94 | } 95 | 96 | // Remove a style rule from the buffer 97 | -int buffer_remove_srule(buffer_t* self, srule_t* srule) { 98 | +int buffer_remove_srule(buffer_t* self, srule_t* srule, bint_t start_line_index, bint_t num_lines) { 99 | int found; 100 | srule_node_t** head; 101 | srule_node_t* node; 102 | srule_node_t* node_tmp; 103 | + 104 | + bline_t * start_line; 105 | + if (start_line_index > 0) 106 | + buffer_get_bline(self, start_line_index, &start_line); 107 | + else 108 | + start_line = self->first_line; 109 | + 110 | + if (num_lines <= 0) 111 | + num_lines = self->line_count - start_line->line_index; 112 | + 113 | if (srule->type == MLBUF_SRULE_TYPE_SINGLE) { 114 | head = &self->single_srules; 115 | } else { 116 | @@ -790,7 +831,7 @@ int buffer_remove_srule(buffer_t* self, srule_t* srule) { 117 | break; 118 | } 119 | if (!found) return MLBUF_ERR; 120 | - return buffer_apply_styles(self, self->first_line, self->line_count - 1); 121 | + return buffer_apply_styles(self, start_line, num_lines); 122 | } 123 | 124 | // Set callback to cb. Pass in NULL to unset callback. 125 | @@ -982,6 +1023,9 @@ int buffer_apply_styles(buffer_t* self, bline_t* start_line, bint_t line_delta) 126 | return MLBUF_OK; 127 | } 128 | 129 | + // TODO: optimize when line delta is too high 130 | + // if (line_delta > 10000) printf(" ----- line delta: %ld\n", line_delta); 131 | + 132 | // min_nlines, minimum number of lines to style 133 | // line_delta < 0: 2 (start_line + 1) 134 | // line_delta == 0: 1 (start_line) 135 | @@ -1426,6 +1470,7 @@ static bline_t* _buffer_bline_new(buffer_t* self) { 136 | bline_t* bline; 137 | bline = calloc(1, sizeof(bline_t)); 138 | bline->buffer = self; 139 | + bline->bg = 0; 140 | return bline; 141 | } 142 | 143 | diff --git a/mlbuf.h b/mlbuf.h 144 | index e05b7d9..38a419b 100644 145 | --- a/mlbuf.h 146 | +++ b/mlbuf.h 147 | @@ -82,6 +82,7 @@ struct bline_s { 148 | int is_chars_dirty; 149 | int is_slabbed; 150 | int is_data_slabbed; 151 | + int bg; 152 | bline_t* next; 153 | bline_t* prev; 154 | }; 155 | @@ -182,8 +183,8 @@ int buffer_get_bline_col(buffer_t* self, bint_t offset, bline_t** ret_bline, bin 156 | int buffer_get_offset(buffer_t* self, bline_t* bline, bint_t col, bint_t* ret_offset); 157 | int buffer_undo(buffer_t* self); 158 | int buffer_redo(buffer_t* self); 159 | -int buffer_add_srule(buffer_t* self, srule_t* srule); 160 | -int buffer_remove_srule(buffer_t* self, srule_t* srule); 161 | +int buffer_add_srule(buffer_t* self, srule_t* srule, bint_t start_line_index, bint_t num_lines); 162 | +int buffer_remove_srule(buffer_t* self, srule_t* srule, bint_t start_line_index, bint_t num_lines); 163 | int buffer_set_callback(buffer_t* self, buffer_callback_t fn_cb, void* udata); 164 | int buffer_set_tab_width(buffer_t* self, int tab_width); 165 | int buffer_set_styles_enabled(buffer_t* self, int is_enabled); 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LUAJIT_VERSION := 2.0.5 2 | # LUAJIT_VERSION := 2.1.0-beta3 3 | WITH_PLUGINS=1 4 | # WITH_LIBCURL=1 5 | # SHELL=/bin/sh 6 | DESTDIR?=/usr/local/bin/ 7 | LC_ALL=C 8 | CC=gcc 9 | STRIP=strip 10 | CFLAGS+=-I./luajit/src 11 | 12 | WARNINGS=-Wall -Wno-missing-braces -Wno-unused-variable -Wno-unused-but-set-variable 13 | eon_cflags:=$(CFLAGS) -O2 -D_GNU_SOURCE $(WARNINGS) -g -I./mlbuf/ -I./termbox/src/ -I ./src/libs -I~/.nix-profile/include 14 | eon_ldlibs:=$(LDLIBS) 15 | eon_objects:=$(patsubst %.c,%.o,$(wildcard src/*.c)) 16 | eon_static:= 17 | 18 | # this is required for building under nix environments 19 | # PKG_CONFIG_PATH=${HOME}/.nix-profile/lib/pkgconfig/ 20 | 21 | UNAME_S := $(shell uname -s) 22 | ifeq ($(UNAME_S),Darwin) 23 | eon_ldlibs+=`pkg-config --libs libpcre` 24 | else 25 | eon_ldlibs+=-lrt -lpcre 26 | endif 27 | 28 | ifdef WITH_PLUGINS 29 | eon_cflags+=-I./luajit/src 30 | eon_ldlibs+=-L./luajit/src -lluajit -lm -ldl 31 | ifeq ($(UNAME_S),Darwin) # needed for luajit to work 32 | eon_ldlibs+=-pagezero_size 10000 -image_base 100000000 33 | endif 34 | else 35 | eon_ldlibs+=-lm 36 | # remove plugins stuff from list of objects 37 | eon_objects:=$(subst src/plugins.o ,,$(eon_objects)) 38 | eon_objects:=$(subst src/plugin_api.o ,,$(eon_objects)) 39 | endif 40 | 41 | ifdef WITH_LIBCURL 42 | eon_cflags+=-DWITH_LIBCURL 43 | eon_ldlibs+=`pkg-config --libs-only-l --libs-only-L libcurl` 44 | endif 45 | 46 | all: eon 47 | 48 | eon: ./mlbuf/libmlbuf.a ./termbox/build/libtermbox.a luajit/src/libluajit.a $(eon_objects) 49 | $(CC) $(eon_objects) $(eon_static) ./mlbuf/libmlbuf.a luajit/src/libluajit.a ./termbox/build/libtermbox.a $(eon_ldlibs) -o eon 50 | $(STRIP) eon 51 | 52 | eon_static: eon_static:=-static 53 | eon_static: eon_ldlibs:=$(eon_ldlibs) -lpthread 54 | ifdef WITH_LIBCURL # include ssl/crypto and libz 55 | eon_static: eon_ldlibs:=$(eon_ldlibs) -lssl -lcrypto -ldl -lz 56 | endif 57 | eon_static: eon 58 | 59 | $(eon_objects): %.o: %.c 60 | $(CC) -c $(eon_cflags) $< -o $@ 61 | 62 | ./mlbuf/libmlbuf.a: ./mlbuf/patched 63 | $(MAKE) -C mlbuf 64 | 65 | ./mlbuf/patched: 01-mlbuf-patch.diff 66 | @echo "Patching mlbuf..." 67 | if [ -e $@ ]; then cd mlbuf; git reset --hard HEAD; cd ..; fi 68 | cd mlbuf; patch -p1 < ../$<; cd .. 69 | cp $< $@ 70 | 71 | ./termbox/build/libtermbox.a: 72 | @echo "Building termbox..." 73 | if [ ! -e termbox/build ]; then mkdir termbox/build; cd termbox/build; cmake ..; cd ..; fi 74 | cd termbox/build && make 75 | 76 | luajit/src/libluajit.a: luajit 77 | # these below are slitaz compile flags 78 | # CFLAGS = -march=i486 -Os -pipe -fomit-frame-pointer 79 | # CPPFLAGS : -D_GLIBCXX_USE_C99_MATH=1 80 | # LDFLAGS : -Wl,-Os,--as-needed 81 | @cd luajit; make -j2 82 | 83 | luajit: 84 | @wget "http://luajit.org/download/LuaJIT-$(LUAJIT_VERSION).tar.gz" 85 | @tar xfv LuaJIT-$(LUAJIT_VERSION).tar.gz 86 | @ln -s LuaJIT-$(LUAJIT_VERSION) luajit 87 | 88 | test: eon test_eon 89 | $(MAKE) -C mlbuf test 90 | 91 | test_eon: eon 92 | $(MAKE) -C tests && ./eon -v 93 | 94 | sloc: 95 | find . -name '*.c' -or -name '*.h' | \ 96 | grep -Pv '(termbox|test|ut)' | \ 97 | xargs -rn1 cat | \ 98 | wc -l 99 | 100 | install: eon 101 | install -v -m 755 eon $(DESTDIR) 102 | 103 | clean: 104 | rm -f src/*.o eon.bak.* gmon.out perf.data perf.data.old eon 105 | $(MAKE) -C mlbuf clean 106 | rm -Rf termbox/build 107 | 108 | list: 109 | @grep '^[a-z]*:' Makefile 110 | 111 | .PHONY: all eon_static test test_eon sloc install clean 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eon 2 | 3 | A clever little console text editor. Written in C, forked out of [mle](https://github.com/adsr/mle). 4 | 5 | [![asciicast](https://asciinema.org/a/bcd88x89esx6ckmopicrjr0vp.png?123)](https://asciinema.org/a/bcd88x89esx6ckmopicrjr0vp) 6 | 7 | ## Building 8 | 9 | Install main deps first: 10 | 11 | $ apt install tree cmake libpcre-dev patch # or brew install / apk add 12 | 13 | The `tree` command is for browsing directories. It's optional but you definitely want it. 14 | 15 | If you want to try the experimental plugin system, you'll need to install LuaJIT and pkg-config: 16 | 17 | $ apt install libluajit-5.1-dev pkg-config # brew install luajit pkg-config, etc 18 | 19 | Clone the repo with its submodules: 20 | 21 | $ git clone --recursive https://github.com/tomas/eon.git 22 | 23 | And off you go! 24 | 25 | $ make 26 | 27 | To disable the plugin system open the Makefile and comment the WITH_PLUGINS line at the top. You can also run `make eon_static` in which case you'll get a static binary. 28 | 29 | ## Usage 30 | 31 | You can open `eon` by providing a directory or a file name. In the first case, it'll show a list of files within that directory (provided you installed the `tree` command). 32 | 33 | $ eon path/to/stuff 34 | 35 | In the second case it will, ehm, open the file. 36 | 37 | $ eon index.js 38 | 39 | You can also pass a line number if you want to: 40 | 41 | $ eon index.js:82 42 | 43 | Or simply start with an empty document: 44 | 45 | $ eon 46 | 47 | ## Tabs 48 | 49 | To open a new tab within the editor, you can either hit `Ctrl-B` or `Ctrl-N`. The former will open a new file browser view, and the latter will start a new empty document. 50 | 51 | As you'll see, `eon` fully supports mouse movement, so you can move around by clicking in a tab, and you can also click the middle button to close one. Double click is supported (word select on editor view and open file in file browser view) as well as text selection. Like you'd expect on the 21st century. :) 52 | 53 | ## Keybindings 54 | 55 | Yes, `eon` have a very sane set of default keybindings. `Ctrl-C` copies, `Ctrl-V` pastes, `Ctrl-Z` performs an undo and `Ctrl-Shift-Z` triggers a redo. `Ctrl-F` starts the incremental search function. To exit, either hit `Ctrl-D` or `Ctrl-Q`. 56 | 57 | Meta keys are supported, so you can also hit `Shift+Arrow Keys` to select text and then cut-and-paste it as you please. Last but not least, `eon` supports multi-cursor editing. To insert new cursors, either hit `Ctrl+Shift+Up/Down` or `Ctrl+Alt+Up/Down`. To cancel multi-cursor mode hit `Ctrl-D` or the `Esc` key. 58 | 59 | The reason why `eon` has two keybindings for a few things is because every terminal supports a different set of key combos. The officially list of supported terminals is currently xterm, urxvt (rxvt-unicode), mrxvt, xfce4-terminal and iTerm. Please don't try to use `eon` from within the default OSX terminal, as most key combos won't work so you won't get the full `eon` experience. If you really want to, then read below for a few configuration tips. 60 | 61 | ## Mouse mode 62 | 63 | If you want to disable the mouse mode you can toggle it by hitting `Alt-Backspace` or `Shift-Backspace`. This is useful if you want to copy or paste a chunk of text from or to another window in your session. 64 | 65 | ## Setting up OSX Terminal 66 | 67 | Apple's official terminal doesn't handle two 'meta' keys simultaneously (like Ctrl or Alt + Shift) and by default doesn't event send even the basic escape sequences other terminals do. However you can change the latter so at least some of the key combinations will work. To do this, open up the app's Preferences pane and open the "Keyboard" tab within Settings. Tick the "Use option as meta key" checkbox, and then hit the Plus sign above to add the following: 68 | 69 | - key: cursor up, modifier: control, action: send string to shell --> \033Oa 70 | - key: cursor down, modifier: control, action: send string to shell --> \033Ob 71 | - key: cursor right, modifier: control, action: send string to shell --> \033Oc 72 | - key: cursor left, modifier: control, action: send string to shell --> \033Ob 73 | 74 | - key: cursor up, modifier: shift, action: send string to shell --> \033[a 75 | - key: cursor down, modifier: shift, action: send string to shell --> \033[b 76 | - key: cursor right, modifier: shift, action: send string to shell --> \033[c 77 | - key: cursor left, modifier: shift, action: send string to shell --> \033[b 78 | 79 | These will let you use Shift and Control + Arrow Keys. Note that you might have some of these combinations assigned to Mission Control functions (e.g. Move left a space). In this case you'll 80 | need to decide which one you'll want to keep. My suggestion is to remove them given that most of 81 | these commands can be accessed via mouse gestures anyway. 82 | 83 | ## Setting up xfce4-terminal 84 | 85 | By default Xfce's terminal maps Shift+Up/Down to scroll-one-line behaviour. In order to deactivate this so you regain that mapping for `eon`, just untick the "Scroll single line using Shift-Up/Down keys" option in the app's preferences pane. 86 | 87 | ## TODO 88 | 89 | A bunch of stuff, but most importantly: 90 | 91 | - [ ] finish the plugin API (currently in the works) 92 | - [ ] provide the ability to customize keybindings 93 | - [ ] autocompletion (keywords, code snippets, etc), probably via a plugin 94 | - [ ] language-specific syntax highlighting (it currently uses a generic highlighter for all languages) 95 | - [ ] ability to customize colours for syntax highlight and parts of the UI 96 | - [ ] update this readme 97 | 98 | ## Credits 99 | 100 | Original code by [Adam Saponara](http://github.com/adsr). 101 | Refactoring and additional features by Tomás Pollak. 102 | Contributions by you, hopefully. :) 103 | 104 | ## Copyright 105 | 106 | (c) Apache License 2.0 107 | -------------------------------------------------------------------------------- /plugins/align/plugin.lua: -------------------------------------------------------------------------------- 1 | local plugin = {} 2 | plugin.name = "Align Tokens" 3 | plugin.version = "1.0" 4 | 5 | local lines = {} 6 | 7 | local function rtrim(s) 8 | local n = #s 9 | while n > 0 and s:find("^%s", n) do n = n - 1 end 10 | return s:sub(1, n) 11 | end 12 | 13 | local function get_line(i) 14 | if lines[i] then 15 | return lines[i] 16 | else 17 | line = rtrim(get_buffer_at_line(i)) 18 | lines[i] = line 19 | return line 20 | end 21 | end 22 | 23 | local function find_position_of_farthest_token(pattern, first_line, last_line) 24 | 25 | last = -1 26 | for i = first_line, last_line, 1 do 27 | line = get_line(i) 28 | a, b = string.find(line, pattern) 29 | if b and b > last then last = b end 30 | end 31 | 32 | return last 33 | end 34 | 35 | local function align_tokens(first_line, last_line) 36 | 37 | last_space_after_string = "[^%s]+%s+" 38 | 39 | -- first col is the last space before the second non-space block 40 | -- for example, in " foo: bar" it would be the 6th char, just before the 'b' 41 | -- in something like " :foo => 'bar'" it would be the 7th char, just before the '=' 42 | first_col = find_position_of_farthest_token(last_space_after_string, first_line, last_line) 43 | if first_col <= 0 then return first_col end 44 | 45 | for i = first_line, last_line, 1 do 46 | line = get_line(i) 47 | if string.len(line) > 0 then 48 | 49 | a, b = string.find(line, last_space_after_string) 50 | 51 | if a and b < first_col then 52 | offset = first_col - b 53 | insert_buffer_at_line(i, string.rep(" ", offset), b - 1) 54 | else 55 | offset = 0 56 | end 57 | 58 | x, second_col = string.find(line, "[^%s]+%s+[^%s]+%s") -- first space after second block 59 | if second_col then -- let's find the position of the second block of text 60 | 61 | c, d = string.find(line, "[^%s]+%s+[^%s]+%s+") -- last space in second block of space 62 | if c and d > second_col then 63 | diff = d - second_col 64 | delete_chars_at_line(i, second_col+offset, diff) 65 | end 66 | end 67 | end 68 | end 69 | 70 | for k in pairs(lines) do lines[k] = nil end 71 | 72 | end 73 | 74 | function plugin.toggle() 75 | if has_selection() then 76 | selection = get_selection() -- start line, start col, end line, end col 77 | local first_line = selection[0] 78 | local last_line = selection[2] 79 | return align_tokens(first_line, last_line) 80 | end 81 | end 82 | 83 | function plugin.boot() 84 | register_function("toggle") 85 | add_keybinding("CM-a", "toggle") 86 | end 87 | 88 | return plugin 89 | -------------------------------------------------------------------------------- /plugins/comment-lines/plugin.lua: -------------------------------------------------------------------------------- 1 | local plugin = {} 2 | plugin.name = "Comment Lines" 3 | plugin.version = "1.0" 4 | 5 | local function toggle_comment_on(line_number) 6 | line = get_buffer_at_line(line_number) 7 | regex = "// ?%s" 8 | comment = "// " 9 | column = string.find(line, regex) 10 | 11 | if column then 12 | count = string.len(comment) 13 | delete_chars_at_line(line_number, column-1, count) 14 | else 15 | first = string.find(line, "[^ \t]") 16 | if not first then -- empty line, continue 17 | return false 18 | elseif first < 2 then 19 | prepend_buffer_at_line(line_number, comment) 20 | else 21 | insert_buffer_at_line(line_number, comment, first - 1) 22 | end 23 | end 24 | end 25 | 26 | function plugin.toggle() 27 | lines = 0 28 | 29 | if has_selection() then 30 | selection = get_selection() -- start line, start col, end line, end col 31 | local first_line = selection[0] 32 | local last_line = selection[3] == 0 and selection[2]-1 or selection[2] 33 | for i = first_line, last_line, 1 do 34 | lines = lines + 1 35 | toggle_comment_on(i) 36 | end 37 | else 38 | toggle_comment_on(current_line_number()) 39 | lines = 1 40 | end 41 | 42 | return lines 43 | end 44 | 45 | function plugin.boot() 46 | register_function("toggle") 47 | add_keybinding("C-/", "toggle") 48 | -- before("save", "toggle") 49 | end 50 | 51 | return plugin 52 | -------------------------------------------------------------------------------- /plugins/eslint/eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "indent": [ 9 | "error", 10 | 2 11 | ], 12 | "linebreak-style": [ 13 | "error", 14 | "unix" 15 | ], 16 | "quotes": [ 17 | "error", 18 | "single" 19 | ], 20 | "semi": [ 21 | "error", 22 | "always" 23 | ] 24 | } 25 | } -------------------------------------------------------------------------------- /plugins/eslint/plugin.lua: -------------------------------------------------------------------------------- 1 | local plugin = {} 2 | plugin.name = "ESlint Wrapper" 3 | plugin.version = "1.0" 4 | 5 | local function script_path() 6 | return debug.getinfo(1).source:match("@?(.*/)") 7 | end 8 | 9 | local function exec(cmd) 10 | local handle = io.popen(cmd) 11 | local result = handle:read("*a") 12 | trimmed, idx = result:gsub("%s+$", "") 13 | handle:close() 14 | return trimmed 15 | end 16 | 17 | local current_error = -1 18 | local error_count = 0 19 | local errors = {} 20 | local lines = {} 21 | 22 | local function parse_result(output) 23 | count = 0 24 | 25 | for x, y, type, err in string.gmatch(output, "(%d+):(%d+)%s+(%w+)%s+([^\n]+)") do 26 | count = count + 1 27 | lines[count] = x 28 | errors[count] = err 29 | end 30 | 31 | error_count = count 32 | return count 33 | end 34 | 35 | --[[ 36 | local function reset_lines() 37 | line_count = get_line_count() 38 | for i = 1, line_count, 1 do 39 | set_line_bg_color(i, 0) 40 | end 41 | end 42 | ]]-- 43 | 44 | local function paint_errors(color_number) 45 | for i = 1, error_count, 1 do 46 | line_number = lines[i] 47 | -- if color_number == 0 then print("----------", line_number, color_number) end 48 | set_line_bg_color(line_number-1, color_number) 49 | end 50 | end 51 | 52 | local function clean_errors() 53 | paint_errors(0) 54 | end 55 | 56 | --[[ 57 | plugin.on_item_select = function(tab_id, line) 58 | x, y = line:match("^%s*(%d+):(%d+)%s") 59 | 60 | title = get_tab_title(tab_id) 61 | filename = line:match("ESLint: (.+)") 62 | tab_id = get_last_tab_with_title(filename) 63 | 64 | if tab_id then move_cursor_to_position(tag_id, x, y) end 65 | end 66 | ]]-- 67 | 68 | plugin.error_message = function(direction) 69 | current_error = current_error + direction 70 | reason = errors[current_error] 71 | prompt = string.format("[%d/%d errors]", current_error, errors.size(), reason) 72 | return prompt 73 | end 74 | 75 | plugin.on_prompt_input = function(key) 76 | if key == 'down' then 77 | prompt = error_message(1) 78 | elseif key == 'up' then 79 | prompt = error_message(-1) 80 | else 81 | return "cancel_prompt" 82 | end 83 | 84 | return string.format("set_prompt:%s;goto_line:%d", prompt, lines[current_error]) 85 | end 86 | 87 | plugin.check_current_file = function() 88 | filename = current_file_path() 89 | if not filename or string.len(filename) == 0 then 90 | return 0 91 | end 92 | 93 | -- print("Checking file", filename) 94 | local command = "eslint" 95 | local config = "eslintrc" 96 | local options = string.format('-c "%s%s"', script_path(), config) 97 | 98 | cmd = string.format('%s %s "%s"', command, options, filename) 99 | out = exec(cmd) 100 | -- print(out) 101 | 102 | -- title = string.format("ESLint: %s", filename) 103 | -- open_new_tab(title, out) 104 | 105 | if error_count > 0 then clean_errors() end 106 | 107 | parse_result(out) 108 | if error_count == 0 then 109 | show_message("No errors!") 110 | else 111 | -- print(error_count, "errors found.") 112 | paint_errors(2) 113 | goto_line(number) 114 | prompt = error_message(1) 115 | show_prompt(prompt, plugin.on_prompt_input) 116 | end 117 | end 118 | 119 | plugin.boot = function() 120 | register_function("check_current_file") 121 | add_keybinding("CS-L", "check_current_file") 122 | before("grep", "check_current_file") 123 | end 124 | 125 | return plugin -------------------------------------------------------------------------------- /plugins/git-file-changes/plugin.lua: -------------------------------------------------------------------------------- 1 | local plugin = {} 2 | plugin.name = "Git Commit File Changes" 3 | plugin.version = "1.0" 4 | 5 | local git = {} 6 | git.file_changed = function(filename) 7 | out = exec(string.format("git status | grep %s", filename)) 8 | end 9 | 10 | git.add = function(filename) 11 | exec(string.format("git add %s", filename)) 12 | end 13 | 14 | git.commit = function(msg) 15 | exec(string.format("git commit -m '%s'", msg)) 16 | end 17 | 18 | plugin.commit_changes = function() 19 | file = get_current_file() 20 | if git.file_changed(file.name) then 21 | default_msg = string.format("Update %s", file.name) 22 | commit_msg = prompt_user("Commit message:", default_msg) 23 | if not commit_msg then -- cancelled 24 | return 0 25 | else 26 | git.add(file.name) 27 | return git.commit(commit_msg) 28 | end 29 | end 30 | end 31 | 32 | function plugin.boot() 33 | register_function("commit_changes") 34 | add_keybinding("CS-c", "commit-changes") -- Ctrl+Shift+C 35 | end 36 | 37 | return plugin -------------------------------------------------------------------------------- /plugins/htmltidy/plugin.lua: -------------------------------------------------------------------------------- 1 | require('htmltidy') 2 | 3 | local plugin = {} 4 | plugin.name = "HTMLTidy" 5 | plugin.version = "1.0" 6 | 7 | local function tidy_html() 8 | -- filename = current_file_path() 9 | -- if not filename or string.len(filename) == 0 then 10 | -- return 0 11 | -- end 12 | 13 | buf = get_buffer() 14 | 15 | -- f = (io.open('test.html', 'r')) 16 | -- r = f:read("*all") 17 | -- f:close() 18 | 19 | local c = htmltidy.new() 20 | c:parse(buf) 21 | local x = c:toTable() 22 | -- this one prints the whole table. It's a great function 23 | -- don't miss out. 24 | if table.print then 25 | table.print(x) 26 | end 27 | 28 | c:setOpt(htmltidy.opt.XhtmlOut, true) 29 | local cleaned = c:saveToString() 30 | 31 | -- print(cleaned) 32 | set_buffer(cleaned) 33 | end 34 | 35 | plugin.boot = function() 36 | register_function("tidy_html") 37 | add_keybinding("CS-T", "tidy_html") 38 | end 39 | 40 | return plugin 41 | -------------------------------------------------------------------------------- /plugins/test/plugin.conf: -------------------------------------------------------------------------------- 1 | name: "foobar" 2 | online: true 3 | number: 3 -------------------------------------------------------------------------------- /plugins/test/plugin.lua: -------------------------------------------------------------------------------- 1 | local plugin = {} 2 | plugin.name = "Lower" 3 | plugin.version = "1.0" 4 | 5 | function plugin.after_cmd_toggle_mouse_mode(text) 6 | res = eon_indent() 7 | -- print("Result: %s\n", res) 8 | return tostring(text):lower() 9 | end 10 | 11 | function plugin.on_prompt_callback(action) 12 | print("prompt callback on lua!", action) 13 | end 14 | 15 | function plugin.start_review() 16 | goto_line(10) 17 | start_nav("String one .... ", plugin.on_prompt_callback) 18 | end 19 | 20 | function plugin.boot() 21 | --body = get_url("http://bootlog.org/foo") 22 | -- print(body) 23 | 24 | register_function("start_review") 25 | add_keybinding("M-j", "start_review") 26 | 27 | foo = get_option("name") 28 | -- print(foo, type(foo)) 29 | 30 | boo = get_option("online") 31 | -- print(boo, type(boo)) 32 | 33 | num = get_option("number") 34 | -- print(num, type(num)) 35 | end 36 | 37 | return plugin -------------------------------------------------------------------------------- /plugins/test/upper.lua: -------------------------------------------------------------------------------- 1 | -- local api = require('plugins/api') 2 | local ffi = require('ffi') 3 | ffi.cdef([[ 4 | void printf(const char * fmt, ...); 5 | ]]) 6 | 7 | local plugin = {} 8 | plugin.name = "Upper" 9 | plugin.version = "1.3" 10 | 11 | function plugin.run(text) 12 | ffi.C.printf("integer value: %d\n", 10) 13 | test(1) 14 | return tostring(text):upper() 15 | end 16 | 17 | return plugin -------------------------------------------------------------------------------- /plugins/trailing-spaces/plugin.lua: -------------------------------------------------------------------------------- 1 | local plugin = {} 2 | plugin.name = "Remove Trailing Spaces on Save" 3 | plugin.version = "1.0" 4 | 5 | function plugin.remove_trailing_spaces() 6 | trim_count = 0 7 | line_count = get_line_count() 8 | 9 | for i = 0, line_count-1, 1 do 10 | line = get_buffer_at_line(i) 11 | col = string.find(line, "([ \t]+)$") 12 | if col then 13 | trim_count = trim_count+1 14 | delete_chars_at_line(i, col-1) 15 | end 16 | end 17 | 18 | return trim_count 19 | end 20 | 21 | function plugin.boot() 22 | -- register_function("remove_trailing_spaces") 23 | before("save", "remove_trailing_spaces") 24 | -- after("git.commit_changes", "remove_trailing_spaces") 25 | end 26 | 27 | return plugin -------------------------------------------------------------------------------- /src/async.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "utlist.h" 9 | #include "eon.h" 10 | 11 | // Return a new async_proc_t 12 | async_proc_t* async_proc_new(editor_t* editor, void* owner, async_proc_t** owner_aproc, char* shell_cmd, int rw, async_proc_cb_t callback) { 13 | async_proc_t* aproc; 14 | aproc = calloc(1, sizeof(async_proc_t)); 15 | aproc->editor = editor; 16 | async_proc_set_owner(aproc, owner, owner_aproc); 17 | 18 | if (rw) { 19 | if (!util_popen2(shell_cmd, NULL, &aproc->rfd, &aproc->wfd, &aproc->pid)) { 20 | goto async_proc_new_failure; 21 | } 22 | 23 | aproc->rpipe = fdopen(aproc->rfd, "r"); 24 | aproc->wpipe = fdopen(aproc->wfd, "w"); 25 | 26 | } else { 27 | if (!(aproc->rpipe = popen(shell_cmd, "r"))) { 28 | goto async_proc_new_failure; 29 | } 30 | 31 | aproc->rfd = fileno(aproc->rpipe); 32 | } 33 | 34 | setvbuf(aproc->rpipe, NULL, _IONBF, 0); 35 | 36 | if (aproc->wpipe) setvbuf(aproc->wpipe, NULL, _IONBF, 0); 37 | 38 | aproc->callback = callback; 39 | DL_APPEND(editor->async_procs, aproc); 40 | return aproc; 41 | 42 | async_proc_new_failure: 43 | free(aproc); 44 | return NULL; 45 | } 46 | 47 | // Set aproc owner 48 | int async_proc_set_owner(async_proc_t* aproc, void* owner, async_proc_t** owner_aproc) { 49 | if (aproc->owner_aproc) { 50 | *aproc->owner_aproc = NULL; 51 | } 52 | 53 | *owner_aproc = aproc; 54 | aproc->owner = owner; 55 | aproc->owner_aproc = owner_aproc; 56 | return EON_OK; 57 | } 58 | 59 | // Destroy an async_proc_t 60 | int async_proc_destroy(async_proc_t* aproc, int preempt) { 61 | DL_DELETE(aproc->editor->async_procs, aproc); 62 | 63 | if (aproc->owner_aproc) *aproc->owner_aproc = NULL; 64 | 65 | if (preempt) { 66 | if (aproc->rfd) close(aproc->rfd); 67 | if (aproc->wfd) close(aproc->wfd); 68 | if (aproc->pid) kill(aproc->pid, SIGTERM); 69 | } 70 | 71 | if (aproc->rpipe) pclose(aproc->rpipe); 72 | if (aproc->wpipe) pclose(aproc->wpipe); 73 | 74 | free(aproc); 75 | return EON_OK; 76 | } 77 | 78 | // Manage async procs, giving priority to user input. Return 1 if drain should 79 | // be called again, else return 0. 80 | int async_proc_drain_all(async_proc_t* aprocs, int* ttyfd) { 81 | int maxfd; 82 | fd_set readfds; 83 | async_proc_t* aproc; 84 | async_proc_t* aproc_tmp; 85 | char buf[1024 + 1]; 86 | ssize_t nbytes; 87 | int rc; 88 | 89 | // Exit early if no aprocs 90 | if (!aprocs) return 0; 91 | 92 | // Open ttyfd if not already open 93 | if (!*ttyfd) { 94 | if ((*ttyfd = open("/dev/tty", O_RDONLY)) < 0) { 95 | // TODO error 96 | return 0; 97 | } 98 | } 99 | 100 | // Add tty to readfds 101 | FD_ZERO(&readfds); 102 | FD_SET(*ttyfd, &readfds); 103 | 104 | // Add async procs to readfds 105 | // Simultaneously check for solo, which takes precedence over everything 106 | maxfd = *ttyfd; 107 | DL_FOREACH(aprocs, aproc) { 108 | if (aproc->is_solo) { 109 | FD_ZERO(&readfds); 110 | FD_SET(aproc->rfd, &readfds); 111 | maxfd = aproc->rfd; 112 | break; 113 | 114 | } else { 115 | FD_SET(aproc->rfd, &readfds); 116 | 117 | if (aproc->rfd > maxfd) maxfd = aproc->rfd; 118 | } 119 | } 120 | 121 | // Perform select 122 | rc = select(maxfd + 1, &readfds, NULL, NULL, NULL); 123 | 124 | if (rc < 0) { 125 | return 0; // TODO error 126 | 127 | } else if (rc == 0) { 128 | return 1; // Nothing to read, call again 129 | } 130 | 131 | if (FD_ISSET(*ttyfd, &readfds)) { 132 | // Immediately give priority to user input 133 | return 0; 134 | 135 | } else { 136 | // Read async procs 137 | DL_FOREACH_SAFE(aprocs, aproc, aproc_tmp) { 138 | // Read and invoke callback 139 | if (FD_ISSET(aproc->rfd, &readfds)) { 140 | nbytes = read(aproc->rfd, &buf, 1024); 141 | buf[nbytes] = '\0'; 142 | aproc->callback(aproc, buf, nbytes); 143 | 144 | if (nbytes == 0) aproc->is_done = 1; 145 | } 146 | 147 | // Destroy on eof. 148 | // Not sure if ferror and feof have any effect here given we're not 149 | // using fread. 150 | if (ferror(aproc->rpipe) || feof(aproc->rpipe) || aproc->is_done) { 151 | async_proc_destroy(aproc, 0); 152 | } 153 | } 154 | } 155 | 156 | return 1; 157 | } 158 | -------------------------------------------------------------------------------- /src/bview.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "eon.h" 6 | #include "colors.h" 7 | 8 | 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); 9 | static void _bview_init(bview_t* self, buffer_t* buffer); 10 | static void _bview_init_resized(bview_t* self); 11 | static kmap_t* _bview_get_init_kmap(editor_t* editor); 12 | static void _bview_buffer_callback(buffer_t* buffer, baction_t* action, void* udata); 13 | static int _bview_set_linenum_width(bview_t* self); 14 | static void _bview_deinit(bview_t* self); 15 | static void _bview_set_tab_width(bview_t* self, int tab_width); 16 | 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); 17 | static buffer_t* _bview_open_buffer(bview_t* self, char* opt_path, int opt_path_len); 18 | static void _bview_draw_prompt(bview_t* self); 19 | static void _bview_draw_status(bview_t* self); 20 | static void _bview_draw_edit(bview_t* self, int x, int y, int w, int h); 21 | static void _bview_draw_bline(bview_t* self, bline_t* bline, int rect_y, bline_t** optret_bline, int* optret_rect_y); 22 | static void _bview_highlight_bracket_pair(bview_t* self, mark_t* mark); 23 | 24 | // Create a new bview 25 | bview_t* bview_new(editor_t* editor, char* opt_path, int opt_path_len, buffer_t* opt_buffer) { 26 | bview_t* self; 27 | buffer_t* buffer; 28 | 29 | // Allocate and init bview 30 | self = calloc(1, sizeof(bview_t)); 31 | self->editor = editor; 32 | 33 | // self->rect_caption.fg = RECT_CAPTION_FG; 34 | // self->rect_caption.bg = RECT_CAPTION_BG; 35 | self->rect_lines.fg = RECT_LINES_FG; 36 | self->rect_lines.bg = RECT_LINES_BG; 37 | self->rect_margin_left.fg = RECT_MARGIN_LEFT_FG; 38 | self->rect_margin_right.fg = RECT_MARGIN_RIGHT_FG; 39 | self->rect_buffer.h = 10; // TODO hack to fix _bview_set_linenum_width before bview_resize 40 | 41 | self->tab_width = editor->tab_width; 42 | self->tab_to_space = editor->tab_to_space; 43 | self->viewport_scope_x = editor->viewport_scope_x; 44 | self->viewport_scope_y = editor->viewport_scope_y; 45 | 46 | char * res; 47 | res = getcwd(self->init_cwd, PATH_MAX + 1); 48 | 49 | if (opt_buffer) { // Open buffer 50 | buffer = opt_buffer; 51 | } else { 52 | buffer = _bview_open_buffer(self, opt_path, opt_path_len); 53 | } 54 | 55 | _bview_init(self, buffer); 56 | return self; 57 | } 58 | 59 | // Open a buffer in an existing bview 60 | int bview_open(bview_t* self, char* path, int path_len) { 61 | buffer_t* buffer; 62 | buffer = _bview_open_buffer(self, path, path_len); 63 | _bview_init(self, buffer); 64 | return EON_OK; 65 | } 66 | 67 | // Free a bview 68 | int bview_destroy(bview_t* self) { 69 | _bview_deinit(self); 70 | free(self); 71 | return EON_OK; 72 | } 73 | 74 | // Move and resize a bview to the given position and dimensions 75 | int bview_resize(bview_t* self, int x, int y, int w, int h) { 76 | int aw, ah; 77 | 78 | self->x = x; 79 | self->y = y; 80 | self->w = w; 81 | self->h = h; 82 | 83 | aw = w; 84 | ah = h; 85 | 86 | if (self->split_child) { 87 | if (self->split_is_vertical) { 88 | aw = EON_MAX(1, (int)((float)aw * self->split_factor)); 89 | } else { 90 | ah = EON_MAX(1, (int)((float)ah * self->split_factor)); 91 | } 92 | } 93 | 94 | if (EON_BVIEW_IS_EDIT(self)) { 95 | self->rect_caption.x = x; 96 | self->rect_caption.y = y; 97 | self->rect_caption.w = aw; 98 | self->rect_caption.h = 1; 99 | 100 | self->rect_lines.x = x; 101 | self->rect_lines.y = y + 1; 102 | self->rect_lines.w = self->linenum_width; 103 | self->rect_lines.h = ah - 1; 104 | 105 | self->rect_margin_left.x = x + self->linenum_width; 106 | self->rect_margin_left.y = y + 1; 107 | self->rect_margin_left.w = 1; 108 | self->rect_margin_left.h = ah - 1; 109 | 110 | self->rect_buffer.x = x; 111 | if (self->editor->linenum_type != EON_LINENUM_TYPE_NONE) 112 | self->rect_buffer.x += self->linenum_width + 1; 113 | 114 | self->rect_buffer.y = y + 1; 115 | self->rect_buffer.w = aw - (self->linenum_width + 1 + 1); 116 | self->rect_buffer.h = ah - 1; 117 | 118 | self->rect_margin_right.x = x + (aw - 1); 119 | self->rect_margin_right.y = y + 1; 120 | self->rect_margin_right.w = 1; 121 | self->rect_margin_right.h = ah - 1; 122 | 123 | } else { 124 | self->rect_buffer.x = x; 125 | self->rect_buffer.y = y; 126 | self->rect_buffer.w = aw; 127 | self->rect_buffer.h = ah; 128 | } 129 | 130 | if (self->split_child) { 131 | bview_resize( 132 | self->split_child, 133 | x + (self->split_is_vertical ? aw : 0), 134 | y + (self->split_is_vertical ? 0 : ah), 135 | w - (self->split_is_vertical ? aw : 0), 136 | h - (self->split_is_vertical ? 0 : ah) 137 | ); 138 | } 139 | 140 | if (!self->is_resized) { 141 | _bview_init_resized(self); 142 | self->is_resized = 1; 143 | } 144 | 145 | bview_rectify_viewport(self); 146 | return EON_OK; 147 | } 148 | 149 | // Return top-most split_parent of a bview 150 | bview_t* bview_get_split_root(bview_t* self) { 151 | bview_t* root; 152 | root = self; 153 | 154 | while (root->split_parent) { 155 | root = root->split_parent; 156 | } 157 | 158 | return root; 159 | } 160 | 161 | // Draw bview to screen 162 | int bview_draw(bview_t* self) { 163 | if (EON_BVIEW_IS_PROMPT(self)) { 164 | _bview_draw_prompt(self); 165 | } else if (EON_BVIEW_IS_STATUS(self)) { 166 | _bview_draw_status(self); 167 | } 168 | 169 | _bview_draw_edit(self, self->x, self->y, self->w, self->h); 170 | return EON_OK; 171 | } 172 | 173 | // Set cursor to screen 174 | int bview_draw_cursor(bview_t* self, int set_real_cursor) { 175 | cursor_t* cursor; 176 | mark_t* mark; 177 | int screen_x; 178 | int screen_y; 179 | struct tb_cell* cell; 180 | DL_FOREACH(self->cursors, cursor) { 181 | mark = cursor->mark; 182 | 183 | if (bview_get_screen_coords(self, mark, &screen_x, &screen_y, &cell) != EON_OK) { 184 | // Out of bounds 185 | continue; 186 | } 187 | 188 | if (set_real_cursor && cursor == self->active_cursor) { // Set terminal cursor 189 | tb_set_cursor(screen_x, screen_y); 190 | } else { // Set fake cursor 191 | tb_char(screen_x, screen_y, cell->fg, cell->bg | (cursor->is_asleep ? ASLEEP_CURSOR_BG : AWAKE_CURSOR_BG), cell->ch); 192 | } 193 | 194 | if (self->editor->highlight_bracket_pairs) { 195 | _bview_highlight_bracket_pair(self, mark); 196 | } 197 | } 198 | return EON_OK; 199 | } 200 | 201 | // Push a kmap 202 | int bview_push_kmap(bview_t* bview, kmap_t* kmap) { 203 | kmap_node_t* node; 204 | node = calloc(1, sizeof(kmap_node_t)); 205 | node->kmap = kmap; 206 | node->bview = bview; 207 | 208 | DL_APPEND(bview->kmap_stack, node); 209 | bview->kmap_tail = node; 210 | 211 | return EON_OK; 212 | } 213 | 214 | // Pop a kmap 215 | int bview_pop_kmap(bview_t* bview, kmap_t** optret_kmap) { 216 | kmap_node_t* node_to_pop; 217 | node_to_pop = bview->kmap_tail; 218 | 219 | if (!node_to_pop) { 220 | return EON_ERR; 221 | } 222 | 223 | if (optret_kmap) { 224 | *optret_kmap = node_to_pop->kmap; 225 | } 226 | 227 | bview->kmap_tail = node_to_pop->prev != node_to_pop ? node_to_pop->prev : NULL; 228 | DL_DELETE(bview->kmap_stack, node_to_pop); 229 | free(node_to_pop); 230 | return EON_OK; 231 | } 232 | 233 | // Split a bview 234 | int bview_split(bview_t* self, int is_vertical, float factor, bview_t** optret_bview) { 235 | bview_t* child; 236 | 237 | if (self->split_child) { 238 | EON_RETURN_ERR(self->editor, "bview %p is already split", self); 239 | 240 | } else if (!EON_BVIEW_IS_EDIT(self)) { 241 | EON_RETURN_ERR(self->editor, "bview %p is not an edit bview", self); 242 | } 243 | 244 | // Make child 245 | editor_open_bview(self->editor, self, self->type, NULL, 0, 1, 0, NULL, self->buffer, &child); 246 | child->split_parent = self; 247 | self->split_child = child; 248 | self->split_factor = factor; 249 | self->split_is_vertical = is_vertical; 250 | 251 | // Move cursor to same position 252 | mark_move_to(child->active_cursor->mark, self->active_cursor->mark->bline->line_index, self->active_cursor->mark->col); 253 | bview_center_viewport_y(child); 254 | 255 | // Resize self 256 | bview_resize(self, self->x, self->y, self->w, self->h); 257 | 258 | if (optret_bview) { 259 | *optret_bview = child; 260 | } 261 | 262 | return EON_OK; 263 | } 264 | 265 | // Return number of active cursors 266 | int bview_get_active_cursor_count(bview_t* self) { 267 | int count; 268 | cursor_t* cursor; 269 | count = 0; 270 | DL_FOREACH(self->cursors, cursor) { 271 | if (!cursor->is_asleep) { 272 | count += 1; 273 | } 274 | } 275 | return count; 276 | } 277 | 278 | // Add a cursor to a bview 279 | int bview_add_cursor(bview_t* self, bline_t* bline, bint_t col, cursor_t** optret_cursor) { 280 | cursor_t* cursor; 281 | 282 | cursor = calloc(1, sizeof(cursor_t)); 283 | cursor->bview = self; 284 | cursor->mark = buffer_add_mark(self->buffer, bline, col); 285 | DL_APPEND(self->cursors, cursor); 286 | 287 | if (!self->active_cursor) { 288 | self->active_cursor = cursor; 289 | } 290 | 291 | if (optret_cursor) { 292 | *optret_cursor = cursor; 293 | } 294 | 295 | return EON_OK; 296 | } 297 | 298 | // Add sleeping cursor 299 | int bview_add_cursor_asleep(bview_t* self, bline_t* bline, bint_t col, cursor_t** optret_cursor) { 300 | cursor_t* cursor; 301 | 302 | bview_add_cursor(self, bline, col, &cursor); 303 | cursor->is_asleep = 1; 304 | if (optret_cursor) *optret_cursor = cursor; 305 | 306 | return EON_OK; 307 | } 308 | 309 | // Wake all sleeping cursors 310 | int bview_wake_sleeping_cursors(bview_t* self) { 311 | cursor_t* cursor; 312 | DL_FOREACH(self->cursors, cursor) { 313 | if (cursor->is_asleep) { 314 | cursor->is_asleep = 0; 315 | } 316 | } 317 | return EON_OK; 318 | } 319 | 320 | // Remove all cursors except one 321 | int bview_remove_cursors_except(bview_t* self, cursor_t* one) { 322 | cursor_t* cursor; 323 | cursor_t* cursor_tmp; 324 | DL_FOREACH_SAFE(self->cursors, cursor, cursor_tmp) { 325 | if (cursor != one) { 326 | bview_remove_cursor(self, cursor); 327 | } 328 | } 329 | return EON_OK; 330 | } 331 | 332 | // Remove a cursor from a bview 333 | int bview_remove_cursor(bview_t* self, cursor_t* cursor) { 334 | cursor_t* el; 335 | cursor_t* tmp; 336 | DL_FOREACH_SAFE(self->cursors, el, tmp) { 337 | if (el == cursor) { 338 | self->active_cursor = el->prev && el->prev != el ? el->prev : el->next; 339 | DL_DELETE(self->cursors, el); 340 | 341 | if (el->sel_rule) { 342 | buffer_remove_srule(el->bview->buffer, el->sel_rule, 0, 0); 343 | srule_destroy(el->sel_rule); 344 | el->sel_rule = NULL; 345 | } 346 | 347 | if (el->cut_buffer) free(el->cut_buffer); 348 | 349 | free(el); 350 | return EON_OK; 351 | } 352 | } 353 | return EON_ERR; 354 | } 355 | 356 | int bview_is_line_visible(bview_t* self, bint_t number) { 357 | int res = 0; 358 | 359 | if (self->viewport_y < number && number < self->viewport_y + self->rect_buffer.h) { 360 | return 1; 361 | } 362 | 363 | return res; 364 | } 365 | 366 | int bview_move_to_line(bview_t* self, bint_t number) { 367 | if (bview_is_line_visible(self, number)) 368 | return EON_OK; 369 | 370 | bint_t y = number - (self->rect_buffer.h / 2); 371 | 372 | if (y < 0) y = 0; 373 | 374 | // if (y + self->rect_buffer.h - 2 < self->buffer->line_count) { 375 | self->viewport_y = y; 376 | buffer_get_bline(self->buffer, self->viewport_y, &self->viewport_bline); 377 | // } 378 | 379 | return EON_OK; 380 | } 381 | 382 | int bview_set_line_bg(bview_t * self, bint_t line_index, int color) { 383 | bline_t* bline; 384 | buffer_get_bline(self->buffer, line_index, &bline); 385 | 386 | if (!bline) return EON_ERR; 387 | 388 | bline->bg = color; 389 | return EON_OK; 390 | } 391 | 392 | int bview_scroll_viewport(bview_t* self, int offset) { 393 | bint_t y; 394 | 395 | if (offset < 0 && self->viewport_y < offset * -1) { 396 | y = 0; 397 | } else { 398 | y = self->viewport_y + (bint_t) offset; 399 | } 400 | 401 | if (y + self->rect_buffer.h - 2 < self->buffer->line_count) { 402 | self->viewport_y = y; 403 | buffer_get_bline(self->buffer, self->viewport_y, &self->viewport_bline); 404 | } 405 | 406 | return EON_OK; 407 | } 408 | 409 | // Center the viewport vertically 410 | int bview_center_viewport_y(bview_t* self) { 411 | bint_t center = self->active_cursor->mark->bline->line_index - (self->rect_buffer.h / 2); 412 | 413 | if (center < 0) center = 0; 414 | 415 | self->viewport_y = center; 416 | bview_rectify_viewport(self); 417 | buffer_get_bline(self->buffer, self->viewport_y, &self->viewport_bline); 418 | return EON_OK; 419 | } 420 | 421 | // Zero the viewport vertically 422 | int bview_zero_viewport_y(bview_t* self) { 423 | self->viewport_y = self->active_cursor->mark->bline->line_index; 424 | bview_rectify_viewport(self); 425 | buffer_get_bline(self->buffer, self->viewport_y, &self->viewport_bline); 426 | return EON_OK; 427 | } 428 | 429 | // Maximize the viewport vertically 430 | int bview_max_viewport_y(bview_t* self) { 431 | bint_t max; 432 | max = self->active_cursor->mark->bline->line_index - self->rect_buffer.h; 433 | 434 | if (max < 0) max = 0; 435 | 436 | self->viewport_y = max; 437 | bview_rectify_viewport(self); 438 | buffer_get_bline(self->buffer, self->viewport_y, &self->viewport_bline); 439 | return EON_OK; 440 | } 441 | 442 | // Rectify the viewport 443 | int bview_rectify_viewport(bview_t* self) { 444 | mark_t* mark; 445 | mark = self->active_cursor->mark; 446 | 447 | // Rectify each dimension of the viewport 448 | MLBUF_BLINE_ENSURE_CHARS(mark->bline); 449 | _bview_rectify_viewport_dim(self, mark->bline, EON_MARK_COL_TO_VCOL(mark), self->viewport_scope_x, self->rect_buffer.w, &self->viewport_x_vcol); 450 | bline_get_col_from_vcol(mark->bline, self->viewport_x_vcol, &(self->viewport_x)); 451 | 452 | if (_bview_rectify_viewport_dim(self, mark->bline, mark->bline->line_index, self->viewport_scope_y, self->rect_buffer.h, &self->viewport_y)) { 453 | // TODO viewport_y_vrow (soft-wrapped lines, code folding, etc) 454 | // Refresh viewport_bline 455 | buffer_get_bline(self->buffer, self->viewport_y, &self->viewport_bline); 456 | } 457 | 458 | return EON_OK; 459 | } 460 | 461 | // Add a listener 462 | int bview_add_listener(bview_t* self, bview_listener_cb_t callback, void* udata) { 463 | bview_listener_t* listener; 464 | listener = calloc(1, sizeof(bview_listener_t)); 465 | listener->callback = callback; 466 | listener->udata = udata; 467 | DL_APPEND(self->listeners, listener); 468 | return EON_OK; 469 | } 470 | 471 | // Remove and free a listener 472 | int bview_destroy_listener(bview_t* self, bview_listener_t* listener) { 473 | DL_APPEND(self->listeners, listener); 474 | free(listener); 475 | return EON_OK; 476 | } 477 | 478 | // Rectify a viewport dimension. Return 1 if changed, else 0. 479 | 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) { 480 | int rc; 481 | bint_t vpos_start; 482 | bint_t vpos_stop; 483 | 484 | // Find bounds 485 | if (dim_scope < 0) { 486 | // Keep cursor at least `dim_scope` cells away from edge 487 | // Remember dim_scope is negative here 488 | dim_scope = EON_MAX(dim_scope, ((dim_size / 2) * -1)); 489 | vpos_start = *view_vpos - dim_scope; // N in from left edge 490 | vpos_stop = (*view_vpos + dim_size) + dim_scope; // N in from right edge 491 | 492 | } else { 493 | // Keep cursor within `dim_scope/2` cells of midpoint 494 | dim_scope = EON_MIN(dim_scope, dim_size); 495 | vpos_start = (*view_vpos + (dim_size / 2)) - (int)floorf((float)dim_scope * 0.5); // -N/2 from midpoint 496 | vpos_stop = (*view_vpos + (dim_size / 2)) + (int)ceilf((float)dim_scope * 0.5); // +N/2 from midpoint 497 | } 498 | 499 | // Rectify 500 | rc = 1; 501 | 502 | if (vpos < vpos_start) { 503 | *view_vpos -= EON_MIN(*view_vpos, vpos_start - vpos); 504 | } else if (vpos >= vpos_stop) { 505 | *view_vpos += ((vpos - vpos_stop) + 1); 506 | } else { 507 | rc = 0; 508 | } 509 | 510 | return rc; 511 | } 512 | 513 | // Init a bview with a buffer 514 | static void _bview_init(bview_t* self, buffer_t* buffer) { 515 | cursor_t* cursor_tmp; 516 | kmap_t* kmap_init; 517 | 518 | _bview_deinit(self); 519 | 520 | // Reference buffer 521 | self->buffer = buffer; 522 | self->buffer->ref_count += 1; 523 | _bview_set_linenum_width(self); 524 | 525 | // Push normal mode 526 | kmap_init = _bview_get_init_kmap(self->editor); 527 | 528 | // Make kmap_normal the bottom if init kmap isn't kmap_normal 529 | if (kmap_init != self->editor->kmap_normal) { 530 | bview_push_kmap(self, self->editor->kmap_normal); 531 | } 532 | 533 | bview_push_kmap(self, kmap_init); 534 | bview_set_syntax(self, NULL); 535 | bview_add_cursor(self, self->buffer->first_line, 0, &cursor_tmp); 536 | } 537 | 538 | // Invoked once after a bview has been resized for the first time 539 | static void _bview_init_resized(bview_t* self) { 540 | // Move cursor to startup line if present 541 | if (self->startup_linenum > 0) { 542 | mark_move_to(self->active_cursor->mark, self->startup_linenum, 0); 543 | bview_center_viewport_y(self); 544 | } 545 | } 546 | 547 | // Return initial kmap to use 548 | static kmap_t* _bview_get_init_kmap(editor_t* editor) { 549 | if (!editor->kmap_init) { 550 | if (editor->kmap_init_name) { 551 | HASH_FIND_STR(editor->kmap_map, editor->kmap_init_name, editor->kmap_init); 552 | } 553 | 554 | if (!editor->kmap_init) { 555 | editor->kmap_init = editor->kmap_normal; 556 | } 557 | } 558 | 559 | return editor->kmap_init; 560 | } 561 | 562 | // Called by mlbuf after edits 563 | static void _bview_buffer_callback(buffer_t* buffer, baction_t* action, void* udata) { 564 | editor_t* editor; 565 | bview_t* self; 566 | bview_t* active; 567 | bview_listener_t* listener; 568 | 569 | self = (bview_t*)udata; 570 | editor = self->editor; 571 | active = editor->active; 572 | 573 | // Rectify viewport if edit was on active bview 574 | if (active->buffer == buffer) { 575 | bview_rectify_viewport(active); 576 | } 577 | 578 | if (action && action->line_delta != 0) { 579 | bview_t* bview; 580 | bview_t* tmp1; 581 | bview_t* tmp2; 582 | CDL_FOREACH_SAFE2(editor->all_bviews, bview, tmp1, tmp2, all_prev, all_next) { 583 | if (bview->buffer == buffer) { 584 | // Adjust linenum_width 585 | if (_bview_set_linenum_width(bview)) { 586 | bview_resize(bview, bview->x, bview->y, bview->w, bview->h); 587 | } 588 | 589 | // Adjust viewport_bline 590 | buffer_get_bline(bview->buffer, bview->viewport_y, &bview->viewport_bline); 591 | } 592 | } 593 | } 594 | 595 | // Call bview listeners 596 | DL_FOREACH(self->listeners, listener) { 597 | listener->callback(self, action, listener->udata); 598 | } 599 | } 600 | 601 | // Set linenum_width and return 1 if changed 602 | static int _bview_set_linenum_width(bview_t* self) { 603 | int orig; 604 | orig = self->linenum_width; 605 | self->abs_linenum_width = EON_MAX(1, (int)(floor(log10((double)self->buffer->line_count))) + 1); 606 | 607 | if (self->editor->linenum_type != EON_LINENUM_TYPE_ABS) { 608 | self->rel_linenum_width = EON_MAX( 609 | self->editor->linenum_type == EON_LINENUM_TYPE_BOTH ? 1 : self->abs_linenum_width, 610 | (int)(floor(log10((double)self->rect_buffer.h))) + 1 611 | ); 612 | 613 | } else { 614 | self->rel_linenum_width = 0; 615 | } 616 | 617 | if (self->editor->linenum_type == EON_LINENUM_TYPE_ABS) { 618 | self->linenum_width = self->abs_linenum_width; 619 | 620 | } else if (self->editor->linenum_type == EON_LINENUM_TYPE_REL) { 621 | self->linenum_width = self->abs_linenum_width > self->rel_linenum_width ? self->abs_linenum_width : self->rel_linenum_width; 622 | 623 | } else if (self->editor->linenum_type == EON_LINENUM_TYPE_BOTH) { 624 | self->linenum_width = self->abs_linenum_width + 1 + self->rel_linenum_width; 625 | } 626 | 627 | return orig == self->linenum_width ? 0 : 1; 628 | } 629 | 630 | // Deinit a bview 631 | static void _bview_deinit(bview_t* self) { 632 | bview_listener_t* listener; 633 | bview_listener_t* listener_tmp; 634 | 635 | // Remove all kmaps 636 | while (self->kmap_tail) { 637 | bview_pop_kmap(self, NULL); 638 | } 639 | 640 | // Remove all syntax rules 641 | if (self->syntax) { 642 | srule_node_t* srule_node; 643 | buffer_set_styles_enabled(self->buffer, 0); 644 | DL_FOREACH(self->syntax->srules, srule_node) { 645 | buffer_remove_srule(self->buffer, srule_node->srule, 0, 100); 646 | } 647 | buffer_set_styles_enabled(self->buffer, 1); 648 | } 649 | 650 | // Remove all cursors 651 | while (self->active_cursor) { 652 | bview_remove_cursor(self, self->active_cursor); 653 | self->active_cursor = NULL; 654 | } 655 | 656 | // Destroy async proc 657 | if (self->async_proc) { 658 | async_proc_destroy(self->async_proc, 1); 659 | self->async_proc = NULL; 660 | } 661 | 662 | // Remove all listeners 663 | DL_FOREACH_SAFE(self->listeners, listener, listener_tmp) { 664 | bview_destroy_listener(self, listener); 665 | } 666 | 667 | // Dereference/free buffer 668 | if (self->buffer) { 669 | self->buffer->ref_count -= 1; 670 | 671 | if (self->buffer->ref_count < 1) { 672 | buffer_destroy(self->buffer); 673 | } 674 | } 675 | 676 | // Free last_search 677 | if (self->last_search) { 678 | free(self->last_search); 679 | } 680 | } 681 | 682 | // Set syntax on bview buffer 683 | int bview_set_syntax(bview_t* self, char* opt_syntax) { 684 | syntax_t* syntax; 685 | syntax_t* syntax_tmp; 686 | syntax_t* use_syntax; 687 | srule_node_t* srule_node; 688 | 689 | // Only set syntax on edit bviews 690 | if (!EON_BVIEW_IS_EDIT(self)) { 691 | return EON_ERR; 692 | } 693 | 694 | use_syntax = NULL; 695 | 696 | if (opt_syntax) { // Set by opt_syntax 697 | HASH_FIND_STR(self->editor->syntax_map, opt_syntax, use_syntax); 698 | } else if (self->editor->is_in_init && self->editor->syntax_override) { // Set by override at init 699 | HASH_FIND_STR(self->editor->syntax_map, self->editor->syntax_override, use_syntax); 700 | } else if (self->buffer->path) { // Set by path 701 | HASH_ITER(hh, self->editor->syntax_map, syntax, syntax_tmp) { 702 | if (util_pcre_match(syntax->path_pattern, self->buffer->path, strlen(self->buffer->path), NULL, NULL)) { 703 | use_syntax = syntax; 704 | break; 705 | } 706 | } 707 | } 708 | 709 | buffer_set_styles_enabled(self->buffer, 0); 710 | 711 | // Remove current syntax 712 | if (self->syntax) { 713 | DL_FOREACH(self->syntax->srules, srule_node) { 714 | buffer_remove_srule(self->buffer, srule_node->srule, 0, 100); 715 | self->syntax = NULL; 716 | } 717 | } 718 | 719 | // Set syntax if found 720 | if (use_syntax) { 721 | DL_FOREACH(use_syntax->srules, srule_node) { 722 | buffer_add_srule(self->buffer, srule_node->srule, 0, 100); 723 | } 724 | self->syntax = use_syntax; 725 | self->tab_to_space = use_syntax->tab_to_space >= 0 726 | ? use_syntax->tab_to_space 727 | : self->editor->tab_to_space; 728 | 729 | _bview_set_tab_width(self, use_syntax->tab_width >= 1 730 | ? use_syntax->tab_width 731 | : self->editor->tab_width 732 | ); 733 | } else { // sync bview and buffer tab with with editors' 734 | _bview_set_tab_width(self, self->editor->tab_width); 735 | } 736 | 737 | buffer_set_styles_enabled(self->buffer, 1); 738 | 739 | return use_syntax ? EON_OK : EON_ERR; 740 | } 741 | 742 | static void _bview_set_tab_width(bview_t* self, int tab_width) { 743 | self->tab_width = tab_width; 744 | 745 | if (self->buffer && self->buffer->tab_width != self->tab_width) { 746 | buffer_set_tab_width(self->buffer, self->tab_width); 747 | } 748 | } 749 | 750 | // Attempt to fix path by stripping away git-style diff prefixes ([ab/]) and/or 751 | // by extracting a trailing line number after a colon (:) 752 | 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) { 753 | char* tmp; 754 | int tmp_len; 755 | char* colon; 756 | int is_valid; 757 | int fix_nudge; 758 | int fix_len; 759 | bint_t line_num; 760 | 761 | fix_nudge = 0; 762 | fix_len = path_len; 763 | line_num = 0; 764 | 765 | // Path already valid? 766 | if (util_is_file(path, NULL, NULL) || util_is_dir(path)) { 767 | goto _bview_fix_path_ret; 768 | } 769 | 770 | // Path valid if we strip "[ab]/" prefix? 771 | if (path_len >= 3 772 | && (strncmp(path, "a/", 2) == 0 || strncmp(path, "b/", 2) == 0) 773 | && (util_is_file(path + 2, NULL, NULL) || util_is_dir(path + 2)) 774 | ) { 775 | fix_nudge = 2; 776 | fix_len -= 2; 777 | goto _bview_fix_path_ret; 778 | } 779 | 780 | // Path valid if we extract line num after colon? 781 | if ((colon = strrchr(path, ':')) != NULL) { 782 | tmp_len = colon - path; 783 | tmp = strndup(path, tmp_len); 784 | is_valid = util_is_file(tmp, NULL, NULL) ? 1 : 0; 785 | free(tmp); 786 | 787 | if (is_valid) { 788 | fix_len = tmp_len; 789 | line_num = strtoul(colon + 1, NULL, 10); 790 | goto _bview_fix_path_ret; 791 | } 792 | } 793 | 794 | // Path valid if we strip "[ab]/" prefix and extract line num? 795 | if (path_len >= 3 796 | && (strncmp(path, "a/", 2) == 0 || strncmp(path, "b/", 2) == 0) 797 | && (colon = strrchr(path, ':')) != NULL 798 | ) { 799 | tmp_len = (colon - path) - 2; 800 | tmp = strndup(path + 2, tmp_len); 801 | is_valid = util_is_file(tmp, NULL, NULL) ? 1 : 0; 802 | free(tmp); 803 | 804 | if (is_valid) { 805 | fix_nudge = 2; 806 | fix_len = tmp_len; 807 | line_num = strtoul(colon + 1, NULL, 10); 808 | goto _bview_fix_path_ret; 809 | } 810 | } 811 | 812 | _bview_fix_path_ret: 813 | *ret_path = strndup(path + fix_nudge, fix_len); 814 | *ret_path_len = strlen(*ret_path); 815 | *ret_line_num = line_num > 0 ? line_num - 1 : 0; 816 | } 817 | 818 | // Open a buffer with an optional path to load, otherwise empty 819 | static buffer_t* _bview_open_buffer(bview_t* self, char* opt_path, int opt_path_len) { 820 | buffer_t* buffer; 821 | int has_path; 822 | char* fix_path; 823 | char* exp_path; 824 | int fix_path_len; 825 | int exp_path_len; 826 | bint_t startup_line_num; 827 | 828 | buffer = NULL; 829 | has_path = opt_path && opt_path_len > 0 ? 1 : 0; 830 | 831 | if (has_path) { 832 | util_expand_tilde(opt_path, opt_path_len, &exp_path); 833 | exp_path_len = strlen(exp_path); 834 | 835 | _bview_fix_path(self, exp_path, exp_path_len, &fix_path, &fix_path_len, &startup_line_num); 836 | buffer = buffer_new_open(fix_path); 837 | 838 | if (buffer) self->startup_linenum = startup_line_num; 839 | 840 | free(fix_path); 841 | free(exp_path); 842 | } 843 | 844 | if (!buffer) { 845 | buffer = buffer_new(); 846 | 847 | if (has_path) { 848 | buffer->path = strndup(opt_path, opt_path_len); 849 | } 850 | } 851 | 852 | buffer_set_callback(buffer, _bview_buffer_callback, self); 853 | _bview_set_tab_width(self, self->tab_width); 854 | return buffer; 855 | } 856 | 857 | static void _bview_draw_prompt(bview_t* self) { 858 | _bview_draw_bline(self, self->buffer->first_line, 0, NULL, NULL); 859 | } 860 | 861 | static void _bview_draw_status(bview_t* self) { 862 | editor_t* editor; 863 | bview_t* active; 864 | bview_t* active_edit; 865 | mark_t* mark; 866 | 867 | editor = self->editor; 868 | active = editor->active; 869 | active_edit = editor->active_edit; 870 | mark = active_edit->active_cursor->mark; 871 | 872 | // Prompt 873 | if (active == editor->prompt) { 874 | rect_printf(editor->rect_status, 0, 0, PROMPT_FG, PROMPT_BG, "%-*.*s -- ", editor->rect_status.w, editor->rect_status.w, 875 | self->editor->prompt->prompt_str); 876 | goto _bview_draw_status_end; 877 | } 878 | 879 | // Macro indicator 880 | int i_macro_fg, i_macro_bg; 881 | char* i_macro; 882 | 883 | if (editor->is_recording_macro) { 884 | i_macro_fg = MACRO_RECORDING_FG; 885 | i_macro_bg = MACRO_RECORDING_BG; 886 | i_macro = "r"; 887 | 888 | } else if (editor->macro_apply) { 889 | i_macro_fg = MACRO_PLAYING_FG; 890 | i_macro_bg = MACRO_PLAYING_BG; 891 | i_macro = "p"; 892 | 893 | } else { 894 | i_macro_fg = 0; 895 | i_macro_bg = 0; 896 | i_macro = "."; 897 | } 898 | 899 | // Anchor indicator 900 | int i_anchor_fg, i_anchor_bg; 901 | char* i_anchor; 902 | 903 | if (active_edit->active_cursor->is_anchored) { 904 | i_anchor_fg = ANCHOR_FG; 905 | i_anchor_bg = ANCHOR_BG; 906 | i_anchor = "\xe2\x97\xa8"; 907 | 908 | } else { 909 | i_anchor_fg = 0; 910 | i_anchor_bg = 0; 911 | i_anchor = "."; 912 | } 913 | 914 | // Async indicator 915 | int i_async_fg, i_async_bg; 916 | char* i_async; 917 | 918 | if (editor->async_procs) { 919 | i_async_fg = ASYNC_FG; 920 | i_async_bg = ASYNC_BG; 921 | i_async = "x"; 922 | 923 | } else { 924 | i_async_fg = 0; 925 | i_async_bg = 0; 926 | i_async = "."; 927 | } 928 | 929 | // Need-more-input icon 930 | int i_needinput_fg; 931 | int i_needinput_bg; 932 | char* i_needinput; 933 | 934 | if (editor->loop_ctx->need_more_input) { 935 | i_needinput_fg = NEEDINPUT_FG; 936 | i_needinput_bg = NEEDINPUT_BG; 937 | i_needinput = "n"; 938 | 939 | } else { 940 | i_needinput_fg = 0; 941 | i_needinput_bg = 0; 942 | i_needinput = "."; 943 | } 944 | 945 | // Render status line 946 | MLBUF_BLINE_ENSURE_CHARS(mark->bline); 947 | rect_printf(editor->rect_status, 0, 0, 0, RECT_STATUS_BG, "%*.*s", editor->rect_status.w, editor->rect_status.w, " "); 948 | rect_printf_attr(editor->rect_status, 0, 0, 949 | // "@%d,%d;%s@%d,%d;" // eon_normal mode 950 | " (@%d,%d;%s@%d,%d;%s@%d,%d;%s@%d,%d;%s@%d,%d;) " // (....) need_input,anchor,macro,async 951 | " [@%d,%d;%s@%d,%d;] " // syntax 952 | " [@%d,%d;%s@%d,%d;] " // mouse on/off 953 | " line: @%d,%d;%llu@%d,%d;/@%d,%d;%llu@%d,%d; " // line:1/100 line 954 | " col: @%d,%d;%llu@%d,%d;/@%d,%d;%llu@%d,%d; ", // col:0/80 col 955 | // MODE_FG, 0, active->kmap_tail->kmap->name, 0, 0, 956 | i_needinput_fg, i_needinput_bg, i_needinput, 957 | i_anchor_fg, i_anchor_bg, i_anchor, 958 | i_macro_fg, i_macro_bg, i_macro, 959 | i_async_fg, i_async_bg, i_async, 0, 0, 960 | SYNTAX_FG, 0, active_edit->syntax ? active_edit->syntax->name : "none", 0, 0, 961 | MOUSE_STATUS_FG, 0, editor->no_mouse ? "mouse off" : "mouse on", 0, 0, 962 | LINECOL_CURRENT_FG, 0, mark->bline->line_index + 1, 0, 0, LINECOL_TOTAL_FG, 0, active_edit->buffer->line_count, 0, 0, 963 | LINECOL_CURRENT_FG, 0, mark->col, 0, 0, LINECOL_TOTAL_FG, 0, mark->bline->char_count, 0, 0 964 | ); 965 | 966 | rect_printf(editor->rect_status, editor->rect_status.w - 11, 0, TB_WHITE | TB_BOLD, RECT_STATUS_BG, " eon %s", EON_VERSION); 967 | 968 | // Overlay errstr if present 969 | _bview_draw_status_end: 970 | 971 | if (editor->errstr[0] != '\0') { 972 | int errstrlen = strlen(editor->errstr) + 5; // Add 5 for "err! " 973 | rect_printf(editor->rect_status, editor->rect_status.w - errstrlen, 0, ERROR_FG, ERROR_BG, "err! %s", editor->errstr); 974 | editor->errstr[0] = '\0'; // Clear errstr 975 | 976 | } else if (editor->infostr[0] != '\0') { 977 | int infostrlen = strlen(editor->infostr); 978 | rect_printf(editor->rect_status, editor->rect_status.w - infostrlen, 0, INFO_FG, INFO_BG, "%s", editor->infostr); 979 | editor->infostr[0] = '\0'; // Clear errstr 980 | } 981 | } 982 | 983 | static void _bview_draw_edit(bview_t* self, int x, int y, int w, int h) { 984 | int split_w; 985 | int split_h; 986 | int min_w; 987 | int min_h; 988 | int rect_y; 989 | int fg_attr; 990 | int bg_attr; 991 | bline_t* bline; 992 | 993 | // Handle split 994 | if (self->split_child) { 995 | 996 | // Calc split dimensions 997 | if (self->split_is_vertical) { 998 | split_w = w - (int)((float)w * self->split_factor); 999 | split_h = h; 1000 | 1001 | } else { 1002 | split_w = w; 1003 | split_h = h - (int)((float)h * self->split_factor); 1004 | } 1005 | 1006 | // Draw child 1007 | _bview_draw_edit(self->split_child, x + (w - split_w), y + (h - split_h), split_w, split_h); 1008 | 1009 | // Continue drawing self minus split dimensions 1010 | w -= (w - split_w); 1011 | h -= (h - split_h); 1012 | } 1013 | 1014 | // Calc min dimensions 1015 | min_w = self->linenum_width + 3; 1016 | min_h = 2; 1017 | 1018 | // Ensure renderable 1019 | if (w < min_w || h < min_h 1020 | || x + w > self->editor->w 1021 | || y + h > self->editor->h 1022 | ) { 1023 | return; 1024 | } 1025 | 1026 | bview_t* bview_tmp; 1027 | int bview_count = 0; 1028 | int offset = 0; 1029 | char * desc; 1030 | 1031 | // render titlebar/tabs 1032 | CDL_FOREACH2(self->editor->all_bviews, bview_tmp, all_next) { 1033 | 1034 | // TODO: find out if this can be optimized 1035 | if (EON_BVIEW_IS_EDIT(bview_tmp) && ((self->split_parent && bview_tmp == self) || (!self->split_parent && !bview_tmp->split_parent && self->split_parent != bview_tmp))) { 1036 | bview_count += 1; 1037 | 1038 | if (bview_tmp == self->editor->active_edit) { 1039 | fg_attr = CAPTION_ACTIVE_FG; 1040 | bg_attr = CAPTION_ACTIVE_BG; 1041 | 1042 | } else { 1043 | fg_attr = CAPTION_INACTIVE_FG; 1044 | bg_attr = CAPTION_INACTIVE_BG; 1045 | } 1046 | 1047 | desc = bview_tmp->buffer && bview_tmp->buffer->path 1048 | ? basename(bview_tmp->buffer->path) 1049 | : EON_BVIEW_IS_MENU(bview_tmp) ? "Results" : "Untitled"; 1050 | 1051 | if (offset + self->editor->bview_tab_width <= w) { 1052 | rect_printf(self->rect_caption, offset, 0, fg_attr, bg_attr, "%*.*s", 1053 | self->rect_caption.w, self->rect_caption.w, " "); 1054 | 1055 | rect_printf(self->rect_caption, offset, 0, fg_attr, bg_attr, " [%d] %s %c", 1056 | bview_count, desc, !EON_BVIEW_IS_MENU(bview_tmp) && bview_tmp->buffer->is_unsaved ? '*' : ' '); 1057 | 1058 | offset += self->editor->bview_tab_width; 1059 | } 1060 | } 1061 | } 1062 | 1063 | // Render lines and margins 1064 | if (!self->viewport_bline) { 1065 | buffer_get_bline(self->buffer, EON_MAX(0, self->viewport_y), &self->viewport_bline); 1066 | } 1067 | 1068 | bline = self->viewport_bline; 1069 | 1070 | for (rect_y = 0; rect_y < self->rect_buffer.h; rect_y++) { 1071 | if (self->viewport_y + rect_y < 0 || self->viewport_y + rect_y >= self->buffer->line_count || !bline) { // "|| !bline" See TODOs below 1072 | // Draw pre/post blank 1073 | rect_printf(self->rect_lines, 0, rect_y, 0, 0, "%*c", self->linenum_width, ' '); 1074 | rect_printf(self->rect_margin_left, 0, rect_y, 0, 0, "%c", ' '); 1075 | rect_printf(self->rect_margin_right, 0, rect_y, 0, 0, "%c", ' '); 1076 | rect_printf(self->rect_buffer, 0, rect_y, 0, 0, "%-*.*s", self->rect_buffer.w, self->rect_buffer.w, " "); 1077 | 1078 | } else { 1079 | // Draw bline at self->rect_buffer self->viewport_y + rect_y 1080 | // TODO How can bline be NULL here? 1081 | // TODO How can self->viewport_y != self->viewport_bline->line_index ? 1082 | _bview_draw_bline(self, bline, rect_y, &bline, &rect_y); 1083 | bline = bline->next; 1084 | } 1085 | } 1086 | } 1087 | 1088 | static void _bview_draw_bline(bview_t* self, bline_t* bline, int rect_y, bline_t** optret_bline, int* optret_rect_y) { 1089 | int rect_x; 1090 | bint_t char_col; 1091 | int fg; 1092 | int bg; 1093 | uint32_t ch; 1094 | int char_w; 1095 | bint_t viewport_x; 1096 | bint_t viewport_x_vcol; 1097 | int i; 1098 | int is_cursor_line; 1099 | int is_soft_wrap; 1100 | int orig_rect_y; 1101 | 1102 | MLBUF_BLINE_ENSURE_CHARS(bline); 1103 | 1104 | // Set is_cursor_line 1105 | is_cursor_line = self->active_cursor->mark->bline == bline ? 1 : 0; 1106 | 1107 | // Soft wrap only for current line 1108 | is_soft_wrap = self->editor->soft_wrap && is_cursor_line && EON_BVIEW_IS_EDIT(self) ? 1 : 0; 1109 | 1110 | // Use viewport_x only for current line when not soft wrapping 1111 | viewport_x = 0; 1112 | viewport_x_vcol = 0; 1113 | 1114 | if (is_cursor_line && !is_soft_wrap) { 1115 | viewport_x = self->viewport_x; 1116 | viewport_x_vcol = self->viewport_x_vcol; 1117 | } 1118 | 1119 | // Draw linenums and margins 1120 | if (EON_BVIEW_IS_EDIT(self)) { 1121 | if (self->editor->linenum_type != EON_LINENUM_TYPE_NONE) { 1122 | 1123 | int linenum_fg = is_cursor_line ? LINENUM_FG_CURSOR : LINENUM_FG; 1124 | 1125 | if (self->editor->linenum_type == EON_LINENUM_TYPE_ABS 1126 | || self->editor->linenum_type == EON_LINENUM_TYPE_BOTH 1127 | || (self->editor->linenum_type == EON_LINENUM_TYPE_REL && is_cursor_line)) { 1128 | 1129 | rect_printf(self->rect_lines, 0, rect_y, linenum_fg, LINENUM_BG, "%*d", self->abs_linenum_width, (int)(bline->line_index + 1) % (int)pow(10, self->linenum_width)); 1130 | 1131 | if (self->editor->linenum_type == EON_LINENUM_TYPE_BOTH) { 1132 | rect_printf(self->rect_lines, self->abs_linenum_width, rect_y, linenum_fg, LINENUM_BG, " %*d", self->rel_linenum_width, (int)labs(bline->line_index - self->active_cursor->mark->bline->line_index)); 1133 | } 1134 | 1135 | } else if (self->editor->linenum_type == EON_LINENUM_TYPE_REL) { 1136 | rect_printf(self->rect_lines, 0, rect_y, linenum_fg, LINENUM_BG, "%*d", self->rel_linenum_width, (int)labs(bline->line_index - self->active_cursor->mark->bline->line_index)); 1137 | } 1138 | 1139 | rect_printf(self->rect_margin_left, 0, rect_y, 0, 0, "%c", viewport_x > 0 && bline->char_count > 0 ? '^' : ' '); 1140 | } 1141 | 1142 | if (!is_soft_wrap && bline->char_vwidth - viewport_x_vcol > self->rect_buffer.w) { 1143 | rect_printf(self->rect_margin_right, 0, rect_y, 0, 0, "%c", '$'); 1144 | } 1145 | } 1146 | 1147 | // Render 0 thru rect_buffer.w cell by cell 1148 | orig_rect_y = rect_y; 1149 | rect_x = 0; 1150 | char_col = viewport_x; 1151 | 1152 | while (1) { 1153 | char_w = 1; 1154 | 1155 | if (char_col < bline->char_count) { 1156 | ch = bline->chars[char_col].ch; 1157 | fg = bline->chars[char_col].style.fg; 1158 | bg = bline->bg > 0 ? bline->bg : bline->chars[char_col].style.bg; 1159 | char_w = char_col == bline->char_count - 1 1160 | ? bline->char_vwidth - bline->chars[char_col].vcol 1161 | : bline->chars[char_col + 1].vcol - bline->chars[char_col].vcol; 1162 | 1163 | if (ch == '\t') { 1164 | ch = ' '; 1165 | 1166 | } else if (ch == TB_KEY_ESC) { 1167 | ch = '['; 1168 | 1169 | } else if (!iswprint(ch) && !iswalpha(ch)) { 1170 | ch = '?'; 1171 | } 1172 | 1173 | if (self->editor->color_col == char_col && EON_BVIEW_IS_EDIT(self)) { 1174 | bg |= CURSOR_BG; 1175 | } 1176 | 1177 | } else { 1178 | break; 1179 | } 1180 | 1181 | if (EON_BVIEW_IS_MENU(self) && is_cursor_line) { 1182 | bg |= MENU_CURSOR_LINE_BG; 1183 | 1184 | } else if (EON_BVIEW_IS_PROMPT(self)) { 1185 | bg = PROMPT_BG; 1186 | } 1187 | 1188 | for (i = 0; i < char_w && rect_x < self->rect_buffer.w; i++) { 1189 | tb_char(self->rect_buffer.x + rect_x + i, self->rect_buffer.y + rect_y, fg, bg, ch); 1190 | } 1191 | 1192 | if (is_soft_wrap && rect_x + 1 >= self->rect_buffer.w && rect_y + 1 < self->rect_buffer.h) { 1193 | rect_x = 0; 1194 | rect_y += 1; 1195 | 1196 | if (self->editor->linenum_type != EON_LINENUM_TYPE_NONE) { 1197 | for (i = 0; i < self->linenum_width; i++) { 1198 | rect_printf(self->rect_lines, i, rect_y, 0, 0, "%c", '.'); 1199 | } 1200 | } 1201 | 1202 | } else { 1203 | rect_x += char_w; 1204 | } 1205 | 1206 | char_col += 1; 1207 | } 1208 | 1209 | for (i = orig_rect_y; i < rect_y && bline->next; i++) { 1210 | bline = bline->next; 1211 | } 1212 | 1213 | if (optret_bline) *optret_bline = bline; 1214 | if (optret_rect_y) *optret_rect_y = rect_y; 1215 | } 1216 | 1217 | // Highlight matching bracket pair under mark 1218 | static void _bview_highlight_bracket_pair(bview_t* self, mark_t* mark) { 1219 | bline_t* line; 1220 | bint_t brkt; 1221 | bint_t col; 1222 | mark_t pair; 1223 | int screen_x; 1224 | int screen_y; 1225 | struct tb_cell* cell; 1226 | 1227 | MLBUF_BLINE_ENSURE_CHARS(mark->bline); 1228 | 1229 | if (mark_is_at_eol(mark) || !util_get_bracket_pair(mark->bline->chars[mark->col].ch, NULL)) { 1230 | // Not a bracket 1231 | return; 1232 | } 1233 | 1234 | if (mark_find_bracket_pair(mark, EON_BRACKET_PAIR_MAX_SEARCH, &line, &col, &brkt) != MLBUF_OK) { 1235 | // No pair found 1236 | return; 1237 | } 1238 | 1239 | if (mark->bline == line && (mark->col == col - 1 || mark->col == col + 1)) { 1240 | // One char away, do not highlight (looks confusing in UI) 1241 | return; 1242 | } 1243 | 1244 | pair.bline = line; 1245 | pair.col = col; 1246 | 1247 | if (bview_get_screen_coords(self, &pair, &screen_x, &screen_y, &cell) != EON_OK) { 1248 | // Out of bounds 1249 | return; 1250 | } 1251 | 1252 | tb_char(screen_x, screen_y, cell->fg, cell->bg | BRACKET_HIGHLIGHT, cell->ch); // TODO configurable 1253 | } 1254 | 1255 | // Find screen coordinates for a mark 1256 | int bview_get_screen_coords(bview_t* self, mark_t* mark, int* ret_x, int* ret_y, struct tb_cell** optret_cell) { 1257 | int screen_x; 1258 | int screen_y; 1259 | int is_soft_wrapped; 1260 | 1261 | MLBUF_BLINE_ENSURE_CHARS(mark->bline); 1262 | 1263 | is_soft_wrapped = self->editor->soft_wrap 1264 | && self->active_cursor->mark->bline == mark->bline 1265 | && EON_BVIEW_IS_EDIT(self) ? 1 : 0; 1266 | 1267 | if (is_soft_wrapped) { 1268 | screen_x = self->rect_buffer.x + EON_MARK_COL_TO_VCOL(mark) % self->rect_buffer.w; 1269 | screen_y = self->rect_buffer.y + (mark->bline->line_index - self->viewport_bline->line_index) + (EON_MARK_COL_TO_VCOL(mark) / self->rect_buffer.w); 1270 | 1271 | } else { 1272 | screen_x = self->rect_buffer.x + EON_MARK_COL_TO_VCOL(mark) - EON_COL_TO_VCOL(mark->bline, self->viewport_x, mark->bline->char_vwidth); 1273 | screen_y = self->rect_buffer.y + (mark->bline->line_index - self->viewport_bline->line_index); 1274 | } 1275 | 1276 | if (screen_x < self->rect_buffer.x || screen_x >= self->rect_buffer.x + self->rect_buffer.w 1277 | || screen_y < self->rect_buffer.y || screen_y >= self->rect_buffer.y + self->rect_buffer.h 1278 | ) { 1279 | // Out of bounds 1280 | return EON_ERR; 1281 | } 1282 | 1283 | *ret_x = screen_x; 1284 | *ret_y = screen_y; 1285 | 1286 | if (optret_cell) { 1287 | *optret_cell = tb_cell_buffer() + (ptrdiff_t)(tb_width() * screen_y + screen_x); 1288 | } 1289 | 1290 | return EON_OK; 1291 | } 1292 | -------------------------------------------------------------------------------- /src/colors.h: -------------------------------------------------------------------------------- 1 | #define MACRO_RECORDING_FG (TB_RED | TB_BOLD) 2 | #define MACRO_RECORDING_BG TB_BLACK 3 | 4 | #define MACRO_PLAYING_FG (TB_GREEN | TB_BOLD) 5 | #define MACRO_PLAYING_BG TB_BLACK 6 | 7 | #define ANCHOR_FG (TB_WHITE | TB_BOLD) 8 | #define ANCHOR_BG TB_BLACK 9 | 10 | #define ASYNC_FG (TB_YELLOW | TB_BOLD) 11 | #define ASYNC_BG TB_BLACK 12 | 13 | #define NEEDINPUT_FG (TB_BLUE | TB_BOLD) 14 | #define NEEDINPUT_BG TB_BLACK 15 | 16 | #define ERROR_FG (TB_WHITE | TB_BOLD) 17 | #define ERROR_BG TB_RED 18 | 19 | #define INFO_FG TB_WHITE 20 | #define INFO_BG TB_DEFAULT 21 | 22 | #define MODE_FG TB_LIGHT_MAGENTA 23 | #define SYNTAX_FG TB_LIGHT_CYAN 24 | #define MOUSE_STATUS_FG TB_LIGHTEST_GREY 25 | 26 | #define LINECOL_CURRENT_FG TB_LIGHT_YELLOW 27 | #define LINECOL_TOTAL_FG TB_YELLOW 28 | 29 | #define CURSOR_BG TB_RED 30 | #define ASLEEP_CURSOR_BG TB_RED 31 | #define AWAKE_CURSOR_BG TB_CYAN 32 | #define MENU_CURSOR_LINE_BG TB_REVERSE 33 | 34 | #define BRACKET_HIGHLIGHT TB_REVERSE 35 | 36 | // #define RECT_CAPTION_FG TB_DARK_GREY 37 | // #define RECT_CAPTION_BG TB_BLACK 38 | 39 | #define RECT_LINES_FG TB_CYAN 40 | #define RECT_LINES_BG TB_DEFAULT // TB_BLACK 41 | 42 | #define RECT_MARGIN_LEFT_FG TB_RED 43 | #define RECT_MARGIN_RIGHT_FG TB_RED 44 | 45 | #define RECT_STATUS_FG TB_LIGHT_GREY 46 | #define RECT_STATUS_BG TB_BLACK 47 | 48 | // prompt window 49 | #define PROMPT_FG (TB_GREEN | TB_BOLD) 50 | #define PROMPT_BG TB_BLACK 51 | 52 | // active tab 53 | #define CAPTION_ACTIVE_FG TB_WHITE 54 | #define CAPTION_ACTIVE_BG TB_BLUE 55 | 56 | // inactive tab 57 | #define CAPTION_INACTIVE_FG (TB_DEFAULT | TB_REVERSE) 58 | #define CAPTION_INACTIVE_BG (TB_DEFAULT | TB_REVERSE) 59 | 60 | #define LINENUM_FG TB_DEFAULT 61 | #define LINENUM_FG_CURSOR TB_BOLD 62 | #define LINENUM_BG TB_DEFAULT 63 | 64 | // syntax highlighting 65 | #define KEYWORD_FG TB_BLUE 66 | #define KEYWORD_BG TB_DEFAULT 67 | 68 | #define PUNCTUATION_FG (TB_BLUE | TB_BOLD) 69 | #define PUNCTUATION_BG TB_DEFAULT 70 | 71 | #define SINGLE_QUOTE_STRING_FG (TB_YELLOW | TB_BOLD) 72 | #define SINGLE_QUOTE_STRING_BG TB_DEFAULT 73 | 74 | #define DOUBLE_QUOTE_STRING_FG (TB_YELLOW | TB_BOLD) 75 | #define DOUBLE_QUOTE_STRING_BG TB_DEFAULT 76 | 77 | #define CONSTANTS_FG (TB_MAGENTA | TB_BOLD) 78 | #define CONSTANTS_BG TB_DEFAULT 79 | 80 | #define VARIABLES_FG TB_MAGENTA 81 | #define VARIABLES_BG TB_DEFAULT 82 | 83 | #define BOOLS_INTS_FG (TB_BLUE | TB_BOLD) 84 | #define BOOLS_INTS_BG TB_DEFAULT 85 | 86 | #define REGEX_FG TB_YELLOW 87 | #define REGEX_BG TB_DEFAULT 88 | 89 | #define COMMENT_FG TB_CYAN 90 | #define COMMENT_BG TB_DEFAULT 91 | 92 | #define CODE_TAG_FG TB_GREEN 93 | #define CODE_TAG_BG TB_DEFAULT 94 | 95 | #define CODE_BLOCK_FG TB_WHITE 96 | #define CODE_BLOCK_BG TB_DEFAULT 97 | 98 | #define TRIPLE_QUOTE_COMMENT_FG (TB_YELLOW | TB_BOLD) 99 | #define TRIPLE_QUOTE_COMMENT_BG TB_DEFAULT 100 | 101 | #define TAB_WHITESPACE_FG (TB_RED | TB_UNDERLINE) 102 | #define TAB_WHITESPACE_BG TB_DEFAULT 103 | 104 | #define WHITESPACE_FG TB_DEFAULT 105 | #define WHITESPACE_BG TB_YELLOW -------------------------------------------------------------------------------- /src/cursor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "eon.h" 4 | 5 | char* shared_cutbuf; 6 | 7 | // Clone cursor 8 | int cursor_clone(cursor_t* cursor, int use_srules, cursor_t** ret_clone) { 9 | cursor_t* clone; 10 | bview_add_cursor(cursor->bview, cursor->mark->bline, cursor->mark->col, &clone); 11 | 12 | if (cursor->is_anchored) { 13 | cursor_toggle_anchor(clone, use_srules); 14 | mark_join(clone->anchor, cursor->anchor); 15 | } 16 | 17 | *ret_clone = clone; 18 | return EON_OK; 19 | } 20 | 21 | // Remove cursor 22 | int cursor_destroy(cursor_t* cursor) { 23 | return bview_remove_cursor(cursor->bview, cursor); 24 | } 25 | 26 | // Select by mark 27 | int cursor_select_between(cursor_t* cursor, mark_t* a, mark_t* b, int use_srules) { 28 | cursor_drop_anchor(cursor, use_srules); 29 | 30 | if (mark_is_lt(a, b)) { 31 | mark_join(cursor->mark, a); 32 | mark_join(cursor->anchor, b); 33 | 34 | } else { 35 | mark_join(cursor->mark, b); 36 | mark_join(cursor->anchor, a); 37 | } 38 | 39 | return EON_OK; 40 | } 41 | 42 | // Toggle cursor anchor 43 | int cursor_toggle_anchor(cursor_t* cursor, int use_srules) { 44 | // TODO: check if this fixes the segfault when closing tabs 45 | if (cursor == NULL) return EON_OK; 46 | 47 | if (!cursor->is_anchored) { 48 | mark_clone(cursor->mark, &(cursor->anchor)); 49 | 50 | if (use_srules) { 51 | cursor->sel_rule = srule_new_range(cursor->mark, cursor->anchor, 0, TB_REVERSE); 52 | buffer_add_srule(cursor->bview->buffer, cursor->sel_rule, EON_MAX(cursor->mark->bline->line_index - 50, 0), 100); 53 | } 54 | 55 | cursor->is_anchored = 1; 56 | 57 | } else { 58 | if (use_srules && cursor->sel_rule) { 59 | buffer_remove_srule(cursor->bview->buffer, cursor->sel_rule, EON_MAX(cursor->mark->bline->line_index - 50, 0), 100); 60 | srule_destroy(cursor->sel_rule); 61 | cursor->sel_rule = NULL; 62 | } 63 | 64 | mark_destroy(cursor->anchor); 65 | cursor->is_anchored = 0; 66 | } 67 | 68 | return EON_OK; 69 | } 70 | 71 | // Drop cursor anchor 72 | int cursor_drop_anchor(cursor_t* cursor, int use_srules) { 73 | if (cursor->is_anchored) return EON_OK; 74 | 75 | return cursor_toggle_anchor(cursor, use_srules); 76 | } 77 | 78 | // Lift cursor anchor 79 | int cursor_lift_anchor(cursor_t* cursor) { 80 | if (!cursor->is_anchored) return EON_OK; 81 | 82 | return cursor_toggle_anchor(cursor, cursor->sel_rule ? 1 : 0); 83 | } 84 | 85 | // Get lo and hi marks in a is_anchored=1 cursor 86 | int cursor_get_lo_hi(cursor_t* cursor, mark_t** ret_lo, mark_t** ret_hi) { 87 | if (!cursor->is_anchored) { 88 | return EON_ERR; 89 | } 90 | 91 | if (mark_is_gt(cursor->anchor, cursor->mark)) { 92 | *ret_lo = cursor->mark; 93 | *ret_hi = cursor->anchor; 94 | 95 | } else { 96 | *ret_lo = cursor->anchor; 97 | *ret_hi = cursor->mark; 98 | } 99 | 100 | return EON_OK; 101 | } 102 | 103 | // Make selection by strat 104 | int cursor_select_by(cursor_t* cursor, const char* strat) { 105 | if (cursor->is_anchored) { 106 | return EON_ERR; 107 | } 108 | 109 | if (strcmp(strat, "bracket") == 0) { 110 | return cursor_select_by_bracket(cursor); 111 | 112 | } else if (strcmp(strat, "word") == 0) { 113 | return cursor_select_by_word(cursor); 114 | 115 | } else if (strcmp(strat, "word_back") == 0) { 116 | return cursor_select_by_word_back(cursor); 117 | 118 | } else if (strcmp(strat, "word_forward") == 0) { 119 | return cursor_select_by_word_forward(cursor); 120 | 121 | } else if (strcmp(strat, "eol") == 0 && !mark_is_at_eol(cursor->mark)) { 122 | cursor_toggle_anchor(cursor, 0); 123 | mark_move_eol(cursor->anchor); 124 | 125 | } else if (strcmp(strat, "bol") == 0 && !mark_is_at_bol(cursor->mark)) { 126 | cursor_toggle_anchor(cursor, 0); 127 | mark_move_bol(cursor->anchor); 128 | 129 | } else if (strcmp(strat, "string") == 0) { 130 | return cursor_select_by_string(cursor); 131 | 132 | } else { 133 | EON_RETURN_ERR(cursor->bview->editor, "Unrecognized cursor_select_by strat '%s'", strat); 134 | } 135 | 136 | return EON_OK; 137 | } 138 | 139 | // Select by bracket 140 | int cursor_select_by_bracket(cursor_t* cursor) { 141 | mark_t* orig; 142 | mark_clone(cursor->mark, &orig); 143 | 144 | if (mark_move_bracket_top(cursor->mark, EON_BRACKET_PAIR_MAX_SEARCH) != MLBUF_OK) { 145 | mark_destroy(orig); 146 | return EON_ERR; 147 | } 148 | 149 | cursor_toggle_anchor(cursor, 0); 150 | 151 | if (mark_move_bracket_pair(cursor->anchor, EON_BRACKET_PAIR_MAX_SEARCH) != MLBUF_OK) { 152 | cursor_toggle_anchor(cursor, 0); 153 | mark_join(cursor->mark, orig); 154 | mark_destroy(orig); 155 | return EON_ERR; 156 | } 157 | 158 | mark_move_by(cursor->mark, 1); 159 | mark_destroy(orig); 160 | return EON_OK; 161 | } 162 | 163 | // Select by word-back 164 | int cursor_select_by_word_back(cursor_t* cursor) { 165 | if (mark_is_at_word_bound(cursor->mark, -1)) return EON_ERR; 166 | 167 | cursor_toggle_anchor(cursor, 0); 168 | mark_move_prev_re(cursor->mark, EON_RE_WORD_BACK, sizeof(EON_RE_WORD_BACK) - 1); 169 | return EON_OK; 170 | } 171 | 172 | // Select by word-forward 173 | int cursor_select_by_word_forward(cursor_t* cursor) { 174 | if (mark_is_at_word_bound(cursor->mark, 1)) return EON_ERR; 175 | 176 | cursor_toggle_anchor(cursor, 0); 177 | mark_move_next_re(cursor->mark, EON_RE_WORD_FORWARD, sizeof(EON_RE_WORD_FORWARD) - 1); 178 | return EON_OK; 179 | } 180 | 181 | // Select by string 182 | int cursor_select_by_string(cursor_t* cursor) { 183 | mark_t* orig; 184 | uint32_t qchar; 185 | char* qre; 186 | mark_clone(cursor->mark, &orig); 187 | 188 | if (mark_move_prev_re(cursor->mark, "(?mark, &qchar); 195 | mark_move_by(cursor->mark, 1); 196 | 197 | if (qchar == '"') { 198 | qre = "(?anchor, qre, strlen(qre)) != MLBUF_OK) { 208 | cursor_toggle_anchor(cursor, 0); 209 | mark_join(cursor->mark, orig); 210 | mark_destroy(orig); 211 | return EON_ERR; 212 | } 213 | 214 | mark_destroy(orig); 215 | return EON_OK; 216 | } 217 | 218 | // Select by word 219 | int cursor_select_by_word(cursor_t* cursor) { 220 | uint32_t after; 221 | 222 | if (mark_is_at_eol(cursor->mark)) return EON_ERR; 223 | 224 | mark_get_char_after(cursor->mark, &after); 225 | if (!isalnum((char)after) && (char)after != '_') return EON_ERR; 226 | 227 | if (!mark_is_at_word_bound(cursor->mark, -1)) { 228 | mark_move_prev_re(cursor->mark, EON_RE_WORD_BACK, sizeof(EON_RE_WORD_BACK) - 1); 229 | } 230 | 231 | cursor_toggle_anchor(cursor, 0); 232 | mark_move_next_re(cursor->mark, EON_RE_WORD_FORWARD, sizeof(EON_RE_WORD_FORWARD) - 1); 233 | return EON_OK; 234 | } 235 | 236 | // Cut or copy text 237 | int cursor_cut_copy(cursor_t* cursor, int is_cut, int use_srules, int append) { 238 | char* cutbuf; 239 | bint_t cutbuf_len; 240 | bint_t cur_len; 241 | 242 | if (!append && cursor->cut_buffer) { 243 | free(cursor->cut_buffer); 244 | cursor->cut_buffer = NULL; 245 | } 246 | 247 | if (!cursor->is_anchored) { 248 | use_srules = 0; 249 | cursor_toggle_anchor(cursor, use_srules); 250 | mark_move_bol(cursor->mark); 251 | mark_move_eol(cursor->anchor); 252 | mark_move_by(cursor->anchor, 1); 253 | } 254 | 255 | mark_get_between_mark(cursor->mark, cursor->anchor, &cutbuf, &cutbuf_len); 256 | 257 | if (append && cursor->cut_buffer) { 258 | cur_len = strlen(cursor->cut_buffer); 259 | 260 | // cursor buf 261 | cursor->cut_buffer = realloc(cursor->cut_buffer, cur_len + cutbuf_len + 1); 262 | strncat(cursor->cut_buffer, cutbuf, cutbuf_len); 263 | 264 | // shared buf 265 | shared_cutbuf = realloc(shared_cutbuf, cur_len + cutbuf_len + 1); 266 | strncat(shared_cutbuf, cutbuf, cutbuf_len); 267 | 268 | free(cutbuf); 269 | 270 | } else { 271 | 272 | cursor->cut_buffer = cutbuf; 273 | 274 | // copy to shared cutbuf 275 | free(shared_cutbuf); 276 | shared_cutbuf = malloc(cutbuf_len + 1); 277 | strcpy(shared_cutbuf, cursor->cut_buffer); 278 | } 279 | 280 | if (is_cut) { 281 | mark_delete_between_mark(cursor->mark, cursor->anchor); 282 | } 283 | 284 | cursor_toggle_anchor(cursor, use_srules); 285 | return EON_OK; 286 | } 287 | 288 | // Uncut (paste) text 289 | int cursor_uncut(cursor_t* cursor) { 290 | 291 | if (shared_cutbuf && strlen(shared_cutbuf) > 1) { 292 | mark_insert_before(cursor->mark, shared_cutbuf, strlen(shared_cutbuf)); 293 | } else if (cursor->cut_buffer) { 294 | mark_insert_before(cursor->mark, cursor->cut_buffer, strlen(cursor->cut_buffer)); 295 | } 296 | 297 | return EON_OK; 298 | } 299 | 300 | // Regex search and replace 301 | int cursor_replace(cursor_t* cursor, int interactive, char* opt_regex, char* opt_replacement) { 302 | char* regex; 303 | char* replacement; 304 | int wrapped; 305 | int all; 306 | char* yn; 307 | mark_t* lo_mark; 308 | mark_t* hi_mark; 309 | mark_t* orig_mark; 310 | mark_t* search_mark; 311 | mark_t* search_mark_end; 312 | int anchored_before; 313 | srule_t* highlight; 314 | bline_t* bline; 315 | bint_t col; 316 | bint_t char_count; 317 | int pcre_rc; 318 | int pcre_ovector[30]; 319 | str_t repl_backref = {0}; 320 | int num_replacements; 321 | 322 | if (!interactive && (!opt_regex || !opt_replacement)) { 323 | return EON_ERR; 324 | } 325 | 326 | regex = NULL; 327 | replacement = NULL; 328 | wrapped = 0; 329 | lo_mark = NULL; 330 | hi_mark = NULL; 331 | orig_mark = NULL; 332 | search_mark = NULL; 333 | search_mark_end = NULL; 334 | anchored_before = 0; 335 | all = interactive ? 0 : 1; 336 | num_replacements = 0; 337 | 338 | mark_set_pcre_capture(&pcre_rc, pcre_ovector, 30); 339 | 340 | do { 341 | if (!interactive) { 342 | regex = strdup(opt_regex); 343 | replacement = strdup(opt_replacement); 344 | 345 | } else { 346 | editor_prompt(cursor->bview->editor, "[replace] Search regex:", NULL, ®ex); 347 | 348 | if (!regex) break; 349 | 350 | editor_prompt(cursor->bview->editor, "[replace] Replacement string:", NULL, &replacement); 351 | 352 | if (!replacement) break; 353 | } 354 | 355 | orig_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); 356 | lo_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); 357 | hi_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); 358 | search_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); 359 | search_mark_end = buffer_add_mark(cursor->bview->buffer, NULL, 0); 360 | 361 | mark_join(search_mark, cursor->mark); 362 | mark_join(orig_mark, cursor->mark); 363 | 364 | orig_mark->lefty = 1; 365 | lo_mark->lefty = 1; 366 | 367 | if (cursor->is_anchored) { 368 | anchored_before = mark_is_gt(cursor->mark, cursor->anchor); 369 | mark_join(lo_mark, !anchored_before ? cursor->mark : cursor->anchor); 370 | mark_join(hi_mark, anchored_before ? cursor->mark : cursor->anchor); 371 | } else { 372 | mark_move_beginning(lo_mark); 373 | mark_move_end(hi_mark); 374 | } 375 | 376 | while (1) { 377 | pcre_rc = 0; 378 | 379 | if (mark_find_next_re(search_mark, regex, strlen(regex), &bline, &col, &char_count) == MLBUF_OK 380 | && (mark_move_to(search_mark, bline->line_index, col) == MLBUF_OK) 381 | && (mark_is_gte(search_mark, lo_mark)) 382 | && (mark_is_lt(search_mark, hi_mark)) 383 | && (!wrapped || mark_is_lt(search_mark, orig_mark)) 384 | ) { 385 | mark_move_to(search_mark_end, bline->line_index, col + char_count); 386 | mark_join(cursor->mark, search_mark); 387 | yn = NULL; 388 | 389 | if (all) { 390 | yn = EON_PROMPT_YES; 391 | 392 | } else if (interactive) { 393 | highlight = srule_new_range(search_mark, search_mark_end, 0, TB_REVERSE); 394 | buffer_add_srule(cursor->bview->buffer, highlight, 0, 100); 395 | bview_rectify_viewport(cursor->bview); 396 | bview_draw(cursor->bview); 397 | editor_prompt(cursor->bview->editor, "[replace] Go ahead and replace? (Yes/No/All)", 398 | &(editor_prompt_params_t) { .kmap = cursor->bview->editor->kmap_prompt_yna }, &yn); 399 | buffer_remove_srule(cursor->bview->buffer, highlight, 0, 100); 400 | srule_destroy(highlight); 401 | bview_draw(cursor->bview); 402 | } 403 | 404 | if (!yn) { 405 | break; 406 | 407 | } else if (0 == strcmp(yn, EON_PROMPT_YES) || 0 == strcmp(yn, EON_PROMPT_ALL)) { 408 | str_append_replace_with_backrefs(&repl_backref, search_mark->bline->data, replacement, pcre_rc, pcre_ovector, 30); 409 | mark_replace_between_mark(search_mark, search_mark_end, repl_backref.data, repl_backref.len); 410 | str_free(&repl_backref); 411 | num_replacements += 1; 412 | 413 | if (0 == strcmp(yn, EON_PROMPT_ALL)) all = 1; 414 | 415 | } else { 416 | mark_move_by(search_mark, 1); 417 | } 418 | 419 | } else if (!wrapped) { 420 | mark_join(search_mark, lo_mark); 421 | mark_move_by(search_mark, -1); 422 | wrapped = 1; 423 | 424 | } else { 425 | break; 426 | } 427 | } 428 | } while (0); 429 | 430 | if (cursor->is_anchored && lo_mark && hi_mark) { 431 | mark_join(cursor->mark, anchored_before ? hi_mark : lo_mark); 432 | mark_join(cursor->anchor, anchored_before ? lo_mark : hi_mark); 433 | } else if (orig_mark) { 434 | mark_join(cursor->mark, orig_mark); 435 | } 436 | 437 | mark_set_pcre_capture(NULL, NULL, 0); 438 | 439 | if (regex) free(regex); 440 | if (replacement) free(replacement); 441 | if (lo_mark) mark_destroy(lo_mark); 442 | if (hi_mark) mark_destroy(hi_mark); 443 | if (orig_mark) mark_destroy(orig_mark); 444 | if (search_mark) mark_destroy(search_mark); 445 | if (search_mark_end) mark_destroy(search_mark_end); 446 | 447 | if (interactive) { 448 | EON_SET_INFO(cursor->bview->editor, "replace: Replaced %d instance(s)", num_replacements); 449 | bview_rectify_viewport(cursor->bview); 450 | bview_draw(cursor->bview); 451 | } 452 | 453 | return EON_OK; 454 | } 455 | -------------------------------------------------------------------------------- /src/eon.h: -------------------------------------------------------------------------------- 1 | #ifndef __EON_H 2 | #define __EON_H 3 | 4 | #include 5 | #include 6 | #include "termbox.h" 7 | #include "uthash.h" 8 | #include "mlbuf.h" 9 | 10 | typedef struct editor_s editor_t; // A container for editor-wide globals 11 | typedef struct bview_s bview_t; // A view of a buffer 12 | typedef struct bview_rect_s bview_rect_t; // A rectangle in bview with a default styling 13 | typedef struct bview_listener_s bview_listener_t; // A listener to buffer events in a bview 14 | typedef void (*bview_listener_cb_t)(bview_t* bview, baction_t* action, void* udata); // A bview_listener_t callback 15 | typedef struct cursor_s cursor_t; // A cursor (insertion mark + selection bound mark) in a buffer 16 | typedef struct loop_context_s loop_context_t; // Context for a single _editor_loop 17 | typedef struct cmd_s cmd_t; // A command definition 18 | typedef struct cmd_context_s cmd_context_t; // Context for a single command invocation 19 | typedef struct kinput_s kinput_t; // A single key input (similar to a tb_event from termbox) 20 | typedef struct kmacro_s kmacro_t; // A sequence of kinputs and a name 21 | typedef struct kmap_s kmap_t; // A map of keychords to functions 22 | typedef struct kmap_node_s kmap_node_t; // A node in a list of keymaps 23 | typedef struct kbinding_def_s kbinding_def_t; // A definition of a keymap 24 | typedef struct kbinding_s kbinding_t; // A single binding in a keymap 25 | typedef struct syntax_s syntax_t; // A syntax definition 26 | typedef struct syntax_node_s syntax_node_t; // A node in a linked list of syntaxes 27 | typedef struct srule_def_s srule_def_t; // A definition of a syntax 28 | typedef struct async_proc_s async_proc_t; // An asynchronous process 29 | typedef void (*async_proc_cb_t)(async_proc_t* self, char* buf, size_t buf_len); // An async_proc_t callback 30 | typedef struct editor_prompt_params_s editor_prompt_params_t; // Extra params for editor_prompt 31 | typedef struct tb_event tb_event_t; // A termbox event 32 | typedef struct prompt_history_s prompt_history_t; // A map of prompt histories keyed by prompt_str 33 | typedef struct prompt_hnode_s prompt_hnode_t; // A node in a linked list of prompt history 34 | typedef int (*cmd_func_t)(cmd_context_t* ctx); // A command function 35 | typedef int (*cb_func_t)(cmd_context_t* ctx, char * action); // A command function 36 | 37 | 38 | // kinput_t 39 | struct kinput_s { 40 | uint32_t ch; 41 | uint16_t key; 42 | uint8_t meta; 43 | }; 44 | 45 | // bview_rect_t 46 | struct bview_rect_s { 47 | int x; 48 | int y; 49 | int w; 50 | int h; 51 | uint16_t fg; 52 | uint16_t bg; 53 | }; 54 | 55 | // editor_t 56 | struct editor_s { 57 | int w; 58 | int h; 59 | bview_t* top_bviews; 60 | bview_t* all_bviews; 61 | bview_t* active; 62 | bview_t* active_edit; 63 | bview_t* active_edit_root; 64 | bview_t* status; 65 | bview_t* prompt; 66 | bview_rect_t rect_edit; 67 | bview_rect_t rect_status; 68 | bview_rect_t rect_prompt; 69 | syntax_t* syntax_map; 70 | int is_display_disabled; 71 | kmacro_t* macro_map; 72 | kinput_t macro_toggle_key; 73 | kmacro_t* macro_record; 74 | kmacro_t* macro_apply; 75 | size_t macro_apply_input_index; 76 | int is_recording_macro; 77 | char* startup_macro_name; 78 | cmd_t* cmd_map; 79 | kmap_t* kmap_map; 80 | kmap_t* kmap_normal; 81 | kmap_t* kmap_prompt_input; 82 | kmap_t* kmap_prompt_yn; 83 | kmap_t* kmap_prompt_yna; 84 | kmap_t* kmap_prompt_ok; 85 | kmap_t* kmap_prompt_isearch; 86 | kmap_t* kmap_prompt_menu; 87 | kmap_t* kmap_menu; 88 | prompt_history_t* prompt_history; 89 | char* kmap_init_name; 90 | kmap_t* kmap_init; 91 | async_proc_t* async_procs; 92 | FILE* tty; 93 | int ttyfd; 94 | char* syntax_override; 95 | int linenum_type; 96 | int tab_width; 97 | int tab_to_space; 98 | int trim_paste; 99 | int smart_indent; 100 | int read_rc_file; 101 | int highlight_bracket_pairs; 102 | int color_col; 103 | int soft_wrap; 104 | int viewport_scope_x; // TODO cli option 105 | int viewport_scope_y; // TODO cli option 106 | int headless_mode; 107 | loop_context_t* loop_ctx; 108 | int loop_depth; 109 | int is_in_init; 110 | char* insertbuf; 111 | size_t insertbuf_size; 112 | #define EON_ERRSTR_SIZE 256 113 | char errstr[EON_ERRSTR_SIZE]; 114 | char infostr[EON_ERRSTR_SIZE]; 115 | int exit_code; 116 | int bview_tab_width; 117 | int no_mouse; 118 | char * start_dir; 119 | }; 120 | 121 | // srule_def_t 122 | struct srule_def_s { 123 | char* re; 124 | char* re_end; 125 | uint16_t fg; 126 | uint16_t bg; 127 | }; 128 | 129 | // syntax_node_t 130 | struct syntax_node_s { 131 | srule_t* srule; 132 | syntax_node_t* next; 133 | syntax_node_t* prev; 134 | }; 135 | 136 | // syntax_t 137 | struct syntax_s { 138 | char* name; 139 | char* path_pattern; 140 | int tab_width; 141 | int tab_to_space; 142 | srule_node_t* srules; 143 | UT_hash_handle hh; 144 | }; 145 | 146 | // bview_t 147 | struct bview_s { 148 | #define EON_BVIEW_TYPE_EDIT 0 149 | #define EON_BVIEW_TYPE_STATUS 1 150 | #define EON_BVIEW_TYPE_PROMPT 2 151 | editor_t* editor; 152 | int x; 153 | int y; 154 | int w; 155 | int h; 156 | int is_resized; 157 | int type; 158 | int linenum_width; 159 | int abs_linenum_width; 160 | int rel_linenum_width; 161 | bview_rect_t rect_caption; 162 | bview_rect_t rect_lines; 163 | bview_rect_t rect_margin_left; 164 | bview_rect_t rect_buffer; 165 | bview_rect_t rect_margin_right; 166 | buffer_t* buffer; 167 | bint_t viewport_x; 168 | bint_t viewport_x_vcol; 169 | bint_t viewport_y; 170 | bline_t* viewport_bline; 171 | int viewport_scope_x; 172 | int viewport_scope_y; 173 | bview_t* split_parent; 174 | bview_t* split_child; 175 | float split_factor; 176 | int split_is_vertical; 177 | char* prompt_str; 178 | // char* path; 179 | bint_t startup_linenum; 180 | kmap_node_t* kmap_stack; 181 | kmap_node_t* kmap_tail; 182 | cursor_t* cursors; 183 | cursor_t* active_cursor; 184 | char* last_search; 185 | srule_t* isearch_rule; 186 | int tab_width; 187 | int tab_to_space; 188 | syntax_t* syntax; 189 | async_proc_t* async_proc; 190 | cb_func_t menu_callback; 191 | int is_menu; 192 | char init_cwd[PATH_MAX + 1]; 193 | bview_listener_t* listeners; 194 | bview_t* top_next; 195 | bview_t* top_prev; 196 | bview_t* all_next; 197 | bview_t* all_prev; 198 | }; 199 | 200 | // bview_listener_t 201 | struct bview_listener_s { 202 | bview_listener_cb_t callback; 203 | void* udata; 204 | bview_listener_t* next; 205 | bview_listener_t* prev; 206 | }; 207 | 208 | // cursor_t 209 | struct cursor_s { 210 | bview_t* bview; 211 | mark_t* mark; 212 | mark_t* anchor; 213 | int is_anchored; 214 | int is_asleep; 215 | srule_t* sel_rule; 216 | char* cut_buffer; 217 | cursor_t* next; 218 | cursor_t* prev; 219 | }; 220 | 221 | // kmacro_t 222 | struct kmacro_s { 223 | char* name; 224 | kinput_t* inputs; 225 | size_t inputs_len; 226 | size_t inputs_cap; 227 | UT_hash_handle hh; 228 | }; 229 | 230 | // cmd_t 231 | struct cmd_s { 232 | char* name; 233 | cmd_func_t func; 234 | int (*func_viewport)(cmd_t* self); 235 | int (*func_init)(cmd_t* self, int is_deinit); 236 | int (*func_display)(cmd_t* self); 237 | void* udata; 238 | int is_resolved; 239 | int is_dead; 240 | UT_hash_handle hh; 241 | }; 242 | 243 | // kbinding_def_t 244 | struct kbinding_def_s { 245 | #define EON_KBINDING_DEF(pcmdname, pkeypatt) { (pcmdname), (pkeypatt), NULL } 246 | #define EON_KBINDING_DEF_EX(pcmdname, pkeypatt, pstatp) { (pcmdname), (pkeypatt), (pstatp) } 247 | char* cmd_name; 248 | char* key_patt; 249 | char* static_param; 250 | }; 251 | 252 | // kbinding_t 253 | struct kbinding_s { 254 | kinput_t input; 255 | char* cmd_name; 256 | cmd_t* cmd; 257 | char* static_param; 258 | char* key_patt; 259 | int is_leaf; 260 | kbinding_t* children; 261 | UT_hash_handle hh; 262 | }; 263 | 264 | // kmap_node_t 265 | struct kmap_node_s { 266 | kmap_t* kmap; 267 | bview_t* bview; 268 | kmap_node_t* next; 269 | kmap_node_t* prev; 270 | }; 271 | 272 | // kmap_t 273 | struct kmap_s { 274 | char* name; 275 | kbinding_t* bindings; 276 | int allow_fallthru; 277 | char* default_cmd_name; 278 | cmd_t* default_cmd; 279 | UT_hash_handle hh; 280 | }; 281 | 282 | // cmd_context_t 283 | struct cmd_context_s { 284 | #define EON_PASTEBUF_INCR 1024 285 | editor_t* editor; 286 | loop_context_t* loop_ctx; 287 | cmd_t* cmd; 288 | buffer_t* buffer; 289 | bview_t* bview; 290 | cursor_t* cursor; 291 | kinput_t input; 292 | char* static_param; 293 | int is_user_input; 294 | kinput_t* pastebuf; 295 | size_t pastebuf_len; 296 | size_t pastebuf_size; 297 | int has_pastebuf_leftover; 298 | kinput_t pastebuf_leftover; 299 | }; 300 | 301 | // loop_context_t 302 | struct loop_context_s { 303 | #define EON_LOOP_CTX_MAX_NUMERIC_LEN 20 304 | #define EON_LOOP_CTX_MAX_NUMERIC_PARAMS 8 305 | #define EON_LOOP_CTX_MAX_WILDCARD_PARAMS 8 306 | #define EON_LOOP_CTX_MAX_COMPLETE_TERM_SIZE 256 307 | bview_t* invoker; 308 | char numeric[EON_LOOP_CTX_MAX_NUMERIC_LEN + 1]; 309 | kbinding_t* numeric_node; 310 | int numeric_len; 311 | uintmax_t numeric_params[EON_LOOP_CTX_MAX_NUMERIC_PARAMS]; 312 | int numeric_params_len; 313 | uint32_t wildcard_params[EON_LOOP_CTX_MAX_WILDCARD_PARAMS]; 314 | int wildcard_params_len; 315 | kbinding_t* binding_node; 316 | int need_more_input; 317 | int should_exit; 318 | char* prompt_answer; 319 | // cmd_func_t prompt_callack; 320 | prompt_hnode_t* prompt_hnode; 321 | int tab_complete_index; 322 | char tab_complete_term[EON_LOOP_CTX_MAX_COMPLETE_TERM_SIZE]; 323 | cmd_t* last_cmd; 324 | str_t last_insert; 325 | }; 326 | 327 | // async_proc_t 328 | struct async_proc_s { 329 | editor_t* editor; 330 | void* owner; 331 | async_proc_t** owner_aproc; 332 | FILE* rpipe; 333 | FILE* wpipe; 334 | pid_t pid; 335 | int rfd; 336 | int wfd; 337 | int is_done; 338 | int is_solo; 339 | async_proc_cb_t callback; 340 | async_proc_t* next; 341 | async_proc_t* prev; 342 | }; 343 | 344 | // editor_prompt_params_t 345 | struct editor_prompt_params_s { 346 | char* data; 347 | int data_len; 348 | kmap_t* kmap; 349 | bview_listener_cb_t prompt_cb; 350 | void* prompt_cb_udata; 351 | }; 352 | 353 | // prompt_history_t 354 | struct prompt_history_s { 355 | char* prompt_str; 356 | prompt_hnode_t* prompt_hlist; 357 | UT_hash_handle hh; 358 | }; 359 | 360 | // prompt_hnode_t 361 | struct prompt_hnode_s { 362 | char* data; 363 | bint_t data_len; 364 | prompt_hnode_t* prev; 365 | prompt_hnode_t* next; 366 | }; 367 | 368 | // editor functions 369 | int editor_init(editor_t* editor, int argc, char** argv); 370 | int editor_deinit(editor_t* editor); 371 | int editor_run(editor_t* editor); 372 | int editor_bview_edit_count(editor_t* editor); 373 | int editor_close_bview(editor_t* editor, bview_t* bview, int* optret_num_closed); 374 | int editor_count_bviews_by_buffer(editor_t* editor, buffer_t* buffer); 375 | int editor_page_menu(editor_t* editor, cb_func_t callback, char* opt_buf_data, int opt_buf_data_len, async_proc_t* opt_aproc, bview_t** optret_menu); 376 | int editor_prompt_menu(editor_t* editor, cb_func_t callback, char* opt_buf_data, int opt_buf_data_len); 377 | int editor_open_bview(editor_t* editor, bview_t* parent, int type, char* opt_path, int opt_path_len, int make_active, bint_t linenum, bview_rect_t* opt_rect, buffer_t* opt_buffer, bview_t** optret_bview); 378 | int editor_prompt(editor_t* editor, char* prompt, editor_prompt_params_t* params, char** optret_answer); 379 | int editor_close_prompt(editor_t* editor, bview_t * invoker); 380 | int editor_set_prompt_str(editor_t* editor, char * str); 381 | int editor_set_active(editor_t* editor, bview_t* bview); 382 | int editor_register_cmd(editor_t* editor, cmd_t* cmd); 383 | int editor_add_binding_to_keymap(editor_t* editor, kmap_t* kmap, kbinding_def_t* binding_def); 384 | 385 | // bview functions 386 | bview_t* bview_get_split_root(bview_t* self); 387 | bview_t* bview_new(editor_t* editor, char* opt_path, int opt_path_len, buffer_t* opt_buffer); 388 | int bview_add_cursor_asleep(bview_t* self, bline_t* bline, bint_t col, cursor_t** optret_cursor); 389 | int bview_add_cursor(bview_t* self, bline_t* bline, bint_t col, cursor_t** optret_cursor); 390 | int bview_add_listener(bview_t* self, bview_listener_cb_t callback, void* udata); 391 | int bview_set_line_bg(bview_t* self, bint_t line_index, int color); 392 | int bview_move_to_line(bview_t* self, bint_t number); 393 | int bview_scroll_viewport(bview_t* self, int offset); 394 | int bview_center_viewport_y(bview_t* self); 395 | int bview_destroy(bview_t* self); 396 | int bview_destroy_listener(bview_t* self, bview_listener_t* listener); 397 | int bview_draw(bview_t* self); 398 | int bview_draw_cursor(bview_t* self, int set_real_cursor); 399 | int bview_get_active_cursor_count(bview_t* self); 400 | int bview_get_screen_coords(bview_t* self, mark_t* mark, int* ret_x, int* ret_y, struct tb_cell** optret_cell); 401 | int bview_max_viewport_y(bview_t* self); 402 | int bview_open(bview_t* self, char* path, int path_len); 403 | int bview_pop_kmap(bview_t* bview, kmap_t** optret_kmap); 404 | int bview_push_kmap(bview_t* bview, kmap_t* kmap); 405 | int bview_rectify_viewport(bview_t* self); 406 | int bview_remove_cursor(bview_t* self, cursor_t* cursor); 407 | int bview_remove_cursors_except(bview_t* self, cursor_t* one); 408 | int bview_resize(bview_t* self, int x, int y, int w, int h); 409 | int bview_set_syntax(bview_t* self, char* opt_syntax); 410 | int bview_split(bview_t* self, int is_vertical, float factor, bview_t** optret_bview); 411 | int bview_unsplit(bview_t* parent, bview_t* child); 412 | int bview_wake_sleeping_cursors(bview_t* self); 413 | int bview_zero_viewport_y(bview_t* self); 414 | 415 | // cursor functions 416 | int cursor_clone(cursor_t* cursor, int use_srules, cursor_t** ret_clone); 417 | int cursor_cut_copy(cursor_t* cursor, int is_cut, int use_srules, int append); 418 | int cursor_destroy(cursor_t* cursor); 419 | int cursor_drop_anchor(cursor_t* cursor, int use_srules); 420 | int cursor_get_lo_hi(cursor_t* cursor, mark_t** ret_lo, mark_t** ret_hi); 421 | int cursor_lift_anchor(cursor_t* cursor); 422 | int cursor_replace(cursor_t* cursor, int interactive, char* opt_regex, char* opt_replacement); 423 | int cursor_select_between(cursor_t* cursor, mark_t* a, mark_t* b, int use_srules); 424 | int cursor_select_by(cursor_t* cursor, const char* strat); 425 | int cursor_select_by_bracket(cursor_t* cursor); 426 | int cursor_select_by_string(cursor_t* cursor); 427 | int cursor_select_by_word_back(cursor_t* cursor); 428 | int cursor_select_by_word(cursor_t* cursor); 429 | int cursor_select_by_word_forward(cursor_t* cursor); 430 | int cursor_toggle_anchor(cursor_t* cursor, int use_srules); 431 | int cursor_uncut(cursor_t* cursor); 432 | 433 | // cmd functions 434 | int cmd_apply_macro_by(cmd_context_t* ctx); 435 | int cmd_apply_macro(cmd_context_t* ctx); 436 | int cmd_browse(cmd_context_t* ctx); 437 | int cmd_close(cmd_context_t* ctx); 438 | int cmd_copy_by(cmd_context_t* ctx); 439 | int cmd_copy(cmd_context_t* ctx); 440 | int cmd_ctag(cmd_context_t* ctx); 441 | int cmd_cut_by(cmd_context_t* ctx); 442 | int cmd_cut(cmd_context_t* ctx); 443 | int cmd_cut_or_close(cmd_context_t* ctx); 444 | int cmd_delete_after(cmd_context_t* ctx); 445 | int cmd_delete_before(cmd_context_t* ctx); 446 | int cmd_delete_word_after(cmd_context_t* ctx); 447 | int cmd_delete_word_before(cmd_context_t* ctx); 448 | int cmd_drop_cursor_column(cmd_context_t* ctx); 449 | int cmd_drop_sleeping_cursor(cmd_context_t* ctx); 450 | int cmd_find_word(cmd_context_t* ctx); 451 | int cmd_fsearch(cmd_context_t* ctx); 452 | int cmd_grep(cmd_context_t* ctx); 453 | int cmd_indent(cmd_context_t* ctx); 454 | int cmd_insert_data(cmd_context_t* ctx); 455 | int cmd_insert_newline_above(cmd_context_t* ctx); 456 | int cmd_insert_newline(cmd_context_t* ctx); 457 | int cmd_insert_tab(cmd_context_t* ctx); 458 | int cmd_isearch(cmd_context_t* ctx); 459 | int cmd_less(cmd_context_t* ctx); 460 | int cmd_mouse_move(cmd_context_t* ctx, int mouse_down, int x, int y); 461 | int cmd_scroll_up(cmd_context_t* ctx); 462 | int cmd_scroll_down(cmd_context_t* ctx); 463 | int cmd_move_beginning(cmd_context_t* ctx); 464 | int cmd_move_bol(cmd_context_t* ctx); 465 | int cmd_move_bracket_back(cmd_context_t* ctx); 466 | int cmd_move_bracket_forward(cmd_context_t* ctx); 467 | int cmd_move_down(cmd_context_t* ctx); 468 | int cmd_move_end(cmd_context_t* ctx); 469 | int cmd_move_eol(cmd_context_t* ctx); 470 | int cmd_move_left(cmd_context_t* ctx); 471 | int cmd_move_page_down(cmd_context_t* ctx); 472 | int cmd_move_page_up(cmd_context_t* ctx); 473 | int cmd_move_relative(cmd_context_t* ctx); 474 | int cmd_move_right(cmd_context_t* ctx); 475 | int cmd_move_to_line(cmd_context_t* ctx); 476 | int cmd_move_until_back(cmd_context_t* ctx); 477 | int cmd_move_until_forward(cmd_context_t* ctx); 478 | int cmd_move_up(cmd_context_t* ctx); 479 | int cmd_move_word_back(cmd_context_t* ctx); 480 | int cmd_move_word_forward(cmd_context_t* ctx); 481 | int cmd_next(cmd_context_t* ctx); 482 | int cmd_noop(cmd_context_t* ctx); 483 | int cmd_open_file(cmd_context_t* ctx); 484 | int cmd_open_new(cmd_context_t* ctx); 485 | int cmd_open_replace_file(cmd_context_t* ctx); 486 | int cmd_open_replace_new(cmd_context_t* ctx); 487 | int cmd_outdent(cmd_context_t* ctx); 488 | int cmd_pop_kmap(cmd_context_t* ctx); 489 | int cmd_prev(cmd_context_t* ctx); 490 | int cmd_push_kmap(cmd_context_t* ctx); 491 | int cmd_quit(cmd_context_t* ctx); 492 | int cmd_redo(cmd_context_t* ctx); 493 | int cmd_redraw(cmd_context_t* ctx); 494 | int cmd_remove_extra_cursors(cmd_context_t* ctx); 495 | int cmd_replace(cmd_context_t* ctx); 496 | int cmd_save_as(cmd_context_t* ctx); 497 | int cmd_save(cmd_context_t* ctx); 498 | int cmd_search(cmd_context_t* ctx); 499 | int cmd_search_next(cmd_context_t* ctx); 500 | int cmd_select_beginning(cmd_context_t* ctx); 501 | int cmd_select_end(cmd_context_t* ctx); 502 | int cmd_select_bol(cmd_context_t* ctx); 503 | int cmd_select_eol(cmd_context_t* ctx); 504 | int cmd_select_up(cmd_context_t* ctx); 505 | int cmd_select_down(cmd_context_t* ctx); 506 | int cmd_select_left(cmd_context_t* ctx); 507 | int cmd_select_right(cmd_context_t* ctx); 508 | int cmd_select_word_back(cmd_context_t* ctx); 509 | int cmd_select_word_forward(cmd_context_t* ctx); 510 | int cmd_select_current_word(cmd_context_t* ctx); 511 | int cmd_select_current_line(cmd_context_t* ctx); 512 | int cmd_new_cursor_up(cmd_context_t* ctx); 513 | int cmd_new_cursor_down(cmd_context_t* ctx); 514 | int cmd_set_opt(cmd_context_t* ctx); 515 | int cmd_shell(cmd_context_t* ctx); 516 | int cmd_show_help(cmd_context_t* ctx); 517 | int cmd_split_horizontal(cmd_context_t* ctx); 518 | int cmd_split_vertical(cmd_context_t* ctx); 519 | int cmd_toggle_anchor(cmd_context_t* ctx); 520 | int cmd_toggle_mouse_mode(cmd_context_t* ctx); 521 | int cmd_uncut(cmd_context_t* ctx); 522 | int cmd_undo(cmd_context_t* ctx); 523 | int cmd_viewport_bot(cmd_context_t* ctx); 524 | int cmd_viewport_mid(cmd_context_t* ctx); 525 | int cmd_viewport_top(cmd_context_t* ctx); 526 | int cmd_wake_sleeping_cursors(cmd_context_t* ctx); 527 | 528 | // async functions 529 | async_proc_t* async_proc_new(editor_t* editor, void* owner, async_proc_t** owner_aproc, char* shell_cmd, int rw, async_proc_cb_t callback); 530 | int async_proc_set_owner(async_proc_t* aproc, void* owner, async_proc_t** owner_aproc); 531 | int async_proc_destroy(async_proc_t* aproc, int preempt); 532 | int async_proc_drain_all(async_proc_t* aprocs, int* ttyfd); 533 | 534 | // util functions 535 | const char * util_get_url(const char * url); 536 | size_t util_download_file(const char * url, const char * target); 537 | int util_shell_exec(editor_t* editor, char* cmd, long timeout_s, char* input, size_t input_len, char* opt_shell, char** optret_output, size_t* optret_output_len); 538 | int util_popen2(char* cmd, char* opt_shell, int* optret_fdread, int* optret_fdwrite, pid_t* optret_pid); 539 | int util_get_bracket_pair(uint32_t ch, int* optret_is_closing); 540 | int util_is_file(char* path, char* opt_mode, FILE** optret_file); 541 | int util_is_dir(char* path); 542 | char * util_read_file(char* path); 543 | void util_expand_tilde(char* path, int path_len, char** ret_path); 544 | int util_pcre_match(char* re, char* subject, int subject_len, char** optret_capture, int* optret_capture_len); 545 | int util_pcre_replace(char* re, char* subj, char* repl, char** ret_result, int* ret_result_len); 546 | int util_timeval_is_gt(struct timeval* a, struct timeval* b); 547 | char* util_escape_shell_arg(char* str, int l); 548 | int rect_printf(bview_rect_t rect, int x, int y, uint16_t fg, uint16_t bg, const char *fmt, ...); 549 | int rect_printf_attr(bview_rect_t rect, int x, int y, const char *fmt, ...); 550 | void str_append_stop(str_t* str, char* data, char* data_stop); 551 | void str_append(str_t* str, char* data); 552 | void str_append_len(str_t* str, char* data, size_t data_len); 553 | void str_ensure_cap(str_t* str, size_t cap); 554 | void str_clear(str_t* str); 555 | void str_free(str_t* str); 556 | void str_append_replace_with_backrefs(str_t* str, char* subj, char* repl, int pcre_rc, int* pcre_ovector, int pcre_ovecsize); 557 | 558 | // plugin_opt struct, used on plugin boot 559 | // when doing get_option(value) 560 | typedef struct plugin_opt { 561 | int type; 562 | char * value; 563 | } plugin_opt; 564 | 565 | // Globals 566 | extern editor_t _editor; 567 | extern cmd_context_t * plugin_ctx; 568 | 569 | // Macros 570 | #define EON_VERSION "1.1.1" 571 | 572 | #define EON_OK 0 573 | #define EON_ERR 1 574 | 575 | #define EON_PROMPT_YES "yes" 576 | #define EON_PROMPT_NO "no" 577 | #define EON_PROMPT_ALL "all" 578 | 579 | #define EON_DEFAULT_TAB_WIDTH 2 580 | #define EON_DEFAULT_TAB_TO_SPACE 1 581 | #define EON_DEFAULT_TRIM_PASTE 1 582 | #define EON_DEFAULT_SMART_INDENT 0 583 | #define EON_DEFAULT_MACRO_TOGGLE_KEY "M-r" 584 | #define EON_DEFAULT_HILI_BRACKET_PAIRS 1 585 | #define EON_DEFAULT_READ_RC_FILE 1 586 | #define EON_DEFAULT_SOFT_WRAP 0 587 | 588 | #define EON_LOG_ERR(fmt, ...) do { \ 589 | fprintf(stderr, (fmt), __VA_ARGS__); \ 590 | } while (0) 591 | 592 | #define EON_SET_ERR(editor, fmt, ...) do { \ 593 | snprintf((editor)->errstr, EON_ERRSTR_SIZE, (fmt), __VA_ARGS__); \ 594 | } while (0) 595 | 596 | #define EON_SET_INFO(editor, fmt, ...) do { \ 597 | snprintf((editor)->infostr, EON_ERRSTR_SIZE, (fmt), __VA_ARGS__); \ 598 | } while (0) 599 | 600 | #define EON_RETURN_ERR(editor, fmt, ...) do { \ 601 | EON_SET_ERR((editor), (fmt), __VA_ARGS__); \ 602 | return EON_ERR; \ 603 | } while (0) 604 | 605 | #define EON_MIN(a,b) (((a)<(b)) ? (a) : (b)) 606 | #define EON_MAX(a,b) (((a)>(b)) ? (a) : (b)) 607 | 608 | #define EON_BVIEW_IS_EDIT(bview) ((bview)->type == EON_BVIEW_TYPE_EDIT) 609 | #define EON_BVIEW_IS_MENU(bview) ((bview)->is_menu && EON_BVIEW_IS_EDIT(bview)) 610 | #define EON_BVIEW_IS_POPUP(bview) ((bview)->type == EON_BVIEW_TYPE_POPUP) 611 | #define EON_BVIEW_IS_STATUS(bview) ((bview)->type == EON_BVIEW_TYPE_STATUS) 612 | #define EON_BVIEW_IS_PROMPT(bview) ((bview)->type == EON_BVIEW_TYPE_PROMPT) 613 | 614 | #define EON_MARK_COL_TO_VCOL(pmark) ( \ 615 | (pmark)->col >= (pmark)->bline->char_count \ 616 | ? (pmark)->bline->char_vwidth \ 617 | : ( (pmark)->col <= 0 ? 0 : (pmark)->bline->chars[(pmark)->col].vcol ) \ 618 | ) 619 | 620 | #define EON_COL_TO_VCOL(pline, pcol, pmax) ( \ 621 | (pcol) >= (pline)->char_count \ 622 | ? (pmax) \ 623 | : ( (pcol) <= 0 ? 0 : (pline)->chars[(pcol)].vcol ) \ 624 | ) 625 | 626 | // Sentinel values for numeric and wildcard kinputs 627 | #define EON_KINPUT_NUMERIC (kinput_t){ 0xffffffff, 0xffff, 0x40 } 628 | #define EON_KINPUT_WILDCARD (kinput_t){ 0xffffffff, 0xffff, 0x80 } 629 | 630 | #define EON_LINENUM_TYPE_NONE -1 631 | #define EON_LINENUM_TYPE_ABS 0 632 | #define EON_LINENUM_TYPE_REL 1 633 | #define EON_LINENUM_TYPE_BOTH 2 634 | 635 | #define EON_PARAM_WILDCARD(pctx, pn) ( \ 636 | (pn) < (pctx)->loop_ctx->wildcard_params_len \ 637 | ? (pctx)->loop_ctx->wildcard_params[(pn)] \ 638 | : 0 \ 639 | ) 640 | 641 | #define EON_BRACKET_PAIR_MAX_SEARCH 10000 642 | #define EON_RE_WORD_FORWARD "((?<=\\w)\\W|$)" 643 | #define EON_RE_WORD_BACK "((?<=\\W)\\w|^)" 644 | 645 | /* 646 | TODO 647 | --- HIGH 648 | [ ] pass in (bline_t* opt_hint) to buffer_get_* and start from there instead of first_line 649 | [ ] refactor buffer_set_mmapped to avoid huge mallocs 650 | [ ] review default key bindings 651 | [ ] review lel command letters 652 | [ ] guard against mixed api use, refcounting 653 | [ ] overlapping multi rules / range+hili should be separate in styling / srule priority / isearch hili in middle of multiline rule 654 | [ ] rewrite _buffer_apply_styles_multis and _buffer_bline_apply_style_multi 655 | [ ] get rid of bol_rule 656 | [ ] test at tests/test_buffer_srule_overlap.c 657 | [ ] bugfix: insert lines, drop anchor at eof, delete up, type on 1st line, leftover styling? 658 | [ ] crash when M-e cat'ing huge files? (why does malloc crash program with large values?) 659 | [ ] move macros out of eon.h if only used in one source file 660 | --- LOW 661 | [ ] after bad shell cmd, EBADF on stdin/stdout ? 662 | [ ] consider find_budge=0 by default, emulate find_budge=1 in calling code 663 | [ ] use_srules sucks 664 | [ ] undo/redo should center viewport? 665 | [ ] smart indent 666 | [ ] func_viewport, func_display 667 | [ ] ctrl-enter in prompt inserts newline 668 | [ ] when opening path check if a buffer exists that already has it open via inode 669 | [ ] undo stack with same loop# should get undone as a group option 670 | [ ] refactor kmap, ** and ## is kind of inelegant, trie code not easy to grok 671 | [ ] refactor aproc and menu code 672 | [ ] ensure multi_cursor_code impl for all appropriate 673 | [ ] segfault hunt: async proc broken pipe 674 | [ ] use EON_RETURN_ERR more 675 | [ ] pgup/down in isearch to control viewport 676 | [ ] drop/goto mark with char 677 | [ ] last cmd status code indicator 678 | [ ] click to set cursor/focus 679 | [ ] buffer_repeat 680 | [ ] multi-level undo/redo 681 | [ ] prompt history view 682 | [ ] bview_config_t to use in editor and individual bviews 683 | [ ] configurable colors, status line, caption line 684 | */ 685 | 686 | #endif 687 | -------------------------------------------------------------------------------- /src/keys.h: -------------------------------------------------------------------------------- 1 | EON_KEY_DEF("F1", 0, 0, TB_KEY_F1) 2 | EON_KEY_DEF("F2", 0, 0, TB_KEY_F2) 3 | EON_KEY_DEF("F3", 0, 0, TB_KEY_F3) 4 | EON_KEY_DEF("F4", 0, 0, TB_KEY_F4) 5 | EON_KEY_DEF("F5", 0, 0, TB_KEY_F5) 6 | EON_KEY_DEF("F6", 0, 0, TB_KEY_F6) 7 | EON_KEY_DEF("F7", 0, 0, TB_KEY_F7) 8 | EON_KEY_DEF("F8", 0, 0, TB_KEY_F8) 9 | EON_KEY_DEF("F9", 0, 0, TB_KEY_F9) 10 | EON_KEY_DEF("F10", 0, 0, TB_KEY_F10) 11 | EON_KEY_DEF("F11", 0, 0, TB_KEY_F11) 12 | EON_KEY_DEF("F12", 0, 0, TB_KEY_F12) 13 | EON_KEY_DEF("insert", 0, 0, TB_KEY_INSERT) 14 | EON_KEY_DEF("delete", 0, 0, TB_KEY_DELETE) 15 | EON_KEY_DEF("home", 0, 0, TB_KEY_HOME) 16 | EON_KEY_DEF("end", 0, 0, TB_KEY_END) 17 | EON_KEY_DEF("page-up", 0, 0, TB_KEY_PGUP) 18 | EON_KEY_DEF("page-down", 0, 0, TB_KEY_PGDN) 19 | EON_KEY_DEF("up", 0, 0, TB_KEY_ARROW_UP) 20 | EON_KEY_DEF("down", 0, 0, TB_KEY_ARROW_DOWN) 21 | EON_KEY_DEF("left", 0, 0, TB_KEY_ARROW_LEFT) 22 | EON_KEY_DEF("right", 0, 0, TB_KEY_ARROW_RIGHT) 23 | 24 | EON_KEY_DEF("C-~", TB_META_CTRL, 0, TB_KEY_CTRL_TILDE) 25 | EON_KEY_DEF("C-2", TB_META_CTRL, 0, TB_KEY_CTRL_2) 26 | EON_KEY_DEF("C-a", TB_META_CTRL, 0, TB_KEY_CTRL_A) 27 | EON_KEY_DEF("C-b", TB_META_CTRL, 0, TB_KEY_CTRL_B) 28 | EON_KEY_DEF("C-c", TB_META_CTRL, 0, TB_KEY_CTRL_C) 29 | EON_KEY_DEF("C-d", TB_META_CTRL, 0, TB_KEY_CTRL_D) 30 | EON_KEY_DEF("C-e", TB_META_CTRL, 0, TB_KEY_CTRL_E) 31 | EON_KEY_DEF("C-f", TB_META_CTRL, 0, TB_KEY_CTRL_F) 32 | EON_KEY_DEF("C-g", TB_META_CTRL, 0, TB_KEY_CTRL_G) 33 | EON_KEY_DEF("backspace", 0, 0, TB_KEY_BACKSPACE) 34 | EON_KEY_DEF("C-h", TB_META_CTRL, 0, TB_KEY_CTRL_H) 35 | EON_KEY_DEF("tab", TB_META_CTRL, 0, TB_KEY_TAB) 36 | EON_KEY_DEF("C-i", TB_META_CTRL, 9, TB_KEY_CTRL_I) 37 | EON_KEY_DEF("C-j", TB_META_CTRL, 0, TB_KEY_CTRL_J) 38 | EON_KEY_DEF("C-k", TB_META_CTRL, 0, TB_KEY_CTRL_K) 39 | EON_KEY_DEF("C-l", TB_META_CTRL, 0, TB_KEY_CTRL_L) 40 | EON_KEY_DEF("enter", 0, 0, TB_KEY_ENTER) 41 | EON_KEY_DEF("C-m", TB_META_CTRL, 0, TB_KEY_CTRL_M) 42 | EON_KEY_DEF("C-n", TB_META_CTRL, 0, TB_KEY_CTRL_N) 43 | EON_KEY_DEF("C-o", TB_META_CTRL, 0, TB_KEY_CTRL_O) 44 | EON_KEY_DEF("C-p", TB_META_CTRL, 0, TB_KEY_CTRL_P) 45 | EON_KEY_DEF("C-q", TB_META_CTRL, 0, TB_KEY_CTRL_Q) 46 | EON_KEY_DEF("C-r", TB_META_CTRL, 0, TB_KEY_CTRL_R) 47 | EON_KEY_DEF("C-s", TB_META_CTRL, 0, TB_KEY_CTRL_S) 48 | EON_KEY_DEF("C-t", TB_META_CTRL, 0, TB_KEY_CTRL_T) 49 | EON_KEY_DEF("C-u", TB_META_CTRL, 0, TB_KEY_CTRL_U) 50 | EON_KEY_DEF("C-v", TB_META_CTRL, 0, TB_KEY_CTRL_V) 51 | EON_KEY_DEF("C-w", TB_META_CTRL, 0, TB_KEY_CTRL_W) 52 | EON_KEY_DEF("C-x", TB_META_CTRL, 0, TB_KEY_CTRL_X) 53 | EON_KEY_DEF("C-y", TB_META_CTRL, 0, TB_KEY_CTRL_Y) 54 | EON_KEY_DEF("C-z", TB_META_CTRL, 0, TB_KEY_CTRL_Z) 55 | EON_KEY_DEF("escape", 0, 0, TB_KEY_ESC) 56 | EON_KEY_DEF("C-[", TB_META_CTRL, 0, TB_KEY_CTRL_LSQ_BRACKET) 57 | EON_KEY_DEF("C-3", TB_META_CTRL, 0, TB_KEY_CTRL_3) 58 | EON_KEY_DEF("C-4", TB_META_CTRL, 0, TB_KEY_CTRL_4) 59 | EON_KEY_DEF("C-\\", TB_META_CTRL, 0, TB_KEY_CTRL_BACKSLASH) 60 | EON_KEY_DEF("C-5", TB_META_CTRL, 0, TB_KEY_CTRL_5) 61 | EON_KEY_DEF("C-]", TB_META_CTRL, 0, TB_KEY_CTRL_RSQ_BRACKET) 62 | EON_KEY_DEF("C-6", TB_META_CTRL, 0, TB_KEY_CTRL_6) 63 | EON_KEY_DEF("C-7", TB_META_CTRL, 0, TB_KEY_CTRL_7) 64 | EON_KEY_DEF("C-/", TB_META_CTRL, 0, TB_KEY_CTRL_SLASH) 65 | EON_KEY_DEF("C-_", TB_META_CTRL, 0, TB_KEY_CTRL_UNDERSCORE) 66 | EON_KEY_DEF("space", 0, 0, TB_KEY_SPACE) 67 | EON_KEY_DEF("comma", 0, 44, 0) 68 | EON_KEY_DEF("backspace2", 0, 0, TB_KEY_BACKSPACE2) 69 | EON_KEY_DEF("C-8", TB_META_CTRL, 0, TB_KEY_CTRL_8) 70 | 71 | EON_KEY_DEF("M-enter", TB_META_ALT, 0, TB_KEY_ENTER) 72 | EON_KEY_DEF("M-backspace", TB_META_ALT, 0, TB_KEY_BACKSPACE) 73 | 74 | EON_KEY_DEF("S-up", TB_META_SHIFT, 0, TB_KEY_ARROW_UP) 75 | EON_KEY_DEF("S-down", TB_META_SHIFT, 0, TB_KEY_ARROW_DOWN) 76 | EON_KEY_DEF("S-left", TB_META_SHIFT, 0, TB_KEY_ARROW_LEFT) 77 | EON_KEY_DEF("S-right", TB_META_SHIFT, 0, TB_KEY_ARROW_RIGHT) 78 | 79 | EON_KEY_DEF("C-up", TB_META_CTRL, 0, TB_KEY_ARROW_UP) 80 | EON_KEY_DEF("C-down", TB_META_CTRL, 0, TB_KEY_ARROW_DOWN) 81 | EON_KEY_DEF("C-left", TB_META_CTRL, 0, TB_KEY_ARROW_LEFT) 82 | EON_KEY_DEF("C-right", TB_META_CTRL, 0, TB_KEY_ARROW_RIGHT) 83 | 84 | EON_KEY_DEF("CM-a", TB_META_ALTCTRL, 97, 1) 85 | 86 | EON_KEY_DEF("CS-up", TB_META_CTRLSHIFT, 0, TB_KEY_ARROW_UP) 87 | EON_KEY_DEF("CS-down", TB_META_CTRLSHIFT, 0, TB_KEY_ARROW_DOWN) 88 | EON_KEY_DEF("CS-left", TB_META_CTRLSHIFT, 0, TB_KEY_ARROW_LEFT) 89 | EON_KEY_DEF("CS-right", TB_META_CTRLSHIFT, 0, TB_KEY_ARROW_RIGHT) 90 | 91 | EON_KEY_DEF("MS-up", TB_META_ALTSHIFT, 0, TB_KEY_ARROW_UP) 92 | EON_KEY_DEF("MS-down", TB_META_ALTSHIFT, 0, TB_KEY_ARROW_DOWN) 93 | EON_KEY_DEF("MS-left", TB_META_ALTSHIFT, 0, TB_KEY_ARROW_LEFT) 94 | EON_KEY_DEF("MS-right", TB_META_ALTSHIFT, 0, TB_KEY_ARROW_RIGHT) 95 | 96 | EON_KEY_DEF("MS-w", TB_META_ALTSHIFT, 87, 0) 97 | EON_KEY_DEF("MS-s", TB_META_ALTSHIFT, 83, 0) 98 | EON_KEY_DEF("MS-a", TB_META_ALTSHIFT, 65, 0) 99 | EON_KEY_DEF("MS-d", TB_META_ALTSHIFT, 68, 0) 100 | 101 | EON_KEY_DEF("S-home", TB_META_SHIFT, 0, TB_KEY_HOME) 102 | EON_KEY_DEF("S-end", TB_META_SHIFT, 0, TB_KEY_END) 103 | EON_KEY_DEF("S-delete", TB_META_SHIFT, 0, TB_KEY_DELETE) 104 | 105 | EON_KEY_DEF("C-page-up", TB_META_CTRL, 0, TB_KEY_PGUP) 106 | EON_KEY_DEF("C-page-down", TB_META_CTRL, 0, TB_KEY_PGDN) 107 | 108 | EON_KEY_DEF("CS-page-up", TB_META_CTRLSHIFT, 0, TB_KEY_PGUP) 109 | EON_KEY_DEF("CS-page-down", TB_META_CTRLSHIFT, 0, TB_KEY_PGDN) 110 | 111 | EON_KEY_DEF("CS-home", TB_META_CTRLSHIFT, 0, TB_KEY_HOME) 112 | EON_KEY_DEF("CS-end", TB_META_CTRLSHIFT, 0, TB_KEY_END) 113 | 114 | EON_KEY_DEF("C-home", TB_META_CTRL, 0, TB_KEY_HOME) 115 | EON_KEY_DEF("C-end", TB_META_CTRL, 0, TB_KEY_END) 116 | 117 | // EON_KEY_DEF("S-enter", TB_META_SHIFT, 0, 13) 118 | // EON_KEY_DEF("C-space", TB_META_CTRL, 0, TB_KEY_SPACE) 119 | EON_KEY_DEF("S-tab", TB_META_SHIFT, 0, TB_KEY_TAB) 120 | 121 | EON_KEY_DEF("CS-f", TB_META_CTRLSHIFT, 95, 0) 122 | EON_KEY_DEF("CS-q", TB_META_CTRLSHIFT, 113, 0) 123 | EON_KEY_DEF("CS-z", TB_META_CTRLSHIFT, 122, 0) 124 | -------------------------------------------------------------------------------- /src/libs/jsmn.h: -------------------------------------------------------------------------------- 1 | #ifndef __JSMN_H_ 2 | #define __JSMN_H_ 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /** 11 | * JSON type identifier. Basic types are: 12 | * o Object 13 | * o Array 14 | * o String 15 | * o Other primitive: number, boolean (true/false) or null 16 | */ 17 | typedef enum { 18 | JSMN_UNDEFINED = 0, 19 | JSMN_OBJECT = 1, 20 | JSMN_ARRAY = 2, 21 | JSMN_STRING = 3, 22 | JSMN_PRIMITIVE = 4 23 | } jsmntype_t; 24 | 25 | enum jsmnerr { 26 | /* Not enough tokens were provided */ 27 | JSMN_ERROR_NOMEM = -1, 28 | /* Invalid character inside JSON string */ 29 | JSMN_ERROR_INVAL = -2, 30 | /* The string is not a full JSON packet, more bytes expected */ 31 | JSMN_ERROR_PART = -3 32 | }; 33 | 34 | /** 35 | * JSON token description. 36 | * type type (object, array, string etc.) 37 | * start start position in JSON data string 38 | * end end position in JSON data string 39 | */ 40 | typedef struct { 41 | jsmntype_t type; 42 | int start; 43 | int end; 44 | int size; 45 | #ifdef JSMN_PARENT_LINKS 46 | int parent; 47 | #endif 48 | } jsmntok_t; 49 | 50 | /** 51 | * JSON parser. Contains an array of token blocks available. Also stores 52 | * the string being parsed now and current position in that string 53 | */ 54 | typedef struct { 55 | unsigned int pos; /* offset in the JSON string */ 56 | unsigned int toknext; /* next token to allocate */ 57 | int toksuper; /* superior token node, e.g parent object or array */ 58 | } jsmn_parser; 59 | 60 | 61 | /** 62 | * Allocates a fresh unused token from the token pull. 63 | */ 64 | static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, 65 | jsmntok_t *tokens, size_t num_tokens) { 66 | jsmntok_t *tok; 67 | if (parser->toknext >= num_tokens) { 68 | return NULL; 69 | } 70 | tok = &tokens[parser->toknext++]; 71 | tok->start = tok->end = -1; 72 | tok->size = 0; 73 | #ifdef JSMN_PARENT_LINKS 74 | tok->parent = -1; 75 | #endif 76 | return tok; 77 | } 78 | 79 | /** 80 | * Fills token type and boundaries. 81 | */ 82 | static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, 83 | int start, int end) { 84 | token->type = type; 85 | token->start = start; 86 | token->end = end; 87 | token->size = 0; 88 | } 89 | 90 | /** 91 | * Fills next available token with JSON primitive. 92 | */ 93 | static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, 94 | size_t len, jsmntok_t *tokens, size_t num_tokens) { 95 | jsmntok_t *token; 96 | int start; 97 | 98 | start = parser->pos; 99 | 100 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 101 | switch (js[parser->pos]) { 102 | #ifndef JSMN_STRICT 103 | /* In strict mode primitive must be followed by "," or "}" or "]" */ 104 | case ':': 105 | #endif 106 | case '\t' : case '\r' : case '\n' : case ' ' : 107 | case ',' : case ']' : case '}' : 108 | goto found; 109 | } 110 | if (js[parser->pos] < 32 || js[parser->pos] >= 127) { 111 | parser->pos = start; 112 | return JSMN_ERROR_INVAL; 113 | } 114 | } 115 | #ifdef JSMN_STRICT 116 | /* In strict mode primitive must be followed by a comma/object/array */ 117 | parser->pos = start; 118 | return JSMN_ERROR_PART; 119 | #endif 120 | 121 | found: 122 | if (tokens == NULL) { 123 | parser->pos--; 124 | return 0; 125 | } 126 | token = jsmn_alloc_token(parser, tokens, num_tokens); 127 | if (token == NULL) { 128 | parser->pos = start; 129 | return JSMN_ERROR_NOMEM; 130 | } 131 | jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); 132 | #ifdef JSMN_PARENT_LINKS 133 | token->parent = parser->toksuper; 134 | #endif 135 | parser->pos--; 136 | return 0; 137 | } 138 | 139 | /** 140 | * Fills next token with JSON string. 141 | */ 142 | static int jsmn_parse_string(jsmn_parser *parser, const char *js, 143 | size_t len, jsmntok_t *tokens, size_t num_tokens) { 144 | jsmntok_t *token; 145 | 146 | int start = parser->pos; 147 | 148 | parser->pos++; 149 | 150 | /* Skip starting quote */ 151 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 152 | char c = js[parser->pos]; 153 | 154 | /* Quote: end of string */ 155 | if (c == '\"') { 156 | if (tokens == NULL) { 157 | return 0; 158 | } 159 | token = jsmn_alloc_token(parser, tokens, num_tokens); 160 | if (token == NULL) { 161 | parser->pos = start; 162 | return JSMN_ERROR_NOMEM; 163 | } 164 | jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); 165 | #ifdef JSMN_PARENT_LINKS 166 | token->parent = parser->toksuper; 167 | #endif 168 | return 0; 169 | } 170 | 171 | /* Backslash: Quoted symbol expected */ 172 | if (c == '\\' && parser->pos + 1 < len) { 173 | int i; 174 | parser->pos++; 175 | switch (js[parser->pos]) { 176 | /* Allowed escaped symbols */ 177 | case '\"': case '/' : case '\\' : case 'b' : 178 | case 'f' : case 'r' : case 'n' : case 't' : 179 | break; 180 | /* Allows escaped symbol \uXXXX */ 181 | case 'u': 182 | parser->pos++; 183 | for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { 184 | /* If it isn't a hex character we have an error */ 185 | if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ 186 | (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ 187 | (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ 188 | parser->pos = start; 189 | return JSMN_ERROR_INVAL; 190 | } 191 | parser->pos++; 192 | } 193 | parser->pos--; 194 | break; 195 | /* Unexpected symbol */ 196 | default: 197 | parser->pos = start; 198 | return JSMN_ERROR_INVAL; 199 | } 200 | } 201 | } 202 | parser->pos = start; 203 | return JSMN_ERROR_PART; 204 | } 205 | 206 | /** 207 | * Parse JSON string and fill tokens. 208 | */ 209 | int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 210 | jsmntok_t *tokens, unsigned int num_tokens) { 211 | int r; 212 | int i; 213 | jsmntok_t *token; 214 | int count = parser->toknext; 215 | 216 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 217 | char c; 218 | jsmntype_t type; 219 | 220 | c = js[parser->pos]; 221 | switch (c) { 222 | case '{': case '[': 223 | count++; 224 | if (tokens == NULL) { 225 | break; 226 | } 227 | token = jsmn_alloc_token(parser, tokens, num_tokens); 228 | if (token == NULL) 229 | return JSMN_ERROR_NOMEM; 230 | if (parser->toksuper != -1) { 231 | tokens[parser->toksuper].size++; 232 | #ifdef JSMN_PARENT_LINKS 233 | token->parent = parser->toksuper; 234 | #endif 235 | } 236 | token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); 237 | token->start = parser->pos; 238 | parser->toksuper = parser->toknext - 1; 239 | break; 240 | case '}': case ']': 241 | if (tokens == NULL) 242 | break; 243 | type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); 244 | #ifdef JSMN_PARENT_LINKS 245 | if (parser->toknext < 1) { 246 | return JSMN_ERROR_INVAL; 247 | } 248 | token = &tokens[parser->toknext - 1]; 249 | for (;;) { 250 | if (token->start != -1 && token->end == -1) { 251 | if (token->type != type) { 252 | return JSMN_ERROR_INVAL; 253 | } 254 | token->end = parser->pos + 1; 255 | parser->toksuper = token->parent; 256 | break; 257 | } 258 | if (token->parent == -1) { 259 | if(token->type != type || parser->toksuper == -1) { 260 | return JSMN_ERROR_INVAL; 261 | } 262 | break; 263 | } 264 | token = &tokens[token->parent]; 265 | } 266 | #else 267 | for (i = parser->toknext - 1; i >= 0; i--) { 268 | token = &tokens[i]; 269 | if (token->start != -1 && token->end == -1) { 270 | if (token->type != type) { 271 | return JSMN_ERROR_INVAL; 272 | } 273 | parser->toksuper = -1; 274 | token->end = parser->pos + 1; 275 | break; 276 | } 277 | } 278 | /* Error if unmatched closing bracket */ 279 | if (i == -1) return JSMN_ERROR_INVAL; 280 | for (; i >= 0; i--) { 281 | token = &tokens[i]; 282 | if (token->start != -1 && token->end == -1) { 283 | parser->toksuper = i; 284 | break; 285 | } 286 | } 287 | #endif 288 | break; 289 | case '\"': 290 | r = jsmn_parse_string(parser, js, len, tokens, num_tokens); 291 | if (r < 0) return r; 292 | count++; 293 | if (parser->toksuper != -1 && tokens != NULL) 294 | tokens[parser->toksuper].size++; 295 | break; 296 | case '\t' : case '\r' : case '\n' : case ' ': 297 | break; 298 | case ':': 299 | parser->toksuper = parser->toknext - 1; 300 | break; 301 | case ',': 302 | if (tokens != NULL && parser->toksuper != -1 && 303 | tokens[parser->toksuper].type != JSMN_ARRAY && 304 | tokens[parser->toksuper].type != JSMN_OBJECT) { 305 | #ifdef JSMN_PARENT_LINKS 306 | parser->toksuper = tokens[parser->toksuper].parent; 307 | #else 308 | for (i = parser->toknext - 1; i >= 0; i--) { 309 | if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { 310 | if (tokens[i].start != -1 && tokens[i].end == -1) { 311 | parser->toksuper = i; 312 | break; 313 | } 314 | } 315 | } 316 | #endif 317 | } 318 | break; 319 | #ifdef JSMN_STRICT 320 | /* In strict mode primitives are: numbers and booleans */ 321 | case '-': case '0': case '1' : case '2': case '3' : case '4': 322 | case '5': case '6': case '7' : case '8': case '9': 323 | case 't': case 'f': case 'n' : 324 | /* And they must not be keys of the object */ 325 | if (tokens != NULL && parser->toksuper != -1) { 326 | jsmntok_t *t = &tokens[parser->toksuper]; 327 | if (t->type == JSMN_OBJECT || 328 | (t->type == JSMN_STRING && t->size != 0)) { 329 | return JSMN_ERROR_INVAL; 330 | } 331 | } 332 | #else 333 | /* In non-strict mode every unquoted value is a primitive */ 334 | default: 335 | #endif 336 | r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); 337 | if (r < 0) return r; 338 | count++; 339 | if (parser->toksuper != -1 && tokens != NULL) 340 | tokens[parser->toksuper].size++; 341 | break; 342 | 343 | #ifdef JSMN_STRICT 344 | /* Unexpected char in strict mode */ 345 | default: 346 | return JSMN_ERROR_INVAL; 347 | #endif 348 | } 349 | } 350 | 351 | if (tokens != NULL) { 352 | for (i = parser->toknext - 1; i >= 0; i--) { 353 | /* Unmatched opened object or array */ 354 | if (tokens[i].start != -1 && tokens[i].end == -1) { 355 | return JSMN_ERROR_PART; 356 | } 357 | } 358 | } 359 | 360 | return count; 361 | } 362 | 363 | /** 364 | * Creates a new parser based over a given buffer with an array of tokens 365 | * available. 366 | */ 367 | void jsmn_init(jsmn_parser *parser) { 368 | parser->pos = 0; 369 | parser->toknext = 0; 370 | parser->toksuper = -1; 371 | } 372 | 373 | 374 | ///////////////////////////////////////////////////////////////// 375 | // utility functions, by @tomas. 376 | 377 | char* get_string_from_token(char * json, jsmntok_t* token) { 378 | if (token == 0 || (token->type != JSMN_PRIMITIVE && token->type != JSMN_STRING && token->type != JSMN_OBJECT)) 379 | return 0; 380 | 381 | json[token->end] = 0; 382 | return json + token->start; 383 | } 384 | 385 | int get_nested_token_count(jsmntok_t* token) { 386 | int end = token->end; 387 | int count = 0; 388 | token++; 389 | 390 | while (token->start < end) { 391 | token++; 392 | count++; 393 | } 394 | 395 | return count; 396 | } 397 | 398 | jsmntok_t* get_hash_token(char * json, jsmntok_t * tokens, const char* wanted) { 399 | if (tokens == 0 || wanted == 0) 400 | return 0; 401 | 402 | int offset = 1; 403 | if (tokens[0].type == JSMN_PRIMITIVE) { 404 | offset = 0; // not an array or object 405 | } 406 | 407 | int size = tokens[0].size + 1 - offset; 408 | jsmntok_t* current = tokens + offset; 409 | 410 | int i; 411 | for (i = 0; i < size; i++) { 412 | // if key matches, return it 413 | if (strncmp(wanted, json + current->start, current->end - current->start) == 0) { 414 | return current + 1; 415 | } 416 | 417 | // move forward: key + value + nested tokens 418 | current += 2 + get_nested_token_count(current + 1); 419 | } 420 | 421 | // printf("token not found: %s\n", desiredKey); 422 | return 0; 423 | } 424 | 425 | jsmntok_t* get_array_token(char * json, jsmntok_t * tokens, int index) { 426 | if (json == 0 || tokens == 0 || index < 0 || index >= tokens[0].size) 427 | return 0; 428 | 429 | jsmntok_t* current = tokens + 1; 430 | 431 | // skip all tokens before the specified index 432 | int i; 433 | for (i = 0; i < index; i++) { 434 | // move forward: current + nested tokens 435 | current += 1 + get_nested_token_count(current); 436 | } 437 | 438 | return current; 439 | } 440 | 441 | 442 | #ifdef __cplusplus 443 | } 444 | #endif 445 | 446 | #endif /* __JSMN_H_ */ 447 | -------------------------------------------------------------------------------- /src/libs/vector.h: -------------------------------------------------------------------------------- 1 | #ifndef VECTOR_H 2 | #define VECTOR_H 3 | 4 | #include 5 | 6 | // our very own vector implementation. from: 7 | // http://eddmann.com/posts/implementing-a-dynamic-vector-array-in-c/ 8 | 9 | typedef struct vector { 10 | void **items; 11 | int capacity; 12 | int total; 13 | } vector; 14 | 15 | void vector_init(vector *v, int capacity) { 16 | v->capacity = capacity; 17 | v->total = 0; 18 | v->items = malloc(1024); 19 | } 20 | 21 | static void vector_resize(vector *v, int capacity) { 22 | void **items = realloc(v->items, sizeof(void *) * capacity); 23 | if (items) { 24 | v->items = items; 25 | v->capacity = capacity; 26 | } 27 | } 28 | 29 | int vector_add(vector *v, void *item) { 30 | if (v->capacity == v->total) 31 | vector_resize(v, v->capacity * 2); 32 | 33 | v->items[v->total++] = item; 34 | 35 | // printf(" -- added. total is %d\n", v->total); 36 | return v->total; 37 | } 38 | 39 | void *vector_get(vector *v, int index) { 40 | if (index >= 0 && index < v->total) 41 | return v->items[index]; 42 | 43 | // printf(" -- null. total is %d\n", v->total); 44 | return NULL; 45 | } 46 | 47 | void vector_delete(vector *v, int index) { 48 | if (index < 0 || index >= v->total) 49 | return; 50 | 51 | v->items[index] = NULL; 52 | 53 | int i; 54 | for (i = 0; i < v->total - 1; i++) { 55 | v->items[i] = v->items[i + 1]; 56 | v->items[i + 1] = NULL; 57 | } 58 | 59 | v->total--; 60 | 61 | if (v->total > 0 && v->total == v->capacity / 4) 62 | vector_resize(v, v->capacity / 2); 63 | } 64 | 65 | int vector_size(vector *v) { 66 | return v->total; 67 | } 68 | 69 | void vector_free(vector *v) { 70 | free(v->items); 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "termbox.h" 6 | #include "uthash.h" 7 | #include "mlbuf.h" 8 | #include "eon.h" 9 | 10 | editor_t _editor; 11 | 12 | int main(int argc, char** argv) { 13 | 14 | memset(&_editor, 0, sizeof(editor_t)); 15 | setlocale(LC_ALL, ""); 16 | 17 | if (editor_init(&_editor, argc, argv) == EON_OK) { 18 | 19 | if (!_editor.headless_mode) { 20 | _editor.linenum_type = EON_LINENUM_TYPE_NONE; 21 | tb_init(); 22 | 23 | if (!_editor.no_mouse) 24 | tb_enable_mouse(); 25 | 26 | // tb_select_output_mode(TB_OUTPUT_256); 27 | } 28 | 29 | editor_run(&_editor); 30 | 31 | if (_editor.headless_mode && _editor.active_edit) { 32 | buffer_write_to_fd(_editor.active_edit->buffer, STDOUT_FILENO, NULL); 33 | } 34 | 35 | editor_deinit(&_editor); 36 | 37 | // shut down termbox if not on headless mode 38 | if (!_editor.headless_mode) { 39 | tb_shutdown(); 40 | } 41 | 42 | } else { // init failed 43 | editor_deinit(&_editor); 44 | } 45 | 46 | return _editor.exit_code; 47 | } 48 | -------------------------------------------------------------------------------- /src/plugin_api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "eon.h" 6 | #include "termbox.h" 7 | 8 | cmd_context_t * plugin_ctx; // shared global, from eon.h 9 | 10 | // from plugins.c 11 | plugin_opt * get_plugin_option(const char * key); 12 | int add_listener(const char * when, const char * event, const char * func); 13 | int register_func_as_command(const char * func); 14 | int add_plugin_keybinding(const char * keys, const char * func); 15 | int start_callback_prompt(cmd_context_t * ctx, char * text, int); 16 | 17 | /* plugin functions 18 | -----------------------------------------------------------*/ 19 | 20 | static int get_option(lua_State * L) { 21 | const char *key = luaL_checkstring(L, 1); 22 | plugin_opt * opt = get_plugin_option(key); 23 | if (!opt) return 0; // NULL 24 | 25 | switch(opt->type) { 26 | case 3: // JSMN_STRING 27 | lua_pushstring(L, opt->value); 28 | break; 29 | 30 | case 4: // JSMN_PRIMITIVE 31 | if (opt->value[0] == 'n') // null 32 | return 0; 33 | else if (opt->value[0] == 't' || opt->value[0] == 'f') // true or false 34 | lua_pushboolean(L, !!opt->value); 35 | else 36 | lua_pushnumber(L, atoi(opt->value)); 37 | break; 38 | 39 | default: // undefined 0, object 1, array 2 40 | printf("Unknown type for plugin option %s: %d\n", key, opt->type); 41 | return 0; 42 | // break; 43 | } 44 | 45 | free(opt); 46 | return 1; 47 | } 48 | 49 | static int get_url(lua_State * L) { 50 | const char * url = luaL_checkstring(L, 1); 51 | const char * data = util_get_url(url); 52 | if (!data) return 0; 53 | 54 | lua_pushstring(L, data); 55 | free((char *)data); 56 | return 1; 57 | } 58 | 59 | static int download_file(lua_State * L) { 60 | const char * url = luaL_checkstring(L, 1); 61 | const char * target = luaL_checkstring(L, 2); 62 | 63 | size_t bytes = util_download_file(url, target); 64 | if (bytes == -1) return 0; 65 | 66 | lua_pushnumber(L, bytes); 67 | return 1; 68 | } 69 | 70 | static int register_function(lua_State * L) { 71 | const char * func = luaL_checkstring(L, 1); 72 | return register_func_as_command(func); // in plugins.c 73 | } 74 | 75 | static int add_keybinding(lua_State * L) { 76 | const char *keys = luaL_checkstring(L, 1); 77 | const char *func = luaL_checkstring(L, 2); 78 | int res = add_plugin_keybinding(keys, func); 79 | return 0; 80 | } 81 | 82 | static int before(lua_State * L) { 83 | const char *event = luaL_checkstring(L, 1); 84 | const char *func = luaL_checkstring(L, 2); 85 | int res = add_listener("before", event, func); 86 | return 0; 87 | } 88 | 89 | static int after(lua_State * L) { 90 | const char *event = luaL_checkstring(L, 1); 91 | const char *func = luaL_checkstring(L, 2); 92 | int res = add_listener("after", event, func); 93 | return 0; 94 | } 95 | 96 | static int start_nav(lua_State * L) { 97 | const char *text = luaL_checkstring(L, 1); 98 | 99 | static int callback; 100 | if (!lua_isfunction(L, 2) || !(callback = luaL_ref(L, LUA_REGISTRYINDEX))) { 101 | return -1; 102 | } 103 | 104 | return start_callback_prompt(plugin_ctx, (char *)text, callback); 105 | } 106 | 107 | static int set_nav_text(lua_State * L) { 108 | const char *text = luaL_checkstring(L, 2); 109 | editor_set_prompt_str(plugin_ctx->editor, (char *)text); 110 | return 0; 111 | } 112 | 113 | static int close_nav(lua_State * L) { 114 | // int pos = func_ref_get(L, cbfunc); 115 | // free(cbfunc); 116 | // cbfunc = NULL; 117 | 118 | editor_close_prompt(plugin_ctx->editor, plugin_ctx->editor->active_edit); 119 | return 0; 120 | } 121 | 122 | static int prompt_user(lua_State * L) { 123 | const char *prompt = luaL_checkstring(L, 1); 124 | const char *placeholder = luaL_checkstring(L, 2); 125 | 126 | char * reply; 127 | editor_prompt(plugin_ctx->editor, (char *)prompt, &(editor_prompt_params_t) { 128 | .data = placeholder ? (char *)placeholder : "", 129 | .data_len = placeholder ? strlen(placeholder) : 0 130 | }, &reply); 131 | 132 | lua_pushstring(L, reply); 133 | return 1; 134 | } 135 | 136 | // open_new_tab(title, content, close_current) 137 | static int open_new_tab(lua_State * L) { 138 | const char *title = luaL_checkstring(L, 1); 139 | const char *buf = luaL_checkstring(L, 2); 140 | int close_current = lua_tointeger(L, 3); 141 | 142 | bview_t * view; 143 | int res = editor_open_bview(plugin_ctx->editor, NULL, EON_BVIEW_TYPE_EDIT, (char *)title, strlen(title), 1, 0, &plugin_ctx->editor->rect_edit, NULL, &view); 144 | 145 | // view->is_menu = 1; 146 | // view->callback = callback; 147 | 148 | if (res == EON_OK) { 149 | mark_insert_before(view->active_cursor->mark, (char *)buf, strlen(buf)); 150 | mark_move_to(view->active_cursor->mark, 0, 0); 151 | if (close_current) editor_close_bview(plugin_ctx->editor, plugin_ctx->editor->active_edit, NULL); 152 | } 153 | 154 | lua_pushnumber(L, res); 155 | return 1; 156 | } 157 | 158 | static int draw(lua_State * L) { 159 | int x = lua_tointeger(L, 1); 160 | int y = lua_tointeger(L, 2); 161 | int bg = lua_tointeger(L, 3); 162 | int fg = lua_tointeger(L, 4); 163 | const char *str = luaL_checkstring(L, 5); 164 | 165 | tb_string(x, y, bg, fg, (char *)str); 166 | return 0; 167 | } 168 | 169 | // int = current_line_number() 170 | static int current_line_number(lua_State * L) { 171 | int line_number = plugin_ctx->cursor->mark->bline->line_index; 172 | lua_pushnumber(L, line_number); 173 | return 1; 174 | } 175 | 176 | static int current_position(lua_State * L) { 177 | mark_t * mark = plugin_ctx->cursor->mark; 178 | 179 | lua_createtable(L, 2, 0); 180 | lua_pushinteger(L, mark->bline->line_index); 181 | lua_rawseti(L, -2, 0); 182 | lua_pushinteger(L, mark->col); 183 | lua_rawseti(L, -2, 1); 184 | return 1; 185 | } 186 | 187 | static int goto_line(lua_State * L) { 188 | bint_t line = lua_tointeger(L, 1); 189 | if (line < 1) line = 1; 190 | 191 | mark_move_to(plugin_ctx->cursor->mark, line-1, 0); 192 | bview_center_viewport_y(plugin_ctx->bview); 193 | return 0; 194 | } 195 | 196 | // bool = has_selection() 197 | static int has_selection(lua_State * L) { 198 | int anchored = plugin_ctx->cursor->is_anchored; 199 | lua_pushboolean(L, anchored); 200 | return 1; 201 | } 202 | 203 | // selection = get_selection() 204 | // --> [start_line, start_col, end_line, end_col] 205 | static int get_selection(lua_State * L) { 206 | mark_t * mark = plugin_ctx->cursor->mark; 207 | mark_t * anchor = plugin_ctx->cursor->anchor; 208 | mark_t * first; 209 | mark_t * last; 210 | 211 | if (mark->bline->line_index < anchor->bline->line_index) { 212 | first = mark; last = anchor; 213 | } else if (mark->bline->line_index > anchor->bline->line_index) { 214 | first = anchor; last = mark; 215 | } else if (mark->col > anchor->col) { 216 | first = anchor; last = mark; 217 | } else { 218 | first = mark; last = anchor; 219 | } 220 | 221 | lua_createtable(L, 4, 0); 222 | lua_pushinteger(L, first->bline->line_index); 223 | lua_rawseti(L, -2, 0); 224 | lua_pushinteger(L, first->col); 225 | lua_rawseti(L, -2, 1); 226 | lua_pushinteger(L, last->bline->line_index); 227 | lua_rawseti(L, -2, 2); 228 | lua_pushinteger(L, last->col); 229 | lua_rawseti(L, -2, 3); 230 | 231 | return 1; 232 | } 233 | 234 | // int = current_line_number() 235 | static int current_file_path(lua_State * L) { 236 | if (EON_BVIEW_IS_EDIT(plugin_ctx->bview) && plugin_ctx->bview->buffer && plugin_ctx->bview->buffer->path) { 237 | int len = strlen(plugin_ctx->bview->buffer->path); 238 | lua_pushlstring(L, plugin_ctx->bview->buffer->path, len); 239 | return 1; 240 | } else { 241 | return 0; 242 | } 243 | } 244 | 245 | // returns total number of lines in current view 246 | static int get_line_count(lua_State * L) { 247 | int line_count = plugin_ctx->bview->buffer->line_count; 248 | lua_pushnumber(L, line_count); 249 | return 1; // one argument 250 | } 251 | 252 | // get_buffer_at_line(number) 253 | // returns buffer at line N 254 | static int get_buffer_at_line(lua_State *L) { 255 | int line_index = lua_tointeger(L, 1); 256 | 257 | bline_t * line; 258 | buffer_get_bline(plugin_ctx->bview->buffer, line_index, &line); 259 | if (!line) return 0; 260 | 261 | lua_pushlstring(L, line->data, line->data_len); 262 | return 1; // one argument 263 | }; 264 | 265 | // set_buffer_at_line(number, buffer) 266 | static int set_buffer_at_line(lua_State *L) { 267 | int line_index = lua_tointeger(L, 1); 268 | const char *buf = luaL_checkstring(L, 2); 269 | 270 | bline_t * line; 271 | buffer_get_bline(plugin_ctx->bview->buffer, line_index, &line); 272 | if (!line) return 0; 273 | 274 | int col = 0; 275 | int res = bline_replace(line, col, line->data_len, (char *)buf, strlen(buf)); 276 | 277 | lua_pushnumber(L, res); 278 | return 1; // one argument 279 | }; 280 | 281 | // insert_buffer_at_line(line_number, buffer, column) 282 | static int insert_buffer_at_line(lua_State *L) { 283 | int line_index = lua_tointeger(L, 1); 284 | const char *buf = luaL_checkstring(L, 2); 285 | int column = lua_tointeger(L, 3); 286 | if (!column || column < 0) return 0; 287 | 288 | bline_t * line; 289 | buffer_get_bline(plugin_ctx->bview->buffer, line_index, &line); 290 | if (!line) return 0; 291 | 292 | bint_t ret_chars; 293 | bline_insert(line, column, (char *)buf, strlen(buf), &ret_chars); 294 | 295 | lua_pushnumber(L, ret_chars); 296 | return 1; 297 | }; 298 | 299 | // delete_chars_at_line(line_number, column, count) 300 | static int delete_chars_at_line(lua_State *L) { 301 | int line_index = lua_tointeger(L, 1); 302 | int column = lua_tointeger(L, 2); 303 | int count = lua_tointeger(L, 3); 304 | if (line_index < 0 || column < 0) return 0; 305 | 306 | bline_t * line; 307 | buffer_get_bline(plugin_ctx->bview->buffer, line_index, &line); 308 | if (!line) return 0; 309 | 310 | if (!count) count = line->data_len - column; 311 | 312 | int res = bline_delete(line, column, count); 313 | lua_pushnumber(L, res); 314 | return 1; 315 | }; 316 | 317 | // prepend_buffer_at_line(line_number, buffer) 318 | static int prepend_buffer_at_line(lua_State *L) { 319 | int line_index = lua_tointeger(L, 1); 320 | const char *buf = luaL_checkstring(L, 2); 321 | if (line_index < 0) return 0; 322 | 323 | bline_t * line; 324 | buffer_get_bline(plugin_ctx->bview->buffer, line_index, &line); 325 | if (!line) return 0; 326 | 327 | bint_t ret_chars; 328 | bline_insert(line, 0, (char *)buf, strlen(buf), &ret_chars); 329 | 330 | lua_pushnumber(L, ret_chars); 331 | return 1; 332 | }; 333 | 334 | // append_buffer_at_line(line_number, buffer) 335 | static int append_buffer_at_line(lua_State *L) { 336 | int line_index = lua_tointeger(L, 1); 337 | const char *buf = luaL_checkstring(L, 2); 338 | 339 | bline_t * line; 340 | buffer_get_bline(plugin_ctx->bview->buffer, line_index, &line); 341 | if (!line) return 0; 342 | 343 | bint_t ret_chars; 344 | bline_insert(line, line->data_len, (char *)buf, strlen(buf), &ret_chars); 345 | 346 | lua_pushnumber(L, ret_chars); 347 | return 1; 348 | }; 349 | 350 | static int set_line_bg_color(lua_State * L) { 351 | int line_index = lua_tointeger(L, 1); 352 | int color = lua_tointeger(L, 2); 353 | 354 | int res = bview_set_line_bg(plugin_ctx->bview, line_index, color); 355 | 356 | lua_pushnumber(L, res); 357 | return 1; 358 | } 359 | 360 | void load_plugin_api(lua_State *luaMain) { 361 | 362 | lua_pushcfunction(luaMain, get_option); 363 | lua_setglobal(luaMain, "get_option"); 364 | 365 | lua_pushcfunction(luaMain, current_line_number); 366 | lua_setglobal(luaMain, "current_line_number"); 367 | lua_pushcfunction(luaMain, current_position); 368 | lua_setglobal(luaMain, "current_position"); 369 | 370 | lua_pushcfunction(luaMain, current_file_path); 371 | lua_setglobal(luaMain, "current_file_path"); 372 | 373 | lua_pushcfunction(luaMain, has_selection); 374 | lua_setglobal(luaMain, "has_selection"); 375 | lua_pushcfunction(luaMain, get_selection); 376 | lua_setglobal(luaMain, "get_selection"); 377 | 378 | lua_pushcfunction(luaMain, get_line_count); 379 | lua_setglobal(luaMain, "get_line_count"); 380 | lua_pushcfunction(luaMain, get_buffer_at_line); 381 | lua_setglobal(luaMain, "get_buffer_at_line"); 382 | lua_pushcfunction(luaMain, set_buffer_at_line); 383 | lua_setglobal(luaMain, "set_buffer_at_line"); 384 | 385 | lua_pushcfunction(luaMain, insert_buffer_at_line); 386 | lua_setglobal(luaMain, "insert_buffer_at_line"); 387 | lua_pushcfunction(luaMain, delete_chars_at_line); 388 | lua_setglobal(luaMain, "delete_chars_at_line"); 389 | lua_pushcfunction(luaMain, prepend_buffer_at_line); 390 | lua_setglobal(luaMain, "prepend_buffer_at_line"); 391 | lua_pushcfunction(luaMain, append_buffer_at_line); 392 | lua_setglobal(luaMain, "append_buffer_at_line"); 393 | 394 | lua_pushcfunction(luaMain, set_line_bg_color); 395 | lua_setglobal(luaMain, "set_line_bg_color"); 396 | 397 | lua_pushcfunction(luaMain, prompt_user); 398 | lua_setglobal(luaMain, "prompt_user"); 399 | lua_pushcfunction(luaMain, open_new_tab); 400 | lua_setglobal(luaMain, "open_new_tab"); 401 | lua_pushcfunction(luaMain, draw); 402 | lua_setglobal(luaMain, "draw"); 403 | 404 | lua_pushcfunction(luaMain, start_nav); 405 | lua_setglobal(luaMain, "start_nav"); 406 | lua_pushcfunction(luaMain, close_nav); 407 | lua_setglobal(luaMain, "close_nav"); 408 | lua_pushcfunction(luaMain, set_nav_text); 409 | lua_setglobal(luaMain, "set_nav_text"); 410 | lua_pushcfunction(luaMain, goto_line); 411 | lua_setglobal(luaMain, "goto_line"); 412 | 413 | lua_pushcfunction(luaMain, add_keybinding); 414 | lua_setglobal(luaMain, "add_keybinding"); 415 | lua_pushcfunction(luaMain, register_function); 416 | lua_setglobal(luaMain, "register_function"); 417 | 418 | lua_pushcfunction(luaMain, before); 419 | lua_setglobal(luaMain, "before"); 420 | lua_pushcfunction(luaMain, after); 421 | lua_setglobal(luaMain, "after"); 422 | 423 | lua_pushcfunction(luaMain, get_url); 424 | lua_setglobal(luaMain, "get_url"); 425 | lua_pushcfunction(luaMain, download_file); 426 | lua_setglobal(luaMain, "download_file"); 427 | } -------------------------------------------------------------------------------- /src/plugins.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "eon.h" 9 | #include "vector.h" 10 | #include "jsmn.h" 11 | 12 | void load_plugin_api(lua_State *luaMain); 13 | 14 | /*-----------------------------------------------------*/ 15 | 16 | // #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 17 | 18 | lua_State *luaMain = NULL; 19 | vector plugin_names; 20 | vector plugin_versions; 21 | vector events; 22 | vector listeners; 23 | 24 | typedef struct listener { 25 | char plugin[32]; 26 | char func[32]; 27 | struct listener *next; 28 | } listener; 29 | 30 | ///////////////////////////////////////////// 31 | 32 | int plugin_count = 0; 33 | 34 | editor_t * editor_ref; // needed for function calls from lua when booting 35 | char * booting_plugin_name; 36 | 37 | const char * plugin_path = "~/.config/eon/plugins"; 38 | 39 | // for option parsing 40 | #define MAX_TOKENS 32 41 | static char * json_string; 42 | jsmntok_t json_root[MAX_TOKENS]; 43 | 44 | int read_plugin_options(const char * plugin) { 45 | jsmn_parser parser; 46 | jsmn_init(&parser); 47 | 48 | char path[128]; 49 | sprintf(path, "%s/%s/plugin.conf", plugin_path, plugin); 50 | 51 | char* expanded_path; 52 | util_expand_tilde((char *)path, strlen(path), &expanded_path); 53 | 54 | char * str = util_read_file(expanded_path); 55 | if (!str) { 56 | return -1; 57 | } 58 | 59 | int res = jsmn_parse(&parser, str, strlen(str), json_root, MAX_TOKENS); 60 | 61 | if (res >= 0) { // success, so store a reference to string 62 | json_string = str; 63 | } 64 | 65 | return res; 66 | } 67 | 68 | int unload_plugins(void) { 69 | if (luaMain == NULL) 70 | return 0; 71 | 72 | // printf("Unloading plugins...\n"); 73 | lua_close(luaMain); 74 | luaMain = NULL; 75 | 76 | vector_free(&plugin_names); 77 | vector_free(&plugin_versions); 78 | vector_free(&events); 79 | 80 | int i; 81 | listener * temp, * obj; 82 | 83 | // listeners contains the first one for each event 84 | // so for each event process the row, and continue 85 | for (i = 0; i < vector_size(&listeners); i++) { 86 | obj = vector_get(&listeners, i); 87 | while(obj) { 88 | temp = obj->next; 89 | free(obj); 90 | obj = temp; 91 | }; 92 | } 93 | 94 | vector_free(&listeners); 95 | return 0; 96 | } 97 | 98 | int init_plugins(void) { 99 | // printf("Initializing plugins...\n"); 100 | unload_plugins(); 101 | 102 | vector_init(&plugin_names, 1); 103 | vector_init(&plugin_versions, 1); 104 | vector_init(&events, 1); 105 | vector_init(&listeners, 1); 106 | 107 | luaMain = luaL_newstate(); 108 | if (!luaMain) { 109 | fprintf(stderr, "Unable to initialize Lua context for plugins.\n"); 110 | return -1; 111 | } 112 | 113 | luaL_openlibs(luaMain); 114 | load_plugin_api(luaMain); 115 | return 0; 116 | } 117 | 118 | 119 | int call_plugin(const char * pname, const char * func, char * data) { 120 | lua_State *L; 121 | // printf(" ----> Calling function %s on plugin %s\n", func, pname); 122 | 123 | L = lua_newthread(luaMain); 124 | lua_getglobal(L, pname); 125 | if (lua_isnil(L, -1)) { 126 | fprintf(stderr, "Fatal: Could not get plugin: %s\n", pname); 127 | lua_pop(luaMain, 1); 128 | return -1; 129 | } 130 | 131 | lua_getfield(L, -1, func); // get the function 132 | if (data) lua_pushstring(L, data); 133 | 134 | // call it 135 | if (lua_pcall(L, data ? 1 : 0, LUA_MULTRET, 0) != 0) { 136 | // printf("Fatal: Could not run %s function on plugin: %s\n", func, pname); 137 | lua_pop(luaMain, 1); 138 | return -1; 139 | } 140 | 141 | if (lua_gettop(L) == 3) { 142 | fprintf(stderr, "Fatal: plugin failed: %s\n", pname); 143 | lua_pop(luaMain, 1); 144 | return -1; 145 | } else { 146 | // printf("Result from %s: %s\n", func, lua_tostring(L, -1)); 147 | } 148 | 149 | lua_pop(luaMain, 1); 150 | return 0; 151 | } 152 | 153 | void load_plugin(const char * dir, const char * name) { 154 | const char * pname; 155 | const char * pver; 156 | 157 | char path[128]; 158 | sprintf(path, "%s/%s/plugin.lua", dir, name); 159 | 160 | // printf("Loading plugin in path '%s': %s\n", path, name); 161 | if (luaL_dofile(luaMain, path)) { 162 | fprintf(stderr, "Could not load plugin: %s\n", lua_tostring(luaMain, -1)); 163 | return; 164 | } 165 | 166 | // Get and check the plugin's name 167 | lua_getfield(luaMain, -1, "name"); 168 | if (lua_isnil(luaMain, -1)) { 169 | fprintf(stderr, "Could not load file %s: name missing\n", path); 170 | lua_pop(luaMain, 2); 171 | return; 172 | } 173 | 174 | pname = lua_tostring(luaMain, -1); 175 | lua_pop(luaMain, 1); 176 | 177 | // get version 178 | lua_getfield(luaMain, -1, "version"); 179 | pver = lua_tostring(luaMain, -1); 180 | lua_pop(luaMain, 1); 181 | 182 | // successfully loaded, so increase count 183 | plugin_count++; 184 | // printf("Adding %s to list of plugins, as number %d\n", name, plugin_count); 185 | 186 | /* Set the loaded plugin to a global using it's name. */ 187 | lua_setglobal(luaMain, name); 188 | vector_add(&plugin_names, (void *)name); 189 | vector_add(&plugin_versions, (void *)pver); 190 | 191 | // run on_boot function, if present 192 | lua_getfield(luaMain, 0, "boot"); 193 | if (!lua_isnil(luaMain, -1)) { // not nil, so present 194 | 195 | read_plugin_options(name); 196 | booting_plugin_name = (char *)name; 197 | 198 | call_plugin(name, "boot", NULL); 199 | 200 | if (json_string) { 201 | free(json_string); 202 | json_string = NULL; 203 | } 204 | 205 | // json_root = NULL; 206 | booting_plugin_name = NULL; 207 | } 208 | 209 | printf("Finished loading plugin %d: %s\n", plugin_count, name); 210 | } 211 | 212 | int load_plugins(editor_t * editor) { 213 | if (luaMain == NULL && init_plugins() == -1) 214 | return -1; 215 | 216 | editor_ref = editor; 217 | char* expanded_path; 218 | util_expand_tilde((char *)plugin_path, strlen(plugin_path), &expanded_path); 219 | 220 | DIR *dir; 221 | struct dirent *ent; 222 | if ((dir = opendir(expanded_path)) != NULL) { 223 | while ((ent = readdir(dir)) != NULL) { 224 | if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) { 225 | load_plugin(expanded_path, strdup(ent->d_name)); 226 | } 227 | } 228 | free(ent); 229 | closedir(dir); 230 | } else { 231 | fprintf(stderr, "Unable to open plugin directory: %s\n", plugin_path); 232 | editor_ref = NULL; 233 | return -1; 234 | } 235 | 236 | /* Create a global table with name = version of loaded plugins. */ 237 | lua_createtable(luaMain, 0, vector_size(&plugin_names)); 238 | 239 | int i; 240 | for (i = 0; i < vector_size(&plugin_names); i++) { 241 | lua_pushstring(luaMain, vector_get(&plugin_names, i)); 242 | lua_pushstring(luaMain, vector_get(&plugin_versions, i)); 243 | lua_settable(luaMain, -3); 244 | } 245 | 246 | lua_setglobal(luaMain, "plugins"); 247 | 248 | printf("%d plugins initialized.\n", plugin_count); 249 | editor_ref = NULL; 250 | return plugin_count; 251 | } 252 | 253 | /* 254 | void show_plugins() { 255 | lua_State *L; 256 | const char * pname; 257 | const char * pver; 258 | 259 | if (luaMain != NULL) { 260 | L = lua_newthread(luaMain); 261 | 262 | lua_getglobal(L, "plugins"); 263 | lua_pushnil(L); 264 | while (lua_next(L, -2)) { 265 | if (!lua_isstring(L, -2)) { 266 | lua_pop(L, 1); 267 | continue; 268 | } 269 | 270 | pname = lua_tostring(L, -2); 271 | if (lua_isstring(L, -1)) { 272 | pver = lua_tostring(L, -1); 273 | } else { 274 | pver = "?"; 275 | } 276 | 277 | // printf(" --> name: %s, ver: %s\n", pname, pver); 278 | lua_pop(L, 1); 279 | } 280 | 281 | lua_settop(L, 0); 282 | lua_pop(luaMain, 1); 283 | } 284 | } 285 | */ 286 | 287 | /////////////////////////////////////////////////////// 288 | 289 | int run_plugin_function(cmd_context_t * ctx) { 290 | // cmd name should be "plugin_name.function_name" 291 | char * cmd = ctx->cmd->name; 292 | char * delim; 293 | int res, pos, len; 294 | 295 | // so get the position of the dot 296 | delim = strchr(cmd, '.'); 297 | pos = (int)(delim - cmd); 298 | 299 | // get the name of the plugin 300 | len = pos-4; 301 | char plugin[len]; 302 | strncpy(plugin, cmd+4, len); 303 | plugin[len] = '\0'; 304 | 305 | // and the name of the function 306 | len = strlen(cmd) - pos; 307 | char func[len]; 308 | strncpy(func, cmd + pos + 1, len); 309 | func[len] = '\0'; 310 | 311 | // and finally call it 312 | // printf("calling %s function from %s plugin\n", func, plugin); 313 | plugin_ctx = ctx; 314 | res = call_plugin(plugin, func, NULL); 315 | plugin_ctx = NULL; 316 | return res; 317 | } 318 | 319 | int get_event_id(const char * event) { 320 | int i, res = -1; 321 | 322 | const char * name; 323 | for (i = 0; i < vector_size(&events); i++) { 324 | name = vector_get(&events, i); 325 | if (strcmp(event, name) == 0) { 326 | res = i; 327 | break; 328 | } 329 | } 330 | 331 | return res; 332 | } 333 | 334 | int trigger_plugin_event(const char * event, cmd_context_t ctx) { 335 | 336 | int event_id = get_event_id(event); 337 | if (event_id == -1) return 0; // no listeners 338 | 339 | int res = -1; 340 | plugin_ctx = &ctx; 341 | listener * el; 342 | el = vector_get(&listeners, event_id); 343 | 344 | while (el) { 345 | res = call_plugin(el->plugin, el->func, NULL); 346 | // if (res == -1) unload_plugin(name); TODO: stop further calls to this guy. 347 | el = el->next; 348 | }; 349 | 350 | plugin_ctx = NULL; 351 | return res; 352 | } 353 | 354 | plugin_opt * get_plugin_option(const char * key) { 355 | char * plugin = booting_plugin_name; 356 | 357 | if (!plugin) { 358 | fprintf(stderr, "Something's not right. Plugin called boot function out of scope!\n"); 359 | return NULL; 360 | } 361 | 362 | if (!json_string) return NULL; 363 | 364 | jsmntok_t * token = get_hash_token(json_string, json_root, key); 365 | if (!token) return NULL; // not found 366 | 367 | plugin_opt * opt; 368 | opt = (plugin_opt *)malloc(sizeof(plugin_opt)); 369 | opt->type = token->type; 370 | opt->value = strdup(get_string_from_token(json_string, token)); 371 | return opt; 372 | } 373 | 374 | int add_listener(const char * when, const char * event, const char * func) { 375 | 376 | char * plugin = booting_plugin_name; 377 | 378 | if (!plugin) { 379 | fprintf(stderr, "Something's not right. Plugin called boot function out of scope!\n"); 380 | return -1; 381 | } 382 | 383 | // printf("[%s] adding listener %s.%s --> %s\n", plugin, when, event, func); 384 | 385 | int len = strlen(when) * strlen(event) + 1; 386 | char * event_name = malloc(len); 387 | snprintf(event_name, len, "%s.%s", (char *)when, (char *)event); 388 | 389 | listener * obj; 390 | int event_id = get_event_id(event_name); 391 | if (event_id == -1) { 392 | 393 | // new event, so add to event list and initialize new listener list 394 | int count = vector_add(&events, event_name); 395 | event_id = count - 1; 396 | 397 | // we should set this to null, but in that case we'll get a null object afterwards 398 | obj = (listener *)malloc(sizeof(listener) + 1); 399 | strcpy(obj->plugin, plugin); 400 | strcpy(obj->func, func); 401 | obj->next = NULL; 402 | vector_add(&listeners, obj); 403 | 404 | } else { 405 | 406 | // start with the first, and get the last element in array 407 | obj = vector_get(&listeners, event_id); 408 | while (obj->next) { obj = obj->next; } 409 | 410 | listener * el; 411 | el = (listener *)malloc(sizeof(listener) + 1); 412 | strcpy(el->plugin, plugin); 413 | strcpy(el->func, func); 414 | el->next = NULL; // very important 415 | obj->next = el; 416 | 417 | } 418 | 419 | // printf("[%s] added new listener: %d, event id is %d, listener count is %d\n", event_name, plugin_count, event_id, index+1); 420 | return 0; 421 | } 422 | 423 | int register_func_as_command(const char * func) { 424 | 425 | char * plugin = booting_plugin_name; 426 | if (!plugin) { 427 | fprintf(stderr, "Something's not right. Plugin called boot function out of scope!\n"); 428 | return -1; 429 | } 430 | 431 | char * cmd_name; 432 | int len = strlen(plugin) * strlen(func) + 1; 433 | cmd_name = malloc(len); 434 | snprintf(cmd_name, len, "cmd_%s.%s", (char *)plugin, (char *)func); 435 | 436 | printf("[%s] registering cmd --> %s\n", plugin, cmd_name); 437 | 438 | cmd_t cmd = {0}; 439 | cmd.name = cmd_name; 440 | cmd.func = run_plugin_function; 441 | return editor_register_cmd(editor_ref, &cmd); 442 | } 443 | 444 | int add_plugin_keybinding(const char * keys, const char * func) { 445 | 446 | char * plugin = booting_plugin_name; 447 | if (!plugin) { 448 | fprintf(stderr, "Something's not right. Plugin called boot function out of scope!\n"); 449 | return -1; 450 | } 451 | 452 | // TODO: check if plugin func exists 453 | 454 | char * cmd_name; 455 | int len = strlen(plugin) * strlen(func) + 1; 456 | cmd_name = malloc(len); 457 | snprintf(cmd_name, len, "cmd_%s.%s", (char *)plugin, (char *)func); 458 | 459 | printf("[%s] mapping %s to --> %s (%s)\n", plugin, keys, func, cmd_name); 460 | return editor_add_binding_to_keymap(editor_ref, editor_ref->kmap_normal, &((kbinding_def_t) {cmd_name, (char *)keys, NULL})); 461 | 462 | /* 463 | kmap_t* kmap; 464 | if (_editor_init_kmap_by_str(plugin_ctx->editor, &kmap, (char *)keymap_name) != EON_OK) 465 | return 0; 466 | 467 | int res = _editor_init_kmap_add_binding_by_str(plugin_ctx->editor, kmap, (char *)command_name); 468 | lua_pushnumber(L, res); 469 | return 1; 470 | */ 471 | } 472 | 473 | /* 474 | void * prompt_cb_func; 475 | 476 | // retrieve function from registry and place it at the top of the stack 477 | static int get_func(lua_State *L, const void *addr) { 478 | lua_getfield(L, LUA_REGISTRYINDEX, "functions"); 479 | lua_pushlightuserdata(L, (void*)addr); 480 | lua_gettable(L, -2); 481 | lua_remove(L, -2); 482 | 483 | if (!lua_isfunction(L, -1)) { 484 | lua_pop(L, 1); 485 | return 0; 486 | } 487 | return 1; 488 | } 489 | */ 490 | 491 | int prompt_cb_func; 492 | 493 | static int fire_prompt_cb(char * action) { 494 | int top = lua_gettop(luaMain); 495 | lua_rawgeti(luaMain, LUA_REGISTRYINDEX, prompt_cb_func); 496 | lua_pushstring(luaMain, action); 497 | // lua_pushinteger(luaMain, 3); 498 | lua_pcall(luaMain, 1, 1, 0); 499 | lua_settop(luaMain, top); 500 | return 0; 501 | } 502 | 503 | static void unload_callback_prompt(cmd_context_t * ctx) { 504 | // close prompt 505 | editor_close_prompt(ctx->editor, ctx->editor->active_edit); 506 | 507 | // remove callback ref 508 | luaL_unref(luaMain, LUA_REGISTRYINDEX, prompt_cb_func); 509 | prompt_cb_func = 0; 510 | } 511 | 512 | int _nav_menu_callback(cmd_context_t * ctx, char * action) { 513 | if (!action) { 514 | unload_callback_prompt(ctx); 515 | return 0; 516 | } else if (!prompt_cb_func) { 517 | printf("Callback function was removed!"); 518 | return -1; 519 | } 520 | 521 | return fire_prompt_cb(action); 522 | } 523 | 524 | int start_callback_prompt(cmd_context_t * ctx, char * prompt, int cbref) { 525 | if (prompt_cb_func) { 526 | printf("Another plugin is already using the prompt, sorry.\n"); 527 | return -1; 528 | } 529 | 530 | prompt_cb_func = cbref; 531 | return editor_prompt_menu(ctx->editor, _nav_menu_callback, (char *)prompt, strlen(prompt)); 532 | } -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "eon.h" 8 | 9 | struct Data { 10 | char *bytes; 11 | size_t size; 12 | }; 13 | 14 | static size_t write_to_memory(void *contents, size_t size, size_t nmemb, void *userp) { 15 | size_t realsize = size * nmemb; 16 | struct Data *mem = (struct Data *)userp; 17 | 18 | mem->bytes = realloc(mem->bytes, mem->size + realsize + 1); 19 | if (mem->bytes == NULL) { // out of memory! 20 | return 0; 21 | } 22 | 23 | memcpy(&(mem->bytes[mem->size]), contents, realsize); 24 | mem->size += realsize; 25 | mem->bytes[mem->size] = 0; 26 | return realsize; 27 | } 28 | 29 | #ifdef WITH_LIBCURL 30 | #include 31 | 32 | const char * util_get_url(const char * url) { 33 | 34 | CURL *curl; 35 | CURLcode res; 36 | struct Data body; 37 | 38 | curl_global_init(CURL_GLOBAL_ALL); 39 | curl = curl_easy_init(); 40 | if (!curl) { 41 | curl_global_cleanup(); 42 | return NULL; 43 | } 44 | 45 | body.bytes = malloc(1); // start with 1, will be grown as needed 46 | body.size = 0; // no data as this point 47 | 48 | curl_easy_setopt(curl, CURLOPT_URL, url); 49 | curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); 50 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); 51 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "Eon/1.0"); 52 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory); 53 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&body); 54 | 55 | res = curl_easy_perform(curl); // send request 56 | 57 | if (res != CURLE_OK) { 58 | fprintf(stderr, "Couldn't get %s: %s\n", url, curl_easy_strerror(res)); 59 | return NULL; 60 | } 61 | 62 | curl_easy_cleanup(curl); 63 | curl_global_cleanup(); 64 | return body.bytes; 65 | } 66 | 67 | #else 68 | 69 | const char * util_get_url(const char * url) { 70 | int status; 71 | int len = strlen(url) + 29; 72 | char cmd[len]; 73 | snprintf(cmd, len, "wget -S -O - '%s' 2> /dev/null", url); 74 | 75 | FILE *fp; 76 | fp = popen(cmd, "r"); 77 | if (fp == NULL) 78 | return NULL; 79 | 80 | struct Data body; 81 | body.bytes = malloc(1); // start with 1, will be grown as needed 82 | body.size = 0; // no data as this point 83 | 84 | size_t chunk = 1024; 85 | char buf[chunk]; 86 | while (fgets(buf, sizeof(buf), fp) != NULL) { 87 | write_to_memory(buf, strlen(buf), 1, &body); 88 | } 89 | 90 | status = pclose(fp); 91 | // if (status == -1) // something went wrong 92 | 93 | return body.size == 0 ? NULL : body.bytes; 94 | } 95 | 96 | #endif // WITH_LIBCURL 97 | 98 | size_t util_download_file(const char * url, const char * target) { 99 | const char * data = util_get_url(url); 100 | if (!data) return -1; 101 | 102 | FILE *output; 103 | output = fopen(target, "wb"); 104 | if (!output) return -1; 105 | 106 | size_t written = fwrite(data, sizeof(char), strlen(data), output); 107 | fclose(output); 108 | 109 | return written; 110 | } 111 | 112 | // Run a shell command, optionally feeding stdin, collecting stdout 113 | // Specify timeout_s=-1 for no timeout 114 | int util_shell_exec(editor_t* editor, char* cmd, long timeout_s, char* input, size_t input_len, char* opt_shell, char** optret_output, size_t* optret_output_len) { 115 | // TODO clean this crap up 116 | int rv; 117 | int do_read; 118 | int do_write; 119 | int readfd; 120 | int writefd; 121 | ssize_t rc; 122 | ssize_t nbytes; 123 | fd_set readfds; 124 | struct timeval timeout; 125 | struct timeval* timeoutptr; 126 | pid_t pid; 127 | str_t readbuf = {0}; 128 | 129 | readbuf.inc = -2; // double capacity on each allocation 130 | do_read = optret_output != NULL ? 1 : 0; 131 | do_write = input && input_len > 0 ? 1 : 0; 132 | readfd = -1; 133 | writefd = -1; 134 | pid = -1; 135 | nbytes = 0; 136 | rv = EON_OK; 137 | 138 | if (do_read) { 139 | *optret_output = NULL; 140 | *optret_output_len = 0; 141 | } 142 | 143 | // Open cmd 144 | if (!util_popen2(cmd, opt_shell, do_read ? &readfd : NULL, do_write ? &writefd : NULL, &pid)) { 145 | EON_RETURN_ERR(editor, "Failed to exec shell cmd: %s", cmd); 146 | } 147 | 148 | // Read-write loop 149 | do { 150 | // Write to shell cmd if input is remaining 151 | if (do_write && writefd >= 0) { 152 | rc = write(writefd, input, input_len); 153 | 154 | if (rc > 0) { 155 | input += rc; 156 | input_len -= rc; 157 | 158 | if (input_len < 1) { 159 | close(writefd); 160 | writefd = -1; 161 | } 162 | 163 | } else { 164 | // write err 165 | EON_SET_ERR(editor, "write error: %s", strerror(errno)); 166 | rv = EON_ERR; 167 | break; 168 | } 169 | } 170 | 171 | // Read shell cmd, timing out after timeout_sec 172 | if (do_read) { 173 | if (timeout_s >= 0) { 174 | timeout.tv_sec = timeout_s; 175 | timeout.tv_usec = 0; 176 | timeoutptr = &timeout; 177 | 178 | } else { 179 | timeoutptr = NULL; 180 | } 181 | 182 | FD_ZERO(&readfds); 183 | FD_SET(readfd, &readfds); 184 | rc = select(readfd + 1, &readfds, NULL, NULL, timeoutptr); 185 | 186 | if (rc < 0) { 187 | // Err on select 188 | EON_SET_ERR(editor, "select error: %s", strerror(errno)); 189 | rv = EON_ERR; 190 | break; 191 | 192 | } else if (rc == 0) { 193 | // Timed out 194 | rv = EON_ERR; 195 | break; 196 | 197 | } else { 198 | // Read a kilobyte of data 199 | str_ensure_cap(&readbuf, readbuf.len + 1024); 200 | nbytes = read(readfd, readbuf.data + readbuf.len, 1024); 201 | 202 | if (nbytes < 0) { 203 | // read err or EAGAIN/EWOULDBLOCK 204 | EON_SET_ERR(editor, "read error: %s", strerror(errno)); 205 | rv = EON_ERR; 206 | break; 207 | 208 | } else if (nbytes > 0) { 209 | // Got data 210 | readbuf.len += nbytes; 211 | } 212 | } 213 | } 214 | } while (nbytes > 0); // until EOF 215 | 216 | // Close pipes and reap child proc 217 | if (readfd >= 0) close(readfd); 218 | if (writefd >= 0) close(writefd); 219 | waitpid(pid, NULL, do_read ? WNOHANG : 0); 220 | 221 | if (do_read) { 222 | *optret_output = readbuf.data; 223 | *optret_output_len = readbuf.len; 224 | } 225 | 226 | return rv; 227 | } 228 | 229 | // Like popen, but more control over pipes. Returns 1 on success, 0 on failure. 230 | int util_popen2(char* cmd, char* opt_shell, int* optret_fdread, int* optret_fdwrite, pid_t* optret_pid) { 231 | pid_t pid; 232 | int do_read; 233 | int do_write; 234 | int pout[2]; 235 | int pin[2]; 236 | 237 | // Set r/w 238 | do_read = optret_fdread != NULL ? 1 : 0; 239 | do_write = optret_fdwrite != NULL ? 1 : 0; 240 | 241 | // Set shell 242 | opt_shell = opt_shell ? opt_shell : "sh"; 243 | 244 | // Make pipes 245 | if (do_read) if (pipe(pout)) return 0; 246 | if (do_write) if (pipe(pin)) return 0; 247 | 248 | // Fork 249 | pid = fork(); 250 | 251 | if (pid < 0) { // Fork failed 252 | 253 | return 0; 254 | 255 | } else if (pid == 0) { // Child 256 | 257 | if (do_read) { 258 | close(pout[0]); 259 | dup2(pout[1], STDOUT_FILENO); 260 | close(pout[1]); 261 | } 262 | 263 | if (do_write) { 264 | close(pin[1]); 265 | dup2(pin[0], STDIN_FILENO); 266 | close(pin[0]); 267 | } 268 | 269 | setsid(); 270 | execlp(opt_shell, opt_shell, "-c", cmd, NULL); 271 | exit(EXIT_FAILURE); 272 | } 273 | 274 | // Parent 275 | if (do_read) { 276 | close(pout[1]); 277 | *optret_fdread = pout[0]; 278 | } 279 | 280 | if (do_write) { 281 | close(pin[0]); 282 | *optret_fdwrite = pin[1]; 283 | } 284 | 285 | if (optret_pid) *optret_pid = pid; 286 | return 1; 287 | } 288 | 289 | // Return paired bracket if ch is a bracket, else return 0 290 | int util_get_bracket_pair(uint32_t ch, int* optret_is_closing) { 291 | switch (ch) { 292 | case '[': if (optret_is_closing) *optret_is_closing = 0; return ']'; 293 | case '(': if (optret_is_closing) *optret_is_closing = 0; return ')'; 294 | case '{': if (optret_is_closing) *optret_is_closing = 0; return '}'; 295 | case ']': if (optret_is_closing) *optret_is_closing = 1; return '['; 296 | case ')': if (optret_is_closing) *optret_is_closing = 1; return '('; 297 | case '}': if (optret_is_closing) *optret_is_closing = 1; return '{'; 298 | default: return 0; 299 | } 300 | 301 | return 0; 302 | } 303 | 304 | // Return 1 if path is file 305 | int util_is_file(char* path, char* opt_mode, FILE** optret_file) { 306 | struct stat sb; 307 | if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) return 0; 308 | 309 | if (opt_mode && optret_file) { 310 | *optret_file = fopen(path, opt_mode); 311 | 312 | if (!*optret_file) return 0; 313 | } 314 | 315 | return 1; 316 | } 317 | 318 | // Return 1 if path is dir 319 | int util_is_dir(char* path) { 320 | struct stat sb; 321 | if (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode)) return 0; 322 | return 1; 323 | } 324 | 325 | char * util_read_file(char *filename) { 326 | char *buf = NULL; 327 | long length; 328 | int bytes; 329 | 330 | FILE *fp = fopen(filename, "rb"); 331 | if (fp) { 332 | fseek(fp, 0, SEEK_END); 333 | length = ftell(fp); 334 | fseek(fp, 0, SEEK_SET); 335 | 336 | buf = malloc(length); 337 | if (buf) { 338 | bytes = fread(buf, 1, length, fp); 339 | fclose(fp); 340 | return buf; 341 | } 342 | 343 | fclose(fp); 344 | return NULL; 345 | } 346 | 347 | return NULL; 348 | } 349 | 350 | // Attempt to replace leading ~/ with $HOME 351 | void util_expand_tilde(char* path, int path_len, char** ret_path) { 352 | char* homedir; 353 | char* newpath; 354 | 355 | if (!util_is_file("~", NULL, NULL) && strncmp(path, "~/", 2) == 0 356 | && (homedir = getenv("HOME")) != NULL) { 357 | 358 | newpath = malloc(strlen(homedir) + 1 + (path_len - 2) + 1); 359 | sprintf(newpath, "%s/%.*s", homedir, path_len - 2, path + 2); 360 | *ret_path = newpath; 361 | 362 | return; 363 | } 364 | 365 | *ret_path = strndup(path, path_len); 366 | } 367 | 368 | // autocomplete-like function 369 | /* 370 | int util_find_starting_with(char * partial, vector list, vector &matches, int min) { 371 | vector_clear(matches); 372 | 373 | // Handle trivial case. 374 | unsigned int length = strlen(partial); 375 | if (length) { 376 | for (i = 0; i < vector_size(list); i++) { 377 | item = vector_get(list, i); 378 | 379 | if (strcmp(item, partial) == 0) { 380 | add_vector(matches, item); 381 | return 1; 382 | } else if (strtr(item, partial)) { 383 | add_vector(matches, item); 384 | } 385 | } 386 | 387 | return vector_size(matches); 388 | } 389 | */ 390 | 391 | // Return 1 if re matches subject 392 | int util_pcre_match(char* re, char* subject, int subject_len, char** optret_capture, int* optret_capture_len) { 393 | int rc; 394 | pcre* cre; 395 | const char *error; 396 | int erroffset; 397 | 398 | int ovector[3]; 399 | cre = pcre_compile((const char*)re, (optret_capture ? 0 : PCRE_NO_AUTO_CAPTURE) | PCRE_CASELESS, &error, &erroffset, NULL); 400 | if (!cre) return 0; 401 | 402 | rc = pcre_exec(cre, NULL, subject, subject_len, 0, 0, ovector, 3); 403 | pcre_free(cre); 404 | if (optret_capture) { 405 | if (rc >= 0) { 406 | *optret_capture = subject + ovector[0]; 407 | *optret_capture_len = ovector[1] - ovector[0]; 408 | } else { 409 | *optret_capture = NULL; 410 | *optret_capture_len = 0; 411 | } 412 | } 413 | return rc >= 0 ? 1 : 0; 414 | } 415 | 416 | // Perform a regex replace with back-references. Return number of replacements 417 | // made. If regex is invalid, `ret_result` is set to NULL, `ret_result_len` is 418 | // set to 0 and 0 is returned. 419 | int util_pcre_replace(char* re, char* subj, char* repl, char** ret_result, int* ret_result_len) { 420 | int rc; 421 | pcre* cre; 422 | const char *error; 423 | int erroffset; 424 | int subj_offset; 425 | int subj_offset_z; 426 | int subj_len; 427 | int subj_look_offset; 428 | int last_look_offset; 429 | int ovector[30]; 430 | int num_repls; 431 | int got_match = 0; 432 | str_t result = {0}; 433 | 434 | *ret_result = NULL; 435 | *ret_result_len = 0; 436 | 437 | // Compile regex 438 | cre = pcre_compile((const char*)re, PCRE_CASELESS, &error, &erroffset, NULL); 439 | 440 | if (!cre) return 0; 441 | 442 | // Start match-replace loop 443 | num_repls = 0; 444 | subj_len = strlen(subj); 445 | subj_offset = 0; 446 | subj_offset_z = 0; 447 | subj_look_offset = 0; 448 | last_look_offset = 0; 449 | 450 | while (subj_offset < subj_len) { 451 | // Find match 452 | rc = pcre_exec(cre, NULL, subj, subj_len, subj_look_offset, 0, ovector, 30); 453 | 454 | if (rc < 0 || ovector[0] < 0) { 455 | got_match = 0; 456 | subj_offset_z = subj_len; 457 | 458 | } else { 459 | got_match = 1; 460 | subj_offset_z = ovector[0]; 461 | } 462 | 463 | // Append part before match 464 | str_append_stop(&result, subj + subj_offset, subj + subj_offset_z); 465 | subj_offset = ovector[1]; 466 | subj_look_offset = subj_offset + (subj_offset > last_look_offset ? 0 : 1); // Prevent infinite loop 467 | last_look_offset = subj_look_offset; 468 | 469 | // Break if no match 470 | if (!got_match) break; 471 | 472 | // Append replacements with backrefs 473 | str_append_replace_with_backrefs(&result, subj, repl, rc, ovector, 30); 474 | 475 | // Increment num_repls 476 | num_repls += 1; 477 | } 478 | 479 | // Free regex 480 | pcre_free(cre); 481 | 482 | // Return result 483 | *ret_result = result.data ? result.data : strdup(""); 484 | *ret_result_len = result.len; 485 | 486 | // Return number of replacements 487 | return num_repls; 488 | } 489 | 490 | // Return 1 if a > b, else return 0. 491 | int util_timeval_is_gt(struct timeval* a, struct timeval* b) { 492 | if (a->tv_sec > b->tv_sec) { 493 | return 1; 494 | 495 | } else if (a->tv_sec == b->tv_sec) { 496 | return a->tv_usec > b->tv_usec ? 1 : 0; 497 | } 498 | 499 | return 0; 500 | } 501 | 502 | // Ported from php_escape_shell_arg 503 | // https://github.com/php/php-src/blob/master/ext/standard/exec.c 504 | char* util_escape_shell_arg(char* str, int l) { 505 | int x, y = 0; 506 | char *cmd; 507 | 508 | cmd = malloc(4 * l + 3); // worst case 509 | cmd[y++] = '\''; 510 | 511 | for (x = 0; x < l; x++) { 512 | int mb_len = tb_utf8_char_length(*(str + x)); 513 | 514 | // skip non-valid multibyte characters 515 | if (mb_len < 0) { 516 | continue; 517 | 518 | } else if (mb_len > 1) { 519 | memcpy(cmd + y, str + x, mb_len); 520 | y += mb_len; 521 | x += mb_len - 1; 522 | continue; 523 | } 524 | 525 | switch (str[x]) { 526 | case '\'': 527 | cmd[y++] = '\''; 528 | cmd[y++] = '\\'; 529 | cmd[y++] = '\''; 530 | 531 | // fall-through 532 | default: 533 | cmd[y++] = str[x]; 534 | } 535 | } 536 | 537 | cmd[y++] = '\''; 538 | cmd[y] = '\0'; 539 | 540 | return cmd; 541 | } 542 | 543 | // Adapted from termbox src/demo/keyboard.c 544 | int rect_printf(bview_rect_t rect, int x, int y, uint16_t fg, uint16_t bg, const char *fmt, ...) { 545 | char buf[4096]; 546 | va_list vl; 547 | va_start(vl, fmt); 548 | vsnprintf(buf, sizeof(buf), fmt, vl); 549 | va_end(vl); 550 | return tb_string(rect.x + x, rect.y + y, fg ? fg : rect.fg, bg ? bg : rect.bg, buf); 551 | } 552 | 553 | // Like rect_printf, but accepts @fg,bg; attributes inside the string. To print 554 | // a literal '@', use '@@' in the format string. Specify fg or bg of 0 to 555 | // reset that attribute. 556 | int rect_printf_attr(bview_rect_t rect, int x, int y, const char *fmt, ...) { 557 | char bufo[4096]; 558 | char* buf; 559 | int fg; 560 | int bg; 561 | int tfg; 562 | int tbg; 563 | int c; 564 | uint32_t uni; 565 | 566 | va_list vl; 567 | va_start(vl, fmt); 568 | vsnprintf(bufo, sizeof(bufo), fmt, vl); 569 | va_end(vl); 570 | 571 | fg = rect.fg; 572 | bg = rect.bg; 573 | x = rect.x + x; 574 | y = rect.y + y; 575 | 576 | c = 0; 577 | buf = bufo; 578 | 579 | while (*buf) { 580 | buf += utf8_char_to_unicode(&uni, buf, NULL); 581 | 582 | if (uni == '@') { 583 | if (!*buf) break; 584 | 585 | utf8_char_to_unicode(&uni, buf, NULL); 586 | 587 | if (uni != '@') { 588 | tfg = strtol(buf, &buf, 10); 589 | 590 | if (!*buf) break; 591 | 592 | utf8_char_to_unicode(&uni, buf, NULL); 593 | 594 | if (uni == ',') { 595 | buf++; 596 | 597 | if (!*buf) break; 598 | 599 | tbg = strtol(buf, &buf, 10); 600 | fg = tfg <= 0 ? rect.fg : tfg; 601 | bg = tbg <= 0 ? rect.bg : tbg; 602 | 603 | if (!*buf) break; 604 | 605 | utf8_char_to_unicode(&uni, buf, NULL); 606 | 607 | if (uni == ';') buf++; 608 | 609 | continue; 610 | } 611 | } 612 | } 613 | 614 | tb_char(x, y, fg, bg, uni); 615 | x++; 616 | c++; 617 | } 618 | 619 | return c; 620 | } 621 | --------------------------------------------------------------------------------