summaryrefslogtreecommitdiff
path: root/src/dged
diff options
context:
space:
mode:
Diffstat (limited to 'src/dged')
-rw-r--r--src/dged/allocator.c2
-rw-r--r--src/dged/allocator.h1
-rw-r--r--src/dged/binding.h3
-rw-r--r--src/dged/buffer.c279
-rw-r--r--src/dged/buffer.h174
-rw-r--r--src/dged/buffer_view.c170
-rw-r--r--src/dged/buffer_view.h17
-rw-r--r--src/dged/buffers.c7
-rw-r--r--src/dged/bufread.c151
-rw-r--r--src/dged/bufread.h13
-rw-r--r--src/dged/command.h2
-rw-r--r--src/dged/display.c7
-rw-r--r--src/dged/display.h5
-rw-r--r--src/dged/hook.h84
-rw-r--r--src/dged/json.c611
-rw-r--r--src/dged/json.h122
-rw-r--r--src/dged/jsonrpc.c118
-rw-r--r--src/dged/jsonrpc.h58
-rw-r--r--src/dged/keyboard.c2
-rw-r--r--src/dged/lsp.c386
-rw-r--r--src/dged/lsp.h87
-rw-r--r--src/dged/minibuffer.c139
-rw-r--r--src/dged/minibuffer.h22
-rw-r--r--src/dged/path.c31
-rw-r--r--src/dged/process-posix.c6
-rw-r--r--src/dged/s8.c76
-rw-r--r--src/dged/s8.h14
-rw-r--r--src/dged/syntax.c11
-rw-r--r--src/dged/text.c165
-rw-r--r--src/dged/text.h24
-rw-r--r--src/dged/vec.h11
-rw-r--r--src/dged/window.c80
-rw-r--r--src/dged/window.h2
33 files changed, 2410 insertions, 470 deletions
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 <stdlib.h>
+
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 <stddef.h>
#include <stdint.h>
-#include <stdlib.h>
/**
* 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 <time.h>
#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.
*
@@ -433,8 +447,46 @@ void buffer_add_text_property(struct buffer *buffer, struct location start,
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,
@@ -632,6 +699,29 @@ 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.
*
* @param buffer The buffer.
@@ -690,6 +780,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.
*/
struct buffer_render_params {
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 <stddef.h>
+#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 <stdbool.h>
#include <stdlib.h>
@@ -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 <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+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 <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+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
@@ -198,6 +198,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.
*
* The following draw commands will have their formatting reset to the default.
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 <stdint.h>
+
+#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 <stddef.h>
#include <stdio.h>
-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(&current->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(&current->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 <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+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 <stdint.h>
+
+#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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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 <stddef.h>
+
+#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 <stdlib.h>
#include <string.h>
+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, "<cmd>", 5);
+ pk->name[5] = '\0';
+ break;
+ case BindingType_Keymap:
+ memcpy(pk->name, "<map>", 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
@@ -51,6 +51,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.
*
* This will move focus to the minibuffer and wait for user input, with the
@@ -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 <limits.h>
#include <stdint.h>
@@ -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 <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+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 <stdbool.h>
#include <stdint.h>
-
-#define s8(s) ((struct s8){(uint8_t *)s, strlen(s)})
+#include <string.h>
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 <ctype.h>
#include <dlfcn.h>
-#include <errno.h>
#include <fcntl.h>
#include <regex.h>
#include <string.h>
@@ -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 <stdint.h>
#include <stdlib.h>
#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);