diff options
| author | Albert Cervin <albert@acervin.com> | 2024-01-29 23:34:07 +0100 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2024-01-30 15:04:44 +0100 |
| commit | dda591fa33ac10c433289aa3ee862c3ded41eed3 (patch) | |
| tree | 9a25edbc810fd5d8fbc20f367c71ebcd81bb6026 /src | |
| parent | 880199011075afd4f2d9bd16c7ce42a04741b5b7 (diff) | |
| download | dged-dda591fa33ac10c433289aa3ee862c3ded41eed3.tar.gz dged-dda591fa33ac10c433289aa3ee862c3ded41eed3.tar.xz dged-dda591fa33ac10c433289aa3ee862c3ded41eed3.zip | |
Syntax highlight is a go
Diffstat (limited to 'src')
| -rw-r--r-- | src/dged/buffer.c | 81 | ||||
| -rw-r--r-- | src/dged/buffer.h | 24 | ||||
| -rw-r--r-- | src/dged/display.h | 19 | ||||
| -rw-r--r-- | src/dged/lang.c | 3 | ||||
| -rw-r--r-- | src/dged/minibuffer.c | 34 | ||||
| -rw-r--r-- | src/dged/minibuffer.h | 5 | ||||
| -rw-r--r-- | src/dged/path.h | 25 | ||||
| -rw-r--r-- | src/dged/syntax.c | 563 | ||||
| -rw-r--r-- | src/dged/syntax.h | 7 | ||||
| -rw-r--r-- | src/dged/text.c | 20 | ||||
| -rw-r--r-- | src/dged/text.h | 1 | ||||
| -rw-r--r-- | src/main/cmds.c | 1 | ||||
| -rw-r--r-- | src/main/completion.c | 2 | ||||
| -rw-r--r-- | src/main/main.c | 21 |
14 files changed, 794 insertions, 12 deletions
diff --git a/src/dged/buffer.c b/src/dged/buffer.c index 89e7dad..0a7dd16 100644 --- a/src/dged/buffer.c +++ b/src/dged/buffer.c @@ -73,21 +73,27 @@ static struct kill_ring { } typedef VEC(struct create_hook) create_hook_vec; +typedef VEC(struct destroy_hook) destroy_hook_vec; typedef VEC(struct insert_hook) insert_hook_vec; typedef VEC(struct update_hook) update_hook_vec; +typedef VEC(struct reload_hook) reload_hook_vec; typedef VEC(struct delete_hook) delete_hook_vec; +typedef VEC(struct render_hook) render_hook_vec; DECLARE_HOOK(create, create_hook_cb, create_hook_vec); +DECLARE_HOOK(destroy, destroy_hook_cb, destroy_hook_vec); DECLARE_HOOK(insert, insert_hook_cb, insert_hook_vec); DECLARE_HOOK(update, update_hook_cb, update_hook_vec); +DECLARE_HOOK(reload, reload_hook_cb, reload_hook_vec); +DECLARE_HOOK(render, render_hook_cb, render_hook_vec); DECLARE_HOOK(delete, delete_hook_cb, delete_hook_vec); static create_hook_vec g_create_hooks; uint32_t g_create_hook_id; struct hooks { - create_hook_vec create_hooks; - uint32_t create_hook_id; + destroy_hook_vec destroy_hooks; + uint32_t destroy_hook_id; insert_hook_vec insert_hooks; uint32_t insert_hook_id; @@ -95,6 +101,12 @@ struct hooks { update_hook_vec update_hooks; uint32_t update_hook_id; + reload_hook_vec reload_hooks; + uint32_t reload_hook_id; + + render_hook_vec render_hooks; + uint32_t render_hook_id; + delete_hook_vec delete_hooks; uint32_t delete_hook_id; }; @@ -108,7 +120,16 @@ void buffer_remove_create_hook(uint32_t hook_id, remove_hook_cb callback) { remove_create_hook(&g_create_hooks, hook_id, callback); } +uint32_t buffer_add_destroy_hook(struct buffer *buffer, + destroy_hook_cb callback, void *userdata) { + return insert_destroy_hook(&buffer->hooks->destroy_hooks, + &buffer->hooks->destroy_hook_id, callback, + userdata); +} + void buffer_static_init() { + VEC_INIT(&g_create_hooks, 8); + settings_register_setting( "editor.tab-width", (struct setting_value){.type = Setting_Number, .number_value = 4}); @@ -119,6 +140,7 @@ void buffer_static_init() { } void buffer_static_teardown() { + VEC_DESTROY(&g_create_hooks); for (uint32_t i = 0; i < KILL_RING_SZ; ++i) { if (g_kill_ring.buffer[i].allocated) { free(g_kill_ring.buffer[i].text); @@ -142,7 +164,10 @@ static struct buffer create_internal(const char *name, char *filename) { b.hooks = calloc(1, sizeof(struct hooks)); VEC_INIT(&b.hooks->insert_hooks, 8); VEC_INIT(&b.hooks->update_hooks, 8); + VEC_INIT(&b.hooks->reload_hooks, 8); + VEC_INIT(&b.hooks->render_hooks, 8); VEC_INIT(&b.hooks->delete_hooks, 8); + VEC_INIT(&b.hooks->destroy_hooks, 8); undo_init(&b.undo, 100); @@ -404,10 +429,17 @@ void buffer_reload(struct buffer *buffer) { sb.st_mtim.tv_nsec != buffer->last_write.tv_nsec) { text_clear(buffer->text); buffer_read_from_file(buffer); + VEC_FOR_EACH(&buffer->hooks->reload_hooks, struct reload_hook * h) { + h->callback(buffer, h->userdata); + } } } void buffer_destroy(struct buffer *buffer) { + VEC_FOR_EACH(&buffer->hooks->destroy_hooks, struct destroy_hook * h) { + h->callback(buffer, h->userdata); + } + text_destroy(buffer->text); buffer->text = NULL; @@ -418,7 +450,10 @@ void buffer_destroy(struct buffer *buffer) { buffer->filename = NULL; VEC_DESTROY(&buffer->hooks->update_hooks); + VEC_DESTROY(&buffer->hooks->render_hooks); + VEC_DESTROY(&buffer->hooks->reload_hooks); VEC_DESTROY(&buffer->hooks->insert_hooks); + VEC_DESTROY(&buffer->hooks->destroy_hooks); VEC_DESTROY(&buffer->hooks->delete_hooks); free(buffer->hooks); @@ -460,8 +495,12 @@ struct location buffer_add(struct buffer *buffer, struct location at, (struct undo_boundary){.save_point = false}); } + uint32_t begin_idx = text_global_idx(buffer->text, initial.line, initial.col); + uint32_t end_idx = text_global_idx(buffer->text, final.line, final.col); + VEC_FOR_EACH(&buffer->hooks->insert_hooks, struct insert_hook * h) { - h->callback(buffer, region_new(initial, final), h->userdata); + h->callback(buffer, region_new(initial, final), begin_idx, end_idx, + h->userdata); } buffer->modified = true; @@ -592,7 +631,7 @@ struct location buffer_clamp(struct buffer *buffer, int64_t line, int64_t col) { struct location buffer_end(struct buffer *buffer) { uint32_t nlines = buffer_num_lines(buffer); - return (struct location){nlines, buffer_num_chars(buffer, nlines)}; + return (struct location){.line = nlines, .col = 0}; } uint32_t buffer_num_lines(struct buffer *buffer) { @@ -764,12 +803,17 @@ struct location buffer_delete(struct buffer *buffer, struct region region) { undo_push_boundary(&buffer->undo, (struct undo_boundary){.save_point = false}); + uint32_t begin_idx = + text_global_idx(buffer->text, region.begin.line, region.begin.col); + uint32_t end_idx = + text_global_idx(buffer->text, region.end.line, region.end.col); + text_delete(buffer->text, region.begin.line, region.begin.col, region.end.line, region.end.col); buffer->modified = true; VEC_FOR_EACH(&buffer->hooks->delete_hooks, struct delete_hook * h) { - h->callback(buffer, region, h->userdata); + h->callback(buffer, region, begin_idx, end_idx, h->userdata); } return region.begin; @@ -858,6 +902,28 @@ void buffer_remove_update_hook(struct buffer *buffer, uint32_t hook_id, remove_update_hook(&buffer->hooks->update_hooks, hook_id, callback); } +uint32_t buffer_add_render_hook(struct buffer *buffer, render_hook_cb hook, + void *userdata) { + return insert_render_hook(&buffer->hooks->render_hooks, + &buffer->hooks->render_hook_id, hook, userdata); +} + +void buffer_remove_render_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback) { + remove_render_hook(&buffer->hooks->render_hooks, hook_id, callback); +} + +uint32_t buffer_add_reload_hook(struct buffer *buffer, reload_hook_cb hook, + void *userdata) { + return insert_reload_hook(&buffer->hooks->reload_hooks, + &buffer->hooks->reload_hook_id, hook, userdata); +} + +void buffer_remove_reload_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback) { + remove_reload_hook(&buffer->hooks->reload_hooks, hook_id, callback); +} + struct cmdbuf { struct command_list *cmds; struct location origin; @@ -999,6 +1065,11 @@ void buffer_render(struct buffer *buffer, struct buffer_render_params *params) { return; } + VEC_FOR_EACH(&buffer->hooks->render_hooks, struct render_hook * h) { + h->callback(buffer, h->userdata, params->origin, params->width, + params->height); + } + struct setting *show_ws = settings_get("editor.show-whitespace"); struct cmdbuf cmdbuf = (struct cmdbuf){ diff --git a/src/dged/buffer.h b/src/dged/buffer.h index 1e00b9d..2e71fb3 100644 --- a/src/dged/buffer.h +++ b/src/dged/buffer.h @@ -418,8 +418,26 @@ uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, void buffer_remove_update_hook(struct buffer *buffer, uint32_t hook_id, remove_hook_cb callback); +/** Buffer render hook callback function */ +typedef void (*render_hook_cb)(struct buffer *buffer, void *userdata, + struct location origin, uint32_t width, + uint32_t height); + +uint32_t buffer_add_render_hook(struct buffer *buffer, render_hook_cb hook, + void *userdata); +void buffer_remove_render_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback); + +/** Buffer reload hook callback function */ +typedef void (*reload_hook_cb)(struct buffer *buffer, void *userdata); +uint32_t buffer_add_reload_hook(struct buffer *buffer, reload_hook_cb hook, + void *userdata); +void buffer_remove_reload_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback); + /** Buffer insert hook callback function */ typedef void (*insert_hook_cb)(struct buffer *buffer, struct region inserted, + uint32_t begin_idx, uint32_t end_idx, void *userdata); uint32_t buffer_add_insert_hook(struct buffer *buffer, insert_hook_cb callback, @@ -429,6 +447,7 @@ void buffer_remove_insert_hook(struct buffer *buffer, uint32_t hook_id, /** Buffer delete hook callback function */ typedef void (*delete_hook_cb)(struct buffer *buffer, struct region removed, + uint32_t begin_idx, uint32_t end_idx, void *userdata); uint32_t buffer_add_delete_hook(struct buffer *buffer, delete_hook_cb callback, @@ -436,6 +455,11 @@ uint32_t buffer_add_delete_hook(struct buffer *buffer, delete_hook_cb callback, void buffer_remove_delete_hook(struct buffer *buffer, uint32_t hook_id, remove_hook_cb callback); +/** Buffer destroy hook callback function */ +typedef void (*destroy_hook_cb)(struct buffer *buffer, void *userdata); +uint32_t buffer_add_destroy_hook(struct buffer *buffer, + destroy_hook_cb callback, void *userdata); + /** Buffer create hook callback function */ typedef void (*create_hook_cb)(struct buffer *buffer, void *userdata); diff --git a/src/dged/display.h b/src/dged/display.h index 2fc807b..aae2614 100644 --- a/src/dged/display.h +++ b/src/dged/display.h @@ -119,6 +119,25 @@ struct command_list *command_list_create(uint32_t capacity, */ void command_list_set_show_whitespace(struct command_list *list, bool show); +enum colors { + Color_Black = 0, + Color_Red, + Color_Green, + Color_Yellow, + Color_Blue, + Color_Magenta, + Color_Cyan, + Color_White, + Color_BrightBlack = 8, + Color_BrightRed, + Color_BrightGreen, + Color_BrightYellow, + Color_BrightBlue, + Color_BrightMagenta, + Color_BrightCyan, + Color_BrightWhite +}; + /** * Set background color * diff --git a/src/dged/lang.c b/src/dged/lang.c index 1bfe822..fb740e8 100644 --- a/src/dged/lang.c +++ b/src/dged/lang.c @@ -49,7 +49,8 @@ void languages_init(bool register_default) { define_lang("C++", "cxx", ".*\\.(cpp|cxx|cc|c++|hh|h)", 2, "clangd"); define_lang("Rust", "rs", ".*\\.rs", 4, "rust-analyzer"); define_lang("Nix", "nix", ".*\\.nix", 4, "rnix-lsp"); - define_lang("Makefile", "make", ".*(Makefile|\\.mk)", 4, NULL); + define_lang("Make", "make", ".*(Makefile|\\.mk)", 4, NULL); + define_lang("Python", "python", ".*\\.py", 4, NULL); } } diff --git a/src/dged/minibuffer.c b/src/dged/minibuffer.c index eacf8da..634a864 100644 --- a/src/dged/minibuffer.c +++ b/src/dged/minibuffer.c @@ -2,6 +2,7 @@ #include "binding.h" #include "buffer.h" #include "buffer_view.h" +#include "buffers.h" #include "command.h" #include "display.h" @@ -20,6 +21,8 @@ static struct minibuffer { bool clear; struct window *prev_window; + struct buffer *message_buffer; + } g_minibuffer = {0}; uint32_t minibuffer_draw_prompt(struct command_list *commands) { @@ -85,7 +88,7 @@ void update(struct buffer *buffer, void *userdata) { } } -void minibuffer_init(struct buffer *buffer) { +void minibuffer_init(struct buffer *buffer, struct buffers *buffers) { if (g_minibuffer.buffer != NULL) { return; } @@ -96,6 +99,9 @@ void minibuffer_init(struct buffer *buffer) { g_minibuffer.clear = false; g_minibuffer.prompt_active = false; buffer_add_update_hook(g_minibuffer.buffer, update, &g_minibuffer); + + g_minibuffer.message_buffer = + buffers_add(buffers, buffer_create("*messages*")); } void echo(uint32_t timeout, const char *fmt, va_list args) { @@ -107,13 +113,37 @@ void echo(uint32_t timeout, const char *fmt, va_list args) { g_minibuffer.expires.tv_sec += timeout; g_minibuffer.clear = false; - char buff[2048]; + static char buff[2048]; size_t nbytes = vsnprintf(buff, 2048, fmt, args); // vsnprintf returns how many characters it would have wanted to write in case // of overflow buffer_set_text(g_minibuffer.buffer, (uint8_t *)buff, nbytes > 2048 ? 2048 : nbytes); + + // we can get messages before this is set up + if (g_minibuffer.message_buffer != NULL) { + buffer_add(g_minibuffer.message_buffer, + buffer_end(g_minibuffer.message_buffer), (uint8_t *)buff, + nbytes > 2048 ? 2048 : nbytes); + } +} + +void message(const char *fmt, ...) { + // we can get messages before this is set up + if (g_minibuffer.message_buffer == NULL) { + return; + } + + va_list args; + va_start(args, fmt); + static char buff[2048]; + size_t nbytes = vsnprintf(buff, 2048, fmt, args); + va_end(args); + + buffer_add(g_minibuffer.message_buffer, + buffer_end(g_minibuffer.message_buffer), (uint8_t *)buff, + nbytes > 2048 ? 2048 : nbytes); } void minibuffer_destroy() { diff --git a/src/dged/minibuffer.h b/src/dged/minibuffer.h index b7c5171..3e94f8c 100644 --- a/src/dged/minibuffer.h +++ b/src/dged/minibuffer.h @@ -4,6 +4,7 @@ #include <time.h> struct buffer; +struct buffers; struct command_ctx; struct command_list; struct keymap; @@ -15,7 +16,7 @@ struct keymap; * nothing if called more than once. * @param buffer underlying buffer to use for text IO in the minibuffer. */ -void minibuffer_init(struct buffer *buffer); +void minibuffer_init(struct buffer *buffer, struct buffers *buffers); /** * Destroy the minibuffer @@ -28,6 +29,8 @@ struct text_chunk minibuffer_content(); struct buffer *minibuffer_buffer(); +void message(const char *fmt, ...); + /** * Echo a message to the minibuffer. * diff --git a/src/dged/path.h b/src/dged/path.h index da62457..8ea9977 100644 --- a/src/dged/path.h +++ b/src/dged/path.h @@ -38,3 +38,28 @@ static char *to_abspath(const char *path) { return exp; } } + +static const char *join_path_with_delim(const char *p1, const char *p2, + const char delim) { + size_t len1 = strlen(p1); + size_t len2 = strlen(p2); + + char *path = malloc(len1 + len2 + 2); + uint32_t idx = 0; + memcpy(&path[idx], p1, len1); + idx += len1; + path[idx++] = '/'; + memcpy(&path[idx], p2, len2); + idx += len2; + path[idx++] = '\0'; + + return path; +} + +static const char *join_path(const char *p1, const char *p2) { +#ifdef __unix__ + return join_path_with_delim(p1, p2, '/'); +#elif defined(_WIN32) || defined(WIN32) + return join_path_with_delim(p1, p2, '\\'); +#endif +} diff --git a/src/dged/syntax.c b/src/dged/syntax.c new file mode 100644 index 0000000..403cabe --- /dev/null +++ b/src/dged/syntax.c @@ -0,0 +1,563 @@ +#include "syntax.h" + +#include <ctype.h> +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <regex.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <tree_sitter/api.h> + +#include "buffer.h" +#include "display.h" +#include "hash.h" +#include "hashmap.h" +#include "minibuffer.h" +#include "path.h" +#include "text.h" +#include "vec.h" + +static char *treesitter_path = NULL; +static bool treesitter_path_allocated = false; +static const char *parser_filename = "parser"; +static const char *highlight_path = "queries/highlights.scm"; + +HASHMAP_ENTRY_TYPE(re_cache_entry, regex_t); + +struct highlight { + TSParser *parser; + TSTree *tree; + TSQuery *query; + HASHMAP(struct re_cache_entry) re_cache; + void *dlhandle; +}; + +static void delete_parser(struct buffer *buffer, void *userdata) { + struct highlight *highlight = (struct highlight *)userdata; + + if (highlight->query != NULL) { + ts_query_delete(highlight->query); + } + + HASHMAP_FOR_EACH(&highlight->re_cache, struct re_cache_entry * entry) { + regfree(&entry->value); + } + + HASHMAP_DESTROY(&highlight->re_cache); + + ts_tree_delete(highlight->tree); + ts_parser_delete(highlight->parser); + + dlclose(highlight->dlhandle); + + free(highlight); +} + +static const char *read_text(void *payload, uint32_t byte_offset, + TSPoint position, uint32_t *bytes_read) { + + struct text *text = (struct text *)payload; + + if (position.row < text_num_lines(text)) { + struct text_chunk chunk = text_get_line(text, position.row); + + // empty lines + if (chunk.nbytes == 0 || position.column >= chunk.nchars) { + *bytes_read = 1; + return "\n"; + } + + uint32_t bytei = text_col_to_byteindex(text, chunk.line, position.column); + *bytes_read = chunk.nbytes - bytei; + return (const char *)chunk.text + bytei; + } + + // eof + *bytes_read = 0; + return NULL; +} + +static const char *lang_folder(struct buffer *buffer) { + const char *langname = buffer->lang.name; + + size_t tspath_len = strlen(treesitter_path); + size_t lang_len = strlen(langname); + + char *fld = malloc(tspath_len + lang_len + 2); + uint32_t idx = 0; + memcpy(&fld[idx], treesitter_path, tspath_len); + idx += tspath_len; + fld[idx++] = '/'; + for (uint32_t i = 0; i < lang_len; ++i) { + fld[idx + i] = tolower(langname[i]); + } + idx += lang_len; + fld[idx++] = '\0'; + + return fld; +} + +static TSQuery *setup_queries(const char *lang_root, TSTree *tree) { + const char *filename = join_path(lang_root, highlight_path); + + // read queries from file + int fd = open(filename, O_RDONLY); + free((void *)filename); + if (fd < 0) { + return NULL; + } + + size_t len = lseek(fd, 0, SEEK_END); + void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + if (data == NULL) { + return NULL; + } + + // run queries + TSQueryError error = TSQueryErrorNone; + uint32_t error_offset = 0; + TSQuery *q = ts_query_new(ts_tree_language(tree), (char *)data, len, + &error_offset, &error); + + munmap(data, len); + + if (error != TSQueryErrorNone) { + const char *msg = "unknown error"; + switch (error) { + case TSQueryErrorSyntax: + msg = "syntax error"; + break; + case TSQueryErrorNodeType: + msg = "node type"; + break; + case TSQueryErrorField: + msg = "error field"; + break; + case TSQueryErrorCapture: + msg = "capture"; + break; + } + message("ts query error at byte offset %d: %s", error_offset, msg); + return NULL; + } + + return q; +} + +#define s8(s) ((struct s8){s, strlen(s)}) + +struct s8 { + char *s; + uint32_t l; +}; + +static bool s8eq(struct s8 s1, struct s8 s2) { + return s1.l == s2.l && memcmp(s1.s, s2.s, s1.l) == 0; +} + +char *s8tocstr(struct s8 s) { + char *cstr = (char *)malloc(s.l + 1); + memcpy(cstr, s.s, s.l); + cstr[s.l] = '\0'; + return cstr; +} + +static bool eval_match(struct s8 capname, uint32_t argc, struct s8 argv[], + struct s8 value, void *data) { + regex_t *regex = (regex_t *)data; + if (regex == NULL) { + return false; + } + + char *text = s8tocstr(value); + bool match = regexec(regex, text, 0, NULL, 0) == 0; + + free(text); + return match; +} + +static void cleanup_match(void *data) { + regex_t *regex = (regex_t *)data; + if (regex != NULL) { + regfree(regex); + free(regex); + } +} + +struct predicate { + bool (*fn)(struct s8, uint32_t, struct s8[], struct s8, void *); + void (*cleanup)(void *); + uint32_t argc; + struct s8 argv[32]; + void *data; +}; + +typedef VEC(struct predicate) predicate_vec; + +static regex_t *compile_re_cached(struct highlight *h, struct s8 expr) { + char *val = s8tocstr(expr); + HASHMAP_GET(&h->re_cache, struct re_cache_entry, val, regex_t * re); + if (re == NULL) { + regex_t new_re; + if (regcomp(&new_re, val, 0) == 0) { + HASHMAP_APPEND(&h->re_cache, struct re_cache_entry, val, + struct re_cache_entry * new); + if (new != NULL) { + new->value = new_re; + re = &new->value; + } + } + } + + free(val); + return re; +} + +static predicate_vec create_predicates(struct highlight *h, + uint32_t pattern_index) { + predicate_vec predicates; + + uint32_t npreds = 0; + const TSQueryPredicateStep *predicate_steps = + ts_query_predicates_for_pattern(h->query, pattern_index, &npreds); + + VEC_INIT(&predicates, 8); + + bool result = true; + struct s8 capname; + struct s8 args[32] = {0}; + uint32_t argc = 0; + for (uint32_t predi = 0; predi < npreds; ++predi) { + const TSQueryPredicateStep *step = &predicate_steps[predi]; + switch (step->type) { + case TSQueryPredicateStepTypeCapture: + capname.s = (char *)ts_query_capture_name_for_id(h->query, step->value_id, + &capname.l); + break; + + case TSQueryPredicateStepTypeString: + args[argc].s = (char *)ts_query_string_value_for_id( + h->query, step->value_id, &args[argc].l); + ++argc; + break; + + case TSQueryPredicateStepTypeDone: + if (s8eq(args[0], s8("match?"))) { + VEC_APPEND(&predicates, struct predicate * pred); + pred->fn = eval_match; + pred->cleanup = NULL; + pred->argc = 1; + + // cache the regex + pred->data = compile_re_cached(h, args[1]); + + memset(pred->argv, 0, sizeof(struct s8) * 32); + memcpy(pred->argv, args, sizeof(struct s8)); + } + + argc = 0; + break; + } + } + + return predicates; +} + +static void update_parser(struct buffer *buffer, void *userdata, + struct location origin, uint32_t width, + uint32_t height) { + + struct highlight *h = (struct highlight *)userdata; + + if (h->query == NULL) { + return; + } + + // take results and set text properties + // TODO: can reuse the cursor + TSQueryCursor *cursor = ts_query_cursor_new(); + uint32_t end_line = origin.line + height >= buffer_num_lines(buffer) + ? buffer_num_lines(buffer) - 1 + : origin.line + height; + ts_query_cursor_set_point_range( + cursor, (TSPoint){.row = origin.line, .column = origin.col}, + (TSPoint){.row = end_line, .column = buffer_num_chars(buffer, end_line)}); + ts_query_cursor_exec(cursor, h->query, ts_tree_root_node(h->tree)); + + TSQueryMatch match; + while (ts_query_cursor_next_match(cursor, &match)) { + predicate_vec predicates = create_predicates(h, match.pattern_index); + + for (uint32_t capi = 0; capi < match.capture_count; ++capi) { + const TSQueryCapture *cap = &match.captures[capi]; + TSPoint start = ts_node_start_point(cap->node); + TSPoint end = ts_node_end_point(cap->node); + + struct s8 cname; + cname.s = + (char *)ts_query_capture_name_for_id(h->query, cap->index, &cname.l); + + bool predicates_match = true; + VEC_FOR_EACH(&predicates, struct predicate * pred) { + struct text_chunk txt = text_get_region( + buffer->text, start.row, start.column, end.row, end.column); + predicates_match &= + pred->fn(cname, pred->argc, pred->argv, + (struct s8){.s = txt.text, .l = txt.nbytes}, pred->data); + + if (txt.allocated) { + free(txt.text); + } + } + + if (!predicates_match) { + continue; + } + + bool highlight = false; + uint32_t color = 0; + if (s8eq(cname, s8("keyword"))) { + highlight = true; + color = Color_Blue; + } else if (s8eq(cname, s8("operator"))) { + highlight = true; + color = Color_Magenta; + } else if (s8eq(cname, s8("delimiter"))) { + highlight = false; + } else if (s8eq(cname, s8("string")) || + s8eq(cname, s8("string.special")) || + s8eq(cname, s8("string.special.path")) || + s8eq(cname, s8("string.special.uri"))) { + highlight = true; + color = Color_Green; + } else if (s8eq(cname, s8("constant"))) { + highlight = true; + color = Color_Yellow; + } else if (s8eq(cname, s8("attribute"))) { + highlight = true; + color = Color_Yellow; + } else if (s8eq(cname, s8("number"))) { + highlight = false; + } else if (s8eq(cname, s8("function")) || + s8eq(cname, s8("function.macro")) || + s8eq(cname, s8("function.method")) || + s8eq(cname, s8("function.builtin")) || + s8eq(cname, s8("function.special"))) { + highlight = true; + color = Color_Yellow; + } else if (s8eq(cname, s8("property"))) { + highlight = false; + } else if (s8eq(cname, s8("label"))) { + highlight = false; + } else if (s8eq(cname, s8("type")) || s8eq(cname, s8("type.builtin"))) { + highlight = true; + color = Color_Cyan; + } else if (s8eq(cname, s8("variable")) || + s8eq(cname, s8("variable.builtin")) || + s8eq(cname, s8("variable.parameter"))) { + highlight = false; + } else if (s8eq(cname, s8("comment"))) { + highlight = true; + color = Color_BrightBlack; + } + + if (!highlight) { + continue; + } + + buffer_add_text_property( + buffer, (struct location){.line = start.row, .col = start.column}, + (struct location){.line = end.row, .col = end.column - 1}, + (struct text_property){ + .type = TextProperty_Colors, + .colors = + (struct text_property_colors){ + .set_fg = true, + .fg = color, + }, + }); + } + + VEC_FOR_EACH(&predicates, struct predicate * pred) { + if (pred->cleanup != NULL) { + pred->cleanup(pred->data); + } + } + VEC_DESTROY(&predicates); + } + + ts_query_cursor_delete(cursor); +} + +static void text_removed(struct buffer *buffer, struct region removed, + uint32_t begin_idx, uint32_t end_idx, void *userdata) { + struct highlight *h = (struct highlight *)userdata; + + TSInputEdit edit = { + .start_point = + (TSPoint){.row = removed.begin.line, .column = removed.begin.col}, + .old_end_point = + (TSPoint){.row = removed.end.line, .column = removed.end.col}, + .new_end_point = + (TSPoint){.row = removed.begin.line, .column = removed.begin.col}, + .start_byte = begin_idx, + .old_end_byte = end_idx, + .new_end_byte = begin_idx, + }; + + ts_tree_edit(h->tree, &edit); + TSInput i = (TSInput){ + .payload = buffer->text, + .read = read_text, + .encoding = TSInputEncodingUTF8, + }; + + TSTree *new_tree = ts_parser_parse(h->parser, h->tree, i); + if (new_tree != NULL) { + ts_tree_delete(h->tree); + h->tree = new_tree; + } +} + +static void buffer_reloaded(struct buffer *buffer, void *userdata) { + struct highlight *h = (struct highlight *)userdata; + + TSInput i = (TSInput){ + .payload = buffer->text, + .read = read_text, + .encoding = TSInputEncodingUTF8, + }; + + TSTree *new_tree = ts_parser_parse(h->parser, NULL, i); + if (new_tree != NULL) { + ts_tree_delete(h->tree); + h->tree = new_tree; + } +} + +static void text_inserted(struct buffer *buffer, struct region inserted, + uint32_t begin_idx, uint32_t end_idx, + void *userdata) { + struct highlight *h = (struct highlight *)userdata; + + TSInputEdit edit = { + .start_point = + (TSPoint){.row = inserted.begin.line, .column = inserted.begin.col}, + .old_end_point = + (TSPoint){.row = inserted.begin.line, .column = inserted.begin.col}, + .new_end_point = + (TSPoint){.row = inserted.end.line, .column = inserted.end.col}, + .start_byte = begin_idx, + .old_end_byte = begin_idx, + .new_end_byte = end_idx, + }; + + ts_tree_edit(h->tree, &edit); + TSInput i = (TSInput){ + .payload = buffer->text, + .read = read_text, + .encoding = TSInputEncodingUTF8, + }; + + TSTree *new_tree = ts_parser_parse(h->parser, h->tree, i); + if (new_tree != NULL) { + ts_tree_delete(h->tree); + h->tree = new_tree; + } +} + +static void create_parser(struct buffer *buffer, void *userdata) { + const char *lang_root = lang_folder(buffer); + const char *filename = join_path(lang_root, parser_filename); + + void *h = dlopen(filename, RTLD_LAZY); + free((void *)filename); + if (h == NULL) { + free((void *)lang_root); + return; + } + + const char *langname = buffer->lang.name; + size_t lang_len = strlen(langname); + + const char *prefix = "tree_sitter_"; + size_t prefix_len = strlen(prefix); + char *function = malloc(prefix_len + lang_len + 1); + memcpy(function, prefix, prefix_len); + for (uint32_t i = 0; i < lang_len; ++i) { + function[prefix_len + i] = tolower(langname[i]); + } + function[prefix_len + lang_len] = '\0'; + TSLanguage *(*langsym)() = dlsym(h, function); + + free(function); + if (langsym == NULL) { + free((void *)lang_root); + dlclose(h); + return; + } + + struct highlight *hl = + (struct highlight *)calloc(1, sizeof(struct highlight)); + hl->parser = ts_parser_new(); + ts_parser_set_language(hl->parser, langsym()); + + TSInput i = (TSInput){ + .payload = buffer->text, + .read = read_text, + .encoding = TSInputEncodingUTF8, + }; + hl->tree = ts_parser_parse(hl->parser, NULL, i); + hl->query = setup_queries(lang_root, hl->tree); + hl->dlhandle = h; + HASHMAP_INIT(&hl->re_cache, 64, hash_name); + + free((void *)lang_root); + + minibuffer_echo_timeout(4, "syntax set up for %s", langname); + + buffer_add_reload_hook(buffer, buffer_reloaded, hl); + buffer_add_delete_hook(buffer, text_removed, hl); + buffer_add_insert_hook(buffer, text_inserted, hl); + buffer_add_render_hook(buffer, update_parser, hl); + buffer_add_destroy_hook(buffer, delete_parser, hl); +} + +#define xstr(s) str(s) +#define str(s) #s + +void syntax_init() { + treesitter_path = getenv("TREESITTER_GRAMMARS"); + if (treesitter_path == NULL) { + treesitter_path = (char *)join_path(xstr(DATADIR), "grammars"); + treesitter_path_allocated = true; + } + + struct stat buffer; + if (stat(treesitter_path, &buffer) != 0) { + minibuffer_echo_timeout(4, + "failed to initialize syntax, TREESITTER_GRAMMARS " + "not set and grammars dir does not exist at %s.", + treesitter_path); + + if (treesitter_path_allocated) { + free(treesitter_path); + } + + return; + } + + buffer_add_create_hook(create_parser, NULL); +} + +void syntax_teardown() { + if (treesitter_path_allocated) { + free(treesitter_path); + } +} diff --git a/src/dged/syntax.h b/src/dged/syntax.h new file mode 100644 index 0000000..6a2d4a3 --- /dev/null +++ b/src/dged/syntax.h @@ -0,0 +1,7 @@ +#ifndef _SYNTAX_H +#define _SYNTAX_H + +void syntax_init(); +void syntax_teardown(); + +#endif diff --git a/src/dged/text.c b/src/dged/text.c index 4d9a073..bc2b1fc 100644 --- a/src/dged/text.c +++ b/src/dged/text.c @@ -101,6 +101,26 @@ uint32_t text_byteindex_to_col(struct text *text, uint32_t line, return byteidx_to_charidx(&text->lines[line], byteindex); } +uint32_t text_global_idx(struct text *text, uint32_t line, uint32_t col) { + uint32_t byteoff = 0; + uint32_t nlines = text_num_lines(text); + for (uint32_t l = 0; l < line && l < nlines; ++l) { + byteoff += text_line_size(text, l) + 1; + } + + uint32_t l = line < nlines ? line : nlines - 1; + uint32_t nchars = text_line_length(text, l); + uint32_t c = col < nchars ? col : nchars; + byteoff += text_col_to_byteindex(text, l, c); + + if (col > nchars) { + // account for newline + ++byteoff; + } + + return byteoff; +} + void append_empty_lines(struct text *text, uint32_t numlines) { if (text->nlines + numlines >= text->capacity) { diff --git a/src/dged/text.h b/src/dged/text.h index e3bb3e4..922014e 100644 --- a/src/dged/text.h +++ b/src/dged/text.h @@ -34,6 +34,7 @@ uint32_t text_line_size(struct text *text, uint32_t lineidx); uint32_t text_col_to_byteindex(struct text *text, uint32_t line, uint32_t col); uint32_t text_byteindex_to_col(struct text *text, uint32_t line, uint32_t byteindex); +uint32_t text_global_idx(struct text *text, uint32_t line, uint32_t col); struct text_chunk { uint8_t *text; diff --git a/src/main/cmds.c b/src/main/cmds.c index c137ed1..5a1d7b6 100644 --- a/src/main/cmds.c +++ b/src/main/cmds.c @@ -68,6 +68,7 @@ int32_t run_interactive(struct command_ctx ctx, int argc, const char *argv[]) { } int32_t do_switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { + disable_completion(minibuffer_buffer()); const char *bufname = argv[0]; if (argc == 0) { // switch back to prev buffer diff --git a/src/main/completion.c b/src/main/completion.c index 7382805..414df9c 100644 --- a/src/main/completion.c +++ b/src/main/completion.c @@ -203,6 +203,7 @@ static void update_completions(struct buffer *buffer, } static void on_buffer_delete(struct buffer *buffer, struct region deleted, + uint32_t start_idx, uint32_t end_idx, void *userdata) { struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata; @@ -212,6 +213,7 @@ static void on_buffer_delete(struct buffer *buffer, struct region deleted, } static void on_buffer_insert(struct buffer *buffer, struct region inserted, + uint32_t start_idx, uint32_t end_idx, void *userdata) { struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata; diff --git a/src/main/main.c b/src/main/main.c index 02afd2b..7b9a812 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -20,6 +20,10 @@ #include "dged/reactor.h" #include "dged/settings.h" +#ifdef SYNTAX_ENABLE +#include "dged/syntax.h" +#endif + #include "bindings.h" #include "cmds.h" #include "completion.h" @@ -213,11 +217,20 @@ int main(int argc, char *argv[]) { struct buffers buflist = {0}; buffers_init(&buflist, 32); + struct buffer minibuffer = buffer_create("minibuffer"); + minibuffer.lazy_row_add = false; + minibuffer_init(&minibuffer, &buflist); + buffers_add_add_hook(&buflist, watch_file, (void *)reactor); +#ifdef SYNTAX_ENABLE + syntax_init(); +#endif + struct buffer initial_buffer = buffer_create("welcome"); if (filename != NULL) { buffer_destroy(&initial_buffer); initial_buffer = buffer_from_file(filename); + free((void *)filename); } else { const char *welcome_txt = "Welcome to the editor for datagubbar 👴\n"; buffer_set_text(&initial_buffer, (uint8_t *)welcome_txt, @@ -225,9 +238,6 @@ int main(int argc, char *argv[]) { } struct buffer *ib = buffers_add(&buflist, initial_buffer); - struct buffer minibuffer = buffer_create("minibuffer"); - minibuffer.lazy_row_add = false; - minibuffer_init(&minibuffer); windows_init(display_height(display), display_width(display), ib, &minibuffer); @@ -383,6 +393,11 @@ int main(int argc, char *argv[]) { minibuffer_destroy(); buffer_destroy(&minibuffer); buffers_destroy(&buflist); + +#ifdef SYNTAX_ENABLE + syntax_teardown(); +#endif + display_clear(display); display_destroy(display); destroy_bindings(); |
