From 4459b8b3aa9d73895391785a99dcc87134e80601 Mon Sep 17 00:00:00 2001 From: Albert Cervin Date: Tue, 17 Sep 2024 08:47:03 +0200 Subject: More lsp support This makes the LSP support complete for now: - Completion - Diagnostics - Goto implementation/declaration - Rename - Documentation - Find references --- src/dged/allocator.c | 2 + src/dged/allocator.h | 1 - src/dged/binding.h | 3 +- src/dged/buffer.c | 279 +++++++++++++--------- src/dged/buffer.h | 174 +++++++++++++- src/dged/buffer_view.c | 170 ++++++++----- src/dged/buffer_view.h | 17 +- src/dged/buffers.c | 7 +- src/dged/bufread.c | 151 ++++++++++++ src/dged/bufread.h | 13 + src/dged/command.h | 2 +- src/dged/display.c | 7 + src/dged/display.h | 5 + src/dged/hook.h | 84 +++++++ src/dged/json.c | 611 +++++++++++++++++++++++++++++++++++++---------- src/dged/json.h | 122 +++++++++- src/dged/jsonrpc.c | 118 +++++++++ src/dged/jsonrpc.h | 58 +++++ src/dged/keyboard.c | 2 +- src/dged/lsp.c | 386 +++++++++++++++++++++++++++--- src/dged/lsp.h | 87 ++++--- src/dged/minibuffer.c | 139 ++++++++++- src/dged/minibuffer.h | 22 ++ src/dged/path.c | 31 +++ src/dged/process-posix.c | 6 +- src/dged/s8.c | 76 +++++- src/dged/s8.h | 14 +- src/dged/syntax.c | 11 +- src/dged/text.c | 165 ++++++++++++- src/dged/text.h | 24 ++ src/dged/vec.h | 11 +- src/dged/window.c | 80 ++++--- src/dged/window.h | 2 +- 33 files changed, 2410 insertions(+), 470 deletions(-) create mode 100644 src/dged/bufread.c create mode 100644 src/dged/bufread.h create mode 100644 src/dged/hook.h create mode 100644 src/dged/jsonrpc.c create mode 100644 src/dged/jsonrpc.h (limited to 'src/dged') diff --git a/src/dged/allocator.c b/src/dged/allocator.c index 308b97c..a1f8cfb 100644 --- a/src/dged/allocator.c +++ b/src/dged/allocator.c @@ -1,5 +1,7 @@ #include "allocator.h" +#include + struct frame_allocator frame_allocator_create(size_t capacity) { return (struct frame_allocator){ .capacity = capacity, .offset = 0, .buf = (uint8_t *)malloc(capacity)}; diff --git a/src/dged/allocator.h b/src/dged/allocator.h index 49e3aec..16ab796 100644 --- a/src/dged/allocator.h +++ b/src/dged/allocator.h @@ -1,6 +1,5 @@ #include #include -#include /** * Simple bump allocator that can be used for diff --git a/src/dged/binding.h b/src/dged/binding.h index 93de02d..6f8719a 100644 --- a/src/dged/binding.h +++ b/src/dged/binding.h @@ -69,7 +69,8 @@ enum binding_type { #define PREFIX(...) PREFIX_INNER(__VA_ARGS__) /** - * Define an anonymous binding, i.e. a binding directly to a function. + * Define an anonymous binding, i.e. a binding directly to a non- + * registered command that has no name. * * Note the function that this key binds to cannot usually be * executed dynamically (with M-x). diff --git a/src/dged/buffer.c b/src/dged/buffer.c index b833a78..dcaa42c 100644 --- a/src/dged/buffer.c +++ b/src/dged/buffer.c @@ -36,62 +36,16 @@ static struct kill_ring { .paste_idx = 0, .paste_up_to_date = false}; -#define DECLARE_HOOK(name, callback_type, vec_type) \ - struct name##_hook { \ - uint32_t id; \ - callback_type callback; \ - void *userdata; \ - }; \ - \ - static uint32_t insert_##name##_hook( \ - vec_type *hooks, uint32_t *id, callback_type callback, void *userdata) { \ - uint32_t iid = ++(*id); \ - struct name##_hook hook = (struct name##_hook){ \ - .id = iid, \ - .callback = callback, \ - .userdata = userdata, \ - }; \ - VEC_PUSH(hooks, hook); \ - \ - return iid; \ - } \ - \ - static void remove_##name##_hook(vec_type *hooks, uint32_t id, \ - remove_hook_cb callback) { \ - uint64_t found_at = (uint64_t)-1; \ - VEC_FOR_EACH_INDEXED(hooks, struct name##_hook *h, idx) { \ - if (h->id == id) { \ - if (callback != NULL) { \ - callback(h->userdata); \ - } \ - found_at = idx; \ - break; \ - } \ - } \ - if (found_at != (uint64_t)-1) { \ - if (found_at < VEC_SIZE(hooks) - 1) { \ - VEC_SWAP(hooks, found_at, VEC_SIZE(hooks) - 1); \ - } \ - VEC_POP(hooks, struct name##_hook removed); \ - (void)removed; \ - } \ - } - -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) +HOOK_IMPL(create, create_hook_cb); +HOOK_IMPL(destroy, destroy_hook_cb); +HOOK_IMPL(insert, insert_hook_cb); +HOOK_IMPL(update, update_hook_cb); +HOOK_IMPL(reload, reload_hook_cb); +HOOK_IMPL(render, render_hook_cb); +HOOK_IMPL(delete, delete_hook_cb); +HOOK_IMPL(pre_delete, delete_hook_cb); +HOOK_IMPL(pre_save, pre_save_cb); +HOOK_IMPL(post_save, post_save_cb); static create_hook_vec g_create_hooks; uint32_t g_create_hook_id; @@ -114,6 +68,15 @@ struct hooks { delete_hook_vec delete_hooks; uint32_t delete_hook_id; + + pre_delete_hook_vec pre_delete_hooks; + uint32_t pre_delete_hook_id; + + pre_save_hook_vec pre_save_hooks; + uint32_t pre_save_hook_id; + + post_save_hook_vec post_save_hooks; + uint32_t post_save_hook_id; }; uint32_t buffer_add_create_hook(create_hook_cb callback, void *userdata) { @@ -202,9 +165,12 @@ static struct buffer create_internal(const char *name, char *filename) { .modified = false, .readonly = false, .lazy_row_add = true, + .retain_properties = false, .lang = filename != NULL ? lang_from_filename(filename) : lang_from_id("fnd"), .last_write = {0}, + .version = 0, + .needs_render = false, }; b.hooks = calloc(1, sizeof(struct hooks)); @@ -213,7 +179,10 @@ static struct buffer create_internal(const char *name, char *filename) { 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->pre_delete_hooks, 8); VEC_INIT(&b.hooks->destroy_hooks, 8); + VEC_INIT(&b.hooks->pre_save_hooks, 8); + VEC_INIT(&b.hooks->post_save_hooks, 8); undo_init(&b.undo, 100); @@ -280,7 +249,7 @@ static bool is_word_break(const struct codepoint *codepoint) { uint32_t c = codepoint->codepoint; return c == ' ' || c == '.' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == ';' || c == '<' || c == '>' || c == ':' || - c == '"'; + c == '"' || c == '=' || c == ','; } static bool is_word_char(const struct codepoint *c) { @@ -334,8 +303,7 @@ find_prev_in_line(struct buffer *buffer, struct location start, } return (struct match_result){ - .at = - (struct location){.line = start.line, .col = found ? found_at : coli}, + .at = (struct location){.line = start.line, .col = found ? found_at : 0}, .found = found}; } @@ -400,9 +368,7 @@ struct buffer buffer_create(const char *name) { struct buffer b = create_internal(name, NULL); - VEC_FOR_EACH(&g_create_hooks, struct create_hook * h) { - h->callback(&b, h->userdata); - } + dispatch_hook(&g_create_hooks, struct create_hook, &b); return b; } @@ -412,9 +378,7 @@ struct buffer buffer_from_file(const char *path) { struct buffer b = create_internal(basename((char *)path), full_path); buffer_read_from_file(&b); - VEC_FOR_EACH(&g_create_hooks, struct create_hook * h) { - h->callback(&b, h->userdata); - } + dispatch_hook(&g_create_hooks, struct create_hook, &b); return b; } @@ -440,6 +404,8 @@ void buffer_to_file(struct buffer *buffer) { return; } + dispatch_hook(&buffer->hooks->pre_save_hooks, struct pre_save_hook, buffer); + uint32_t nlines = text_num_lines(buffer->text); uint32_t nlines_to_write = nlines; if (nlines > 0) { @@ -452,13 +418,19 @@ void buffer_to_file(struct buffer *buffer) { buffer->filename); fclose(file); - clock_gettime(CLOCK_REALTIME, &buffer->last_write); buffer->modified = false; undo_push_boundary(&buffer->undo, (struct undo_boundary){.save_point = true}); + + struct stat sb; + stat(buffer->filename, &sb); + buffer->last_write = sb.st_mtim; + + dispatch_hook(&buffer->hooks->post_save_hooks, struct post_save_hook, buffer); } void buffer_set_filename(struct buffer *buffer, const char *filename) { buffer->filename = to_abspath(filename); + ++buffer->version; buffer->modified = true; } @@ -478,16 +450,12 @@ 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); - } + dispatch_hook(&buffer->hooks->reload_hooks, struct reload_hook, buffer); } } void buffer_destroy(struct buffer *buffer) { - VEC_FOR_EACH(&buffer->hooks->destroy_hooks, struct destroy_hook * h) { - h->callback(buffer, h->userdata); - } + dispatch_hook(&buffer->hooks->destroy_hooks, struct destroy_hook, buffer); lang_destroy(&buffer->lang); @@ -506,6 +474,9 @@ void buffer_destroy(struct buffer *buffer) { VEC_DESTROY(&buffer->hooks->insert_hooks); VEC_DESTROY(&buffer->hooks->destroy_hooks); VEC_DESTROY(&buffer->hooks->delete_hooks); + VEC_DESTROY(&buffer->hooks->pre_delete_hooks); + VEC_DESTROY(&buffer->hooks->pre_save_hooks); + VEC_DESTROY(&buffer->hooks->post_save_hooks); free(buffer->hooks); undo_destroy(&buffer->undo); @@ -518,6 +489,8 @@ struct location buffer_add(struct buffer *buffer, struct location at, return at; } + buffer->needs_render = true; + // invalidate last paste g_kill_ring.paste_up_to_date = false; @@ -553,26 +526,20 @@ struct location buffer_add(struct buffer *buffer, struct location at, (struct undo_add){.begin = {.row = initial.line, .col = initial.col}, .end = {.row = final.line, .col = final.col}}); - if (lines_added > 0) { - undo_push_boundary(&buffer->undo, - (struct undo_boundary){.save_point = false}); - } + ++buffer->version; + buffer->modified = true; uint32_t begin_idx = to_global_offset(buffer, at_bytes); uint32_t end_idx = to_global_offset(buffer, final_bytes); - VEC_FOR_EACH(&buffer->hooks->insert_hooks, struct insert_hook * h) { - h->callback(buffer, + dispatch_hook(&buffer->hooks->insert_hooks, struct insert_hook, buffer, (struct edit_location){ .coordinates = region_new(initial, final), .bytes = region_new(at_bytes, final_bytes), .global_byte_begin = begin_idx, .global_byte_end = end_idx, - }, - h->userdata); - } + }); - buffer->modified = true; return final; } @@ -636,8 +603,8 @@ struct location buffer_previous_word(struct buffer *buffer, struct location dot) { struct match_result res = find_prev_in_line(buffer, dot, is_word_break); - if (!res.found && res.at.col == dot.col) { - return buffer_previous_char(buffer, res.at); + if (!res.found) { + return (struct location){.line = dot.line, .col = 0}; } // check if we got here from the middle of a word or not @@ -647,7 +614,7 @@ struct location buffer_previous_word(struct buffer *buffer, if (traveled <= 1) { res = find_prev_in_line(buffer, res.at, is_word_char); if (!res.found) { - return buffer_previous_char(buffer, res.at); + return (struct location){.line = dot.line, .col = 0}; } // at this point, we are at the end of the previous word @@ -657,7 +624,7 @@ struct location buffer_previous_word(struct buffer *buffer, } else { res.at = buffer_next_char(buffer, res.at); } - } else { + } else if (res.at.col > 0) { res.at = buffer_next_char(buffer, res.at); } @@ -823,6 +790,11 @@ struct location buffer_indent_alt(struct buffer *buffer, struct location at) { return do_indent(buffer, at, get_tab_width(buffer), !use_tabs(buffer)); } +void buffer_push_undo_boundary(struct buffer *buffer) { + undo_push_boundary(&buffer->undo, + (struct undo_boundary){.save_point = false}); +} + struct location buffer_undo(struct buffer *buffer, struct location dot) { struct undo_stack *undo = &buffer->undo; undo_begin(undo); @@ -958,10 +930,17 @@ struct location buffer_delete(struct buffer *buffer, struct region region) { return region.begin; } + buffer->needs_render = true; + if (!region_has_size(region)) { return region.begin; } + region.begin = buffer_clamp(buffer, (int64_t)region.begin.line, + (int64_t)region.begin.col); + region.end = + buffer_clamp(buffer, (int64_t)region.end.line, (int64_t)region.end.col); + struct location begin_bytes = buffer_location_to_byte_coords(buffer, region.begin); struct location end_bytes = @@ -971,34 +950,37 @@ struct location buffer_delete(struct buffer *buffer, struct region region) { text_get_region(buffer->text, begin_bytes.line, begin_bytes.col, end_bytes.line, end_bytes.col); - undo_push_boundary(&buffer->undo, - (struct undo_boundary){.save_point = false}); - undo_push_delete(&buffer->undo, (struct undo_delete){.data = txt.text, .nbytes = txt.nbytes, .pos = {.row = region.begin.line, .col = region.begin.col}}); - undo_push_boundary(&buffer->undo, - (struct undo_boundary){.save_point = false}); uint64_t begin_idx = to_global_offset(buffer, begin_bytes); uint64_t end_idx = to_global_offset(buffer, end_bytes); + ++buffer->version; + buffer->modified = true; + + dispatch_hook(&buffer->hooks->pre_delete_hooks, struct pre_delete_hook, + buffer, + (struct edit_location){ + .coordinates = region, + .bytes = region_new(begin_bytes, end_bytes), + .global_byte_begin = begin_idx, + .global_byte_end = end_idx, + }); + text_delete(buffer->text, begin_bytes.line, begin_bytes.col, end_bytes.line, end_bytes.col); - buffer->modified = true; - VEC_FOR_EACH(&buffer->hooks->delete_hooks, struct delete_hook * h) { - h->callback(buffer, + dispatch_hook(&buffer->hooks->delete_hooks, struct delete_hook, buffer, (struct edit_location){ .coordinates = region, .bytes = region_new(begin_bytes, end_bytes), .global_byte_begin = begin_idx, .global_byte_end = end_idx, - }, - h->userdata); - } + }); return region.begin; } @@ -1047,8 +1029,13 @@ struct text_chunk buffer_line(struct buffer *buffer, uint32_t line) { } struct text_chunk buffer_region(struct buffer *buffer, struct region region) { - return text_get_region(buffer->text, region.begin.line, region.begin.col, - region.end.line, region.end.col); + struct location begin_bytes = + buffer_location_to_byte_coords(buffer, region.begin); + struct location end_bytes = + buffer_location_to_byte_coords(buffer, region.end); + + return text_get_region(buffer->text, begin_bytes.line, begin_bytes.col, + end_bytes.line, end_bytes.col); } uint32_t buffer_add_insert_hook(struct buffer *buffer, insert_hook_cb hook, @@ -1073,6 +1060,18 @@ void buffer_remove_delete_hook(struct buffer *buffer, uint32_t hook_id, remove_delete_hook(&buffer->hooks->delete_hooks, hook_id, callback); } +uint32_t buffer_add_pre_delete_hook(struct buffer *buffer, delete_hook_cb hook, + void *userdata) { + return insert_pre_delete_hook(&buffer->hooks->pre_delete_hooks, + &buffer->hooks->pre_delete_hook_id, hook, + userdata); +} + +void buffer_remove_pre_delete_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback) { + remove_pre_delete_hook(&buffer->hooks->pre_delete_hooks, hook_id, callback); +} + uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, void *userdata) { return insert_update_hook(&buffer->hooks->update_hooks, @@ -1106,6 +1105,30 @@ void buffer_remove_reload_hook(struct buffer *buffer, uint32_t hook_id, remove_reload_hook(&buffer->hooks->reload_hooks, hook_id, callback); } +uint32_t buffer_add_pre_save_hook(struct buffer *buffer, pre_save_cb callback, + void *userdata) { + return insert_pre_save_hook(&buffer->hooks->pre_save_hooks, + &buffer->hooks->pre_save_hook_id, callback, + userdata); +} + +void buffer_remove_pre_save_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback) { + remove_pre_save_hook(&buffer->hooks->pre_save_hooks, hook_id, callback); +} + +uint32_t buffer_add_post_save_hook(struct buffer *buffer, post_save_cb callback, + void *userdata) { + return insert_post_save_hook(&buffer->hooks->post_save_hooks, + &buffer->hooks->post_save_hook_id, callback, + userdata); +} + +void buffer_remove_post_save_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback) { + remove_post_save_hook(&buffer->hooks->post_save_hooks, hook_id, callback); +} + struct cmdbuf { struct command_list *cmds; struct location origin; @@ -1133,6 +1156,15 @@ static void apply_properties(struct command_list *cmds, if (colors->set_fg) { command_list_set_index_color_fg(cmds, colors->fg); } + + if (colors->underline) { + command_list_set_underline(cmds); + } + + if (colors->inverted) { + command_list_set_inverted_colors(cmds); + } + break; } case TextProperty_Data: @@ -1215,7 +1247,6 @@ void render_line(struct text_chunk *line, void *userdata) { command_list_reset_color(cmdbuf->cmds); command_list_set_show_whitespace(cmdbuf->cmds, false); - // TODO: considering the whole screen is cleared, is this really needed? if (drawn_coli < cmdbuf->width) { command_list_draw_repeated(cmdbuf->cmds, drawn_coli, visual_line, ' ', cmdbuf->width - drawn_coli); @@ -1223,9 +1254,7 @@ void render_line(struct text_chunk *line, void *userdata) { } void buffer_update(struct buffer *buffer) { - VEC_FOR_EACH(&buffer->hooks->update_hooks, struct update_hook * h) { - h->callback(buffer, h->userdata); - } + dispatch_hook(&buffer->hooks->update_hooks, struct update_hook, buffer); } void buffer_render(struct buffer *buffer, struct buffer_render_params *params) { @@ -1233,10 +1262,8 @@ 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); - } + dispatch_hook(&buffer->hooks->render_hooks, struct render_hook, buffer, + params->origin, params->width, params->height); struct setting *show_ws = settings_get("editor.show-whitespace"); @@ -1258,17 +1285,40 @@ void buffer_render(struct buffer *buffer, struct buffer_render_params *params) { ++linei) { command_list_draw_repeated(params->commands, 0, linei, ' ', params->width); } + + buffer->needs_render = false; } void buffer_add_text_property(struct buffer *buffer, struct location start, struct location end, struct text_property property) { + buffer->needs_render = true; struct location bytestart = buffer_location_to_byte_coords(buffer, start); struct location byteend = buffer_location_to_byte_coords(buffer, end); text_add_property(buffer->text, bytestart.line, bytestart.col, byteend.line, byteend.col, property); } +void buffer_add_text_property_to_layer(struct buffer *buffer, + struct location start, + struct location end, + struct text_property property, + layer_id layer) { + buffer->needs_render = true; + struct location bytestart = buffer_location_to_byte_coords(buffer, start); + struct location byteend = buffer_location_to_byte_coords(buffer, end); + text_add_property_to_layer(buffer->text, bytestart.line, bytestart.col, + byteend.line, byteend.col, property, layer); +} + +layer_id buffer_add_text_property_layer(struct buffer *buffer) { + return text_add_property_layer(buffer->text); +} + +void buffer_remove_property_layer(struct buffer *buffer, layer_id layer) { + text_remove_property_layer(buffer->text, layer); +} + void buffer_get_text_properties(struct buffer *buffer, struct location location, struct text_property **properties, uint32_t max_nproperties, @@ -1278,10 +1328,25 @@ void buffer_get_text_properties(struct buffer *buffer, struct location location, max_nproperties, nproperties); } +void buffer_get_text_properties_filtered(struct buffer *buffer, + struct location location, + struct text_property **properties, + uint32_t max_nproperties, + uint32_t *nproperties, + layer_id layer) { + struct location bytecoords = buffer_location_to_byte_coords(buffer, location); + text_get_properties_filtered(buffer->text, bytecoords.line, bytecoords.col, + properties, max_nproperties, nproperties, layer); +} + void buffer_clear_text_properties(struct buffer *buffer) { text_clear_properties(buffer->text); } +void buffer_clear_text_property_layer(struct buffer *buffer, layer_id layer) { + text_clear_property_layer(buffer->text, layer); +} + static int compare_lines(const void *l1, const void *l2) { return s8cmp(*(const struct s8 *)l1, *(const struct s8 *)l2); } diff --git a/src/dged/buffer.h b/src/dged/buffer.h index 0e45b98..25cc42b 100644 --- a/src/dged/buffer.h +++ b/src/dged/buffer.h @@ -7,6 +7,7 @@ #include #include "command.h" +#include "hook.h" #include "lang.h" #include "location.h" #include "text.h" @@ -63,6 +64,17 @@ struct buffer { /** If true, force whitespace indication off for this buffer */ bool force_show_ws_off; + + /** If true, text properties are not immediate */ + bool retain_properties; + + bool needs_render; + + /** + * Version that increases with each edit (including undo). + * Can be used to check if a buffer has changed. + */ + uint64_t version; }; void buffer_static_init(void); @@ -342,6 +354,8 @@ struct location buffer_indent_alt(struct buffer *buffer, struct location at); */ struct location buffer_undo(struct buffer *buffer, struct location dot); +void buffer_push_undo_boundary(struct buffer *buffer); + /** * Search for a substring in the buffer. * @@ -432,9 +446,47 @@ void buffer_add_text_property(struct buffer *buffer, struct location start, struct location end, struct text_property property); +/** + * Add a text property to a region of the buffer and a specified property layer. + * + * @param buffer The buffer to add a text property to. + * @param start The start of the region to set the property for. + * @param end The end of the region to set the property for. + * @param property The text property to set. + * @param layer Id of the layer to add the text property to. + */ +void buffer_add_text_property_to_layer(struct buffer *buffer, + struct location start, + struct location end, + struct text_property property, + layer_id layer); + +/** + * Add a new layer for holding properties. + * + * Note that only the default layer is cleared automatically + * when @ref retain_properties is false. Any other layer + * needs to be cleared manually when needed. + * + * @param [in] buffer The buffer to add the property layer to. + * + * @returns The id of the added layer, -1 on error. + */ +layer_id buffer_add_text_property_layer(struct buffer *buffer); + +/** + * Remove a property layer. + * + * @param [in] buffer The buffer to remove the property layer from + * @param [in] layer The layer id of the layer to remove. + */ +void buffer_remove_property_layer(struct buffer *buffer, layer_id layer); + /** * Get active text properties at @p location in @p buffer. * + * This will retrieve properties from all property layers. + * * @param buffer The buffer to get properties for. * @param location The location to get properties at. * @param properties Caller-provided array of properties set by this function. @@ -447,14 +499,35 @@ void buffer_get_text_properties(struct buffer *buffer, struct location location, uint32_t *nproperties); /** - * Clear any text properties for @p buffer. + * Get active text properties at @p location in @p buffer for the layer @layer. + * + * @param buffer The buffer to get properties for. + * @param location The location to get properties at. + * @param properties Caller-provided array of properties set by this function. + * @param max_nproperties Max num properties to put in @p properties. + * @param nproperties Number of properties that got stored in @p properties. + * @param layer Id of the layer to fetch properties for. + */ +void buffer_get_text_properties_filtered(struct buffer *buffer, + struct location location, + struct text_property **properties, + uint32_t max_nproperties, + uint32_t *nproperties, layer_id layer); + +/** + * Clear any text properties from the default property layer for @p buffer. * * @param buffer The buffer to clear properties for. */ void buffer_clear_text_properties(struct buffer *buffer); -/** Callback when removing hooks to clean up userdata */ -typedef void (*remove_hook_cb)(void *userdata); +/** + * Clear text properties from layer @ref layer. + * + * @param buffer The buffer to clear properties for. + * @param layer The layer to clear. + */ +void buffer_clear_text_property_layer(struct buffer *buffer, layer_id layer); /** * Buffer update hook callback function. @@ -496,9 +569,8 @@ void buffer_remove_update_hook(struct buffer *buffer, uint32_t hook_id, * @param width The width of the rendered region. * @param height The height of the rendered region. */ -typedef void (*render_hook_cb)(struct buffer *buffer, void *userdata, - struct location origin, uint32_t width, - uint32_t height); +typedef void (*render_hook_cb)(struct buffer *buffer, struct location origin, + uint32_t width, uint32_t height, void *userdata); /** * Add a buffer render hook. @@ -567,9 +639,6 @@ struct edit_location { * * @param buffer The buffer. * @param inserted The position in the @p buffer where text was inserted. - * @param begin_idx The global byte offset to the start of where text was - * inserted. - * @param end_idx The global byte offset to the end of where text was inserted. * @param userdata The userdata as sent in to @ref buffer_add_insert_hook. */ typedef void (*insert_hook_cb)(struct buffer *buffer, @@ -602,8 +671,6 @@ void buffer_remove_insert_hook(struct buffer *buffer, uint32_t hook_id, * * @param buffer The buffer. * @param removed The region that was removed from the @p buffer. - * @param begin_idx The global byte offset to the start of the removed text. - * @param end_idx The global byte offset to the end of the removed text. * @param userdata The userdata as sent in to @ref buffer_add_delete_hook. */ typedef void (*delete_hook_cb)(struct buffer *buffer, @@ -631,6 +698,29 @@ 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); +/** + * Add a pre-delete hook, called when text is about to be removed from the @p + * buffer. + * + * @param buffer The buffer to add a delete hook to. + * @param callback The function to call when text is removed from @p buffer. + * @param userdata Data that is passed unmodified to the delete hook. + * @returns The hook id. + */ +uint32_t buffer_add_pre_delete_hook(struct buffer *buffer, + delete_hook_cb callback, void *userdata); + +/** + * Remove a buffer pre-delete hook. + * + * @param [in] buffer The buffer to remove the hook from. + * @param [in] hook_id The hook id as returned from @ref buffer_add_delete_hook. + * @param [in] callback A function called with the userdata pointer to do + * cleanup. + */ +void buffer_remove_pre_delete_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback); + /** * Buffer destroy hook callback function. * @@ -689,6 +779,68 @@ uint32_t buffer_add_create_hook(create_hook_cb callback, void *userdata); */ void buffer_remove_create_hook(uint32_t hook_id, remove_hook_cb callback); +/** + * Buffer pre-save callback function + * + * @param buffer The buffer about to be saved. + * @param userdata The userdata as sent in to @ref buffer_add_pre_save_hook. + */ +typedef void (*pre_save_cb)(struct buffer *buffer, void *userdata); + +/** + * Add a pre-save hook, called when @p buffer is about to be saved. + * + * @param buffer The buffer to add a pre-save hook to. + * @param callback The function to call @p buffer is about to be saved. + * @param userdata Data that is passed unmodified to the pre-save hook. + * @returns The hook id. + */ +uint32_t buffer_add_pre_save_hook(struct buffer *buffer, pre_save_cb callback, + void *userdata); + +/** + * Remove a buffer pre-save hook. + * + * @param [in] buffer The buffer to remove the hook from. + * @param [in] hook_id The hook id as returned from @ref + * buffer_add_pre_save_hook. + * @param [in] callback A function called with the userdata pointer to do + * cleanup. + */ +void buffer_remove_pre_save_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback); + +/** + * Buffer post-save callback function + * + * @param buffer The buffer that was saved. + * @param userdata The userdata as sent in to @ref buffer_add_post_save_hook. + */ +typedef void (*post_save_cb)(struct buffer *buffer, void *userdata); + +/** + * Add a post-save hook, called when @p buffer has been saved. + * + * @param buffer The buffer to add a post-save hook to. + * @param callback The function to call @p buffer is saved. + * @param userdata Data that is passed unmodified to the post-save hook. + * @returns The hook id. + */ +uint32_t buffer_add_post_save_hook(struct buffer *buffer, post_save_cb callback, + void *userdata); + +/** + * Remove a buffer post-save hook. + * + * @param [in] buffer The buffer to remove the hook from. + * @param [in] hook_id The hook id as returned from @ref + * buffer_add_post_save_hook. + * @param [in] callback A function called with the userdata pointer to do + * cleanup. + */ +void buffer_remove_post_save_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback); + /** * Parameters for rendering a buffer. */ diff --git a/src/dged/buffer_view.c b/src/dged/buffer_view.c index a9bbe19..9d998fe 100644 --- a/src/dged/buffer_view.c +++ b/src/dged/buffer_view.c @@ -7,10 +7,10 @@ #include "timers.h" #include "utf8.h" -struct modeline { - uint8_t *buffer; - uint32_t sz; -}; +HOOK_IMPL(modeline, modeline_hook_cb); + +static modeline_hook_vec g_modeline_hooks = {0}; +static uint32_t g_modeline_hook_id = 0; static bool maybe_delete_region(struct buffer_view *view) { struct region reg = region_new(view->dot, view->mark); @@ -32,18 +32,11 @@ struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline, .mark_set = false, .scroll = (struct location){.line = 0, .col = 0}, .buffer = buffer, - .modeline = NULL, + .modeline = modeline, .line_numbers = line_numbers, .fringe_width = 0, }; - if (modeline) { - v.modeline = calloc(1, sizeof(struct modeline)); - v.modeline->buffer = malloc(1024); - v.modeline->sz = 1024; - v.modeline->buffer[0] = '\0'; - } - return v; } @@ -54,32 +47,22 @@ struct buffer_view buffer_view_clone(const struct buffer_view *view) { .mark_set = view->mark_set, .scroll = view->scroll, .buffer = view->buffer, - .modeline = NULL, + .modeline = view->modeline, .line_numbers = view->line_numbers, }; - if (view->modeline) { - c.modeline = calloc(1, sizeof(struct modeline)); - c.modeline->buffer = malloc(view->modeline->sz); - memcpy(c.modeline->buffer, view->modeline->buffer, view->modeline->sz); - } - return c; } -void buffer_view_destroy(struct buffer_view *view) { - if (view->modeline != NULL) { - free(view->modeline->buffer); - free(view->modeline); - view->modeline = NULL; - } - - view->buffer = NULL; -} +void buffer_view_destroy(struct buffer_view *view) { view->buffer = NULL; } void buffer_view_add(struct buffer_view *view, uint8_t *txt, uint32_t nbytes) { maybe_delete_region(view); + struct location before = view->dot; view->dot = buffer_add(view->buffer, view->dot, txt, nbytes); + if (view->dot.line > before.line) { + buffer_push_undo_boundary(view->buffer); + } } void buffer_view_goto_beginning(struct buffer_view *view) { @@ -107,7 +90,11 @@ void buffer_view_forward_word(struct buffer_view *view) { } void buffer_view_backward_word(struct buffer_view *view) { + struct location before = view->dot; view->dot = buffer_previous_word(view->buffer, view->dot); + if (before.col == 0 && view->dot.col == 0) { + buffer_view_backward_char(view); + } } void buffer_view_forward_line(struct buffer_view *view) { @@ -138,6 +125,7 @@ void buffer_view_goto_beginning_of_line(struct buffer_view *view) { void buffer_view_newline(struct buffer_view *view) { view->dot = buffer_newline(view->buffer, view->dot); + buffer_push_undo_boundary(view->buffer); } void buffer_view_indent(struct buffer_view *view) { @@ -202,6 +190,7 @@ void buffer_view_paste_older(struct buffer_view *view) { } void buffer_view_forward_delete_char(struct buffer_view *view) { + buffer_push_undo_boundary(view->buffer); if (maybe_delete_region(view)) { return; } @@ -209,9 +198,11 @@ void buffer_view_forward_delete_char(struct buffer_view *view) { view->dot = buffer_delete( view->buffer, region_new(view->dot, buffer_next_char(view->buffer, view->dot))); + buffer_push_undo_boundary(view->buffer); } void buffer_view_backward_delete_char(struct buffer_view *view) { + buffer_push_undo_boundary(view->buffer); if (maybe_delete_region(view)) { return; } @@ -219,9 +210,11 @@ void buffer_view_backward_delete_char(struct buffer_view *view) { view->dot = buffer_delete( view->buffer, region_new(buffer_previous_char(view->buffer, view->dot), view->dot)); + buffer_push_undo_boundary(view->buffer); } void buffer_view_delete_word(struct buffer_view *view) { + buffer_push_undo_boundary(view->buffer); if (maybe_delete_region(view)) { return; } @@ -232,9 +225,11 @@ void buffer_view_delete_word(struct buffer_view *view) { buffer_delete(view->buffer, word); view->dot = word.begin; } + buffer_push_undo_boundary(view->buffer); } void buffer_view_kill_line(struct buffer_view *view) { + buffer_push_undo_boundary(view->buffer); uint32_t ncols = buffer_line_length(view->buffer, view->dot.line) - view->dot.col; @@ -254,6 +249,7 @@ void buffer_view_kill_line(struct buffer_view *view) { }); buffer_cut(view->buffer, reg); + buffer_push_undo_boundary(view->buffer); } void buffer_view_sort_lines(struct buffer_view *view) { @@ -354,52 +350,87 @@ static uint32_t render_line_numbers(struct buffer_view *view, return longest_nchars + 2; } -static void render_modeline(struct modeline *modeline, struct buffer_view *view, +static void render_modeline(struct buffer_view *view, struct command_list *commands, uint32_t window_id, uint32_t width, uint32_t height, float frame_time) { - char buf[width * 4]; - memset(buf, 0, width * 4); - time_t now = time(NULL); struct tm *lt = localtime(&now); - static char left[128] = {0}; - static char right[128] = {0}; - - snprintf(left, 128, " %c%c %d:%-16s (%d, %d) (%s)", - view->buffer->modified ? '*' : '-', - view->buffer->readonly ? '%' : '-', window_id, view->buffer->name, - view->dot.line + 1, view->dot.col, view->buffer->lang.name); - snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour, - lt->tm_min); - - snprintf(buf, width * 4, "%s%*s%s", left, - (int)(width - (strlen(left) + strlen(right))), "", right); - - if (strcmp(buf, (char *)modeline->buffer) != 0) { - modeline->buffer = realloc(modeline->buffer, width * 4); - modeline->sz = width * 4; - - uint32_t len = strlen(buf); - len = (len + 1) > modeline->sz ? modeline->sz - 1 : len; - memcpy(modeline->buffer, buf, len); - modeline->buffer[len] = '\0'; + + char left[1024] = {}; + char right[1024] = {}; + + size_t left_len = snprintf(left, 1024, " %c%c %d:%-16s (%d, %d) (%s) ", + view->buffer->modified ? '*' : '-', + view->buffer->readonly ? '%' : '-', window_id, + view->buffer->name, view->dot.line + 1, + view->dot.col, view->buffer->lang.name); + + /* insert hook content on the left */ + VEC_FOR_EACH(&g_modeline_hooks, struct modeline_hook * hook) { + struct s8 content = hook->callback(view, hook->userdata); + if (content.l > 0) { + left_len += snprintf(left + left_len, 1024 - left_len, "[%.*s] ", + content.l, content.s); + s8delete(content); + } + } + + size_t right_len = snprintf(right, 1024, " (%.2f ms) %02d:%02d", + frame_time / 1e6, lt->tm_hour, lt->tm_min); + + /* clamp all the widths with priority: + * 1. left + * 2. right + * 3. mid + */ + left_len = left_len > width ? width : left_len; + right_len = left_len + right_len > width ? width - left_len : right_len; + size_t mid_len = + left_len + right_len < width ? width - left_len - right_len : 0; + + char mid[mid_len + 1] = {}; + if (mid_len > 0) { + memset(mid, '-', mid_len); + mid[0] = ' '; + mid[mid_len - 1] = ' '; + mid[mid_len] = '\0'; + } + + if (left_len > 0) { + command_list_set_index_color_bg(commands, Color_BrightBlack); + command_list_set_index_color_fg(commands, Color_White); + command_list_draw_text_copy(commands, 0, height - 1, (uint8_t *)left, + left_len); + } + + if (mid_len > 0) { + command_list_set_index_color_bg(commands, Color_BrightBlack); + command_list_set_index_color_fg(commands, Color_White); + command_list_draw_text_copy(commands, left_len, height - 1, (uint8_t *)mid, + mid_len); + } + + if (right_len > 0) { + command_list_set_index_color_bg(commands, Color_BrightBlack); + command_list_set_index_color_fg(commands, Color_White); + command_list_draw_text_copy(commands, left_len + mid_len, height - 1, + (uint8_t *)right, right_len); } - command_list_set_index_color_bg(commands, Color_BrightBlack); - command_list_set_index_color_fg(commands, Color_White); - command_list_draw_text(commands, 0, height - 1, modeline->buffer, - strlen((char *)modeline->buffer)); command_list_reset_color(commands); } -void buffer_view_update(struct buffer_view *view, +bool buffer_view_update(struct buffer_view *view, struct buffer_view_update_params *params) { + bool needs_render = false; struct timer *buffer_update_timer = timer_start("update-windows.buffer-update"); buffer_update(view->buffer); timer_stop(buffer_update_timer); + needs_render |= view->buffer->needs_render; + uint32_t height = params->height; uint32_t width = params->width; @@ -412,10 +443,10 @@ void buffer_view_update(struct buffer_view *view, struct timer *render_modeline_timer = timer_start("update-windows.modeline-render"); uint32_t modeline_height = 0; - if (view->modeline != NULL) { + if (view->modeline) { modeline_height = 1; - render_modeline(view->modeline, view, params->commands, params->window_id, - params->width, params->height, params->frame_time); + render_modeline(view, params->commands, params->window_id, params->width, + params->height, params->frame_time); } height -= modeline_height; @@ -494,4 +525,21 @@ void buffer_view_update(struct buffer_view *view, // draw buffer commands nested inside this command list command_list_draw_command_list(params->commands, buf_cmds); timer_stop(render_buffer_timer); + + return needs_render; +} + +uint32_t buffer_view_add_modeline_hook(modeline_hook_cb callback, + void *userdata) { + if (VEC_CAPACITY(&g_modeline_hooks) == 0) { + VEC_INIT(&g_modeline_hooks, 8); + } + + return insert_modeline_hook(&g_modeline_hooks, &g_modeline_hook_id, callback, + userdata); +} + +void buffer_view_remove_modeline_hook(uint32_t hook_id, + remove_hook_cb callback) { + remove_modeline_hook(&g_modeline_hooks, hook_id, callback); } diff --git a/src/dged/buffer_view.h b/src/dged/buffer_view.h index 4e23b5d..d1b6b4a 100644 --- a/src/dged/buffer_view.h +++ b/src/dged/buffer_view.h @@ -3,7 +3,9 @@ #include +#include "hook.h" #include "location.h" +#include "s8.h" struct buffer; @@ -25,8 +27,8 @@ struct buffer_view { /** Pointer to the actual buffer */ struct buffer *buffer; - /** Modeline buffer (may be NULL) */ - struct modeline *modeline; + /** Has modeline? */ + bool modeline; /** Current left fringe size */ uint32_t fringe_width; @@ -86,6 +88,15 @@ void buffer_view_undo(struct buffer_view *view); void buffer_view_sort_lines(struct buffer_view *view); +// hack to prevent s8 from being expanded as a macro +// in the function pointer typedef +typedef struct s8 _string; +typedef _string (*modeline_hook_cb)(struct buffer_view *, void *); +uint32_t buffer_view_add_modeline_hook(modeline_hook_cb callback, + void *userdata); +void buffer_view_remove_modeline_hook(uint32_t hook_id, + remove_hook_cb callback); + struct buffer_view_update_params { struct command_list *commands; void *(*frame_alloc)(size_t); @@ -97,7 +108,7 @@ struct buffer_view_update_params { uint32_t window_y; }; -void buffer_view_update(struct buffer_view *view, +bool buffer_view_update(struct buffer_view *view, struct buffer_view_update_params *params); #endif diff --git a/src/dged/buffers.c b/src/dged/buffers.c index d20be39..f6d197d 100644 --- a/src/dged/buffers.c +++ b/src/dged/buffers.c @@ -1,5 +1,6 @@ #include "buffers.h" #include "buffer.h" +#include "s8.h" #include #include @@ -112,7 +113,7 @@ struct buffer *buffers_find(struct buffers *buffers, const char *name) { struct buffer *buffers_find_by_filename(struct buffers *buffers, const char *path) { struct buffer_chunk *chunk = buffers->head; - size_t pathlen = strlen(path); + struct s8 needle = s8(path); while (chunk != NULL) { for (uint32_t i = 0; i < buffers->chunk_size; ++i) { if (!chunk->entries[i].occupied) { @@ -124,8 +125,8 @@ struct buffer *buffers_find_by_filename(struct buffers *buffers, continue; } - size_t bnamelen = strlen(b->filename); - if (bnamelen == pathlen && memcmp(path, b->filename, bnamelen) == 0) { + struct s8 bname = s8(b->filename); + if (s8endswith(bname, needle)) { return b; } } diff --git a/src/dged/bufread.c b/src/dged/bufread.c new file mode 100644 index 0000000..68ef839 --- /dev/null +++ b/src/dged/bufread.c @@ -0,0 +1,151 @@ +#include "bufread.h" + +#include +#include +#include +#include + +struct bufread { + uint8_t *buf; + size_t capacity; + size_t read_pos; + size_t write_pos; + int fd; + bool empty; +}; + +struct bufread *bufread_create(int fd, size_t capacity) { + struct bufread *br = (struct bufread *)calloc(1, sizeof(struct bufread)); + br->buf = calloc(capacity, 1); + br->capacity = capacity; + br->read_pos = 0; + br->write_pos = 0; + br->empty = true; + br->fd = fd; + + return br; +} + +void bufread_destroy(struct bufread *br) { + free(br->buf); + br->buf = NULL; + br->capacity = 0; + br->read_pos = 0; + br->write_pos = 0; + br->empty = true; + br->fd = -1; + + free(br); +} + +static ssize_t fill(struct bufread *br) { + ssize_t rd = 0, ret = 0; + + // special case for empty ring buffer + // in this case, reset read and write pos to beginning. + if (br->empty) { + if ((ret = read(br->fd, br->buf, br->capacity)) < 0) { + return ret; + } + + rd = ret; + br->read_pos = 0; + br->write_pos = ret; + br->empty = false; + + return rd; + } + + size_t space_after = + br->read_pos < br->write_pos ? br->capacity - br->write_pos : 0; + if (space_after > 0) { + if ((ret = read(br->fd, &br->buf[br->write_pos], space_after)) < 0) { + return ret; + } + } + + rd += ret; + + // if we wrapped around, there might be more space + if (br->write_pos == br->capacity) { + br->write_pos = 0; + size_t space_before = br->read_pos; + if (space_before > 0) { + if ((ret = read(br->fd, &br->buf[0], space_before)) < 0) { + return ret; + } + } + + br->write_pos += ret; + rd += ret; + } + + br->empty = rd == 0; + return rd; +} + +static size_t available(struct bufread *br) { + if (br->write_pos > br->read_pos) { + return br->write_pos - br->read_pos; + } else if (br->write_pos < br->read_pos) { + return br->write_pos + (br->capacity - br->read_pos); + } + + /* read == write, either empty or full */ + return br->empty ? 0 : br->capacity; +} + +static void consume(struct bufread *br, size_t amount) { + if (amount >= available(br)) { + br->empty = true; + br->read_pos = br->write_pos; + return; + } + + br->read_pos = (br->read_pos + amount) % br->capacity; +} + +ssize_t bufread_read(struct bufread *br, uint8_t *buf, size_t count) { + if (count == 0) { + return 0; + } + + // for read request larger than the internal buffer + // and an empty internal buffer, just go to the + // underlying source + if (br->empty && count >= br->capacity) { + return read(br->fd, buf, count); + } + + if (available(br) < count && available(br) < br->capacity) { + ssize_t fill_res = 0; + if ((fill_res = fill(br)) <= 0) { + return fill_res; + } + } + + // read (at most) to end + uint8_t *tgt = buf; + size_t to_read = 0, rd = 0; + to_read = (br->read_pos < br->write_pos ? br->write_pos : br->capacity) - + br->read_pos; + to_read = to_read > count ? count : to_read; + + memcpy(tgt, &br->buf[br->read_pos], to_read); + tgt += to_read; + rd += to_read; + consume(br, to_read); + + // did we wrap around and have things left to read? + if (br->read_pos == 0 && !br->empty && rd < count) { + to_read = br->write_pos; + to_read = to_read > count ? count : to_read; + + memcpy(tgt, br->buf, to_read); + tgt += to_read; + rd += to_read; + consume(br, to_read); + } + + return rd; +} diff --git a/src/dged/bufread.h b/src/dged/bufread.h new file mode 100644 index 0000000..11a18ff --- /dev/null +++ b/src/dged/bufread.h @@ -0,0 +1,13 @@ +#ifndef _BUFREAD_H +#define _BUFREAD_H + +#include +#include +#include + +struct bufread; +struct bufread *bufread_create(int fd, size_t capacity); +void bufread_destroy(struct bufread *br); +ssize_t bufread_read(struct bufread *br, uint8_t *buf, size_t count); + +#endif diff --git a/src/dged/command.h b/src/dged/command.h index 2b0f074..ed1c2cc 100644 --- a/src/dged/command.h +++ b/src/dged/command.h @@ -82,7 +82,7 @@ struct command { #define COMMAND_FN(name_, command_name, function, userdata_) \ static struct command command_name##_command = { \ .fn = function, \ - .name = #name_, \ + .name = name_, \ .userdata = userdata_, \ }; diff --git a/src/dged/display.c b/src/dged/display.c index e992cc9..ad9dad2 100644 --- a/src/dged/display.c +++ b/src/dged/display.c @@ -359,6 +359,13 @@ void command_list_set_inverted_colors(struct command_list *list) { cmd->len = 1; } +void command_list_set_underline(struct command_list *list) { + struct push_fmt_cmd *cmd = + add_command(list, RenderCommand_PushFormat)->data.push_fmt; + cmd->fmt[0] = '4'; + cmd->len = 1; +} + void command_list_reset_color(struct command_list *list) { add_command(list, RenderCommand_ClearFormat); } diff --git a/src/dged/display.h b/src/dged/display.h index cfa2eca..6f7f12d 100644 --- a/src/dged/display.h +++ b/src/dged/display.h @@ -197,6 +197,11 @@ void command_list_set_color_fg(struct command_list *list, uint8_t red, */ void command_list_set_inverted_colors(struct command_list *list); +/** + * Enable underline. + */ +void command_list_set_underline(struct command_list *list); + /** * Reset the color and styling information. * diff --git a/src/dged/hook.h b/src/dged/hook.h new file mode 100644 index 0000000..66e2839 --- /dev/null +++ b/src/dged/hook.h @@ -0,0 +1,84 @@ +#ifndef _HOOK_H +#define _HOOK_H + +#include + +#include "vec.h" + +/** Callback when removing hooks to clean up userdata */ +typedef void (*remove_hook_cb)(void *userdata); + +#define HOOK_IMPL(name, callback_type) \ + struct name##_hook { \ + uint32_t id; \ + callback_type callback; \ + void *userdata; \ + }; \ + \ + typedef VEC(struct name##_hook) name##_hook_vec; \ + \ + static inline uint32_t insert_##name##_hook( \ + name##_hook_vec *hooks, uint32_t *id, callback_type callback, \ + void *userdata) { \ + uint32_t iid = ++(*id); \ + struct name##_hook hook = (struct name##_hook){ \ + .id = iid, \ + .callback = callback, \ + .userdata = userdata, \ + }; \ + VEC_PUSH(hooks, hook); \ + \ + return iid; \ + } \ + \ + static inline void remove_##name##_hook(name##_hook_vec *hooks, uint32_t id, \ + remove_hook_cb callback) { \ + uint64_t found_at = (uint64_t) - 1; \ + VEC_FOR_EACH_INDEXED(hooks, struct name##_hook *h, idx) { \ + if (h->id == id) { \ + if (callback != NULL) { \ + callback(h->userdata); \ + } \ + found_at = idx; \ + break; \ + } \ + } \ + if (found_at != (uint64_t) - 1) { \ + if (found_at < VEC_SIZE(hooks) - 1) { \ + VEC_SWAP(hooks, found_at, VEC_SIZE(hooks) - 1); \ + } \ + VEC_POP(hooks, struct name##_hook removed); \ + (void)removed; \ + } \ + } + +#define HOOK_IMPL_NO_REMOVE(name, callback_type) \ + struct name##_hook { \ + uint32_t id; \ + callback_type callback; \ + void *userdata; \ + }; \ + \ + typedef VEC(struct name##_hook) name##_hook_vec; \ + \ + static inline uint32_t insert_##name##_hook( \ + name##_hook_vec *hooks, uint32_t *id, callback_type callback, \ + void *userdata) { \ + uint32_t iid = ++(*id); \ + struct name##_hook hook = (struct name##_hook){ \ + .id = iid, \ + .callback = callback, \ + .userdata = userdata, \ + }; \ + VEC_PUSH(hooks, hook); \ + \ + return iid; \ + } + +#define dispatch_hook(hooks, hook_type, ...) \ + VEC_FOR_EACH(hooks, hook_type *h) { h->callback(__VA_ARGS__, h->userdata); } + +#define dispatch_hook_no_args(hooks, hook_type) \ + VEC_FOR_EACH(hooks, hook_type *h) { h->callback(h->userdata); } + +#endif diff --git a/src/dged/json.c b/src/dged/json.c index 24d5c15..a514f00 100644 --- a/src/dged/json.c +++ b/src/dged/json.c @@ -2,13 +2,24 @@ #include "hash.h" #include "hashmap.h" -#include "utf8.h" #include "vec.h" #include #include -HASHMAP_ENTRY_TYPE(json_object_member, struct json_value); +struct json_key_value { + struct s8 key; + struct json_value value; +}; + +HASHMAP_ENTRY_TYPE(json_object_member, struct json_key_value); + +static char errbuf[1024] = {0}; + +static const char *format_error(uint32_t line, uint32_t col, const char *msg) { + snprintf(errbuf, 1024, "(%d, %d): %s", line, col, msg); + return errbuf; +} struct json_object { HASHMAP(struct json_object_member) members; @@ -18,22 +29,176 @@ struct json_array { VEC(struct json_value) values; }; -static void setarray(struct json_value *val) { - val->type = Json_Array; - val->value.array = calloc(1, sizeof(struct json_array)); - VEC_INIT(&val->value.array->values, 10); +static struct json_value create_array(struct json_value *parent) { + struct json_value val = {0}; + val.type = Json_Array; + val.parent = parent; + val.value.array = calloc(1, sizeof(struct json_array)); + VEC_INIT(&val.value.array->values, 10); + + return val; } -static void setobject(struct json_value *val) { - val->type = Json_Object; - val->value.object = calloc(1, sizeof(struct json_object)); - HASHMAP_INIT(&val->value.object->members, 10, hash_name); +static struct json_value create_object(struct json_value *parent) { + struct json_value val = {0}; + val.type = Json_Object; + val.parent = parent; + val.value.object = calloc(1, sizeof(struct json_object)); + HASHMAP_INIT(&val.value.object->members, 10, hash_name); + + return val; } -static void setstring(struct json_value *val, uint8_t *current) { - val->type = Json_String; - val->value.string.s = current; - val->value.string.l = 0; +struct s8 unescape_json_string(struct s8 input) { + /* FIXME: this is a bit funky and does not take + unicode characters into account and probably also + misses some escape codes. */ + size_t new_size = 0; + bool escape = false; + for (size_t bi = 0; bi < input.l; ++bi) { + uint8_t b = input.s[bi]; + if (b == '\\' && !escape) { + escape = true; + continue; + } + + ++new_size; + escape = false; + } + + escape = false; + uint8_t *buf = calloc(new_size, 1); + size_t bufi = 0; + for (size_t bi = 0; bi < input.l; ++bi) { + uint8_t b = input.s[bi]; + + if (b == '\\' && !escape) { + escape = true; + continue; + } + + if (escape) { + switch (b) { + case 'b': + buf[bufi] = '\b'; + break; + case '\\': + buf[bufi] = '\\'; + break; + case 'f': + buf[bufi] = '\f'; + break; + case 'n': + buf[bufi] = '\n'; + break; + case 'r': + buf[bufi] = '\r'; + break; + case 't': + buf[bufi] = '\t'; + break; + case '"': + buf[bufi] = '"'; + break; + default: + buf[bufi] = b; + } + } else { + buf[bufi] = b; + } + + escape = false; + ++bufi; + } + + return (struct s8){ + .s = buf, + .l = new_size, + }; +} + +struct s8 escape_json_string(struct s8 input) { + size_t new_size = 0; + for (size_t bi = 0; bi < input.l; ++bi) { + uint8_t b = input.s[bi]; + switch (b) { + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + case '"': + new_size += 2; + break; + default: + ++new_size; + } + } + + uint8_t *buf = calloc(new_size, 1); + size_t bufi = 0; + for (size_t bi = 0; bi < input.l; ++bi) { + uint8_t b = input.s[bi]; + switch (b) { + case '\\': + buf[bufi] = '\\'; + buf[bufi + 1] = '\\'; + bufi += 2; + break; + case '\b': + buf[bufi] = '\\'; + buf[bufi + 1] = 'b'; + bufi += 2; + break; + case '\f': + buf[bufi] = '\\'; + buf[bufi + 1] = 'f'; + bufi += 2; + break; + case '\n': + buf[bufi] = '\\'; + buf[bufi + 1] = 'n'; + bufi += 2; + break; + case '\r': + buf[bufi] = '\\'; + buf[bufi + 1] = 'r'; + bufi += 2; + break; + case '\t': + buf[bufi] = '\\'; + buf[bufi + 1] = 't'; + bufi += 2; + break; + case '"': + buf[bufi] = '\\'; + buf[bufi + 1] = '"'; + bufi += 2; + break; + default: + buf[bufi] = b; + ++bufi; + } + } + + return (struct s8){ + .s = buf, + .l = new_size, + }; +} + +static struct json_value create_string(const uint8_t *start, uint32_t len, + struct json_value *parent) { + struct json_value val = {0}; + val.type = Json_String; + val.parent = parent; + val.value.string.s = (uint8_t *)start; + val.value.string.l = len; + val.start = start; + val.end = start + len; + + return val; } static bool is_number(uint8_t byte) { return byte >= '0' && byte <= '9'; } @@ -43,149 +208,299 @@ enum object_parse_state { ObjectParseState_Value, }; -struct json_result json_parse(uint8_t *buf, uint64_t size) { - struct json_result res = { - .ok = true, - .result.document.type = Json_Null, +struct parser_state { + const uint8_t *buf; + uint64_t pos; + uint64_t len; + uint32_t line; + uint32_t col; +}; + +static struct json_result parse_string(struct parser_state *state, + struct json_value *parent) { + uint64_t start_pos = ++state->pos; /* ++ to skip start of string (") */ + bool literal = false; + while (state->pos < state->len && + (literal || state->buf[state->pos] != '"')) { + + // skip literal " escaped with \" + literal = state->buf[state->pos] == '\\'; + ++state->pos; + ++state->col; + } + + if (state->pos < state->len) { + uint64_t len = state->pos - start_pos; + + // skip over " + ++state->pos; + ++state->col; + + return (struct json_result){ + .ok = true, + .result.document = create_string(&state->buf[start_pos], len, parent), + }; + } + + return (struct json_result){ + .ok = false, + .result.error = "expected end of string, found EOF", }; +} - struct json_value *parent = NULL; - struct json_value *current = &res.result.document; - struct json_value tmp_key = {0}; - struct json_value tmp_val = {0}; - uint32_t line = 1, col = 0; - - enum object_parse_state obj_parse_state = ObjectParseState_Key; - for (uint64_t bufi = 0; bufi < size; ++bufi) { - uint8_t byte = buf[bufi]; - - // handle appends to the current scope - if (current->type == Json_Array) { - VEC_PUSH(¤t->value.array->values, tmp_val); - parent = current; - - // start looking for next value - tmp_val.type = Json_Null; - current = &tmp_val; - } else if (current->type == Json_Object && - obj_parse_state == ObjectParseState_Key) { - // key is in tmp_key, start looking for value - obj_parse_state = ObjectParseState_Value; - parent = current; - - tmp_val.type = Json_Null; - current = &tmp_val; - } else if (current->type == Json_Object && - obj_parse_state == ObjectParseState_Value) { - // value is in tmp_val - // TODO: remove this alloc, should not be needed - char *k = s8tocstr(tmp_key.value.string); - uint32_t hash = 0; - HASHMAP_INSERT(¤t->value.object->members, struct json_object_member, - k, tmp_val, hash); - (void)hash; - free(k); - - // start looking for next key - obj_parse_state = ObjectParseState_Key; - parent = current; - - tmp_key.type = Json_Null; - current = &tmp_key; +static struct json_result parse_number(struct parser_state *state, + struct json_value *parent) { + uint64_t start_pos = state->pos; + while (state->pos < state->len && + (is_number(state->buf[state->pos]) || state->buf[state->pos] == '-' || + state->buf[state->pos] == '.')) { + ++state->pos; + ++state->col; + } + + if (state->pos < state->len) { + uint64_t len = state->pos - start_pos; + char *nmbr = + s8tocstr((struct s8){.s = (uint8_t *)&state->buf[start_pos], .l = len}); + struct json_result res = { + .ok = true, + .result.document.type = Json_Number, + .result.document.value.number = atof(nmbr), + .result.document.parent = parent, + .result.document.start = &state->buf[start_pos], + .result.document.end = &state->buf[state->pos], + }; + free(nmbr); + return res; + } + + return (struct json_result){ + .ok = false, + .result.error = "expected end of number, found EOF", + }; +} + +static struct json_result parse_value(struct parser_state *state, + struct json_value *parent) { + uint8_t byte = state->buf[state->pos]; + switch (byte) { + case '"': + return parse_string(state, parent); + case 't': + state->pos += 4; + state->col += 4; + return (struct json_result){ + .ok = true, + .result.document.type = Json_Bool, + .result.document.start = &state->buf[state->pos - 4], + .result.document.end = &state->buf[state->pos], + .result.document.value.boolean = true, + .result.document.parent = parent, + }; + case 'f': + state->pos += 5; + state->col += 5; + return (struct json_result){ + .ok = true, + .result.document.type = Json_Bool, + .result.document.value.boolean = false, + .result.document.start = &state->buf[state->pos - 5], + .result.document.end = &state->buf[state->pos], + .result.document.parent = parent, + }; + case 'n': + state->pos += 4; + state->col += 4; + return (struct json_result){ + .ok = true, + .result.document.type = Json_Null, + .result.document.start = &state->buf[state->pos - 4], + .result.document.end = &state->buf[state->pos], + .result.document.parent = parent, + }; + default: + if (is_number(byte) || byte == '-' || byte == '.') { + return parse_number(state, parent); } + break; + } - switch (byte) { - case '[': - setarray(current); - parent = current; + return (struct json_result){ + .ok = false, + .result.error = format_error(state->line, state->col, "expected value"), + }; +} - tmp_val.type = Json_Null; - current = &tmp_val; - break; - case ']': - current = parent; - break; - case '{': - setobject(current); - obj_parse_state = ObjectParseState_Key; - parent = current; +struct json_value *insert(struct json_value *container, struct json_value *key_, + struct json_value *value) { + + struct json_value *inserted = NULL; + // where to put value? + if (container->type == Json_Object) { + // TODO: remove this alloc, should not be needed + char *k = s8tocstr(key_->value.string); + HASHMAP_APPEND(&container->value.object->members, struct json_object_member, + k, struct json_object_member * val); + + // TODO: duplicate key + if (val != NULL) { + inserted = &val->value.value; + val->value.value = *value; + val->value.key = s8dup(key_->value.string); + } - tmp_key.type = Json_Null; - current = &tmp_key; - break; + free(k); + } else if (container->type == Json_Array) { + VEC_APPEND(&container->value.array->values, struct json_value * val); + inserted = val; + *val = *value; + } else { // root + *container = *value; + inserted = container; + } + + return inserted; +} + +struct json_result json_parse(const uint8_t *buf, uint64_t size) { + + enum object_parse_state expected = ObjectParseState_Value; + struct parser_state state = { + .buf = buf, + .pos = 0, + .len = size, + .line = 1, + .col = 0, + }; + + struct json_value root = {0}, key = {0}, value = {0}; + struct json_value *container = &root; + + while (state.pos < state.len) { + switch (state.buf[state.pos]) { + case ',': + case ' ': + case ':': + case '\r': + case '\t': + ++state.col; + ++state.pos; + continue; + + case '\n': + ++state.line; + ++state.pos; + state.col = 0; + continue; + + case ']': case '}': - current = parent; - break; - case '"': - if (current->type == Json_String) { - // finish off the string - current->value.string.l = (buf + bufi) - current->value.string.s; - current = parent; - } else { - setstring(current, buf + bufi + 1 /* skip " */); + container->end = &state.buf[state.pos + 1]; + container = container->parent; + + if (container->type == Json_Object) { + expected = ObjectParseState_Key; } + + ++state.pos; + ++state.col; + continue; + + case '[': + value = create_array(container); + value.start = &state.buf[state.pos]; + ++state.pos; + ++state.col; break; - case '\n': - ++line; - col = 0; + case '{': + value = create_object(container); + value.start = &state.buf[state.pos]; + ++state.pos; + ++state.col; break; default: - if (current->type == Json_String) { - // append to string - } else if (current->type == Json_Number && - !(is_number(byte) || byte == '-' || byte == '.')) { - // end of number - current->value.string.l = (buf + bufi) - current->value.string.s; - char *nmbr = s8tocstr(current->value.string); - current->value.number = atof(nmbr); - free(nmbr); - - current = parent; - - } else if (current->type == Json_Null && - (is_number(byte) || byte == '-' || byte == '.')) { - // borrow string storage in the value for storing number - // as a string - setstring(current, buf + bufi); - current->type = Json_Number; - } else if (byte == 't') { - current->type = Json_Bool; - current->value.boolean = true; - - current = parent; - } else if (byte == 'f') { - current->type = Json_Bool; - current->value.boolean = false; - - current = parent; - } else if (byte == 'n') { - current->type = Json_Null; - - current = parent; + // parse out a value or a key + switch (expected) { + + case ObjectParseState_Key: { + if (container->type == Json_Object) { + struct json_result res = parse_string(&state, container); + + if (!res.ok) { + json_destroy(&root); + return res; + } + + key = res.result.document; + } + expected = ObjectParseState_Value; + // dont insert anything now, we still need a value + continue; + } + + case ObjectParseState_Value: { + struct json_result res = parse_value(&state, container); + + if (!res.ok) { + json_destroy(&root); + return res; + } + + value = res.result.document; + + if (container->type == Json_Object) { + expected = ObjectParseState_Key; + } + break; + } } break; } - // TODO: not entirely correct - ++col; + // insert the value we have created into the + // structure + struct json_value *inserted = insert(container, &key, &value); + + // did we insert a container? + // In this case, this is the current container and + // set the expectation for value or key correctly + // depending on the type + if (inserted != NULL && + (value.type == Json_Object || value.type == Json_Array)) { + container = inserted; + + if (value.type == Json_Object) { + expected = ObjectParseState_Key; + } else { + expected = ObjectParseState_Value; + } + } } - return res; + + return (struct json_result){ + .ok = true, + .result.document = root, + }; } void json_destroy(struct json_value *value) { switch (value->type) { - case Json_Array: + case Json_Array: { struct json_array *arr = value->value.array; VEC_FOR_EACH(&arr->values, struct json_value * val) { json_destroy(val); } VEC_DESTROY(&arr->values); - break; - case Json_Object: + free(arr); + } break; + case Json_Object: { struct json_object *obj = value->value.object; HASHMAP_FOR_EACH(&obj->members, struct json_object_member * memb) { - json_destroy(&memb->value); + s8delete(memb->value.key); + json_destroy(&memb->value.value); } HASHMAP_DESTROY(&obj->members); + free(obj); + } break; case Json_Null: case Json_Number: case Json_String: @@ -212,6 +527,8 @@ uint64_t json_len(struct json_object *obj) { return HASHMAP_SIZE(&obj->members); } +bool json_empty(struct json_object *obj) { return json_len(obj) == 0; } + bool json_contains(struct json_object *obj, struct s8 key) { // TODO: get rid of alloc char *k = s8tocstr(key); @@ -222,13 +539,45 @@ bool json_contains(struct json_object *obj, struct s8 key) { return res; } +void json_foreach(struct json_object *obj, + void (*cb)(struct s8, struct json_value *, void *), + void *userdata) { + HASHMAP_FOR_EACH(&obj->members, struct json_object_member * entry) { + cb(entry->value.key, &entry->value.value, userdata); + } +} + struct json_value *json_get(struct json_object *obj, struct s8 key) { // TODO: get rid of alloc char *k = s8tocstr(key); HASHMAP_GET(&obj->members, struct json_object_member, k, - struct json_value * result); + struct json_key_value * result); free(k); - return result; + return result != NULL ? &result->value : NULL; +} + +void json_set(struct json_object *obj, struct s8 key_, struct json_value val) { + // TODO: get rid of alloc + char *k = s8tocstr(key_); + uint32_t hash = 0; + + struct json_key_value v = { + .value = val, + .key = s8dup(key_), + }; + HASHMAP_INSERT(&obj->members, struct json_object_member, k, v, hash); + + (void)hash; + (void)key; + free(k); +} + +void json_array_foreach(struct json_array *arr, void *userdata, + void (*cb)(uint64_t, struct json_value *, void *)) { + + VEC_FOR_EACH_INDEXED(&arr->values, struct json_value * val, i) { + cb(i, val, userdata); + } } diff --git a/src/dged/json.h b/src/dged/json.h index c0428b9..7f64e31 100644 --- a/src/dged/json.h +++ b/src/dged/json.h @@ -7,9 +7,9 @@ #include "s8.h" enum json_type { + Json_Null = 0, Json_Array, Json_Object, - Json_Null, Json_Number, Json_String, Json_Bool, @@ -24,6 +24,10 @@ struct json_value { double number; bool boolean; } value; + struct json_value *parent; + + const uint8_t *start; + const uint8_t *end; }; struct json_result { @@ -34,21 +38,125 @@ struct json_result { } result; }; -struct json_writer; +/** + * Parse a json document from a string. + * + * @returns Structure describing the result of the parse + * operation. The member @ref ok, if true represents a + * successful parse, with the result in @ref result.document. + * If @ref ok is false, the parse operation has an error, + * and @ref result.error contains a descriptive error message. + */ +struct json_result json_parse(const uint8_t *buf, uint64_t size); -struct json_result json_parse(uint8_t *buf, uint64_t size); +/** + * Destroy a json value, returning all memory + * allocated for the structure. + * + * @param [in] value The json value to destroy. + */ void json_destroy(struct json_value *value); +/** + * Check if a JSON object is empty. + * + * @param [in] obj The JSON object to check if empty. + * + * @returns True if @ref obj is empty, false otherwise. + */ +bool json_empty(struct json_object *obj); + +/** + * Return the number of members in a JSON object. + * + * @param [in] obj The JSON object to get number of members for. + * + * @returns The number of members in @ref obj. + */ uint64_t json_len(struct json_object *obj); + +/** + * Test if the JSON object contains the specified key. + * + * @param [in] obj The JSON object to look for @ref key in. + * @param [in] key The key to search for. + * + * @returns True if @ref key exists in @ref obj, false otherwise. + */ bool json_contains(struct json_object *obj, struct s8 key); + +/** + * Iterate all key-value pairs in a JSON object. + * + * @param [in] obj The JSON object to iterate. + * @param [in] cb The callback to call for each kv-pair. + * @param [in] userdata Pointer that is sent unmodified to @ref cb. + */ +void json_foreach(struct json_object *obj, + void (*cb)(struct s8, struct json_value *, void *), + void *userdata); + +/** + * Get a value from a JSON object. + * + * @param [in] obj The JSON object to get from. + * @param [in] key The key of the value to get. + * + * @returns A pointer to the json value distinguished by @ref key, + * if it exists, NULL otherwise. + */ struct json_value *json_get(struct json_object *obj, struct s8 key); +/** + * Set a value in a JSON object. + * + * @param [in] obj The JSON object to set in. + * @param [in] key The key of the value to set. + * @param [in] value The JSON value to set. + */ +void json_set(struct json_object *obj, struct s8 key, struct json_value val); + +/** + * Get the length of a JSON array. + * + * @param [in] arr The array to get the length of + * + * @returns The length of @ref arr. + */ uint64_t json_array_len(struct json_array *arr); -void json_array_foreach(struct json_array *arr, - void (*cb)(uint64_t, struct json_value)); + +/** + * Iterate a JSON array. + * + * @param [in] arr The array to iterate. + * @param [in] userdata Pointer to user-defined data that is passed + to the callback. + * @param [in] cb The callback to invoke for each member in @ref arr. + */ +void json_array_foreach(struct json_array *arr, void *userdata, + void (*cb)(uint64_t, struct json_value *, void *)); + +/** + * Get a member from a JSON array by index. + * + * @param [in] arr The array to get from. + * @param [in] idx The index to get the value at. + * + * @returns A pointer to the value at @ref idx in @ref arr. If @ref idx + * is outside the array length, this returns NULL. + */ struct json_value *json_array_get(struct json_array *arr, uint64_t idx); -struct json_writer *json_writer_create(); -struct s8 json_writer_done(struct json_writer *writer); +/** + * Render a JSON value to a string. + * + * @param [in] val The json value to render to a string. + * + * @returns The JSON object rendered as a string. + */ +struct s8 json_value_to_string(const struct json_value *val); + +struct s8 unescape_json_string(struct s8 input); +struct s8 escape_json_string(struct s8 input); #endif diff --git a/src/dged/jsonrpc.c b/src/dged/jsonrpc.c new file mode 100644 index 0000000..215274f --- /dev/null +++ b/src/dged/jsonrpc.c @@ -0,0 +1,118 @@ +#include "jsonrpc.h" + +#include +#include +#include + +struct jsonrpc_message jsonrpc_parse(const uint8_t *buf, uint64_t size) { + + struct json_result res = json_parse(buf, size); + if (!res.ok) { + return (struct jsonrpc_message){ + .type = Jsonrpc_Response, + .document = (struct json_value){.type = Json_Null, .parent = NULL}, + .message.response = (struct jsonrpc_response){ + .id = (struct json_value){.type = Json_Null}, + .ok = false, + .value.error = + (struct jsonrpc_error){ + .code = 0, + .message = s8(res.result.error), + }, + }}; + } + + struct json_value doc = res.result.document; + struct json_object *obj = doc.value.object; + + if (json_contains(obj, s8("error"))) { + struct json_object *err_obj = json_get(obj, s8("error"))->value.object; + return (struct jsonrpc_message){ + .type = Jsonrpc_Response, + .document = doc, + .message.response = + (struct jsonrpc_response){ + .id = *json_get(obj, s8("id")), + .ok = false, + .value.error = + (struct jsonrpc_error){ + .code = json_get(err_obj, s8("code"))->value.number, + .message = + json_get(err_obj, s8("message"))->value.string, + }, + }, + }; + } else if (!json_contains(obj, s8("id"))) { + // no id == notification + return (struct jsonrpc_message){ + .type = Jsonrpc_Notification, + .document = doc, + .message.notification = + (struct jsonrpc_notification){ + .method = json_get(obj, s8("method"))->value.string, + .params = *json_get(obj, s8("params")), + }, + }; + } else if (json_contains(obj, s8("method"))) { + // request + return (struct jsonrpc_message){ + .type = Jsonrpc_Request, + .document = doc, + .message.request = (struct jsonrpc_request){ + .id = *json_get(obj, s8("id")), + .method = json_get(obj, s8("method"))->value.string, + .params = *json_get(obj, s8("params")), + }}; + } + + // response + return (struct jsonrpc_message){ + .type = Jsonrpc_Response, + .document = doc, + .message.response = (struct jsonrpc_response){ + .id = *json_get(obj, s8("id")), + .ok = true, + .value.result = *json_get(obj, s8("result")), + }}; +} + +struct s8 jsonrpc_format_request(struct json_value id, struct s8 method, + struct s8 params) { + const char *fmt = "{ \"jsonrpc\": \"2.0\", \"id\": %d, \"method\": \"%.*s\", " + "\"params\": %.*s }"; + size_t s = snprintf(NULL, 0, fmt, (int)id.value.number, method.l, method.s, + params.l, params.s); + char *buf = calloc(s + 1, 1); + snprintf(buf, s + 1, fmt, (int)id.value.number, method.l, method.s, params.l, + params.s); + + return (struct s8){ + .s = (uint8_t *)buf, + .l = s, + }; +} + +struct s8 jsonrpc_format_response(struct json_value id, struct s8 result) { + const char *fmt = "{ \"jsonrpc\": \"2.0\", \"id\": %d, \"result\": %.*s }"; + size_t s = snprintf(NULL, 0, fmt, (int)id.value.number, result.l, result.s); + char *buf = calloc(s + 1, 1); + snprintf(buf, s + 1, fmt, (int)id.value.number, result.l, result.s); + + return (struct s8){ + .s = (uint8_t *)buf, + .l = s, + }; +} + +struct s8 jsonrpc_format_notification(struct s8 method, struct s8 params) { + const char *fmt = "{ \"jsonrpc\": \"2.0\", \"method\": \"%.*s\", " + "\"params\": %.*s }"; + size_t s = snprintf(NULL, 0, fmt, method.l, method.s, params.l, params.s); + char *buf = calloc(s + 1, 1); + snprintf(buf, s + 1, fmt, method.l, method.s, params.l, params.s); + + return (struct s8){ + .s = (uint8_t *)buf, + .l = s, + }; +} diff --git a/src/dged/jsonrpc.h b/src/dged/jsonrpc.h new file mode 100644 index 0000000..2ac2787 --- /dev/null +++ b/src/dged/jsonrpc.h @@ -0,0 +1,58 @@ +#ifndef _JSONRPC_H +#define _JSONRPC_H + +#include + +#include "json.h" +#include "s8.h" + +enum jsonrpc_type { + Jsonrpc_Request, + Jsonrpc_Response, + Jsonrpc_Notification, +}; + +struct jsonrpc_request { + struct json_value id; + struct s8 method; + struct json_value params; +}; + +struct jsonrpc_error { + int code; + struct s8 message; + struct json_value data; +}; + +struct jsonrpc_notification { + struct s8 method; + struct json_value params; +}; + +struct jsonrpc_response { + struct json_value id; + bool ok; + union jsonrpc_value { + struct json_value result; + struct jsonrpc_error error; + } value; +}; + +struct jsonrpc_message { + enum jsonrpc_type type; + union jsonrpc_msg { + struct jsonrpc_request request; + struct jsonrpc_response response; + struct jsonrpc_notification notification; + } message; + + struct json_value document; +}; + +struct jsonrpc_message jsonrpc_parse(const uint8_t *buf, uint64_t size); +struct s8 jsonrpc_format_request(struct json_value id, struct s8 method, + struct s8 params); +struct s8 jsonrpc_format_response(struct json_value id, struct s8 result); +struct s8 jsonrpc_format_notification(struct s8 method, struct s8 params); + +#endif diff --git a/src/dged/keyboard.c b/src/dged/keyboard.c index 04565e0..5447947 100644 --- a/src/dged/keyboard.c +++ b/src/dged/keyboard.c @@ -124,7 +124,7 @@ struct keyboard_update keyboard_update(struct keyboard *kbd, const uint32_t bufsize = 1024; uint8_t *buf = malloc(bufsize), *writepos = buf; int nbytes = 0, nread = 0; - while ((nread = read(kbd->fd, writepos, bufsize)) == bufsize) { + while ((nread = read(kbd->fd, writepos, bufsize)) == (int)bufsize) { nbytes += bufsize; buf = realloc(buf, nbytes + bufsize); writepos = buf + nbytes; diff --git a/src/dged/lsp.c b/src/dged/lsp.c index 3c699f4..dae0603 100644 --- a/src/dged/lsp.c +++ b/src/dged/lsp.c @@ -1,29 +1,55 @@ #include "lsp.h" #include +#include +#include #include #include #include #include "buffer.h" +#include "bufread.h" +#include "jsonrpc.h" #include "process.h" #include "reactor.h" +struct pending_write { + char headers[256]; + uint64_t headers_len; + uint64_t written; + struct s8 payload; +}; + +typedef VEC(struct pending_write) write_vec; + +enum read_state { + Read_Headers, + Read_Payload, +}; + struct lsp { const char *name; char *const *command; struct process *process; struct reactor *reactor; struct buffer *stderr_buffer; - struct lsp_client client_impl; uint32_t stdin_event; uint32_t stdout_event; uint32_t stderr_event; + + write_vec writes; + + enum read_state read_state; + struct bufread *reader; + uint8_t header_buffer[4096]; + size_t header_len; + size_t content_len; + size_t curr_content_len; + uint8_t *reader_buffer; }; struct lsp *lsp_create(char *const command[], struct reactor *reactor, - struct buffer *stderr_buffer, - struct lsp_client client_impl, const char *name) { + struct buffer *stderr_buffer, const char *name) { // check length of command if (command == NULL) { return NULL; @@ -60,12 +86,15 @@ struct lsp *lsp_create(char *const command[], struct reactor *reactor, } } lsp->stderr_buffer = stderr_buffer; - lsp->client_impl = client_impl; lsp->reactor = reactor; lsp->stdin_event = -1; lsp->stdout_event = -1; lsp->stderr_event = -1; + lsp->reader = NULL; + lsp->read_state = Read_Headers; + lsp->curr_content_len = 0; + VEC_INIT(&lsp->writes, 64); return lsp; } @@ -74,28 +103,131 @@ void lsp_destroy(struct lsp *lsp) { if (lsp->process != NULL) { free(lsp->process); } - if (lsp->command != NULL) { - char *command = lsp->command[0]; - while (command != NULL) { - free(command); - ++command; - } + if (lsp->command != NULL) { free((void *)lsp->command); } + + VEC_DESTROY(&lsp->writes); + free(lsp); } -uint32_t lsp_update(struct lsp *lsp, struct lsp_response **responses, - uint32_t responses_capacity) { +static bool read_headers(struct lsp *lsp) { + bool prev_was_cr = false; + while (true) { + uint8_t b; + ssize_t res = bufread_read(lsp->reader, &b, 1); + + if (res == -1 || res == 0) { + return false; + } + + if (b == '\n' && prev_was_cr && lsp->header_len == 0) { + // end of headers + lsp->reader_buffer = calloc(lsp->content_len, 1); + lsp->curr_content_len = 0; + lsp->read_state = Read_Payload; + + return true; + } else if (b == '\n' && prev_was_cr) { + // end of individual header + lsp->header_buffer[lsp->header_len] = '\0'; + + if (lsp->header_len > 15 && + memcmp(lsp->header_buffer, "Content-Length:", 15) == 0) { + lsp->content_len = atoi((const char *)&lsp->header_buffer[16]); + } + + lsp->header_len = 0; + + continue; + } + + prev_was_cr = false; + if (b == '\r') { + prev_was_cr = true; + continue; + } + + // TODO: handle this case + if (lsp->header_len < 4096) { + lsp->header_buffer[lsp->header_len] = b; + ++lsp->header_len; + } + } +} + +static bool read_payload(struct lsp *lsp) { + ssize_t res = + bufread_read(lsp->reader, &lsp->reader_buffer[lsp->curr_content_len], + lsp->content_len - lsp->curr_content_len); + + if (res == -1) { + return false; + } else if (res == 0) { + return false; + } + + lsp->curr_content_len += res; + return lsp->curr_content_len == lsp->content_len; +} + +static void init_lsp_message(struct lsp_message *lsp_msg, uint8_t *payload, + size_t len) { + + lsp_msg->jsonrpc_msg = jsonrpc_parse(payload, len); + lsp_msg->parsed = true; // this is parsed to json + lsp_msg->payload.s = payload; + lsp_msg->payload.l = len; + + switch (lsp_msg->jsonrpc_msg.type) { + case Jsonrpc_Request: { + lsp_msg->type = Lsp_Request; + struct jsonrpc_request *jreq = &lsp_msg->jsonrpc_msg.message.request; + struct lsp_request *lreq = &lsp_msg->message.request; - (void)responses; - (void)responses_capacity; + lreq->id = (request_id)jreq->id.value.number; + lreq->method = jreq->method; + lreq->params = jreq->params; + } break; + case Jsonrpc_Response: { + lsp_msg->type = Lsp_Response; + struct jsonrpc_response *jresp = &lsp_msg->jsonrpc_msg.message.response; + struct lsp_response *lresp = &lsp_msg->message.response; + + lresp->id = (request_id)jresp->id.value.number; + lresp->ok = jresp->ok; + if (lresp->ok) { + lresp->value.result = jresp->value.result; + } else { + lresp->value.error.code = jresp->value.error.code; + lresp->value.error.message = jresp->value.error.message; + lresp->value.error.data = jresp->value.error.data; + } + } break; + + case Jsonrpc_Notification: { + lsp_msg->type = Lsp_Notification; + struct jsonrpc_notification *jnot = + &lsp_msg->jsonrpc_msg.message.notification; + struct lsp_notification *lnot = &lsp_msg->message.notification; + + lnot->method = jnot->method; + lnot->params = jnot->params; + } break; + } +} + +uint32_t lsp_update(struct lsp *lsp, struct lsp_message *msgs, + uint32_t nmax_msgs) { if (!lsp_server_running(lsp)) { return -1; } + uint32_t nmsgs = 0; + // read stderr if (lsp->stderr_event != (uint32_t)-1) { uint8_t buf[1024]; @@ -109,7 +241,105 @@ uint32_t lsp_update(struct lsp *lsp, struct lsp_response **responses, } } - return 0; + // write pending requests + if (reactor_poll_event(lsp->reactor, lsp->stdin_event)) { + VEC_FOR_EACH(&lsp->writes, struct pending_write * w) { + ssize_t written = 0; + ssize_t to_write = 0; + + // write headers first + if (w->written < w->headers_len) { + to_write = w->headers_len - w->written; + written = write(lsp->process->stdin, w->headers + w->written, to_write); + } + + // did an error occur + if (written < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + // TODO: log error somehow + } + goto cleanup_writes; + } else { + w->written += written; + } + + // write content next + if (w->written >= w->headers_len) { + to_write = w->payload.l + w->headers_len - w->written; + size_t offset = w->written - w->headers_len; + written = write(lsp->process->stdin, w->payload.s + offset, to_write); + } + + // did an error occur + if (written < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + // TODO: log error somehow + } + goto cleanup_writes; + } else { + w->written += written; + } + } + } + +cleanup_writes: + /* lsp->writes = filter(&lsp->writes, x: x.written < x.payload.l + + * x.headers_len) */ + if (!VEC_EMPTY(&lsp->writes)) { + write_vec writes = lsp->writes; + VEC_INIT(&lsp->writes, VEC_CAPACITY(&writes)); + + VEC_FOR_EACH(&writes, struct pending_write * w) { + if (w->written < w->payload.l + w->headers_len) { + // copying 256 bytes, goodbye vaccuum tubes... + VEC_PUSH(&lsp->writes, *w); + } else { + s8delete(w->payload); + } + } + VEC_DESTROY(&writes); + } + + if (VEC_EMPTY(&lsp->writes)) { + reactor_unregister_interest(lsp->reactor, lsp->stdin_event); + lsp->stdin_event = (uint32_t)-1; + } + + // process incoming messages + // TODO: handle the case where we might leave data + if (reactor_poll_event(lsp->reactor, lsp->stdout_event)) { + bool has_data = true; + while (has_data) { + switch (lsp->read_state) { + case Read_Headers: + has_data = read_headers(lsp); + break; + case Read_Payload: { + bool payload_ready = read_payload(lsp); + if (payload_ready) { + if (nmsgs == nmax_msgs) { + return nmsgs; + } + + init_lsp_message(&msgs[nmsgs], lsp->reader_buffer, lsp->content_len); + ++nmsgs; + + // set up for next message + lsp->reader_buffer = NULL; + lsp->content_len = 0; + lsp->read_state = Read_Headers; + } + + // it only returns if we are out of data or if + // the payload is ready + has_data = payload_ready; + break; + } + } + } + } + + return nmsgs; } int lsp_start_server(struct lsp *lsp) { @@ -123,33 +353,61 @@ int lsp_start_server(struct lsp *lsp) { lsp->process = calloc(1, sizeof(struct process)); memcpy(lsp->process, &p, sizeof(struct process)); + + lsp->stdout_event = reactor_register_interest( + lsp->reactor, lsp->process->stdout, ReadInterest); + + if (lsp->stdout_event == (uint32_t)-1) { + return -3; + } + lsp->stderr_event = reactor_register_interest( lsp->reactor, lsp->process->stderr, ReadInterest); + lsp->reader = bufread_create(lsp->process->stdout, 8192); + return 0; } int lsp_restart_server(struct lsp *lsp) { - if (lsp_server_running(lsp)) { - lsp_stop_server(lsp); - } - + lsp_stop_server(lsp); return lsp_start_server(lsp); } void lsp_stop_server(struct lsp *lsp) { - process_kill(lsp->process); - process_destroy(lsp->process); - free(lsp->process); - lsp->process = NULL; -} + if (lsp->stderr_event != (uint32_t)-1) { + reactor_unregister_interest(lsp->reactor, lsp->stderr_event); + lsp->stderr_event = (uint32_t)-1; + } -bool lsp_server_running(const struct lsp *lsp) { - if (lsp->process == NULL) { - return false; + if (lsp->stdin_event != (uint32_t)-1) { + reactor_unregister_interest(lsp->reactor, lsp->stdin_event); + lsp->stdin_event = (uint32_t)-1; + } + + if (lsp->stdout_event != (uint32_t)-1) { + reactor_unregister_interest(lsp->reactor, lsp->stdout_event); + lsp->stdout_event = (uint32_t)-1; + } + + if (lsp_server_running(lsp)) { + process_kill(lsp->process); + } + + if (lsp->process != NULL) { + process_destroy(lsp->process); + free(lsp->process); + lsp->process = NULL; } - return process_running(lsp->process); + if (lsp->reader != NULL) { + bufread_destroy(lsp->reader); + lsp->reader = NULL; + } +} + +bool lsp_server_running(const struct lsp *lsp) { + return lsp->process != NULL ? process_running(lsp->process) : false; } uint64_t lsp_server_pid(const struct lsp *lsp) { @@ -161,3 +419,75 @@ uint64_t lsp_server_pid(const struct lsp *lsp) { } const char *lsp_server_name(const struct lsp *lsp) { return lsp->name; } + +struct lsp_message lsp_create_request(request_id id, struct s8 method, + struct s8 payload) { + struct lsp_message msg = { + .type = Lsp_Request, + .parsed = false, // payload is raw + .message.request.method = method, + .message.request.id = id, + .payload = jsonrpc_format_request( + (struct json_value){ + .type = Json_Number, + .value.number = (double)id, + .parent = NULL, + }, + method, payload.l > 0 ? payload : s8("{}")), + }; + + return msg; +} + +struct lsp_message lsp_create_response(request_id id, bool ok, + struct s8 payload) { + struct lsp_message msg = { + .type = Lsp_Response, + .parsed = false, // payload is raw + .message.response.ok = ok, + .message.response.id = id, + .payload = jsonrpc_format_response( + (struct json_value){ + .type = Json_Number, + .value.number = (double)id, + .parent = NULL, + }, + payload.l > 0 ? payload : s8("{}")), + }; + + return msg; +} + +struct lsp_message lsp_create_notification(struct s8 method, + struct s8 payload) { + struct lsp_message msg = { + .type = Lsp_Notification, + .parsed = false, // payload is raw + .message.notification.method = method, + .payload = jsonrpc_format_notification(method, payload.l > 0 ? payload + : s8("{}")), + }; + + return msg; +} + +void lsp_send(struct lsp *lsp, struct lsp_message message) { + + VEC_APPEND(&lsp->writes, struct pending_write * w); + w->headers_len = snprintf(w->headers, 256, "Content-Length: %d\r\n\r\n", + message.payload.l); + w->payload = message.payload; + w->written = 0; + + if (lsp->stdin_event == (uint32_t)-1) { + lsp->stdin_event = reactor_register_interest( + lsp->reactor, lsp->process->stdin, WriteInterest); + } +} + +void lsp_message_destroy(struct lsp_message *message) { + if (message->parsed) { + json_destroy(&message->jsonrpc_msg.document); + } + s8delete(message->payload); +} diff --git a/src/dged/lsp.h b/src/dged/lsp.h index 3fd6285..32d00bc 100644 --- a/src/dged/lsp.h +++ b/src/dged/lsp.h @@ -1,6 +1,10 @@ #ifndef _LSP_H #define _LSP_H +#include + +#include "json.h" +#include "jsonrpc.h" #include "location.h" #include "s8.h" @@ -8,55 +12,59 @@ struct buffer; struct lsp; struct reactor; -typedef uint32_t request_id; - -struct lsp_response { - request_id id; - bool ok; - union payload_data { - void *result; - struct s8 error; - } payload; -}; +typedef uint64_t request_id; -struct lsp_notification { - int something; +struct lsp_response_error { + int code; + struct s8 message; + struct json_value data; }; -struct lsp_client { - void (*log_message)(int type, struct s8 msg); +enum lsp_message_type { + Lsp_Notification, + Lsp_Request, + Lsp_Response, }; -struct hover { - struct s8 contents; +struct lsp_response { + request_id id; - bool has_range; - struct region *range; + bool ok; + union data { + struct json_value result; + struct lsp_response_error error; + } value; }; -struct text_doc_item { - struct s8 uri; - struct s8 language_id; - uint32_t version; - struct s8 text; +struct lsp_request { + request_id id; + struct s8 method; + struct json_value params; }; -struct text_doc_position { - struct s8 uri; - struct location pos; +struct lsp_notification { + struct s8 method; + struct json_value params; }; -struct initialize_params { - struct s8 client_name; - struct s8 client_version; +struct lsp_message { + enum lsp_message_type type; + bool parsed; + union message_data { + struct lsp_response response; + struct lsp_request request; + struct lsp_notification notification; + } message; + + struct s8 payload; + struct jsonrpc_message jsonrpc_msg; }; // lifecycle functions struct lsp *lsp_create(char *const command[], struct reactor *reactor, - struct buffer *stderr_buffer, - struct lsp_client client_impl, const char *name); -uint32_t lsp_update(struct lsp *lsp, struct lsp_response **responses, - uint32_t responses_capacity); + struct buffer *stderr_buffer, const char *name); +uint32_t lsp_update(struct lsp *lsp, struct lsp_message *msgs, + uint32_t nmax_msgs); void lsp_destroy(struct lsp *lsp); // process control functions @@ -67,9 +75,16 @@ bool lsp_server_running(const struct lsp *lsp); uint64_t lsp_server_pid(const struct lsp *lsp); const char *lsp_server_name(const struct lsp *lsp); +// lsp message creation +struct lsp_message lsp_create_request(request_id id, struct s8 method, + struct s8 payload); +struct lsp_message lsp_create_notification(struct s8 method, struct s8 payload); +struct lsp_message lsp_create_response(request_id id, bool ok, + struct s8 payload); + +void lsp_message_destroy(struct lsp_message *message); + // protocol functions -void lsp_initialize(struct lsp *lsp, struct initialize_params); -void lsp_did_open_document(struct lsp *lsp, struct text_doc_item document); -request_id lsp_hover(struct lsp *lsp, struct text_doc_position); +void lsp_send(struct lsp *lsp, struct lsp_message message); #endif diff --git a/src/dged/minibuffer.c b/src/dged/minibuffer.c index c74a900..59417ab 100644 --- a/src/dged/minibuffer.c +++ b/src/dged/minibuffer.c @@ -11,17 +11,28 @@ #include #include +struct prompt_key { + char key[16]; + char name[128]; +}; + static struct minibuffer { struct buffer *buffer; struct timespec expires; char prompt[128]; + struct prompt_key prompt_keys[16]; + uint32_t nprompt_keys; + struct command_ctx prompt_command_ctx; bool prompt_active; + struct window *prev_window; struct buffer *message_buffer; + struct timespec created_at; + } g_minibuffer = {0}; uint32_t minibuffer_draw_prompt(struct command_list *commands) { @@ -34,7 +45,31 @@ uint32_t minibuffer_draw_prompt(struct command_list *commands) { command_list_draw_text(commands, 0, 0, (uint8_t *)g_minibuffer.prompt, len); command_list_reset_color(commands); - return len; + uint32_t xoffset = len; + for (uint32_t i = 0; i < g_minibuffer.nprompt_keys; ++i) { + struct prompt_key *pk = &g_minibuffer.prompt_keys[i]; + + command_list_set_index_color_fg(commands, Color_Green); + size_t keylen = strlen(pk->key); + command_list_draw_text_copy(commands, xoffset, 0, (uint8_t *)pk->key, + keylen); + command_list_reset_color(commands); + + xoffset += keylen; + + command_list_draw_text(commands, xoffset, 0, (uint8_t *)" -> ", 4); + xoffset += 4; + + command_list_set_index_color_fg(commands, Color_Magenta); + size_t namelen = strlen(pk->name); + command_list_draw_text_copy(commands, xoffset, 0, (uint8_t *)pk->name, + namelen); + command_list_reset_color(commands); + + xoffset += namelen + 1; + } + + return xoffset; } static void minibuffer_abort_prompt_internal(bool clear); @@ -89,6 +124,27 @@ void update(struct buffer *buffer, void *userdata) { } } +static void print_message(const char *buff, size_t len) { + if (g_minibuffer.message_buffer == NULL) { + return; + } + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + uint64_t elapsed = (((uint64_t)ts.tv_sec * 1e9 + (uint64_t)ts.tv_nsec) - + ((uint64_t)g_minibuffer.created_at.tv_sec * 1e9 + + (uint64_t)g_minibuffer.created_at.tv_nsec)) / + 1e6; + + struct s8 timestamp = s8from_fmt("%d: ", elapsed); + struct location at = buffer_add(g_minibuffer.message_buffer, + buffer_end(g_minibuffer.message_buffer), + timestamp.s, timestamp.l); + s8delete(timestamp); + + buffer_add(g_minibuffer.message_buffer, at, (uint8_t *)buff, len); +} + void minibuffer_init(struct buffer *buffer, struct buffers *buffers) { if (g_minibuffer.buffer != NULL) { return; @@ -98,13 +154,17 @@ void minibuffer_init(struct buffer *buffer, struct buffers *buffers) { g_minibuffer.expires.tv_sec = 0; g_minibuffer.expires.tv_nsec = 0; g_minibuffer.prompt_active = false; + g_minibuffer.nprompt_keys = 0; buffer_add_update_hook(g_minibuffer.buffer, update, &g_minibuffer); g_minibuffer.message_buffer = buffers_add(buffers, buffer_create("*messages*")); + + clock_gettime(CLOCK_MONOTONIC, &g_minibuffer.created_at); } -void echo(uint32_t timeout, const char *fmt, va_list args) { +static void echo(uint32_t timeout, const char *fmt, va_list args, + bool message) { if (g_minibuffer.prompt_active || g_minibuffer.buffer == NULL) { return; } @@ -120,11 +180,8 @@ void echo(uint32_t timeout, const char *fmt, va_list args) { 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); + if (message) { + print_message(buff, nbytes > 2048 ? 2048 : nbytes); } } @@ -139,10 +196,7 @@ void message(const char *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); + print_message(buff, nbytes > 2048 ? 2048 : nbytes); } void minibuffer_destroy(void) { @@ -158,14 +212,28 @@ struct buffer *minibuffer_buffer(void) { return g_minibuffer.buffer; } void minibuffer_echo(const char *fmt, ...) { va_list args; va_start(args, fmt); - echo(1000, fmt, args); + echo(1000, fmt, args, true); va_end(args); } void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...) { va_list args; va_start(args, fmt); - echo(timeout, fmt, args); + echo(timeout, fmt, args, true); + va_end(args); +} + +void minibuffer_display(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + echo(1000, fmt, args, false); + va_end(args); +} + +void minibuffer_display_timeout(uint32_t timeout, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + echo(timeout, fmt, args, false); va_end(args); } @@ -226,6 +294,50 @@ int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, return 0; } +int32_t minibuffer_keymap_prompt(struct command_ctx command_ctx, + const char *fmt, struct keymap *keys, ...) { + if (g_minibuffer.buffer == NULL) { + return 1; + } + + for (uint32_t i = 0; i < keys->nbindings; ++i) { + struct prompt_key *pk = &g_minibuffer.prompt_keys[i]; + struct binding *bind = &keys->bindings[i]; + key_name(&bind->key, pk->key, 16); + + switch (bind->type) { + case BindingType_Command: + // FIXME: this is not awesome + memcpy(pk->name, "", 5); + pk->name[5] = '\0'; + break; + case BindingType_Keymap: + memcpy(pk->name, "", 5); + pk->name[5] = '\0'; + break; + case BindingType_DirectCommand: { + const char *n = bind->data.direct_command->name; + size_t l = strlen(n); + if (l > 0) { + l = l > 127 ? 127 : l; + memcpy(pk->name, n, l); + pk->name[l] = '\0'; + } + } break; + } + } + g_minibuffer.nprompt_keys = keys->nbindings; + + minibuffer_setup(command_ctx, NULL); + + va_list args; + va_start(args, keys); + minibuffer_set_prompt_internal(fmt, args); + va_end(args); + + return 0; +} + void minibuffer_set_prompt(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -243,6 +355,7 @@ static void minibuffer_abort_prompt_internal(bool clear) { } g_minibuffer.prompt_active = false; + g_minibuffer.nprompt_keys = 0; } void minibuffer_abort_prompt(void) { minibuffer_abort_prompt_internal(true); } diff --git a/src/dged/minibuffer.h b/src/dged/minibuffer.h index 0b98904..9f16dfc 100644 --- a/src/dged/minibuffer.h +++ b/src/dged/minibuffer.h @@ -50,6 +50,25 @@ void minibuffer_echo(const char *fmt, ...); */ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...); +/** + * Echo a message to the minibuffer without saving it in the message buffer. + * + * @param fmt Format string for the message. + * @param ... Format arguments. + */ +void minibuffer_display(const char *fmt, ...); + +/** + * Echo a message to the minibuffer that disappears after @p timeout + * without saving it in the message buffer. + * + * @param timeout The timeout in seconds after which the message should + * disappear. + * @param fmt Format string for the message. + * @param ... Format arguments. + */ +void minibuffer_display_timeout(uint32_t timeout, const char *fmt, ...); + /** * Prompt for user input in the minibuffer. * @@ -66,6 +85,9 @@ int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...); int32_t minibuffer_prompt_initial(struct command_ctx command_ctx, const char *initial, const char *fmt, ...); +int32_t minibuffer_keymap_prompt(struct command_ctx command_ctx, + const char *fmt, struct keymap *keys, ...); + void minibuffer_set_prompt(const char *fmt, ...); uint32_t minibuffer_draw_prompt(struct command_list *commands); diff --git a/src/dged/path.c b/src/dged/path.c index 735ef0c..d8422f0 100644 --- a/src/dged/path.c +++ b/src/dged/path.c @@ -1,4 +1,5 @@ #include "path.h" +#include "unistd.h" #include #include @@ -32,7 +33,37 @@ char *expanduser(const char *path) { } char *to_abspath(const char *path) { + if (strlen(path) > 0 && path[0] == '/') { + return strdup(path); + } + char *exp = expanduser(path); + if (access(path, F_OK) == -1) { + // anchor to cwd + const char *cwd = getcwd(NULL, 0); + if (cwd == NULL) { + return strdup(path); + } + + size_t cwdlen = strlen(cwd); + size_t pathlen = strlen(path); + size_t len = cwdlen + pathlen + (pathlen > 0 ? 2 : 1); + char *ret = calloc(len, sizeof(char)); + memcpy(ret, cwd, cwdlen); + + if (pathlen > 0) { + ret[cwdlen] = '/'; + memcpy(ret + cwdlen + 1, path, pathlen); + } + + ret[len - 1] = '\0'; + + free((void *)cwd); + free(exp); + + return ret; + } + char *p = realpath(path, NULL); if (p != NULL) { free(exp); diff --git a/src/dged/process-posix.c b/src/dged/process-posix.c index 94ceb5f..7cfb29b 100644 --- a/src/dged/process-posix.c +++ b/src/dged/process-posix.c @@ -117,7 +117,11 @@ struct process_create_result process_create(char *const command[], }; } -void process_destroy(struct process *p) { (void)p; } +void process_destroy(struct process *p) { + close(p->stdin); + close(p->stdout); + close(p->stderr); +} bool process_running(const struct process *p) { return waitpid(p->id, NULL, WNOHANG) == 0; diff --git a/src/dged/s8.c b/src/dged/s8.c index 71b6c6d..e641b11 100644 --- a/src/dged/s8.c +++ b/src/dged/s8.c @@ -1,17 +1,66 @@ #include "s8.h" +#include +#include +#include #include #include +struct s8 s8new(const char *s, uint32_t len) { + uint8_t *mem = calloc(len, 1); + memcpy(mem, s, len); + return (struct s8){ + .s = mem, + .l = len, + }; +} + +void s8delete(struct s8 s) { + if (s.s != NULL) { + free(s.s); + } + s.l = 0; + s.s = NULL; +} + +struct s8 s8from_fmt(const char *fmt, ...) { + va_list args; + + va_start(args, fmt); + ssize_t len = vsnprintf(NULL, 0, fmt, args); + va_end(args); + + if (len == -1) { + return (struct s8){ + .s = NULL, + .l = 0, + }; + } + + char *buf = calloc(len + 1, 1); + + va_list args2; + va_start(args2, fmt); + vsnprintf(buf, len + 1, fmt, args2); + va_end(args2); + + return (struct s8){ + .s = (uint8_t *)buf, + .l = len, + }; +} + bool s8eq(struct s8 s1, struct s8 s2) { return s1.l == s2.l && memcmp(s1.s, s2.s, s1.l) == 0; } int s8cmp(struct s8 s1, struct s8 s2) { if (s1.l < s2.l) { - return memcmp(s1.s, s2.s, s1.l); + int res = memcmp(s1.s, s2.s, s1.l); + return res == 0 ? -s2.s[s1.l] : res; } else if (s2.l < s1.l) { - return memcmp(s1.s, s2.s, s2.l); + int res = memcmp(s1.s, s2.s, s2.l); + return res == 0 ? s1.s[s2.l] : res; } return memcmp(s1.s, s2.s, s1.l); @@ -25,13 +74,22 @@ char *s8tocstr(struct s8 s) { } bool s8startswith(struct s8 s, struct s8 prefix) { - if (prefix.l > s.l) { + if (prefix.l == 0 || prefix.l > s.l) { return false; } return memcmp(s.s, prefix.s, prefix.l) == 0; } +bool s8endswith(struct s8 s, struct s8 suffix) { + if (suffix.l > s.l) { + return false; + } + + size_t ldiff = s.l - suffix.l; + return memcmp(s.s + ldiff, suffix.s, suffix.l) == 0; +} + struct s8 s8dup(struct s8 s) { struct s8 new = {0}; new.l = s.l; @@ -41,3 +99,15 @@ struct s8 s8dup(struct s8 s) { return new; } + +bool s8empty(struct s8 s) { return s.s == NULL || s.l == 0; } + +bool s8onlyws(struct s8 s) { + for (size_t i = 0; i < s.l; ++i) { + if (!isspace(s.s[i])) { + return false; + } + } + + return true; +} diff --git a/src/dged/s8.h b/src/dged/s8.h index 5a2504e..fe0f5b7 100644 --- a/src/dged/s8.h +++ b/src/dged/s8.h @@ -3,18 +3,26 @@ #include #include - -#define s8(s) ((struct s8){(uint8_t *)s, strlen(s)}) +#include struct s8 { uint8_t *s; uint32_t l; }; +#define s8(s) ((struct s8){(uint8_t *)s, strlen(s)}) + +struct s8 s8new(const char *s, uint32_t len); +void s8delete(struct s8 s); +struct s8 s8from_fmt(const char *fmt, ...); +char *s8tocstr(struct s8 s); + bool s8eq(struct s8 s1, struct s8 s2); int s8cmp(struct s8 s1, struct s8 s2); -char *s8tocstr(struct s8 s); bool s8startswith(struct s8 s, struct s8 prefix); +bool s8endswith(struct s8 s, struct s8 suffix); struct s8 s8dup(struct s8 s); +bool s8empty(struct s8 s); +bool s8onlyws(struct s8 s); #endif diff --git a/src/dged/syntax.c b/src/dged/syntax.c index 5d9aeaa..0ffa8d4 100644 --- a/src/dged/syntax.c +++ b/src/dged/syntax.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -14,7 +13,6 @@ #include "buffer.h" #include "display.h" -#include "hash.h" #include "minibuffer.h" #include "path.h" #include "s8.h" @@ -33,6 +31,7 @@ struct predicate { bool (*eval)(struct s8, uint32_t, struct s8[], struct s8, void *); uint32_t argc; + struct s8 argv[32]; void *data; @@ -332,9 +331,8 @@ static bool eval_predicates(struct highlight *h, struct text *text, #define match_cname(cname, capture) \ (s8eq(cname, s8(capture)) || s8startswith(cname, s8(capture "."))) -static void update_parser(struct buffer *buffer, void *userdata, - struct location origin, uint32_t width, - uint32_t height) { +static void update_parser(struct buffer *buffer, struct location origin, + uint32_t width, uint32_t height, void *userdata) { (void)width; @@ -406,7 +404,8 @@ static void update_parser(struct buffer *buffer, void *userdata, highlight = false; } else if (match_cname(cname, "label")) { highlight = false; - } else if (match_cname(cname, "type")) { + } else if (match_cname(cname, "type") || + match_cname(cname, "constructor")) { highlight = true; color = Color_Cyan; } else if (match_cname(cname, "variable")) { diff --git a/src/dged/text.c b/src/dged/text.c index 18ab04f..e609557 100644 --- a/src/dged/text.c +++ b/src/dged/text.c @@ -20,10 +20,13 @@ struct line { uint32_t nbytes; }; -struct text_property_entry { - struct location start; - struct location end; - struct text_property property; +typedef VEC(struct text_property) property_vec; + +#define MAX_LAYERS 16 + +struct property_layer { + layer_id id; + property_vec properties; }; struct text { @@ -31,7 +34,10 @@ struct text { struct line *lines; uint32_t nlines; uint32_t capacity; - VEC(struct text_property_entry) properties; + property_vec properties; + struct property_layer property_layers[MAX_LAYERS]; + uint32_t nproperty_layers; + layer_id current_layer_id; }; struct text *text_create(uint32_t initial_capacity) { @@ -39,6 +45,7 @@ struct text *text_create(uint32_t initial_capacity) { txt->lines = calloc(initial_capacity, sizeof(struct line)); txt->capacity = initial_capacity; txt->nlines = 0; + txt->current_layer_id = 1; VEC_INIT(&txt->properties, 32); @@ -48,6 +55,10 @@ struct text *text_create(uint32_t initial_capacity) { void text_destroy(struct text *text) { VEC_DESTROY(&text->properties); + for (size_t i = 0; i < text->nproperty_layers; ++i) { + VEC_DESTROY(&text->property_layers[i].properties); + } + for (uint32_t li = 0; li < text->nlines; ++li) { free(text->lines[li].data); text->lines[li].data = NULL; @@ -364,6 +375,15 @@ void text_for_each_line(struct text *text, uint32_t line, uint32_t nlines, } struct text_chunk text_get_line(struct text *text, uint32_t line) { + if (line >= text_num_lines(text)) { + return (struct text_chunk){ + .text = NULL, + .nbytes = 0, + .line = line, + .allocated = false, + }; + } + struct line *src_line = &text->lines[line]; return (struct text_chunk){ .text = src_line->data, @@ -453,15 +473,41 @@ struct text_chunk text_get_region(struct text *text, uint32_t start_line, }; } +static property_vec *find_property_layer(struct text *text, layer_id layer) { + if (layer == PropertyLayer_Default) { + return &text->properties; + } + + for (size_t i = 0; i < text->nproperty_layers; ++i) { + if (text->property_layers[i].id == layer) { + return &text->property_layers[i].properties; + } + } + + return NULL; +} + void text_add_property(struct text *text, uint32_t start_line, uint32_t start_offset, uint32_t end_line, uint32_t end_offset, struct text_property property) { - struct text_property_entry entry = { - .start = (struct location){.line = start_line, .col = start_offset}, - .end = (struct location){.line = end_line, .col = end_offset}, - .property = property, - }; - VEC_PUSH(&text->properties, entry); + text_add_property_to_layer(text, start_line, start_offset, end_line, + end_offset, property, PropertyLayer_Default); +} + +void text_add_property_to_layer(struct text *text, uint32_t start_line, + uint32_t start_offset, uint32_t end_line, + uint32_t end_offset, + struct text_property property, layer_id layer) { + + property_vec *target_vec = find_property_layer(text, layer); + + if (target_vec == NULL) { + return; + } + + property.start = (struct location){.line = start_line, .col = start_offset}; + property.end = (struct location){.line = end_line, .col = end_offset}; + VEC_PUSH(target_vec, property); } void text_get_properties(struct text *text, uint32_t line, uint32_t offset, @@ -469,17 +515,110 @@ void text_get_properties(struct text *text, uint32_t line, uint32_t offset, uint32_t max_nproperties, uint32_t *nproperties) { struct location location = {.line = line, .col = offset}; uint32_t nres = 0; - VEC_FOR_EACH(&text->properties, struct text_property_entry * prop) { + VEC_FOR_EACH(&text->properties, struct text_property * prop) { + if (nres == max_nproperties) { + break; + } + if (location_is_between(location, prop->start, prop->end)) { - properties[nres] = &prop->property; + properties[nres] = prop; ++nres; + } + } + for (size_t i = 0; i < text->nproperty_layers; ++i) { + property_vec *pv = &text->property_layers[i].properties; + VEC_FOR_EACH(pv, struct text_property * prop) { if (nres == max_nproperties) { break; } + + if (location_is_between(location, prop->start, prop->end)) { + properties[nres] = prop; + ++nres; + } + } + } + + *nproperties = nres; +} + +void text_get_properties_filtered(struct text *text, uint32_t line, + uint32_t offset, + struct text_property **properties, + uint32_t max_nproperties, + uint32_t *nproperties, layer_id layer) { + + struct location location = {.line = line, .col = offset}; + uint32_t nres = 0; + property_vec *pv = find_property_layer(text, layer); + + if (pv == NULL) { + return; + } + + VEC_FOR_EACH(pv, struct text_property * prop) { + if (nres == max_nproperties) { + break; + } + + if (location_is_between(location, prop->start, prop->end)) { + properties[nres] = prop; + ++nres; } } + *nproperties = nres; } void text_clear_properties(struct text *text) { VEC_CLEAR(&text->properties); } + +layer_id text_add_property_layer(struct text *text) { + if (text->nproperty_layers < MAX_LAYERS) { + + struct property_layer *layer = + &text->property_layers[text->nproperty_layers]; + layer->id = text->current_layer_id; + VEC_INIT(&layer->properties, 16); + + ++text->current_layer_id; + ++text->nproperty_layers; + + return layer->id; + } + + return (layer_id)-1; +} + +void text_remove_property_layer(struct text *text, layer_id layer) { + for (size_t i = 0; i < text->nproperty_layers; ++i) { + struct property_layer *l = &text->property_layers[i]; + if (layer == l->id) { + + // swap to last place + struct property_layer temp = + text->property_layers[text->nproperty_layers - 1]; + text->property_layers[text->nproperty_layers - 1] = + text->property_layers[i]; + text->property_layers[i] = temp; + + // drop from array + text->property_layers[text->nproperty_layers - 1].id = (layer_id)-1; + VEC_DESTROY( + &text->property_layers[text->nproperty_layers - 1].properties); + --text->nproperty_layers; + + return; + } + } +} + +void text_clear_property_layer(struct text *text, layer_id layer) { + property_vec *pv = find_property_layer(text, layer); + + if (pv == NULL) { + return; + } + + VEC_CLEAR(pv); +} diff --git a/src/dged/text.h b/src/dged/text.h index 505c86a..ec14650 100644 --- a/src/dged/text.h +++ b/src/dged/text.h @@ -62,9 +62,13 @@ struct text_property_colors { uint32_t fg; bool set_bg; uint32_t bg; + bool underline; + bool inverted; }; struct text_property { + struct location start; + struct location end; enum text_property_type type; union property_data { struct text_property_colors colors; @@ -72,14 +76,34 @@ struct text_property { } data; }; +typedef uint64_t layer_id; +enum layer_ids { + PropertyLayer_Default = 0, +}; + void text_add_property(struct text *text, uint32_t start_line, uint32_t start_offset, uint32_t end_line, uint32_t end_offset, struct text_property property); +void text_add_property_to_layer(struct text *text, uint32_t start_line, + uint32_t start_offset, uint32_t end_line, + uint32_t end_offset, + struct text_property property, layer_id layer); + +layer_id text_add_property_layer(struct text *text); +void text_remove_property_layer(struct text *text, layer_id layer); + void text_get_properties(struct text *text, uint32_t line, uint32_t offset, struct text_property **properties, uint32_t max_nproperties, uint32_t *nproperties); +void text_get_properties_filtered(struct text *text, uint32_t line, + uint32_t offset, + struct text_property **properties, + uint32_t max_nproperties, + uint32_t *nproperties, layer_id layer); + void text_clear_properties(struct text *text); +void text_clear_property_layer(struct text *text, layer_id layer); #endif diff --git a/src/dged/vec.h b/src/dged/vec.h index 1289a08..59d6bce 100644 --- a/src/dged/vec.h +++ b/src/dged/vec.h @@ -1,6 +1,7 @@ #ifndef _VEC_H #define _VEC_H +#include #include #define VEC(entry) \ @@ -12,7 +13,7 @@ } #define VEC_INIT(vec, initial_capacity) \ - (vec)->entries = malloc(sizeof((vec)->entries[0]) * initial_capacity); \ + (vec)->entries = calloc(initial_capacity, sizeof((vec)->entries[0])); \ (vec)->temp = calloc(1, sizeof((vec)->entries[0])); \ (vec)->capacity = initial_capacity; \ (vec)->nentries = 0; @@ -23,6 +24,7 @@ free((vec)->temp); \ free((vec)->entries); \ (vec)->entries = NULL; \ + (vec)->temp = NULL; \ (vec)->capacity = 0; \ (vec)->nentries = 0; @@ -69,6 +71,13 @@ keep && idx != size; keep = !keep, idx++) \ for (var = (vec)->entries + idx; keep; keep = !keep) +#define VEC_FOR_EACH_REVERSE(vec, var) VEC_FOR_EACH_INDEXED_REVERSE(vec, var, i) + +#define VEC_FOR_EACH_INDEXED_REVERSE(vec, var, idx) \ + for (uint32_t keep = 1, idx = 0, size = (vec)->nentries; \ + keep && idx != size; keep = !keep, idx++) \ + for (var = &(vec)->entries[size - idx - 1]; keep; keep = !keep) + #define VEC_SIZE(vec) (vec)->nentries #define VEC_CAPACITY(vec) (vec)->capacity #define VEC_ENTRIES(vec) (vec)->entries diff --git a/src/dged/window.c b/src/dged/window.c index 7ad4794..82b90d5 100644 --- a/src/dged/window.c +++ b/src/dged/window.c @@ -55,8 +55,6 @@ static void buffer_removed(struct buffer *buffer, void *userdata) { if (window_buffer(w) == buffer) { if (window_has_prev_buffer_view(w)) { window_set_buffer(w, window_prev_buffer_view(w)->buffer); - buffer_view_destroy(&w->prev_buffer_view); - w->has_prev_buffer_view = false; } else { struct buffers *buffers = (struct buffers *)userdata; struct buffer *b = buffers_find(buffers, "*messages*"); @@ -71,9 +69,10 @@ static void buffer_removed(struct buffer *buffer, void *userdata) { window_set_buffer(w, b); } } - buffer_view_destroy(&w->prev_buffer_view); - w->has_prev_buffer_view = false; } + + buffer_view_destroy(&w->prev_buffer_view); + w->has_prev_buffer_view = false; } BINTREE_NEXT(n); @@ -198,7 +197,36 @@ void windows_resize(uint32_t height, uint32_t width) { window_tree_resize(BINTREE_ROOT(&g_windows.windows), height - 1, width); } -void windows_update(void *(*frame_alloc)(size_t), float frame_time) { +bool windows_update(void *(*frame_alloc)(size_t), float frame_time) { + bool needs_render = false; + struct window_node *n = BINTREE_ROOT(&g_windows.windows); + BINTREE_FIRST(n); + uint32_t window_id = 0; + while (n != NULL) { + struct window *w = &BINTREE_VALUE(n); + if (w->type == Window_Buffer) { + char name[16] = {0}; + snprintf(name, 15, "bufview-%s", w->buffer_view.buffer->name); + w->commands = command_list_create(w->height * w->width, frame_alloc, w->x, + w->y, 4, name); + + struct buffer_view_update_params p = { + .commands = w->commands, + .window_id = window_id, + .frame_time = frame_time, + .width = w->width, + .height = w->height, + .window_x = w->x, + .window_y = w->y, + .frame_alloc = frame_alloc, + }; + + needs_render |= buffer_view_update(&w->buffer_view, &p); + ++window_id; + } + + BINTREE_NEXT(n); + } struct window *w = &g_minibuffer_window; w->x = 0; @@ -224,7 +252,7 @@ void windows_update(void *(*frame_alloc)(size_t), float frame_time) { .frame_alloc = frame_alloc, }; - buffer_view_update(&w->buffer_view, &p); + needs_render |= buffer_view_update(&w->buffer_view, &p); command_list_draw_command_list(w->commands, inner_commands); if (g_popup_visible) { @@ -239,9 +267,12 @@ void windows_update(void *(*frame_alloc)(size_t), float frame_time) { struct window *rw = root_window(); uint32_t w_x = w->x; uint32_t w_y = w->y; - uint32_t width = w_x + w->width > rw->width ? rw->width - w_x : w->width; - uint32_t height = - w_y + w->height > rw->height ? rw->height - w_y : w->height; + uint32_t width = w_x + w->width > rw->width + ? (rw->width >= w_x ? rw->width - w_x : 0) + : w->width; + uint32_t height = w_y + w->height > rw->height + ? (rw->height >= w_y ? rw->height - w_y : 0) + : w->height; // is there space for padding? if (w_x > 1 && w_x + width + hpadding <= rw->width) { @@ -317,38 +348,11 @@ void windows_update(void *(*frame_alloc)(size_t), float frame_time) { .frame_alloc = frame_alloc, }; - buffer_view_update(&w->buffer_view, &p); + needs_render |= buffer_view_update(&w->buffer_view, &p); command_list_draw_command_list(w->commands, inner); } - struct window_node *n = BINTREE_ROOT(&g_windows.windows); - BINTREE_FIRST(n); - uint32_t window_id = 0; - while (n != NULL) { - struct window *w = &BINTREE_VALUE(n); - if (w->type == Window_Buffer) { - char name[16] = {0}; - snprintf(name, 15, "bufview-%s", w->buffer_view.buffer->name); - w->commands = command_list_create(w->height * w->width, frame_alloc, w->x, - w->y, 4, name); - - struct buffer_view_update_params p = { - .commands = w->commands, - .window_id = window_id, - .frame_time = frame_time, - .width = w->width, - .height = w->height, - .window_x = w->x, - .window_y = w->y, - .frame_alloc = frame_alloc, - }; - - buffer_view_update(&w->buffer_view, &p); - ++window_id; - } - - BINTREE_NEXT(n); - } + return needs_render; } void windows_render(struct display *display) { diff --git a/src/dged/window.h b/src/dged/window.h index 7738e16..d646d78 100644 --- a/src/dged/window.h +++ b/src/dged/window.h @@ -27,7 +27,7 @@ void windows_init(uint32_t height, uint32_t width, void windows_destroy(void); void windows_resize(uint32_t height, uint32_t width); -void windows_update(void *(*frame_alloc)(size_t), float frame_time); +bool windows_update(void *(*frame_alloc)(size_t), float frame_time); void windows_render(struct display *display); struct window *root_window(void); -- cgit v1.2.3