summaryrefslogtreecommitdiff
path: root/src/dged
diff options
context:
space:
mode:
Diffstat (limited to 'src/dged')
-rw-r--r--src/dged/buffer.c81
-rw-r--r--src/dged/buffer.h24
-rw-r--r--src/dged/display.h19
-rw-r--r--src/dged/lang.c3
-rw-r--r--src/dged/minibuffer.c34
-rw-r--r--src/dged/minibuffer.h5
-rw-r--r--src/dged/path.h25
-rw-r--r--src/dged/syntax.c563
-rw-r--r--src/dged/syntax.h7
-rw-r--r--src/dged/text.c20
-rw-r--r--src/dged/text.h1
11 files changed, 773 insertions, 9 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;