├── tests ├── open_file │ ├── keystream │ ├── output │ └── file ├── hello_world │ ├── keystream │ └── output ├── insert_multiline │ ├── output │ └── keystream └── vertical_scrolling │ ├── output │ ├── keystream │ └── file ├── src ├── draw │ ├── draw.h │ ├── highlight_cursor.h │ ├── highlight_cursor.c │ └── draw.c ├── plugins │ ├── debug.h │ ├── debug.c │ ├── treesitter.h │ ├── navigation.c │ ├── misc.c │ ├── open.c │ └── treesitter.c ├── command.c ├── plugins.h ├── command.h ├── buffer │ ├── buffer.h │ ├── mark.h │ └── mark.c ├── highlight.c ├── highlight.h ├── config.h ├── event.c ├── event.h ├── main.h ├── x.h ├── adt │ ├── map.h │ ├── vec.c │ ├── vec.h │ └── map.c ├── x.c ├── config.c └── main.c ├── .gitignore ├── subprojects ├── libimp.wrap └── libmeraki.wrap ├── .gitmodules ├── update_test ├── config.imp ├── meson.build ├── plugins └── snippets │ └── snippets.c ├── LICENSE ├── run_tests └── tree_sitter └── build /tests/open_file/keystream: -------------------------------------------------------------------------------- 1 | hash 2 | keystream 3 | -------------------------------------------------------------------------------- /tests/open_file/output: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooosh/oswald/HEAD/tests/open_file/output -------------------------------------------------------------------------------- /src/draw/draw.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | void draw_event(struct Event e); 5 | -------------------------------------------------------------------------------- /tests/hello_world/keystream: -------------------------------------------------------------------------------- 1 | keystream hello world 2 | hash 3 | keystream 4 | -------------------------------------------------------------------------------- /tests/hello_world/output: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooosh/oswald/HEAD/tests/hello_world/output -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/*/last_output 2 | oswald 3 | build/ 4 | 5 | tree_sitter/* 6 | !tree_sitter/build 7 | -------------------------------------------------------------------------------- /src/plugins/debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | void report_event(struct Event e); 5 | -------------------------------------------------------------------------------- /subprojects/libimp.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/wooosh/imp.git 3 | revision = head 4 | -------------------------------------------------------------------------------- /subprojects/libmeraki.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/wooosh/meraki.git 3 | revision = head 4 | -------------------------------------------------------------------------------- /tests/insert_multiline/output: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooosh/oswald/HEAD/tests/insert_multiline/output -------------------------------------------------------------------------------- /tests/vertical_scrolling/output: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooosh/oswald/HEAD/tests/vertical_scrolling/output -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tree-sitter"] 2 | path = tree-sitter 3 | url = https://github.com/tree-sitter/tree-sitter 4 | -------------------------------------------------------------------------------- /src/plugins/debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void report_event(struct Event e) { 5 | fprintf(stderr, "event_type: %d\n", e.type); 6 | } 7 | -------------------------------------------------------------------------------- /tests/open_file/file: -------------------------------------------------------------------------------- 1 | hello world! 2 | this is a file with multiple lines 3 | 4 | 5 | 6 | also a few empty lines as well as some trailing lines 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/draw/highlight_cursor.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "highlight.h" 4 | #include "buffer/buffer.h" 5 | 6 | void highlight_cursor(struct Buffer *buffer, size_t line, vec_style *highlight); 7 | -------------------------------------------------------------------------------- /src/command.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void register_commands(struct Command *commands) { 4 | for (int i=0; commands[i].name != NULL; i++) { 5 | map_set(&E.commands, commands[i].name, commands[i]); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /update_test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # args: path to test folder 3 | 4 | p=${1%/} 5 | 6 | args= 7 | 8 | if [ -f "$p/file" ]; then 9 | args="$p/file" 10 | fi 11 | 12 | tui-puppet -r "$p/keystream" ./oswald $args > "$p/output" 13 | -------------------------------------------------------------------------------- /src/plugins/treesitter.h: -------------------------------------------------------------------------------- 1 | #include "event.h" 2 | #include "highlight.h" 3 | #include "buffer/buffer.h" 4 | 5 | void treesitter_update(struct Event e); 6 | void treesitter_highlight(struct Buffer *buffer, size_t line, vec_style *highlight); 7 | -------------------------------------------------------------------------------- /tests/insert_multiline/keystream: -------------------------------------------------------------------------------- 1 | keystream line 1 2 | keystream line 2 3 | keystream line 3 4 | keystream line 4 5 | keystream line 5 6 | hash 7 | keystream 8 | -------------------------------------------------------------------------------- /src/plugins.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef void (*PluginInit)(); 4 | 5 | 6 | void p_open(void); 7 | void p_navigation(void); 8 | void p_misc(void); 9 | 10 | PluginInit plugins[] = { 11 | p_open, 12 | p_navigation, 13 | p_misc, 14 | NULL 15 | }; 16 | -------------------------------------------------------------------------------- /src/command.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct Command { 5 | char *name; 6 | void (*fn)(void *payload, int argc, char **argv); 7 | void *payload; 8 | bool changes_selection; 9 | }; 10 | 11 | void register_commands(struct Command *commands); 12 | -------------------------------------------------------------------------------- /src/buffer/buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "adt/vec.h" 4 | 5 | typedef vec_of(vec_char) vec_line; 6 | struct Buffer { 7 | void *payload; 8 | 9 | void (*save)(struct Buffer*); 10 | void (*close)(struct Buffer*); 11 | 12 | vec_line lines; 13 | vec_char title; 14 | // TODO|FEATURE: filetype 15 | }; 16 | -------------------------------------------------------------------------------- /config.imp: -------------------------------------------------------------------------------- 1 | keybind { 2 | escape enter-mode normal 3 | 4 | a snippets-insert test 5 | c snippets-insert cmain 6 | 7 | backspace backspace 8 | 9 | :normal { 10 | i enter-mode insert 11 | s save-file 12 | } 13 | 14 | up move-up 15 | down move-down 16 | left move-left 17 | right move-right 18 | } 19 | -------------------------------------------------------------------------------- /src/highlight.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "draw/highlight_cursor.h" 3 | #include 4 | 5 | Highlighter highlighters[] = { /* buffer_highlight, move_highlight*/ treesitter_highlight, highlight_cursor, NULL}; 6 | 7 | void highlight_line(struct Buffer *buffer, size_t y, vec_style *style) { 8 | for (int i=0; highlighters[i] != NULL; i++) { 9 | highlighters[i](buffer, y, style); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/vertical_scrolling/keystream: -------------------------------------------------------------------------------- 1 | # test scrolling down 2 | repeat 50 3 | hash 4 | keystream hello world 5 | hash 6 | repeat 5 7 | hash 8 | # test scroll down limit 9 | repeat 100 10 | # test scrolling up 11 | repeat 50 12 | hash 13 | keystream hello world 14 | hash 15 | repeat 5 16 | hash 17 | # test scroll up limit 18 | repeat 100 19 | hash 20 | keystream 21 | -------------------------------------------------------------------------------- /src/highlight.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "buffer/buffer.h" 8 | 9 | typedef vec_of(struct MerakiStyle) vec_style; 10 | typedef void (*Highlighter)(struct Buffer *buffer, size_t y, vec_style *style); 11 | 12 | // style array should be the same length as the contents of the line and have 13 | // all values initialized to the default style 14 | void highlight_line(struct Buffer *buffer, size_t y, vec_style *style); 15 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct Keybind { 10 | struct MerakiKey key; 11 | int argc; 12 | char **argv; 13 | char *mode; 14 | }; 15 | 16 | /* 17 | struct FileType { 18 | char *name; 19 | vec_str *suffixes; 20 | 21 | vec_keybind keybinds; 22 | Highlighter highlighter; 23 | } 24 | */ 25 | 26 | // TODO: replace vec of keybinds with a map 27 | typedef vec_of(struct Keybind) vec_keybind; 28 | struct Config { 29 | vec_keybind keybinds; 30 | // vec_style style 31 | }; 32 | 33 | void load_config(struct Config *c); 34 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef void (*EventHandler)(struct Event); 8 | static EventHandler event_pipelines[][16] = { 9 | [event_open] = {treesitter_update, draw_event, NULL}, [event_edit] = {draw_event, NULL}, [event_mark_move] = {draw_event, NULL}, [event_render]= {draw_event, NULL}}; 10 | 11 | void dispatch_event(struct Event e) { 12 | EventHandler *pipeline = event_pipelines[e.type]; 13 | 14 | // TODO|CLEANUP: size_t vs int 15 | for (int i = 0; pipeline[i] != NULL; i++) { 16 | pipeline[i](e); 17 | } 18 | 19 | // TODO: free event data?? 20 | } 21 | -------------------------------------------------------------------------------- /src/buffer/mark.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // read only struct 10 | struct Mark { 11 | struct Buffer *buffer; 12 | 13 | size_t x; 14 | size_t y; 15 | }; 16 | 17 | // void mark_move_to 18 | 19 | // negative = left/up 20 | // positive = right/down 21 | void mark_move_rel(struct Mark *m, ssize_t x, ssize_t y); 22 | 23 | // deletes text between a and b 24 | void mark_delete(struct Mark *a, struct Mark *b); 25 | 26 | // if keep_pos is false, then the cursor will insert the selection behind the 27 | // cursor 28 | void mark_insert(struct Mark *m, vec_const_char str, bool keep_pos); 29 | 30 | // returns true if a's position <= b's position 31 | bool mark_cmp(struct Mark *a, struct Mark *b); 32 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | enum EventType { 8 | // event_open is triggered when a new buffer initialized with text is created 9 | event_open, 10 | event_edit, 11 | // TODO: replace with cursor move 12 | event_mark_move, 13 | event_render, 14 | // CursorMove, 15 | // SelectionChange, 16 | // BufferClose 17 | }; 18 | 19 | struct Event { 20 | enum EventType type; 21 | union { 22 | struct EditEvent *edit; 23 | struct Buffer *open; 24 | struct Mark *mark_move; 25 | }; 26 | }; 27 | 28 | void dispatch_event(struct Event e); 29 | 30 | struct EditEvent { 31 | struct Buffer *buffer; 32 | 33 | size_t start; 34 | size_t len; 35 | 36 | enum { 37 | EditInsert, 38 | EditDelete, 39 | EditChanged, 40 | } type; 41 | }; 42 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "buffer/buffer.h" 6 | #include "buffer/mark.h" 7 | #include "highlight.h" 8 | #include "command.h" 9 | #include 10 | #include "adt/vec.h" 11 | #include "adt/map.h" 12 | 13 | typedef vec_of(struct Buffer*) vec_buffer; 14 | typedef map_t(Highlighter) map_highlight; 15 | typedef map_t(struct Command) map_command; 16 | 17 | extern struct Editor { 18 | vec_buffer buffers; 19 | 20 | struct MerakiTerm *term; 21 | 22 | size_t screen_width; 23 | size_t screen_height; 24 | 25 | // TODO: array of cursors 26 | // TODO: keep copy buffer for each cursor 27 | // TODO: make cursors not always be a selection 28 | struct Mark cursor; 29 | // the anchor refers to the end of the selection 30 | struct Mark anchor; 31 | 32 | map_highlight highlighters; 33 | map_command commands; 34 | 35 | vec_char mode; 36 | 37 | struct Config config; 38 | } E; 39 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('oswald', 'c') 2 | 3 | src = [ 4 | 'src/adt/vec.c', 5 | 'src/adt/map.c', 6 | 7 | 'src/buffer/mark.c', 8 | 9 | 'src/draw/draw.c', 10 | 11 | 'src/plugins/debug.c', 12 | 'src/plugins/navigation.c', 13 | 'src/plugins/misc.c', 14 | 'src/plugins/open.c', 15 | 'src/plugins/treesitter.c', 16 | 17 | 'src/highlight.c', 18 | 'src/draw/highlight_cursor.c', 19 | 20 | 'src/command.c', 21 | 'src/config.c', 22 | 'src/event.c', 23 | 'src/x.c', 24 | 'src/main.c' 25 | ] 26 | # TODO: fix includes to use "" so we dont need this 27 | inc = include_directories('src') 28 | 29 | # tree_sitter_inc = include_directories('tree_sitter/include') 30 | 31 | libmeraki = subproject('libmeraki').get_variable('libmeraki_dep') 32 | libimp = subproject('libimp').get_variable('libimp_dep') 33 | 34 | executable( 35 | 'oswald', 36 | src, 37 | include_directories : [inc], 38 | link_args : ['-lstdc++', '-ldl', '-export-dynamic'], 39 | dependencies : [libmeraki, libimp], 40 | objects: ['tree_sitter/libtree-sitter.a'] 41 | ) 42 | -------------------------------------------------------------------------------- /src/draw/highlight_cursor.c: -------------------------------------------------------------------------------- 1 | #include "buffer/mark.h" 2 | #include "buffer/buffer.h" 3 | #include "main.h" 4 | #include "highlight.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | const struct MerakiStyle selection_style = {{Meraki8Color, -1}, {Meraki8Color, -1}, MerakiReverse}; 11 | 12 | void highlight_cursor(struct Buffer *buffer, size_t line, vec_style *highlight) { 13 | if (E.cursor.buffer != buffer) return; 14 | 15 | struct Mark start = E.cursor; 16 | struct Mark end = E.anchor; 17 | if (!mark_cmp(&start, &end)) { 18 | start = E.anchor; 19 | end = E.cursor; 20 | } 21 | 22 | if (start.y == end.y && line == start.y) { 23 | vec_set(highlight, selection_style, start.x, end.x - start.x + 1); 24 | } else if (line > start.y && line < end.y) { 25 | vec_set(highlight, selection_style, 0, highlight->len); 26 | } else if (line == start.y) { 27 | vec_set(highlight, selection_style, start.x, highlight->len - start.x); 28 | } else if (line == end.y) { 29 | vec_set(highlight, selection_style, 0, end.x); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plugins/snippets/snippets.c: -------------------------------------------------------------------------------- 1 | #include "adt/map.h" 2 | #include "adt/vec.h" 3 | #include "command.h" 4 | #include "main.h" 5 | #include "buffer/mark.h" 6 | 7 | // TODO: load from disk 8 | // TODO: allow tabbing through fields in macros 9 | 10 | typedef map_t(vec_const_char) map_const_str; 11 | static map_const_str snippets; 12 | 13 | // TODO: editor error system 14 | void snippet_insert(void *payload, int argc, char **argv) { 15 | if (E.cursor.buffer) { 16 | // TODO: test for argv 1 and snippet existing 17 | vec_const_char *snippet = map_get(&snippets, argv[1]); 18 | if (snippet) { 19 | mark_insert(&E.cursor, *snippet, false); 20 | } 21 | } 22 | } 23 | 24 | static struct Command commands[] = { 25 | {"snippets-insert", snippet_insert, NULL, false}, 26 | // {"snippets-reload", reload, NULL, false} 27 | {NULL} 28 | }; 29 | 30 | void plugin_init() { 31 | map_init(&snippets); 32 | 33 | map_set(&snippets, "test", 34 | vec_from_str("hello\nworld")); 35 | map_set(&snippets, "cmain", 36 | vec_from_str("int main(int argc, char **argv) {\nreturn 0;\n}")); 37 | 38 | register_commands(commands); 39 | } 40 | -------------------------------------------------------------------------------- /src/x.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | size_t min_size_t(size_t a, size_t b); 7 | ssize_t min_ssize_t(ssize_t a, ssize_t b); 8 | 9 | size_t max_size_t(size_t a, size_t b); 10 | ssize_t max_ssize_t(ssize_t a, ssize_t b); 11 | 12 | #define min(X, Y) _Generic((X), size_t: min_size_t, ssize_t : min_ssize_t )(X, Y) 13 | 14 | #define max(X, Y) _Generic((X), size_t: max_size_t, ssize_t : max_ssize_t )(X, Y) 15 | 16 | // exits on failed allocation 17 | void *xmalloc(size_t size); 18 | void *xrealloc(void *ptr, size_t size); 19 | 20 | // TODO: make assert do nothing by defining it to ';' when NDEBUG is on 21 | // message must be provided 22 | #define xassert(condition, message) \ 23 | xassert_((condition), (message), __FILE__, __LINE__); 24 | 25 | // message can be NULL 26 | #define xassert_errno(condition, message) \ 27 | xassert_errno_((condition), (message), __FILE__, __LINE__); 28 | 29 | void fatal(const char *format, ...); 30 | // displays an error message on the status line, used for nonfatal 31 | void errmsg(const char *format, ...); 32 | 33 | // TODO: save work on failed assertion 34 | void xassert_(bool condition, char *message, char *file, size_t line); 35 | void xassert_errno_(bool condition, char *message, char *file, size_t line); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Salvatore Sanfilippo 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /src/plugins/navigation.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum MoveDirection { 8 | MoveUp, 9 | MoveDown, 10 | MoveLeft, 11 | MoveRight, 12 | }; 13 | 14 | // TODO: check if mark is in buffer 15 | 16 | // move [distance] 17 | static void move(void *payload, int argc, char **argv) { 18 | // TODO: support distance 19 | enum MoveDirection dir = (enum MoveDirection) payload; 20 | switch(dir) { 21 | case MoveUp: 22 | mark_move_rel(&E.cursor, 0, -1); 23 | break; 24 | case MoveDown: 25 | mark_move_rel(&E.cursor, 0, 1); 26 | break; 27 | case MoveLeft: 28 | mark_move_rel(&E.cursor, -1, 0); 29 | break; 30 | case MoveRight: 31 | mark_move_rel(&E.cursor, 1, 0); 32 | break; 33 | // TODO: handle error case 34 | } 35 | } 36 | 37 | enum LineDirection {LineStart, LineEnd}; 38 | 39 | static void line(void *payload, int argc, char **argv) { 40 | 41 | } 42 | 43 | static struct Command commands[] = { 44 | {"move-up", move, (void*) MoveUp, false}, 45 | {"move-down", move, (void*) MoveDown, false}, 46 | {"move-left", move, (void*) MoveLeft, false}, 47 | {"move-right", move, (void*) MoveRight, false}, 48 | 49 | {"line-start", line, (void*) LineStart, false}, 50 | {"line-end", line, (void*) LineEnd, false}, 51 | {NULL} 52 | }; 53 | 54 | void p_navigation() { 55 | register_commands(commands); 56 | } 57 | -------------------------------------------------------------------------------- /run_tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | passed=0 4 | failed=0 5 | 6 | _test() { 7 | # check if custom command is provided 8 | args= 9 | 10 | if [ -f "$1/file" ]; then 11 | args="$1/file" 12 | fi 13 | 14 | tui-puppet "$1/keystream" ./build/oswald $args > "$1/last_output" 15 | 16 | testname="${1##*/}" 17 | if cmp "$1/last_output" "$1/output" >/dev/null; then 18 | printf "\033[42m PASS \033[0m $testname\n" 19 | : $(( passed = passed + 1 )) 20 | else 21 | printf "\033[41m FAIL \033[0m $testname\n" 22 | status=1 23 | : $(( failed = failed + 1 )) 24 | fi 25 | } 26 | 27 | printf "\033[100m TESTS \033[0m\n" 28 | 29 | for test_dir in tests/*; do 30 | _test "$test_dir" 31 | done 32 | 33 | printf "\033[100m RESULTS \033[0m\n" 34 | printf " PASSED: %04d\n" $passed 35 | printf " FAILED: %04d \033[0m\n" $failed 36 | 37 | printf "\033[100m STATUS \033[0m\n" 38 | if [ $failed -ne 0 ]; then 39 | printf "\033[41m \033[0m\n" 40 | printf "\033[41m !!! FAILING TESTS !!! \033[0m\n" 41 | printf "\033[41m \033[0m\n" 42 | else 43 | printf "\033[42m \033[0m\n" 44 | printf "\033[42m ALL PASSISNG \033[0m\n" 45 | printf "\033[42m \033[0m\n" 46 | fi 47 | 48 | 49 | exit "${status:-0}" 50 | PASSED: 0000 51 | FAILED: 0000 52 | -------------------------------------------------------------------------------- /src/adt/map.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 rxi 3 | * 4 | * This library is free software; you can redistribute it and/or modify it 5 | * under the terms of the MIT license. See LICENSE for details. 6 | */ 7 | 8 | #ifndef MAP_H 9 | #define MAP_H 10 | 11 | #include 12 | 13 | #define MAP_VERSION "0.1.0" 14 | 15 | struct map_node_t; 16 | typedef struct map_node_t map_node_t; 17 | 18 | typedef struct { 19 | map_node_t **buckets; 20 | unsigned nbuckets, nnodes; 21 | } map_base_t; 22 | 23 | typedef struct { 24 | unsigned bucketidx; 25 | map_node_t *node; 26 | } map_iter_t; 27 | 28 | 29 | #define map_t(T)\ 30 | struct { map_base_t base; T *ref; T tmp; } 31 | 32 | 33 | #define map_init(m)\ 34 | memset(m, 0, sizeof(*(m))) 35 | 36 | 37 | #define map_deinit(m)\ 38 | map_deinit_(&(m)->base) 39 | 40 | 41 | #define map_get(m, key)\ 42 | ( (m)->ref = map_get_(&(m)->base, key) ) 43 | 44 | 45 | #define map_set(m, key, value)\ 46 | ( (m)->tmp = (value),\ 47 | map_set_(&(m)->base, key, &(m)->tmp, sizeof((m)->tmp)) ) 48 | 49 | 50 | #define map_remove(m, key)\ 51 | map_remove_(&(m)->base, key) 52 | 53 | 54 | #define map_iter(m)\ 55 | map_iter_() 56 | 57 | 58 | #define map_next(m, iter)\ 59 | map_next_(&(m)->base, iter) 60 | 61 | 62 | void map_deinit_(map_base_t *m); 63 | void *map_get_(map_base_t *m, const char *key); 64 | int map_set_(map_base_t *m, const char *key, void *value, int vsize); 65 | void map_remove_(map_base_t *m, const char *key); 66 | map_iter_t map_iter_(void); 67 | const char *map_next_(map_base_t *m, map_iter_t *iter); 68 | 69 | 70 | typedef map_t(void*) map_void_t; 71 | typedef map_t(char*) map_str_t; 72 | typedef map_t(int) map_int_t; 73 | typedef map_t(char) map_char_t; 74 | typedef map_t(float) map_float_t; 75 | typedef map_t(double) map_double_t; 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /src/x.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | size_t min_size_t(size_t a, size_t b) { return a < b ? a : b; } 14 | 15 | size_t max_size_t(size_t a, size_t b) { return a > b ? a : b; } 16 | 17 | ssize_t min_ssize_t(ssize_t a, ssize_t b) { return a < b ? a : b; } 18 | 19 | ssize_t max_ssize_t(ssize_t a, ssize_t b) { return a > b ? a : b; } 20 | 21 | void xassert_(bool condition, char *message, char *file, size_t line) { 22 | if (!condition) { 23 | meraki_term_restore(E.term); 24 | 25 | fprintf(stderr, "Assertion failed at %s:%zu '%s'\n", file, line, message); 26 | exit(1); 27 | } 28 | } 29 | 30 | void xassert_errno_(bool condition, char *message, char *file, size_t line) { 31 | if (!condition) { 32 | int saved_errno = errno; 33 | meraki_term_restore(E.term); 34 | errno = saved_errno; 35 | fprintf(stderr, "Assertion failed at %s:%zu: ", file, line); 36 | perror(message); 37 | } 38 | } 39 | 40 | void fatal(const char *format, ...) { 41 | va_list args; 42 | va_start(args, format); 43 | 44 | meraki_term_restore(E.term); 45 | vfprintf(stderr, format, args); 46 | 47 | exit(1); 48 | } 49 | // TODO: make gui error messages work 50 | void errmsg(const char *format, ...) { 51 | va_list args; 52 | va_start(args, format); 53 | 54 | meraki_term_restore(E.term); 55 | vfprintf(stderr, format, args); 56 | 57 | exit(1); 58 | } 59 | 60 | // exits on failed allocation 61 | // TODO: save work on failed allocation 62 | void *xmalloc(size_t size) { 63 | void *ptr = malloc(size); 64 | xassert_errno(ptr != NULL, NULL); 65 | return ptr; 66 | } 67 | 68 | void *xrealloc(void *ptr, size_t size) { 69 | ptr = realloc(ptr, size); 70 | xassert_errno(ptr != NULL, NULL); 71 | return ptr; 72 | } 73 | -------------------------------------------------------------------------------- /src/plugins/misc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static void enter_mode(void *payload, int argc, char **argv) { 7 | // TODO: handle wrong amount of args 8 | vec_truncate(&E.mode, 0); 9 | vec_append_str(&E.mode, argv[1]); 10 | } 11 | 12 | // saves the current file 13 | static void save_file(void *payload, int argc, char **argv) { 14 | if (E.cursor.buffer == NULL) return; 15 | E.cursor.buffer->save(E.cursor.buffer); 16 | } 17 | 18 | static void backspace(void *payload, int argc, char **argv) { 19 | if (E.cursor.buffer == NULL) return; 20 | 21 | if (E.cursor.x == E.anchor.x && E.cursor.y == E.anchor.y) { 22 | if (E.cursor.y != 0 && E.cursor.x == 0) { 23 | E.anchor.y--; 24 | E.anchor.x = E.cursor.buffer->lines.data[E.anchor.y].len; 25 | } else if (E.cursor.x != 0) { 26 | E.anchor.x--; 27 | } 28 | } 29 | 30 | mark_delete(&E.anchor, &E.cursor); 31 | } 32 | 33 | #include 34 | static void load_plugin(void *payload, int argc, char **argv) { 35 | // TODO: check argc 36 | void *handle = dlopen(argv[1], RTLD_NOW); 37 | if (handle == NULL) { 38 | // TODO: handle 39 | errmsg("Couldn't open so: %s\n", dlerror()); 40 | return; 41 | } 42 | 43 | void (*plugin_init)(); 44 | *(void**)(&plugin_init) = dlsym(handle, "plugin_init"); 45 | if (plugin_init == NULL) { 46 | // TODO: better error message and nonfatal 47 | errmsg("Couldn't open plugin_init in so: %s\n", dlerror()); 48 | return; 49 | } 50 | 51 | plugin_init(); 52 | } 53 | 54 | static struct Command commands[] = { 55 | {"enter-mode", enter_mode, NULL, false}, 56 | {"save-file", save_file, NULL, false}, 57 | {"backspace", backspace, NULL, false}, 58 | {"plugin-load", load_plugin, NULL, false}, 59 | 60 | // {"quit", move, (void*) MoveUp, false}, 61 | {NULL} 62 | }; 63 | 64 | void p_misc() { 65 | register_commands(commands); 66 | } 67 | -------------------------------------------------------------------------------- /src/adt/vec.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | void vec_init_(struct vec_generic_ v) { 11 | *v.data = NULL; 12 | *v.len = 0; 13 | *v.cap = 0; 14 | } 15 | 16 | void vec_destroy_(struct vec_generic_ v) { 17 | free(*v.data); 18 | vec_init_(v); 19 | } 20 | 21 | void vec_atleast_(struct vec_generic_ v, size_t cap) { 22 | if (*v.cap < cap) { 23 | if (*v.cap == 0) { 24 | *v.cap = 1; 25 | } 26 | 27 | while (*v.cap < cap) 28 | *v.cap *= 2; 29 | 30 | *v.data = xrealloc(*v.data, *v.cap * v.elem_size); 31 | } 32 | } 33 | 34 | void vec_splice_(struct vec_generic_ v, size_t start, size_t end) { 35 | memmove(*v.data + start * v.elem_size, *v.data + end * v.elem_size, 36 | (*v.len - end) * v.elem_size); 37 | *v.len -= end - start; 38 | } 39 | 40 | void vec_insert_gap_(struct vec_generic_ v, size_t idx, size_t len) { 41 | vec_atleast_(v, *v.len + len); 42 | memmove(*v.data + (idx + len) * v.elem_size, *v.data + idx * v.elem_size, 43 | (*v.len - idx) * v.elem_size); 44 | *v.len += len; 45 | } 46 | 47 | void vec_insert_vec_(struct vec_generic_ dest, struct vec_generic_ src, 48 | size_t idx) { 49 | vec_insert_gap_(dest, idx, *src.len); 50 | memcpy(*dest.data + idx * dest.elem_size, *src.data, 51 | *src.len * dest.elem_size); 52 | } 53 | 54 | vec_const_char vec_from_str(char *str) { 55 | return (vec_const_char){str, strlen(str), strlen(str)}; 56 | } 57 | 58 | void vec_printf(vec_char *v, const char* format, ...) { 59 | va_list args; 60 | va_start(args, format); 61 | 62 | // using vsnprintf destroys the first list, so we need to store a copy for 63 | // the second call 64 | va_list args2; 65 | va_copy(args2, args); 66 | 67 | int len = vsnprintf(NULL, 0, format, args); 68 | // we need +1 because vsnprintf will include a null 69 | vec_atleast_(vec_repack_(v), v->len + len + 1); 70 | 71 | vsnprintf(v->data + v->len, len+1, format, args2); 72 | v->len += len; 73 | } 74 | -------------------------------------------------------------------------------- /tree_sitter/build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | languages="json c cpp javascript rust html ql ruby java css python julia go agda php scala bash regex jsdoc" 3 | # verilog ocaml typescript haskell fluent swift embedded-template c-sharp 4 | # dashed language names currently don't work, the dash needs to be converted to an underscore 5 | 6 | if [ ! -d "tree-sitter" ]; then 7 | git clone --depth 1 "https://github.com/tree-sitter/tree-sitter" 8 | cd tree-sitter 9 | make 10 | cd .. 11 | cp tree-sitter/lib/include/tree_sitter/api.h ./ 12 | fi 13 | 14 | mkdir -p include/tree_sitter 15 | cp tree-sitter/lib/include/tree_sitter/api.h ./include/tree_sitter/ 16 | 17 | # download langs 18 | for lang in $languages; do 19 | if [ ! -d "tree-sitter-$lang" ]; then 20 | git clone --depth 1 "https://github.com/tree-sitter/tree-sitter-$lang" 21 | fi 22 | done 23 | 24 | cat << EOF > treesitter.h 25 | #pragma once 26 | #include 27 | 28 | EOF 29 | 30 | # forward declarations 31 | for lang in $languages; do 32 | echo "TSLanguage *tree_sitter_$lang();" >> treesitter.h 33 | done 34 | 35 | cat << EOF >> treesitter.h 36 | 37 | struct FileTypeTS { 38 | char *filetype; 39 | TSLanguage *(*language)(); 40 | }; 41 | 42 | static struct FileTypeTS FileTypeTable[] = { 43 | EOF 44 | 45 | for lang in $languages; do 46 | echo " {\"$lang\", tree_sitter_$lang}," >> treesitter.h 47 | done 48 | 49 | cat << EOF >> treesitter.h 50 | {NULL, NULL} 51 | }; 52 | EOF 53 | 54 | rm -rf obj 55 | mkdir -p obj 56 | 57 | CFLAGS="$CFLAGS -I tree-sitter/lib/include" 58 | CXXFLAGS="$CXXFLAGS -I tree-sitter/lib/include" 59 | 60 | for lang in $languages; do 61 | cc $CFLAGS -c "tree-sitter-$lang/src/parser.c" -o "obj/$lang-parser.o" -O3 62 | 63 | if [ -f "tree-sitter-$lang/src/scanner.c" ]; then 64 | cc $CFLAGS -c "tree-sitter-$lang/src/scanner.c" -o "obj/$lang-scanner.o" -O3 65 | fi 66 | 67 | if [ -f "tree-sitter-$lang/src/scanner.cc" ]; then 68 | c++ $CXXFLAGS -c "tree-sitter-$lang/src/scanner.cc" -o "obj/$lang-scanner.o" -O3 69 | fi 70 | done 71 | 72 | cp tree-sitter/libtree-sitter.a ./ 73 | ar rcs libtree-sitter.a obj/* 74 | 75 | cp treesitter.h include/tree_sitter/ 76 | -------------------------------------------------------------------------------- /tests/vertical_scrolling/file: -------------------------------------------------------------------------------- 1 | #include "main.hxx" 2 | #include "keypress.hxx" 3 | #include "terminal.hxx" 4 | #include "draw.hxx" 5 | #include "portion.hxx" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // FEATURE: unicode support 14 | // FEATURE: portions 15 | // FEATURE: config file 16 | // FEATURE: delete key 17 | // FEATURE: marks 18 | // FEATURE: selections 19 | // FEATURE: cursor column memory when moving cursor vertically 20 | // FEATURE: non-printable characters 21 | // FEATURE: update ui on resize 22 | // FEATURE: keybind to build/debug using fifo 23 | // FEATURE: handle resizing 24 | // FEATURE: non LSP code formatting/on save commands 25 | // FEATURE: line numbers 26 | // FEATURE: LSP 27 | // FEATURE: tests 28 | // FEATURE: grep -r 29 | // FEATURE: undo and redo 30 | // FEATURE: unicode chars for special characters like control codes 31 | // FEATURE: modal 32 | // FEATURE: remote terminal control for build commands 33 | // FEATURE: control down=normal mode, control up = insert mode 34 | 35 | 36 | // TODO: open files 37 | // TODO: fix casing 38 | // TODO: remove char* 39 | // TODO: remove printfs 40 | // TODO: change write(stdin_fileno) 41 | // TODO: https://en.cppreference.com/w/cpp/language/range-for 42 | // TODO: review comments 43 | // TODO: clean up defines 44 | struct Editor E; 45 | 46 | // TODO: move to render.cxx and change code that calls this function to mark the row as dirty 47 | #define TAB_STOP 8 48 | void erow::updateRender() { 49 | this->render.clear(); 50 | this->dirty = true; 51 | 52 | for (size_t j = 0; j < this->raw.length(); j++) { 53 | if (this->raw[j] == '\t') { 54 | this->render += ' '; 55 | while (this->render.length() % TAB_STOP != 0) 56 | this->render += ' '; 57 | } else { 58 | this->render += this->raw[j]; 59 | } 60 | } 61 | } 62 | 63 | int main(int argc, char *argv[]) { 64 | if (argc == 1) { 65 | openScratchPortion(); 66 | } else { 67 | for (int i=1; i 7 | #include 8 | #include 9 | 10 | // TODO: detect changes between saves 11 | void buffer_save_file(struct Buffer *buf) { 12 | FILE *file = fopen(buf->title.data, "w"); 13 | if (file == NULL) { 14 | errmsg("Cannot save '%s'", buf->title); 15 | return; 16 | } 17 | 18 | for (int i=0; ilines.len; i++) { 19 | // TODO: error checking 20 | fwrite(buf->lines.data[i].data, 1, buf->lines.data[i].len, file); 21 | fwrite("\n", 1, 1, file); 22 | } 23 | 24 | fclose(file); 25 | } 26 | 27 | // TODO|CLEANUP: move this elsewhere? 28 | void buffer_destroy(struct Buffer *buf) { 29 | vec_destroy(&buf->title); 30 | 31 | for (size_t i = 0; i < buf->lines.len; i++) { 32 | vec_destroy(&buf->lines.data[i]); 33 | } 34 | 35 | vec_destroy(&buf->lines); 36 | free(buf); 37 | } 38 | 39 | // Returns NULL when the file cannot be opened 40 | // TODO|FEATURE: return proper error messages 41 | void buffer_open_file(void *payload, int argc, char **argv) { 42 | // TODO: proper arg handling 43 | char *path = argv[1]; 44 | struct Buffer *buf = xmalloc(sizeof(struct Buffer)); 45 | buf->save = buffer_save_file; 46 | buf->close = buffer_destroy; 47 | vec_init(&buf->lines); 48 | 49 | vec_init(&buf->title); 50 | vec_append_str(&buf->title, path); 51 | 52 | FILE *file = fopen(path, "r"); 53 | 54 | if (file != NULL) { 55 | while (true) { 56 | vec_char line; 57 | vec_init(&line); 58 | 59 | // TODO: support empty files 60 | // TODO|FEATURE: support other line endings 61 | // to support other line endings, we will store a line_ending char[2] in 62 | // the buffer struct and add the line ending during saving 63 | 64 | ssize_t len = getline(&line.data, &line.cap, file); 65 | // TODO|BUG: getline can return -1 on eof AND error, so we need to check 66 | // for errors as well 67 | if (len == -1) 68 | break; 69 | 70 | line.len = len; 71 | // newlines should not be included in our internal representation 72 | if (line.data[len - 1] == '\n') { 73 | line.len--; 74 | } 75 | 76 | vec_push(&buf->lines, line); 77 | } 78 | 79 | fclose(file); 80 | } 81 | 82 | if (buf->lines.len == 0) { 83 | vec_char line; 84 | vec_init(&line); 85 | vec_push(&buf->lines, line); 86 | } 87 | 88 | vec_push(&E.buffers, buf); 89 | E.cursor.buffer = buf; 90 | E.anchor.buffer = buf; 91 | 92 | dispatch_event((struct Event){event_open, .open = buf}); 93 | } 94 | 95 | static struct Command commands[] = { 96 | {"buffer-open-file", buffer_open_file, NULL, false}, {NULL}}; 97 | 98 | void p_open() { register_commands(commands); } 99 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct KeyName { 7 | char* name; 8 | enum MerakiKeyBase key; 9 | }; 10 | 11 | struct KeyName keynames[] = { 12 | {"escape", MerakiEscape}, 13 | {"backspace", MerakiBackspace}, 14 | {"delete", MerakiDelete}, 15 | {"left", MerakiLeftArrow}, 16 | {"right", MerakiRightArrow}, 17 | {"up", MerakiUpArrow}, 18 | {"down", MerakiDownArrow}, 19 | {"home", MerakiHome}, 20 | {"end", MerakiEnd}, 21 | {"pgup", MerakiPageUp}, 22 | {"pgdown", MerakiPageDown}, 23 | {NULL, MerakiKeyNone} 24 | }; 25 | 26 | 27 | // returns KeyNone if no match 28 | struct MerakiKey parse_key(char *str) { 29 | // TODO: check if key is valid and/or conflicts 30 | // TODO: handle dashes 31 | // TODO: parse modifiers 32 | struct MerakiKey k = {MerakiKeyNone, false, false, false}; 33 | 34 | // if single char use character code 35 | if (str[1] == NULL) { 36 | k.base = str[0]; 37 | } else { 38 | for (int i=0; keynames[i].name != NULL; i++) { 39 | if (strcmp(keynames[i].name, str) == 0) { 40 | k.base = keynames[i].key; 41 | break; 42 | } 43 | } 44 | } 45 | 46 | return k; 47 | } 48 | 49 | void directive_handler(void *payload, size_t line, size_t argc, char **argv) { 50 | struct Config *c = payload; 51 | if (argc < 2) 52 | fatal("Expected atleast one directive and one argument on line %zu\n", line); 53 | 54 | if (strcmp(argv[0], "keybind") == 0) { 55 | char *mode = NULL; 56 | if (argv[1][0] == ':') { 57 | mode = strdup(argv[1]+1); 58 | argv++; 59 | argc--; 60 | if (argc < 2) 61 | fatal("Expected keybind following mode on line %zu\n", line); 62 | } 63 | 64 | struct MerakiKey k = parse_key(argv[1]); 65 | if (k.base == MerakiKeyNone) 66 | fatal("Could not parse key '%s' on line %zu\n", argv[1], line); 67 | 68 | // TODO: cleanup 69 | // copy argv 70 | argc -= 2; 71 | argv += 2; 72 | char **nargv = malloc((argc+1)*sizeof(char*)); 73 | for (int i=0; ikeybinds, kb); 85 | } else { 86 | fatal("Unknown directive on line %zu\n", argv[1], line); 87 | } 88 | } 89 | 90 | void load_config(struct Config *c) { 91 | FILE *f = fopen("config.imp", "r"); 92 | if (f == NULL) 93 | // TODO: replace xasserts with fatals in most places 94 | fatal("Could not open config.imp\n"); 95 | 96 | vec_init(&c->keybinds); 97 | 98 | struct imp_parser imp = imp_init(directive_handler, c, f); 99 | // TODO: handle errors 100 | while (imp_next_command(&imp) == imp_success) {}; 101 | } 102 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "plugins.h" 8 | #include 9 | #include 10 | 11 | #include "adt/vec.h" 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | struct Editor E; 19 | 20 | // TODO: add unit tests with --test argument 21 | // TODO: rename vec_push to vec_append 22 | 23 | static void restore_term() { meraki_term_restore(E.term); } 24 | 25 | int main(int argc, char **argv) { 26 | E.term = meraki_term_create(); 27 | 28 | map_init(&E.commands); 29 | 30 | // init plugins 31 | for (int i = 0; plugins[i] != NULL; i++) { 32 | plugins[i](); 33 | } 34 | 35 | load_config(&E.config); 36 | vec_init(&E.mode); 37 | vec_append_str(&E.mode, "normal"); 38 | 39 | if (argc < 2) { 40 | printf("requires one or more filename arguments\n"); 41 | return 1; 42 | } 43 | 44 | if (!meraki_term_raw(E.term)) { 45 | fprintf(stderr, "Cannot initialize terminal.\n"); 46 | return 1; 47 | } 48 | 49 | atexit(restore_term); 50 | 51 | // TODO: execute_command 52 | // TODO: execute_command_str 53 | struct Command *openc = map_get(&E.commands, "buffer-open-file"); 54 | char *cmdargs[3] = {"buffer-open-file", NULL, NULL}; 55 | 56 | argv++; 57 | argc--; 58 | for (int i = 0; i < argc; i++) { 59 | cmdargs[1] = argv[i]; 60 | openc->fn(openc->payload, 2, cmdargs); 61 | } 62 | 63 | struct MerakiInput *mi = meraki_term_input(E.term); 64 | while (true) { 65 | struct MerakiKey key = meraki_read_key(mi); 66 | fprintf(stderr, "%zu\n", key.base); 67 | if (key.base == '\r') key.base = '\n'; 68 | if (key.base == 'q' && key.control) 69 | break; 70 | bool found_keybind = false; 71 | for (int i = 0; i < E.config.keybinds.len; i++) { 72 | struct Keybind kb = E.config.keybinds.data[i]; 73 | if (key.base == kb.key.base && 74 | (kb.mode == NULL || strncmp(E.mode.data, kb.mode, E.mode.len) == 0)) { 75 | struct Command *c = map_get(&E.commands, kb.argv[0]); 76 | // TODO: handle failure 77 | if (c) { 78 | c->fn(c->payload, kb.argc, kb.argv); 79 | } 80 | found_keybind = true; 81 | break; 82 | } 83 | } 84 | if (!found_keybind && strncmp("insert", E.mode.data, E.mode.len) == 0) { 85 | // TODO: stop pgup/pgdown etc from being written here 86 | char txt[] = {key.base, 0}; 87 | mark_insert(&E.cursor, vec_from_str(txt), false); 88 | } 89 | 90 | // don't keep selection if shift isn't held down 91 | if (!key.shift) { 92 | E.anchor = E.cursor; 93 | } 94 | dispatch_event((struct Event){event_render}); 95 | } 96 | 97 | meraki_term_restore(E.term); 98 | return 0; 99 | } 100 | -------------------------------------------------------------------------------- /src/plugins/treesitter.c: -------------------------------------------------------------------------------- 1 | #include "buffer/buffer.h" 2 | #include "main.h" 3 | #include "highlight.h" 4 | #include "event.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | // TODO: store one for each buffer using a map from buffer id to parser 12 | TSParser *parser; 13 | TSTree *tree; 14 | 15 | static const char* treesitter_input(void *vbuffer, uint32_t byte_offset, TSPoint pos, uint32_t *bytes_read) { 16 | struct Buffer *buffer = vbuffer; 17 | 18 | if (pos.row >= buffer->lines.len) { 19 | *bytes_read = 0; 20 | return NULL; 21 | } 22 | 23 | // tree sitter needs a newline, so we use a temporary line buffer that stores 24 | // the line it asks for until the next invocation while reusing memory 25 | static vec_char line; 26 | vec_truncate(&line, 0); 27 | vec_append_vec(&line, &buffer->lines.data[pos.row]); 28 | vec_push(&line, '\n'); 29 | 30 | *bytes_read = line.len - pos.column; 31 | return line.data + pos.column; 32 | } 33 | 34 | void treesitter_update(struct Event e) { 35 | switch (e.type) { 36 | case event_open: 37 | parser = ts_parser_new(); 38 | if (parser == NULL) exit(1); 39 | ts_parser_set_language(parser, tree_sitter_c()); 40 | tree = ts_parser_parse(parser, NULL, (TSInput){(void*) e.open, treesitter_input, TSInputEncodingUTF8}); 41 | break; 42 | } 43 | } 44 | 45 | bool treesitter_cursor_next(TSTreeCursor* cur, size_t line) { 46 | TSNode node = ts_tree_cursor_current_node(cur); 47 | TSPoint start = ts_node_start_point(node); 48 | TSPoint end = ts_node_end_point(node); 49 | 50 | if (start.row <= line && end.row >= line && ts_tree_cursor_goto_first_child(cur)) { 51 | return true; 52 | } else if (end.row <= line && ts_tree_cursor_goto_next_sibling(cur)) { 53 | return true; 54 | } 55 | 56 | while (ts_tree_cursor_goto_parent(cur)) { 57 | if (ts_tree_cursor_goto_next_sibling(cur)) { 58 | return true; 59 | } 60 | } 61 | 62 | return false; 63 | } 64 | 65 | void treesitter_highlight(struct Buffer *buffer, size_t line, vec_style *highlight) { 66 | if (tree != NULL) { 67 | TSNode root = ts_tree_root_node(tree); 68 | TSNode top_node = ts_node_descendant_for_point_range(root, (TSPoint){line, 0}, (TSPoint){line, buffer->lines.data[line].len}); 69 | 70 | TSTreeCursor cur = ts_tree_cursor_new(top_node); 71 | do { 72 | TSNode node = ts_tree_cursor_current_node(&cur); 73 | TSPoint start = ts_node_start_point(node); 74 | TSPoint end = ts_node_end_point(node); 75 | if (start.row == line && end.row == line && ( 76 | // TODO: look up in hashmap? 77 | strcmp(ts_node_type(node), "primitive_type") == 0 || 78 | strcmp(ts_node_type(node), "enumerator") == 0 || 79 | strcmp(ts_node_type(node), "type_identifier") == 0)) { 80 | if (start.row == end.row) { 81 | struct MerakiStyle s = {{Meraki8Color, 35}, {Meraki8Color, -1}, MerakiBright}; 82 | for (int i=start.column; idata[i] = s; 84 | } 85 | } // TODO: handle else for multiline 86 | } 87 | } while (treesitter_cursor_next(&cur, line)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/buffer/mark.c: -------------------------------------------------------------------------------- 1 | #include "string.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | vec_char *mark_line(struct Mark *m) { 10 | return &m->buffer->lines.data[m->y]; 11 | } 12 | 13 | // TODO: should we use size_t everywhere because it messes up signed comparisons 14 | // clamps value to [min, max] 15 | ssize_t clamp(ssize_t min, ssize_t max, ssize_t val) { 16 | if (val < min) 17 | return min; 18 | if (val > max) 19 | return max; 20 | return val; 21 | } 22 | 23 | // TODO: mark_move that makes left on position zero move up a line, and right on 24 | // position go down a line 25 | 26 | // negative = left/up 27 | // positive = right/down 28 | void mark_move_rel(struct Mark *m, ssize_t x, ssize_t y) { 29 | m->y = clamp(0, m->buffer->lines.len - 1, m->y + y); 30 | m->x = clamp(0, mark_line(m)->len, m->x + x); 31 | dispatch_event((struct Event){event_mark_move, .mark_move = m}); 32 | } 33 | 34 | // TODO: should this be mark_order(mark **start, mark **end) and automatically 35 | // swap marks to be in order? 36 | // TODO: assert in same buffer 37 | // TODO: assert valid mark (make a function for this 38 | bool mark_cmp(struct Mark *a, struct Mark *b) { 39 | // marks on different lines 40 | if (a->y < b->y) 41 | return true; 42 | if (a->y > b->y) 43 | return false; 44 | 45 | // marks on same line 46 | if (a->x <= b->x) 47 | return true; 48 | else 49 | return false; 50 | } 51 | 52 | // TODO: multiline delete (maybe delete the area between two marks? 53 | void mark_delete(struct Mark *a, struct Mark *b) { 54 | struct Mark *start = a; 55 | struct Mark *end = b; 56 | 57 | vec_char *line_start = mark_line(start); 58 | vec_char *line_end = mark_line(end); 59 | 60 | // make sure marks are actually in order 61 | if (!mark_cmp(start, end)) { 62 | start = b; 63 | end = a; 64 | } 65 | 66 | // delete within single line 67 | if (start->y == end->y) { 68 | vec_splice(line_start, start->x, end->x); 69 | 70 | *end = *start; 71 | 72 | struct EditEvent ev = {start->buffer, start->y, 1, EditChanged}; 73 | dispatch_event((struct Event){event_edit, .edit = &ev}); 74 | return; 75 | } 76 | 77 | // multline deletes 78 | 79 | // delete start and end lines first because the positions will not have to be 80 | // adjusted when we remove the lines between 81 | vec_truncate(line_start, start->x); 82 | 83 | vec_splice(line_end, 0, end->x); 84 | 85 | vec_line *lines = &start->buffer->lines; 86 | // remove all of the lines in between 87 | for (size_t i = start->y + 1; i < end->y; i++) { 88 | vec_destroy(&lines->data[i]); 89 | } 90 | 91 | vec_splice(lines, start->y + 1, end->y); 92 | } 93 | 94 | // TODO: cleanup 95 | void mark_insert(struct Mark *m, vec_const_char str, bool keep_pos) { 96 | struct EditEvent ev = {m->buffer, m->y, 1, EditChanged}; 97 | 98 | char *nl = memchr(str.data, '\n', str.len); 99 | // TODO: combine these two cases 100 | if (nl == NULL) { 101 | vec_insert_vec(mark_line(m), &str, m->x); 102 | if (!keep_pos) m->x += str.len; 103 | } else { 104 | // split current line in two at the marks position, and save the last 105 | // half for later as the last line 106 | vec_char last_line; 107 | vec_init(&last_line); 108 | vec_append_vec(&last_line, mark_line(m)); 109 | vec_splice(&last_line, 0, m->x); 110 | vec_splice(mark_line(m), m->x, mark_line(m)->len); 111 | 112 | ev.len = 0; 113 | do { 114 | vec_const_char lineview = str; 115 | lineview.len = nl - str.data; 116 | vec_append_vec(&m->buffer->lines.data[m->y + ev.len], &lineview); 117 | 118 | // insert next line 119 | ev.len++; 120 | vec_char line; 121 | vec_init(&line); 122 | vec_insert(&m->buffer->lines, m->y + ev.len, line); 123 | 124 | str.len -= nl - str.data + 1; 125 | str.data = nl + 1; 126 | } while ((nl = memchr(str.data, '\n', str.len)) != NULL); 127 | 128 | vec_append_vec(&m->buffer->lines.data[m->y + ev.len], &str); 129 | vec_append_vec(&m->buffer->lines.data[m->y + ev.len], &last_line); 130 | ev.len++; 131 | if (!keep_pos) m->x = str.len; 132 | } 133 | 134 | if (!keep_pos) { 135 | m->y += ev.len - 1; 136 | } 137 | dispatch_event((struct Event){event_edit, .edit = &ev}); 138 | } 139 | -------------------------------------------------------------------------------- /src/adt/vec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // heavily based off of github.com/rxi/vec 4 | 5 | // TODO: bounds checking 6 | // TODO: rough type assertions using sizeof 7 | // TODO: note that value must contain a constant expression and may be used 8 | // multiple times 9 | 10 | #include 11 | 12 | #define vec_of(T) \ 13 | struct { \ 14 | T *data; \ 15 | size_t len, cap; \ 16 | } 17 | 18 | typedef vec_of(int) vec_int; 19 | typedef vec_of(char) vec_char; 20 | typedef vec_of(const char) vec_const_char; 21 | 22 | struct vec_generic_ { 23 | char **data; 24 | size_t *len; 25 | size_t *cap; 26 | size_t elem_size; 27 | }; 28 | 29 | #define vec_repack_(v) \ 30 | (struct vec_generic_) { \ 31 | (char **)&(v)->data, &(v)->len, &(v)->cap, sizeof(*(v)->data) \ 32 | } 33 | 34 | vec_const_char vec_from_str(char *str); 35 | 36 | void vec_init_(struct vec_generic_ v); 37 | #define vec_init(v) vec_init_(vec_repack_(v)) 38 | 39 | void vec_copy_(struct vec_generic_ dst, struct vec_generic_ src); 40 | #define vec_copy(dst, src) vec_copy_(vec_repack_(dst), vec_repack(src)) 41 | 42 | void vec_destroy_(struct vec_generic_ v); 43 | #define vec_destroy(v) vec_destroy_(vec_repack_(v)) 44 | // removes a portion of a vector 45 | void vec_splice_(struct vec_generic_ v, size_t start, size_t end); 46 | #define vec_splice(v, start, end) vec_splice_(vec_repack_(v), (start), (end)) 47 | 48 | // memory management functions 49 | void vec_atleast_(struct vec_generic_ v, size_t cap); 50 | void vec_insert_gap_(struct vec_generic_ v, size_t idx, size_t len); 51 | 52 | #define vec_push(v, elem) \ 53 | do { \ 54 | vec_atleast_(vec_repack_(v), (v)->len + 1); \ 55 | (v)->data[(v)->len] = (elem); \ 56 | (v)->len++; \ 57 | } while (0) 58 | 59 | #define vec_truncate(v, len_) \ 60 | do { \ 61 | if ((v)->len > len_) (v)->len = (len_); \ 62 | } while (0) 63 | 64 | // TODO: cache idx and len everywhere 65 | #define vec_set(v, value, idx, _len) \ 66 | for (size_t i__ = 0; i__ < (_len); i__++) \ 67 | (v)->data[i__ + (idx)] = (value); 68 | 69 | #define vec_fill(v, value, idx, len) \ 70 | do { \ 71 | size_t idx__ = (idx);\ 72 | size_t len__ = (len);\ 73 | vec_insert_gap_(vec_repack_(v), idx__, len__); \ 74 | vec_set(v, value, idx__, len__); \ 75 | } while (0) 76 | 77 | #define vec_insert(v, idx, val)\ 78 | do {\ 79 | size_t idx__ = (idx);\ 80 | vec_insert_gap_(vec_repack_(v), idx__, 1);\ 81 | (v)->data[idx__] = val;\ 82 | } while (0) 83 | 84 | void vec_insert_vec_(struct vec_generic_ dest, struct vec_generic_ src, 85 | size_t idx); 86 | #define vec_insert_vec(dest, src, idx) \ 87 | vec_insert_vec_(vec_repack_(dest), vec_repack_(src), (idx)); 88 | 89 | // TODO: should not be macro, should be function taking vec_char 90 | #define vec_insert_str(dst, src, idx) \ 91 | do { \ 92 | vec_const_char v__ = vec_from_str(src); \ 93 | vec_insert_vec(dst, &v__, idx); \ 94 | } while (0) 95 | 96 | #define vec_append_vec(dst, src) vec_insert_vec(dst, src, (dst)->len); 97 | 98 | // TODO: should not be macro, should be function taking vec_char 99 | // TODO|CLEANUP: append instead of insert to avoid memmove 100 | #define vec_append_str(dst, src) vec_insert_str(dst, src, (dst)->len) 101 | 102 | // appends printf'd string to end 103 | void vec_printf(vec_char *v, const char* format, ...); 104 | -------------------------------------------------------------------------------- /src/adt/map.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 rxi 3 | * 4 | * This library is free software; you can redistribute it and/or modify it 5 | * under the terms of the MIT license. See LICENSE for details. 6 | */ 7 | 8 | #include 9 | #include 10 | #include "map.h" 11 | 12 | struct map_node_t { 13 | unsigned hash; 14 | void *value; 15 | map_node_t *next; 16 | /* char key[]; */ 17 | /* char value[]; */ 18 | }; 19 | 20 | 21 | static unsigned map_hash(const char *str) { 22 | unsigned hash = 5381; 23 | while (*str) { 24 | hash = ((hash << 5) + hash) ^ *str++; 25 | } 26 | return hash; 27 | } 28 | 29 | 30 | static map_node_t *map_newnode(const char *key, void *value, int vsize) { 31 | map_node_t *node; 32 | int ksize = strlen(key) + 1; 33 | int voffset = ksize + ((sizeof(void*) - ksize) % sizeof(void*)); 34 | node = malloc(sizeof(*node) + voffset + vsize); 35 | if (!node) return NULL; 36 | memcpy(node + 1, key, ksize); 37 | node->hash = map_hash(key); 38 | node->value = ((char*) (node + 1)) + voffset; 39 | memcpy(node->value, value, vsize); 40 | return node; 41 | } 42 | 43 | 44 | static int map_bucketidx(map_base_t *m, unsigned hash) { 45 | /* If the implementation is changed to allow a non-power-of-2 bucket count, 46 | * the line below should be changed to use mod instead of AND */ 47 | return hash & (m->nbuckets - 1); 48 | } 49 | 50 | 51 | static void map_addnode(map_base_t *m, map_node_t *node) { 52 | int n = map_bucketidx(m, node->hash); 53 | node->next = m->buckets[n]; 54 | m->buckets[n] = node; 55 | } 56 | 57 | 58 | static int map_resize(map_base_t *m, int nbuckets) { 59 | map_node_t *nodes, *node, *next; 60 | map_node_t **buckets; 61 | int i; 62 | /* Chain all nodes together */ 63 | nodes = NULL; 64 | i = m->nbuckets; 65 | while (i--) { 66 | node = (m->buckets)[i]; 67 | while (node) { 68 | next = node->next; 69 | node->next = nodes; 70 | nodes = node; 71 | node = next; 72 | } 73 | } 74 | /* Reset buckets */ 75 | buckets = realloc(m->buckets, sizeof(*m->buckets) * nbuckets); 76 | if (buckets != NULL) { 77 | m->buckets = buckets; 78 | m->nbuckets = nbuckets; 79 | } 80 | if (m->buckets) { 81 | memset(m->buckets, 0, sizeof(*m->buckets) * m->nbuckets); 82 | /* Re-add nodes to buckets */ 83 | node = nodes; 84 | while (node) { 85 | next = node->next; 86 | map_addnode(m, node); 87 | node = next; 88 | } 89 | } 90 | /* Return error code if realloc() failed */ 91 | return (buckets == NULL) ? -1 : 0; 92 | } 93 | 94 | 95 | static map_node_t **map_getref(map_base_t *m, const char *key) { 96 | unsigned hash = map_hash(key); 97 | map_node_t **next; 98 | if (m->nbuckets > 0) { 99 | next = &m->buckets[map_bucketidx(m, hash)]; 100 | while (*next) { 101 | if ((*next)->hash == hash && !strcmp((char*) (*next + 1), key)) { 102 | return next; 103 | } 104 | next = &(*next)->next; 105 | } 106 | } 107 | return NULL; 108 | } 109 | 110 | 111 | void map_deinit_(map_base_t *m) { 112 | map_node_t *next, *node; 113 | int i; 114 | i = m->nbuckets; 115 | while (i--) { 116 | node = m->buckets[i]; 117 | while (node) { 118 | next = node->next; 119 | free(node); 120 | node = next; 121 | } 122 | } 123 | free(m->buckets); 124 | } 125 | 126 | 127 | void *map_get_(map_base_t *m, const char *key) { 128 | map_node_t **next = map_getref(m, key); 129 | return next ? (*next)->value : NULL; 130 | } 131 | 132 | 133 | int map_set_(map_base_t *m, const char *key, void *value, int vsize) { 134 | int n, err; 135 | map_node_t **next, *node; 136 | /* Find & replace existing node */ 137 | next = map_getref(m, key); 138 | if (next) { 139 | memcpy((*next)->value, value, vsize); 140 | return 0; 141 | } 142 | /* Add new node */ 143 | node = map_newnode(key, value, vsize); 144 | if (node == NULL) goto fail; 145 | if (m->nnodes >= m->nbuckets) { 146 | n = (m->nbuckets > 0) ? (m->nbuckets << 1) : 1; 147 | err = map_resize(m, n); 148 | if (err) goto fail; 149 | } 150 | map_addnode(m, node); 151 | m->nnodes++; 152 | return 0; 153 | fail: 154 | if (node) free(node); 155 | return -1; 156 | } 157 | 158 | 159 | void map_remove_(map_base_t *m, const char *key) { 160 | map_node_t *node; 161 | map_node_t **next = map_getref(m, key); 162 | if (next) { 163 | node = *next; 164 | *next = (*next)->next; 165 | free(node); 166 | m->nnodes--; 167 | } 168 | } 169 | 170 | 171 | map_iter_t map_iter_(void) { 172 | map_iter_t iter; 173 | iter.bucketidx = -1; 174 | iter.node = NULL; 175 | return iter; 176 | } 177 | 178 | 179 | const char *map_next_(map_base_t *m, map_iter_t *iter) { 180 | if (iter->node) { 181 | iter->node = iter->node->next; 182 | if (iter->node == NULL) goto nextBucket; 183 | } else { 184 | nextBucket: 185 | do { 186 | if (++iter->bucketidx >= m->nbuckets) { 187 | return NULL; 188 | } 189 | iter->node = m->buckets[iter->bucketidx]; 190 | } while (iter->node == NULL); 191 | } 192 | return (char*) (iter->node + 1); 193 | } 194 | -------------------------------------------------------------------------------- /src/draw/draw.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "highlight.h" 7 | #include "buffer/buffer.h" 8 | #include "event.h" 9 | #include "main.h" 10 | #include "adt/vec.h" 11 | #include "x.h" 12 | 13 | struct DrawState { 14 | struct MerakiOutput *out; 15 | // terminal size 16 | size_t width; 17 | size_t height; 18 | 19 | // used for scrolling 20 | size_t offset_x; 21 | size_t offset_y; 22 | 23 | // buffer for currently drawn line 24 | vec_char line; 25 | vec_style style; 26 | }; 27 | 28 | // writes the current line to the screen then clears it 29 | static void draw_line(struct DrawState *ds, size_t y) { 30 | xassert(y < ds->height, NULL); 31 | meraki_output_draw(ds->out, y, ds->line.len, ds->line.data, ds->style.data); 32 | vec_truncate(&ds->line, 0); 33 | vec_truncate(&ds->style, 0); 34 | } 35 | 36 | // TODO: document how this function works 37 | static void style_previous(struct DrawState *ds, struct MerakiStyle style) { 38 | // TODO: document which vec function fields cannot contain expressions or 39 | // vector fields in vec.h 40 | vec_fill(&ds->style, style, ds->style.len, ds->line.len - ds->style.len); 41 | } 42 | 43 | static void draw_status(struct DrawState *ds) { 44 | // TODO: truncate status to screen 45 | vec_append_str(&ds->line, " ._. "); 46 | 47 | // TODO: pull styles from config 48 | struct MerakiStyle s = { {Meraki8Color, {-1}}, {Meraki8Color, {-1}}, MerakiBright | MerakiUnderscore}; 49 | style_previous(ds, s); 50 | 51 | vec_append_vec(&ds->line, &E.mode); 52 | vec_fill(&ds->line, ' ', ds->line.len, ds->width - ds->line.len); 53 | 54 | s.attr = MerakiUnderscore; 55 | style_previous(ds, s); 56 | 57 | draw_line(ds, 0); 58 | } 59 | 60 | static void draw_buffer_title(struct DrawState *ds, struct Buffer *buffer, size_t y) { 61 | vec_printf(&ds->line, " [%.*s]", buffer->title.len, buffer->title.data); 62 | vec_fill(&ds->line, ' ', ds->line.len, ds->width - ds->line.len); 63 | 64 | struct MerakiStyle s = {{Meraki8Color, {-1}}, {Meraki8Color, {-1}}, MerakiReverse}; 65 | style_previous(ds, s); 66 | 67 | draw_line(ds, y); 68 | } 69 | 70 | static void draw_screen(struct DrawState *ds) { 71 | size_t lines_read = 0; 72 | // start at one since the status bar is at the top 73 | size_t screen_y = 1; 74 | for (size_t i=0; i= ds->offset_y + ds->height) break; 80 | if (lines_read > ds->offset_y) { 81 | draw_buffer_title(ds, buffer, screen_y); 82 | screen_y++; 83 | } 84 | 85 | // draw buffer contents 86 | if (lines_read + buffer->lines.len > ds->offset_y) { 87 | size_t buffer_y = 0; 88 | 89 | if (lines_read < ds->offset_y) { 90 | buffer_y = ds->offset_y - lines_read; 91 | } 92 | 93 | for (; buffer_y < buffer->lines.len; buffer_y++) { 94 | vec_append_vec(&ds->line, &buffer->lines.data[buffer_y]); 95 | // add space for cursor at end of line 96 | vec_push(&ds->line, ' '); 97 | 98 | struct MerakiStyle s = {{Meraki8Color, -1}, {Meraki8Color, -1}, MerakiNone}; 99 | vec_fill(&ds->style, s, 0, ds->line.len); 100 | highlight_line(buffer, buffer_y, &ds->style); 101 | 102 | // TODO: unicode support for trimming 103 | vec_splice(&ds->line, 0, ds->offset_x); 104 | vec_splice(&ds->style, 0, ds->offset_x); 105 | vec_truncate(&ds->line, ds->width); 106 | vec_truncate(&ds->style, ds->width); 107 | draw_line(ds, screen_y); 108 | 109 | screen_y++; 110 | lines_read++; 111 | if (screen_y >= ds->height) break; 112 | } 113 | 114 | if (screen_y >= ds->height) break; 115 | } 116 | } 117 | 118 | for (;screen_y < ds->height; screen_y++) { 119 | struct MerakiStyle s = {{Meraki8Color, -1}, {Meraki8Color, -1}, MerakiDim}; 120 | vec_push(&ds->style, s); 121 | vec_push(&ds->line, '~'); 122 | draw_line(ds, screen_y); 123 | } 124 | } 125 | 126 | static void clamp_cursor(struct DrawState *ds) { 127 | if (E.cursor.buffer == NULL) return; 128 | // get viewport y of cursor 129 | size_t vy = 0; 130 | for (int i=0; E.buffers.data[i] != E.cursor.buffer; i++) { 131 | // + 1 for buffer title 132 | vy += E.buffers.data[i]->lines.len + 1; 133 | } 134 | vy += E.cursor.y + 1; 135 | 136 | // TODO: use clamp function 137 | if (vy < ds->offset_y) { 138 | ds->offset_y = vy; 139 | } else if (vy >= (ds->height - 1) + ds->offset_y) { 140 | // TODO: explain how this math works because ??? 141 | ds->offset_y = vy - (ds->height - 1) + 1; 142 | } 143 | 144 | // x scrolling 145 | if (E.cursor.x < ds->offset_x) { 146 | ds->offset_x = E.cursor.x; 147 | } else if (E.cursor.x >= ds->offset_x + ds->width - 1) { 148 | ds->offset_x = E.cursor.x - (ds->width-1); 149 | } 150 | } 151 | 152 | void draw_event(struct Event e) { 153 | static struct DrawState ds = {NULL}; 154 | 155 | // TODO: event switch to add fast paths for expensive operations like 156 | // inserting lines 157 | 158 | if (ds.out == NULL) { 159 | ds.out = meraki_term_output(E.term); 160 | meraki_term_size(E.term, &ds.width, &ds.height); 161 | vec_init(&ds.line); 162 | vec_init(&ds.style); 163 | meraki_output_cursor_hide(ds.out); 164 | } 165 | 166 | clamp_cursor(&ds); 167 | draw_status(&ds); 168 | draw_screen(&ds); 169 | 170 | meraki_output_commit(ds.out); 171 | } 172 | --------------------------------------------------------------------------------