summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
-rw-r--r--src/main/bindings.c25
-rw-r--r--src/main/bindings.h9
-rw-r--r--src/main/cmds.c149
-rw-r--r--src/main/completion.c774
-rw-r--r--src/main/completion.h140
-rw-r--r--src/main/completion/buffer.c148
-rw-r--r--src/main/completion/buffer.h18
-rw-r--r--src/main/completion/command.c151
-rw-r--r--src/main/completion/command.h17
-rw-r--r--src/main/completion/path.c268
-rw-r--r--src/main/completion/path.h14
-rw-r--r--src/main/frame-hooks.c27
-rw-r--r--src/main/frame-hooks.h13
-rw-r--r--src/main/lsp.c917
-rw-r--r--src/main/lsp.h44
-rw-r--r--src/main/lsp/actions.c129
-rw-r--r--src/main/lsp/actions.h10
-rw-r--r--src/main/lsp/choice-buffer.c201
-rw-r--r--src/main/lsp/choice-buffer.h23
-rw-r--r--src/main/lsp/completion.c405
-rw-r--r--src/main/lsp/completion.h18
-rw-r--r--src/main/lsp/diagnostics.c386
-rw-r--r--src/main/lsp/diagnostics.h26
-rw-r--r--src/main/lsp/format.c149
-rw-r--r--src/main/lsp/format.h18
-rw-r--r--src/main/lsp/goto.c297
-rw-r--r--src/main/lsp/goto.h23
-rw-r--r--src/main/lsp/help.c101
-rw-r--r--src/main/lsp/help.h16
-rw-r--r--src/main/lsp/references.c248
-rw-r--r--src/main/lsp/references.h19
-rw-r--r--src/main/lsp/rename.c61
-rw-r--r--src/main/lsp/rename.h16
-rw-r--r--src/main/lsp/types.c1081
-rw-r--r--src/main/lsp/types.h385
-rw-r--r--src/main/main.c79
69 files changed, 8100 insertions, 1185 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);
diff --git a/src/main/bindings.c b/src/main/bindings.c
index 889c32b..db6c924 100644
--- a/src/main/bindings.c
+++ b/src/main/bindings.c
@@ -8,6 +8,11 @@
static struct keymap g_global_keymap, g_ctrlx_map, g_windows_keymap,
g_buffer_default_keymap;
+HOOK_IMPL(buffer_keymaps, buffer_keymaps_cb);
+
+static buffer_keymaps_hook_vec g_buffer_keymaps_hooks;
+uint32_t g_buffer_keymaps_hook_id = 0;
+
struct buffer_keymap {
buffer_keymap_id id;
struct buffer *buffer;
@@ -17,6 +22,8 @@ struct buffer_keymap {
static VEC(struct buffer_keymap) g_buffer_keymaps;
static buffer_keymap_id g_current_keymap_id;
+struct keymap *buffer_default_keymap(void) { return &g_buffer_default_keymap; }
+
void set_default_buffer_bindings(struct keymap *keymap) {
struct binding buffer_bindings[] = {
BINDING(Ctrl, 'B', "backward-char"),
@@ -144,6 +151,8 @@ void init_bindings(void) {
VEC_INIT(&g_buffer_keymaps, 32);
g_current_keymap_id = 0;
+ VEC_INIT(&g_buffer_keymaps_hooks, 16);
+
/* Minibuffer binds.
* This map is actually never removed so forget about the id.
*/
@@ -204,6 +213,14 @@ uint32_t buffer_keymaps(struct buffer *buffer, struct keymap *keymaps[],
}
}
+ // hooks
+ VEC_FOR_EACH(&g_buffer_keymaps_hooks, struct buffer_keymaps_hook * hook) {
+ if (nkeymaps < max_nkeymaps) {
+ nkeymaps += hook->callback(buffer, &keymaps[nkeymaps],
+ max_nkeymaps - nkeymaps, hook->userdata);
+ }
+ }
+
return nkeymaps;
}
@@ -219,3 +236,11 @@ void destroy_bindings(void) {
VEC_DESTROY(&g_buffer_keymaps);
}
+
+uint32_t buffer_add_keymaps_hook(buffer_keymaps_cb callback, void *userdata) {
+ return insert_buffer_keymaps_hook(
+ &g_buffer_keymaps_hooks, &g_buffer_keymaps_hook_id, callback, userdata);
+}
+void buffer_remove_keymaps_hook(uint32_t id, remove_hook_cb callback) {
+ remove_buffer_keymaps_hook(&g_buffer_keymaps_hooks, id, callback);
+}
diff --git a/src/main/bindings.h b/src/main/bindings.h
index 96f20fd..74f43e3 100644
--- a/src/main/bindings.h
+++ b/src/main/bindings.h
@@ -1,15 +1,24 @@
#include <stdint.h>
+#include "dged/hook.h"
+
struct keymap;
struct buffer;
struct binding;
void init_bindings(void);
+struct keymap *buffer_default_keymap(void);
+
typedef uint64_t buffer_keymap_id;
buffer_keymap_id buffer_add_keymap(struct buffer *buffer, struct keymap keymap);
void buffer_remove_keymap(buffer_keymap_id id);
uint32_t buffer_keymaps(struct buffer *buffer, struct keymap *keymaps[],
uint32_t max_nkeymaps);
+typedef uint32_t (*buffer_keymaps_cb)(struct buffer *, struct keymap **,
+ uint32_t, void *);
+uint32_t buffer_add_keymaps_hook(buffer_keymaps_cb callback, void *userdata);
+void buffer_remove_keymaps_hook(uint32_t id, remove_hook_cb callback);
+
void destroy_bindings(void);
diff --git a/src/main/cmds.c b/src/main/cmds.c
index fdd1d87..7d63661 100644
--- a/src/main/cmds.c
+++ b/src/main/cmds.c
@@ -5,6 +5,7 @@
#include <sys/stat.h>
#include "dged/binding.h"
+
#include "dged/buffer.h"
#include "dged/buffer_view.h"
#include "dged/buffers.h"
@@ -18,6 +19,9 @@
#include "bindings.h"
#include "completion.h"
+#include "completion/buffer.h"
+#include "completion/command.h"
+#include "completion/path.h"
#include "search-replace.h"
static void (*g_terminate_cb)(void) = NULL;
@@ -32,7 +36,7 @@ static int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) {
disable_completion(minibuffer_buffer());
minibuffer_abort_prompt();
buffer_view_clear_mark(window_buffer_view(ctx.active_window));
- minibuffer_echo_timeout(4, "💣 aborted");
+ minibuffer_display_timeout(4, "💣 aborted");
return 0;
}
@@ -63,17 +67,13 @@ static int32_t write_file(struct command_ctx ctx, int argc,
const char *argv[]) {
const char *pth = NULL;
if (argc == 0) {
- struct completion_provider providers[] = {path_provider()};
- enable_completion(minibuffer_buffer(),
- ((struct completion_trigger){
- .kind = CompletionTrigger_Input,
- .data.input =
- (struct completion_trigger_input){
- .nchars = 0, .trigger_initially = false}}),
- providers, 1, write_file_comp_inserted);
+ struct completion_provider providers[] = {
+ create_path_provider(write_file_comp_inserted)};
+ add_completion_providers(minibuffer_buffer(), providers, 1);
return minibuffer_prompt(ctx, "write to file: ");
}
+ disable_completion(minibuffer_buffer());
pth = argv[0];
buffer_set_filename(window_buffer(ctx.active_window), pth);
buffer_to_file(window_buffer(ctx.active_window));
@@ -81,18 +81,16 @@ static int32_t write_file(struct command_ctx ctx, int argc,
return 0;
}
-static void run_interactive_comp_inserted(void) { minibuffer_execute(); }
+static void run_interactive_comp_inserted(struct command *cmd) {
+ (void)cmd;
+ minibuffer_execute();
+}
int32_t run_interactive(struct command_ctx ctx, int argc, const char *argv[]) {
if (argc == 0) {
- struct completion_provider providers[] = {commands_provider()};
- enable_completion(minibuffer_buffer(),
- ((struct completion_trigger){
- .kind = CompletionTrigger_Input,
- .data.input =
- (struct completion_trigger_input){
- .nchars = 0, .trigger_initially = false}}),
- providers, 1, run_interactive_comp_inserted);
+ struct completion_provider providers[] = {
+ create_commands_provider(ctx.commands, run_interactive_comp_inserted)};
+ add_completion_providers(minibuffer_buffer(), providers, 1);
return minibuffer_prompt(ctx, "execute: ");
}
@@ -134,19 +132,18 @@ int32_t do_switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) {
COMMAND_FN("do-switch-buffer", do_switch_buffer, do_switch_buffer, NULL)
-static void switch_buffer_comp_inserted(void) { minibuffer_execute(); }
+static void switch_buffer_comp_inserted(struct buffer *buffer) {
+ // TODO: do useful stuff with buffer here
+ (void)buffer;
+ minibuffer_execute();
+}
int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) {
if (argc == 0) {
minibuffer_clear();
- struct completion_provider providers[] = {buffer_provider()};
- enable_completion(minibuffer_buffer(),
- ((struct completion_trigger){
- .kind = CompletionTrigger_Input,
- .data.input =
- (struct completion_trigger_input){
- .nchars = 0, .trigger_initially = false}}),
- providers, 1, switch_buffer_comp_inserted);
+ struct completion_provider providers[] = {
+ create_buffer_provider(ctx.buffers, switch_buffer_comp_inserted)};
+ add_completion_providers(minibuffer_buffer(), providers, 1);
ctx.self = &do_switch_buffer_command;
if (window_has_prev_buffer_view(ctx.active_window)) {
@@ -184,19 +181,18 @@ int32_t do_kill_buffer(struct command_ctx ctx, int argc, const char *argv[]) {
COMMAND_FN("do-kill-buffer", do_kill_buffer, do_kill_buffer, NULL)
-static void kill_buffer_comp_inserted(void) { minibuffer_execute(); }
+static void kill_buffer_comp_inserted(struct buffer *buffer) {
+ // TODO: do something with buffer
+ (void)buffer;
+ minibuffer_execute();
+}
int32_t kill_buffer(struct command_ctx ctx, int argc, const char *argv[]) {
if (argc == 0) {
minibuffer_clear();
- struct completion_provider providers[] = {buffer_provider()};
- enable_completion(minibuffer_buffer(),
- ((struct completion_trigger){
- .kind = CompletionTrigger_Input,
- .data.input =
- (struct completion_trigger_input){
- .nchars = 0, .trigger_initially = false}}),
- providers, 1, kill_buffer_comp_inserted);
+ struct completion_provider providers[] = {
+ create_buffer_provider(ctx.buffers, kill_buffer_comp_inserted)};
+ add_completion_providers(minibuffer_buffer(), providers, 1);
ctx.self = &do_kill_buffer_command;
return minibuffer_prompt(ctx, "kill buffer (default %s): ",
@@ -254,8 +250,11 @@ void buffer_to_list_line(struct buffer *buffer, void *userdata) {
struct buffer *listbuf = (struct buffer *)userdata;
const char *path = buffer->filename != NULL ? buffer->filename : "<no-file>";
+ const char *modified =
+ buffer->filename != NULL && buffer->modified ? "*" : "";
char buf[1024];
- size_t written = snprintf(buf, 1024, "%-24s %s", buffer->name, path);
+ size_t written =
+ snprintf(buf, 1024, "%-24s %s%s", buffer->name, path, modified);
if (written > 0) {
struct location begin = buffer_end(listbuf);
@@ -275,9 +274,9 @@ void buffer_to_list_line(struct buffer *buffer, void *userdata) {
size_t pathlen = strlen(path);
uint32_t nchars_path = utf8_nchars((uint8_t *)path, pathlen);
buffer_add_text_property(
- listbuf, (struct location){.line = begin.line, .col = begin.col + 24},
+ listbuf, (struct location){.line = begin.line, .col = begin.col + 25},
(struct location){.line = begin.line,
- .col = begin.col + 24 + nchars_path},
+ .col = begin.col + 25 + nchars_path},
(struct text_property){.type = TextProperty_Colors,
.data.colors = (struct text_property_colors){
.set_bg = false,
@@ -294,8 +293,7 @@ void buffer_to_list_line(struct buffer *buffer, void *userdata) {
}
}
-int32_t buflist_visit_cmd(struct command_ctx ctx, int argc,
- const char *argv[]) {
+int32_t buflist_visit_cmd(struct command_ctx ctx, int argc, const char **argv) {
(void)argc;
(void)argv;
@@ -321,7 +319,6 @@ int32_t buflist_close_cmd(struct command_ctx ctx, int argc,
const char *argv[]) {
return execute_command(&do_switch_buffer_command, ctx.commands,
ctx.active_window, ctx.buffers, argc, argv);
- return 0;
}
void buflist_refresh(struct buffer *buffer, void *userdata) {
@@ -371,6 +368,35 @@ int32_t buflist_kill_cmd(struct command_ctx ctx, int argc, const char *argv[]) {
return 0;
}
+int32_t buflist_save_cmd(struct command_ctx ctx, int argc, const char *argv[]) {
+ (void)argc;
+ (void)argv;
+
+ struct window *w = ctx.active_window;
+
+ struct buffer_view *bv = window_buffer_view(w);
+ struct text_chunk text = buffer_line(bv->buffer, bv->dot.line);
+
+ char *end = (char *)memchr(text.text, ' ', text.nbytes);
+
+ if (end != NULL) {
+ uint32_t len = end - (char *)text.text;
+ char *bufname = (char *)malloc(len + 1);
+ strncpy(bufname, (const char *)text.text, len);
+ bufname[len] = '\0';
+
+ struct buffer *buffer = buffers_find(ctx.buffers, bufname);
+ if (buffer != NULL) {
+ buffer_to_file(buffer);
+ }
+ free(bufname);
+ execute_command(&buflist_refresh_command, ctx.commands, ctx.active_window,
+ ctx.buffers, 0, NULL);
+ }
+
+ return 0;
+}
+
int32_t buffer_list(struct command_ctx ctx, int argc, const char *argv[]) {
(void)argc;
(void)argv;
@@ -401,10 +427,16 @@ int32_t buffer_list(struct command_ctx ctx, int argc, const char *argv[]) {
.fn = buflist_close_cmd,
};
+ static struct command buflist_save = {
+ .name = "buflist-save",
+ .fn = buflist_save_cmd,
+ };
+
struct binding bindings[] = {
ANONYMOUS_BINDING(ENTER, &buflist_visit),
ANONYMOUS_BINDING(None, 'k', &buflist_kill),
ANONYMOUS_BINDING(None, 'q', &buflist_close),
+ ANONYMOUS_BINDING(None, 's', &buflist_save),
ANONYMOUS_BINDING(None, 'g', &buflist_refresh_command),
};
struct keymap km = keymap_create("buflist", 8);
@@ -456,15 +488,16 @@ static int32_t open_file(struct buffers *buffers, struct window *active_window,
int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) {
if (argc == 0) {
minibuffer_clear();
- struct completion_provider providers[] = {path_provider()};
- enable_completion(minibuffer_buffer(),
- ((struct completion_trigger){
- .kind = CompletionTrigger_Input,
- .data.input =
- (struct completion_trigger_input){
- .nchars = 0, .trigger_initially = true}}),
- providers, 1, find_file_comp_inserted);
- return minibuffer_prompt(ctx, "find file: ");
+ struct completion_provider providers[] = {
+ create_path_provider(find_file_comp_inserted)};
+ add_completion_providers(minibuffer_buffer(), providers, 1);
+
+ int32_t r = minibuffer_prompt(ctx, "find file: ");
+
+ // Trigger directly
+ complete(minibuffer_buffer(), buffer_end(minibuffer_buffer()));
+
+ return r;
}
disable_completion(minibuffer_buffer());
@@ -487,14 +520,9 @@ int32_t find_file_relative(struct command_ctx ctx, int argc,
size_t dirlen = strlen(dir);
if (argc == 0) {
minibuffer_clear();
- struct completion_provider providers[] = {path_provider()};
- enable_completion(minibuffer_buffer(),
- ((struct completion_trigger){
- .kind = CompletionTrigger_Input,
- .data.input =
- (struct completion_trigger_input){
- .nchars = 0, .trigger_initially = true}}),
- providers, 1, find_file_comp_inserted);
+ struct completion_provider providers[] = {
+ create_path_provider(find_file_comp_inserted)};
+ add_completion_providers(minibuffer_buffer(), providers, 1);
ctx.self = &find_file_command;
@@ -505,6 +533,9 @@ int32_t find_file_relative(struct command_ctx ctx, int argc,
minibuffer_prompt_initial(ctx, dir_with_slash, "find file: ");
free(filename);
free(dir_with_slash);
+
+ complete(minibuffer_buffer(), buffer_end(minibuffer_buffer()));
+
return 0;
}
diff --git a/src/main/completion.c b/src/main/completion.c
index 38d75ab..d777408 100644
--- a/src/main/completion.c
+++ b/src/main/completion.c
@@ -12,91 +12,75 @@
#include "dged/buffer.h"
#include "dged/buffer_view.h"
#include "dged/buffers.h"
+#include "dged/display.h"
#include "dged/minibuffer.h"
#include "dged/path.h"
#include "dged/window.h"
#include "bindings.h"
+#include "frame-hooks.h"
-struct active_completion_ctx {
- struct completion_trigger trigger;
- uint32_t trigger_current_nchars;
- struct completion_provider *providers;
- uint32_t nproviders;
- insert_cb on_completion_inserted;
-};
-
-struct completion_state {
- struct completion completions[50];
- uint32_t ncompletions;
- uint32_t current_completion;
- bool active;
- buffer_keymap_id keymap_id;
- bool keymap_active;
- struct active_completion_ctx *ctx;
-} g_state = {0};
-
-static struct buffer *g_target_buffer = NULL;
-
-static void hide_completion(void);
-
-static bool is_space(const struct codepoint *c) {
- // TODO: utf8 whitespace and other whitespace
- return c->codepoint == ' ';
-}
-
-static uint32_t complete_path(struct completion_context ctx, void *userdata);
-static struct completion_provider g_path_provider = {
- .name = "path",
- .complete = complete_path,
- .userdata = NULL,
-};
+struct buffer_completion {
+ struct buffer *buffer;
+ uint32_t insert_hook_id;
+ uint32_t remove_hook_id;
-static uint32_t complete_buffers(struct completion_context ctx, void *userdata);
-static struct completion_provider g_buffer_provider = {
- .name = "buffers",
- .complete = complete_buffers,
- .userdata = NULL,
+ VEC(struct completion_provider) providers;
};
-static uint32_t complete_commands(struct completion_context ctx,
- void *userdata);
-static struct completion_provider g_commands_provider = {
- .name = "commands",
- .complete = complete_commands,
- .userdata = NULL,
+struct completion_item {
+ struct region area;
+ struct completion completion;
};
-struct completion_provider path_provider(void) { return g_path_provider; }
+static struct completion_state {
+ VEC(struct buffer_completion) buffer_completions;
+ VEC(struct completion_item) completions;
+ uint64_t completion_index;
+ struct buffer *completions_buffer;
+ buffer_keymap_id keymap_id;
+ struct buffer *target;
+ layer_id highlight_current_layer;
+ bool insert_in_progress;
+ bool paused;
+} g_state;
-struct completion_provider buffer_provider(void) { return g_buffer_provider; }
+static struct region active_completion_region(struct completion_state *state) {
+ struct region reg =
+ region_new((struct location){0, 0}, (struct location){0, 0});
+ if (state->completion_index < VEC_SIZE(&state->completions)) {
+ reg = VEC_ENTRIES(&state->completions)[state->completion_index].area;
+ }
-struct completion_provider commands_provider(void) {
- return g_commands_provider;
+ return reg;
}
-struct active_completion {
- struct buffer *buffer;
- uint32_t insert_hook_id;
- uint32_t remove_hook_id;
-};
-
-VEC(struct active_completion) g_active_completions;
-
static int32_t goto_next_completion(struct command_ctx ctx, int argc,
const char *argv[]) {
(void)ctx;
(void)argc;
(void)argv;
- if (g_state.current_completion < g_state.ncompletions - 1) {
- ++g_state.current_completion;
+ if (!completion_active()) {
+ return 0;
+ }
+
+ if (VEC_EMPTY(&g_state.completions)) {
+ g_state.completion_index = 0;
+ return 0;
+ }
+
+ size_t ncompletions = VEC_SIZE(&g_state.completions);
+ if (g_state.completion_index >= ncompletions - 1) {
+ g_state.completion_index = ncompletions - 1;
+ return 0;
}
+ ++g_state.completion_index;
+
if (completion_active()) {
- buffer_view_goto(
- window_buffer_view(popup_window()),
- ((struct location){.line = g_state.current_completion, .col = 0}));
+ buffer_view_goto(window_buffer_view(popup_window()),
+ active_completion_region(&g_state).begin);
}
return 0;
@@ -108,14 +92,19 @@ static int32_t goto_prev_completion(struct command_ctx ctx, int argc,
(void)argc;
(void)argv;
- if (g_state.current_completion > 0) {
- --g_state.current_completion;
+ if (!completion_active()) {
+ return 0;
}
+ if (g_state.completion_index == 0) {
+ return 0;
+ }
+
+ --g_state.completion_index;
+
if (completion_active()) {
- buffer_view_goto(
- window_buffer_view(popup_window()),
- ((struct location){.line = g_state.current_completion, .col = 0}));
+ buffer_view_goto(window_buffer_view(popup_window()),
+ active_completion_region(&g_state).begin);
}
return 0;
@@ -127,524 +116,325 @@ static int32_t insert_completion(struct command_ctx ctx, int argc,
(void)argc;
(void)argv;
- // is it in the popup?
- struct completion *comp = &g_state.completions[g_state.current_completion];
- bool done = comp->complete;
- const char *ins = comp->insert;
- size_t inslen = strlen(ins);
- buffer_view_add(window_buffer_view(windows_get_active()), (uint8_t *)ins,
- inslen);
+ if (!completion_active()) {
+ return 0;
+ }
- if (done) {
- g_state.ctx->on_completion_inserted();
- abort_completion();
+ struct buffer_view *bv = window_buffer_view(popup_window());
+ struct window *target_window = windows_get_active();
+ struct buffer_view *target = window_buffer_view(target_window);
+ VEC_FOR_EACH(&g_state.completions, struct completion_item * item) {
+ if (region_is_inside(item->area, bv->dot)) {
+ g_state.insert_in_progress = true;
+ item->completion.selected(item->completion.data, target);
+ g_state.insert_in_progress = false;
+ return 0;
+ }
}
return 0;
}
-static void clear_completions(void) {
- for (uint32_t ci = 0; ci < g_state.ncompletions; ++ci) {
- free((void *)g_state.completions[ci].display);
- free((void *)g_state.completions[ci].insert);
- g_state.completions[ci].display = NULL;
- g_state.completions[ci].insert = NULL;
- g_state.completions[ci].complete = false;
- }
- g_state.ncompletions = 0;
-}
-
COMMAND_FN("next-completion", next_completion, goto_next_completion, NULL)
COMMAND_FN("prev-completion", prev_completion, goto_prev_completion, NULL)
COMMAND_FN("insert-completion", insert_completion, insert_completion, NULL)
-static void update_completions(struct buffer *buffer,
- struct active_completion_ctx *ctx,
- struct location location) {
- clear_completions();
- for (uint32_t pi = 0; pi < ctx->nproviders; ++pi) {
- struct completion_provider *provider = &ctx->providers[pi];
-
- struct completion_context comp_ctx = (struct completion_context){
- .buffer = buffer,
- .location = location,
- .max_ncompletions = 50 - g_state.ncompletions,
- .completions = g_state.completions,
- };
-
- g_state.ncompletions += provider->complete(comp_ctx, provider->userdata);
- }
-
- window_set_buffer_e(popup_window(), g_target_buffer, false, false);
- struct buffer_view *v = window_buffer_view(popup_window());
-
- size_t max_width = 0;
- uint32_t prev_selection = g_state.current_completion;
-
- buffer_clear(v->buffer);
- buffer_view_goto(v, (struct location){.line = 0, .col = 0});
- if (g_state.ncompletions > 0) {
- for (uint32_t compi = 0; compi < g_state.ncompletions; ++compi) {
- const char *disp = g_state.completions[compi].display;
- size_t width = strlen(disp);
- if (width > max_width) {
- max_width = width;
- }
- buffer_view_add(v, (uint8_t *)disp, width);
- buffer_view_add(v, (uint8_t *)"\n", 1);
+static void clear_completions(struct completion_state *state) {
+ buffer_clear(state->completions_buffer);
+ VEC_FOR_EACH(&state->completions, struct completion_item * item) {
+ if (item->completion.cleanup != NULL) {
+ item->completion.cleanup(item->completion.data);
}
-
- // select the closest one to previous selection
- g_state.current_completion = prev_selection < g_state.ncompletions
- ? prev_selection
- : g_state.ncompletions - 1;
-
- buffer_view_goto(
- v, (struct location){.line = g_state.current_completion, .col = 0});
-
- struct window *target_window = window_find_by_buffer(buffer);
- struct window_position winpos = window_position(target_window);
- struct buffer_view *view = window_buffer_view(target_window);
- uint32_t height = g_state.ncompletions > 10 ? 10 : g_state.ncompletions;
- windows_show_popup(winpos.y + location.line - height - 1,
- winpos.x + view->fringe_width + location.col + 1,
- max_width + 2, height);
-
- if (!g_state.keymap_active) {
- struct keymap km = keymap_create("completion", 8);
- struct binding comp_bindings[] = {
- ANONYMOUS_BINDING(Ctrl, 'N', &next_completion_command),
- ANONYMOUS_BINDING(Ctrl, 'P', &prev_completion_command),
- ANONYMOUS_BINDING(ENTER, &insert_completion_command),
- };
- keymap_bind_keys(&km, comp_bindings,
- sizeof(comp_bindings) / sizeof(comp_bindings[0]));
- g_state.keymap_id = buffer_add_keymap(buffer, km);
- g_state.keymap_active = true;
- }
- } else {
- hide_completion();
- }
-}
-
-static void on_buffer_delete(struct buffer *buffer,
- struct edit_location deleted, void *userdata) {
- struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata;
-
- if (g_state.active) {
- update_completions(buffer, ctx, deleted.coordinates.begin);
}
-}
-static void on_buffer_insert(struct buffer *buffer,
- struct edit_location inserted, void *userdata) {
- struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata;
-
- if (!g_state.active) {
- uint32_t nchars = 0;
- switch (ctx->trigger.kind) {
- case CompletionTrigger_Input:
- for (uint32_t line = inserted.coordinates.begin.line;
- line <= inserted.coordinates.end.line; ++line) {
- nchars += buffer_line_length(buffer, line);
- }
- nchars -= inserted.coordinates.begin.col +
- (buffer_line_length(buffer, inserted.coordinates.end.line) -
- inserted.coordinates.end.col);
-
- ctx->trigger_current_nchars += nchars;
-
- if (ctx->trigger_current_nchars < ctx->trigger.data.input.nchars) {
- return;
- }
-
- ctx->trigger_current_nchars = 0;
- break;
+ VEC_CLEAR(&state->completions);
+ state->completion_index = 0;
- case CompletionTrigger_Char:
- // TODO
- break;
- }
-
- // activate completion
- g_state.active = true;
- g_state.ctx = ctx;
+ if (completion_active()) {
+ buffer_view_goto(window_buffer_view(popup_window()),
+ (struct location){0, 0});
}
-
- update_completions(buffer, ctx, inserted.coordinates.end);
-}
-
-static void update_completion_buffer(struct buffer *buffer, void *userdata) {
- (void)buffer;
- (void)userdata;
-
- buffer_add_text_property(
- g_target_buffer,
- (struct location){.line = g_state.current_completion, .col = 0},
- (struct location){.line = g_state.current_completion,
- .col = buffer_line_length(g_target_buffer,
- g_state.current_completion)},
- (struct text_property){.type = TextProperty_Colors,
- .data.colors = (struct text_property_colors){
- .set_bg = false,
- .bg = 0,
- .set_fg = true,
- .fg = 4,
- }});
}
-void init_completion(struct buffers *buffers, struct commands *commands) {
- if (g_target_buffer == NULL) {
- g_target_buffer = buffers_add(buffers, buffer_create("*completions*"));
- buffer_add_update_hook(g_target_buffer, update_completion_buffer, NULL);
- }
+static void update_window_position(struct completion_state *state) {
- g_buffer_provider.userdata = buffers;
- g_commands_provider.userdata = commands;
- VEC_INIT(&g_active_completions, 32);
-}
+ size_t ncompletions = VEC_SIZE(&state->completions);
-struct oneshot_completion {
- uint32_t hook_id;
- struct active_completion_ctx *ctx;
-};
+ struct window *target_window = windows_get_active();
+ struct window *root_wind = root_window();
-static void cleanup_oneshot(void *userdata) { free(userdata); }
+ size_t nlines = buffer_num_lines(state->completions_buffer);
+ size_t max_width = 10;
-static void oneshot_completion_hook(struct buffer *buffer, void *userdata) {
- struct oneshot_completion *comp = (struct oneshot_completion *)userdata;
+ window_set_buffer_e(popup_window(), state->completions_buffer, false, false);
+ struct window_position winpos = window_position(target_window);
+ struct buffer_view *view = window_buffer_view(target_window);
+ uint32_t height = ncompletions > 10 ? 10 : ncompletions;
- // activate completion
- g_state.active = true;
- g_state.ctx = comp->ctx;
+ size_t xpos =
+ winpos.x + view->fringe_width + (view->dot.col - view->scroll.col) + 1;
- struct window *w = window_find_by_buffer(buffer);
- if (w != NULL) {
- struct buffer_view *v = window_buffer_view(w);
- update_completions(buffer, comp->ctx, v->dot);
+ // should it be over or under?
+ size_t relative_line = (view->dot.line - view->scroll.line);
+ size_t ypos = winpos.y + relative_line;
+ if (ypos > 10) {
+ ypos -= height + 1;
} else {
- update_completions(buffer, comp->ctx,
- (struct location){.line = 0, .col = 0});
+ ypos += 3;
}
- // this is a oneshot after all
- buffer_remove_update_hook(buffer, comp->hook_id, cleanup_oneshot);
-}
-
-void enable_completion(struct buffer *source, struct completion_trigger trigger,
- struct completion_provider *providers,
- uint32_t nproviders, insert_cb on_completion_inserted) {
- // check if we are already active
- VEC_FOR_EACH(&g_active_completions, struct active_completion * c) {
- if (c->buffer == source) {
- disable_completion(source);
+ for (uint64_t i = 0; i < nlines; ++i) {
+ size_t linelen = buffer_line_length(state->completions_buffer, i);
+ if (linelen > max_width) {
+ max_width = linelen;
}
}
- struct active_completion_ctx *ctx =
- calloc(1, sizeof(struct active_completion_ctx));
- ctx->trigger = trigger;
- ctx->on_completion_inserted = on_completion_inserted;
- ctx->nproviders = nproviders;
- ctx->providers = calloc(nproviders, sizeof(struct completion_provider));
- memcpy(ctx->providers, providers,
- sizeof(struct completion_provider) * nproviders);
-
- uint32_t insert_hook_id =
- buffer_add_insert_hook(source, on_buffer_insert, ctx);
- uint32_t remove_hook_id =
- buffer_add_delete_hook(source, on_buffer_delete, ctx);
-
- VEC_PUSH(&g_active_completions, ((struct active_completion){
- .buffer = source,
- .insert_hook_id = insert_hook_id,
- .remove_hook_id = remove_hook_id,
- }));
-
- // do we want to trigger initially?
- if (ctx->trigger.kind == CompletionTrigger_Input &&
- ctx->trigger.data.input.trigger_initially) {
- struct oneshot_completion *comp =
- calloc(1, sizeof(struct oneshot_completion));
- comp->ctx = ctx;
- comp->hook_id =
- buffer_add_update_hook(source, oneshot_completion_hook, comp);
- }
-}
+ size_t available = window_width(root_wind) - xpos - 5;
+ max_width = max_width >= available ? available : max_width;
-static void hide_completion(void) {
- windows_close_popup();
- if (g_state.active) {
- buffer_remove_keymap(g_state.keymap_id);
- g_state.keymap_active = false;
- }
+ windows_show_popup(ypos, xpos, max_width, height);
}
-void abort_completion(void) {
- hide_completion();
- g_state.active = false;
- clear_completions();
+static void update_window_pos_frame_hook(void *data) {
+ struct completion_state *state = (struct completion_state *)data;
+ update_window_position(state);
}
-bool completion_active(void) {
- return popup_window_visible() &&
- window_buffer(popup_window()) == g_target_buffer && g_state.active;
-}
+static void open_completion(struct completion_state *state) {
-static void cleanup_active_comp_ctx(void *userdata) {
- struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata;
+ size_t ncompletions = VEC_SIZE(&state->completions);
- if (g_state.ctx == ctx && g_state.active) {
+ if (ncompletions == 0) {
abort_completion();
+ return;
}
- free(ctx->providers);
- free(ctx);
-}
+ struct window *target_window = windows_get_active();
+ struct buffer *buffer = window_buffer(target_window);
+ if (!completion_active() || state->target != buffer) {
-static void do_nothing(void *userdata) { (void)userdata; }
+ // clear any previous keymaps
+ abort_completion();
-static void cleanup_active_completion(struct active_completion *comp) {
- buffer_remove_delete_hook(comp->buffer, comp->remove_hook_id, do_nothing);
- buffer_remove_insert_hook(comp->buffer, comp->insert_hook_id,
- cleanup_active_comp_ctx);
-}
+ struct keymap km = keymap_create("completion", 8);
+ struct binding comp_bindings[] = {
+ ANONYMOUS_BINDING(Ctrl, 'N', &next_completion_command),
+ ANONYMOUS_BINDING(Ctrl, 'P', &prev_completion_command),
+ ANONYMOUS_BINDING(ENTER, &insert_completion_command),
+ };
+ keymap_bind_keys(&km, comp_bindings,
+ sizeof(comp_bindings) / sizeof(comp_bindings[0]));
-void disable_completion(struct buffer *buffer) {
- VEC_FOR_EACH_INDEXED(&g_active_completions, struct active_completion * comp,
- i) {
- if (buffer == comp->buffer) {
- VEC_SWAP(&g_active_completions, i, VEC_SIZE(&g_active_completions) - 1);
- VEC_POP(&g_active_completions, struct active_completion removed);
- cleanup_active_completion(&removed);
- }
+ state->keymap_id = buffer_add_keymap(buffer, km);
}
-}
-void destroy_completion(void) {
- // clean up any active completions we might have
- VEC_FOR_EACH(&g_active_completions, struct active_completion * comp) {
- cleanup_active_completion(comp);
- }
- VEC_DESTROY(&g_active_completions);
+ // need to run next frame to have the correct position
+ run_next_frame(update_window_pos_frame_hook, state);
}
-static bool is_hidden(const char *filename) {
- return filename[0] == '.' && filename[1] != '\0' && filename[1] != '.';
-}
+static void add_completions_impl(struct completion *completions,
+ size_t ncompletions) {
+ for (uint32_t i = 0; i < ncompletions; ++i) {
+ struct completion *c = &completions[i];
+ struct region area = c->render(c->data, g_state.completions_buffer);
+ VEC_APPEND(&g_state.completions, struct completion_item * new);
+ new->area = area;
+ new->completion = *c;
+ }
-static int cmp_completions(const void *comp_a, const void *comp_b) {
- struct completion *a = (struct completion *)comp_a;
- struct completion *b = (struct completion *)comp_b;
- return strcmp(a->display, b->display);
+ open_completion(&g_state);
}
-static uint32_t complete_path(struct completion_context ctx, void *userdata) {
- (void)userdata;
-
- // obtain path from the buffer
- struct text_chunk txt = {0};
- if (ctx.buffer == minibuffer_buffer()) {
- txt = minibuffer_content();
- } else {
- struct match_result start =
- buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
- if (!start.found) {
- start.at = (struct location){.line = ctx.location.line, .col = 0};
- return 0;
+static void update_completions(struct completion_state *state,
+ struct buffer *buffer, struct location location,
+ bool deletion) {
+ clear_completions(state);
+ struct buffer_completion *buffer_config = NULL;
+ VEC_FOR_EACH(&state->buffer_completions, struct buffer_completion * bc) {
+ if (buffer == bc->buffer) {
+ buffer_config = bc;
+ break;
}
- txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
}
- char *path = calloc(txt.nbytes + 1, sizeof(char));
- memcpy(path, txt.text, txt.nbytes);
- path[txt.nbytes] = '\0';
-
- if (txt.allocated) {
- free(txt.text);
+ if (buffer_config == NULL) {
+ return;
}
- uint32_t n = 0;
- char *p1 = to_abspath(path);
- char *p2 = strdup(p1);
-
- size_t inlen = strlen(path);
+ VEC_FOR_EACH(&buffer_config->providers,
+ struct completion_provider * provider) {
+ struct completion_context comp_ctx = (struct completion_context){
+ .buffer = buffer,
+ .location = location,
+ .add_completions = add_completions_impl,
+ };
- if (ctx.max_ncompletions == 0) {
- goto done;
+ provider->complete(comp_ctx, deletion, provider->userdata);
}
+}
- const char *dir = p1;
- const char *file = "";
+static void update_comp_buffer(struct buffer *buffer, void *userdata) {
+ struct completion_state *state = (struct completion_state *)userdata;
- // check the input path here since
- // to_abspath removes trailing slashes
- if (inlen == 0 || path[inlen - 1] != '/') {
- dir = dirname(p1);
- file = basename(p2);
+ buffer_clear_text_property_layer(buffer, state->highlight_current_layer);
+
+ if (buffer_is_empty(buffer)) {
+ abort_completion();
}
- DIR *d = opendir(dir);
- if (d == NULL) {
- goto done;
+ struct region reg = active_completion_region(state);
+ if (region_has_size(reg)) {
+ buffer_add_text_property_to_layer(buffer, reg.begin, reg.end,
+ (struct text_property){
+ .type = TextProperty_Colors,
+ .data.colors =
+ (struct text_property_colors){
+ .inverted = true,
+ .set_fg = false,
+ .set_bg = false,
+ .underline = false,
+ },
+ },
+ state->highlight_current_layer);
}
+}
- errno = 0;
- size_t filelen = strlen(file);
- bool file_is_curdir = (filelen == 1 && memcmp(file, ".", 1) == 0);
- while (n < ctx.max_ncompletions) {
- struct dirent *de = readdir(d);
- if (de == NULL && errno != 0) {
- // skip the erroring entry
- errno = 0;
- continue;
- } else if (de == NULL && errno == 0) {
- break;
- }
+static void on_buffer_changed(struct buffer *buffer, struct edit_location edit,
+ bool deletion, void *userdata) {
+ struct completion_state *state = (struct completion_state *)userdata;
- switch (de->d_type) {
- case DT_DIR:
- case DT_REG:
- case DT_LNK:
- if (!is_hidden(de->d_name) &&
- (filelen == 0 || file_is_curdir ||
- (filelen <= strlen(de->d_name) &&
- memcmp(file, de->d_name, filelen) == 0))) {
-
- const char *disp = strdup(de->d_name);
- ctx.completions[n] = (struct completion){
- .display = disp,
- .insert = strdup(disp + (file_is_curdir ? 0 : filelen)),
- .complete = de->d_type == DT_REG,
- };
- ++n;
- }
- break;
- }
+ if (state->insert_in_progress || state->paused) {
+ return;
}
- closedir(d);
+ update_completions(state, buffer, edit.coordinates.end, deletion);
+}
-done:
- free(path);
- free(p1);
- free(p2);
+static void on_buffer_insert(struct buffer *buffer, struct edit_location edit,
+ void *userdata) {
+ on_buffer_changed(buffer, edit, false, userdata);
+}
- qsort(ctx.completions, n, sizeof(struct completion), cmp_completions);
- return n;
+static void on_buffer_delete(struct buffer *buffer, struct edit_location edit,
+ void *userdata) {
+ on_buffer_changed(buffer, edit, true, userdata);
}
-struct needle_match_ctx {
- const char *needle;
- struct completion *completions;
- uint32_t max_ncompletions;
- uint32_t ncompletions;
-};
+void init_completion(struct buffers *buffers) {
+ if (g_state.completions_buffer == NULL) {
+ struct buffer b = buffer_create("*completions*");
+ b.lazy_row_add = false;
+ b.force_show_ws_off = true;
+ b.retain_properties = true;
+ g_state.completions_buffer = buffers_add(buffers, b);
+ }
-static void buffer_matches(struct buffer *buffer, void *userdata) {
- struct needle_match_ctx *ctx = (struct needle_match_ctx *)userdata;
+ g_state.highlight_current_layer =
+ buffer_add_text_property_layer(g_state.completions_buffer);
+ buffer_add_update_hook(g_state.completions_buffer, update_comp_buffer,
+ &g_state);
- if (strncmp(ctx->needle, buffer->name, strlen(ctx->needle)) == 0 &&
- ctx->ncompletions < ctx->max_ncompletions) {
- ctx->completions[ctx->ncompletions] = (struct completion){
- .display = strdup(buffer->name),
- .insert = strdup(buffer->name + strlen(ctx->needle)),
- .complete = true,
- };
- ++ctx->ncompletions;
- }
+ g_state.keymap_id = (uint64_t)-1;
+ g_state.target = NULL;
+
+ VEC_INIT(&g_state.buffer_completions, 50);
+ VEC_INIT(&g_state.completions, 50);
+ g_state.completion_index = 0;
+ g_state.insert_in_progress = false;
+ g_state.paused = false;
}
-static uint32_t complete_buffers(struct completion_context ctx,
- void *userdata) {
- struct buffers *buffers = (struct buffers *)userdata;
- if (buffers == NULL) {
- return 0;
- }
+void add_completion_providers(struct buffer *source,
+ struct completion_provider *providers,
+ uint32_t nproviders) {
- struct text_chunk txt = {0};
- if (ctx.buffer == minibuffer_buffer()) {
- txt = minibuffer_content();
- } else {
- struct match_result start =
- buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
- if (!start.found) {
- start.at = (struct location){.line = ctx.location.line, .col = 0};
- return 0;
+ struct buffer_completion *comp = NULL;
+ VEC_FOR_EACH(&g_state.buffer_completions, struct buffer_completion * c) {
+ if (c->buffer == source) {
+ comp = c;
+ break;
}
- txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
}
- char *needle = calloc(txt.nbytes + 1, sizeof(char));
- memcpy(needle, txt.text, txt.nbytes);
- needle[txt.nbytes] = '\0';
+ if (comp == NULL) {
+ VEC_APPEND(&g_state.buffer_completions,
+ struct buffer_completion * new_comp);
+
+ uint32_t insert_hook_id =
+ buffer_add_insert_hook(source, on_buffer_insert, &g_state);
+ uint32_t remove_hook_id =
+ buffer_add_delete_hook(source, on_buffer_delete, &g_state);
- if (txt.allocated) {
- free(txt.text);
+ new_comp->buffer = source;
+ new_comp->insert_hook_id = insert_hook_id;
+ new_comp->remove_hook_id = remove_hook_id;
+ VEC_INIT(&new_comp->providers, nproviders);
+ comp = new_comp;
}
- struct needle_match_ctx match_ctx = (struct needle_match_ctx){
- .needle = needle,
- .max_ncompletions = ctx.max_ncompletions,
- .completions = ctx.completions,
- .ncompletions = 0,
- };
- buffers_for_each(buffers, buffer_matches, &match_ctx);
+ for (uint32_t i = 0; i < nproviders; ++i) {
+ VEC_PUSH(&comp->providers, providers[i]);
+ }
+}
- free(needle);
- return match_ctx.ncompletions;
+void complete(struct buffer *buffer, struct location at) {
+ update_completions(&g_state, buffer, at, false);
}
-static void command_matches(struct command *command, void *userdata) {
- struct needle_match_ctx *ctx = (struct needle_match_ctx *)userdata;
+void abort_completion(void) {
+ windows_close_popup();
- if (strncmp(ctx->needle, command->name, strlen(ctx->needle)) == 0 &&
- ctx->ncompletions < ctx->max_ncompletions) {
- ctx->completions[ctx->ncompletions] = (struct completion){
- .display = strdup(command->name),
- .insert = strdup(command->name + strlen(ctx->needle)),
- .complete = true,
- };
- ++ctx->ncompletions;
+ if (g_state.keymap_id != (uint64_t)-1) {
+ buffer_remove_keymap(g_state.keymap_id);
}
+
+ g_state.keymap_id = (uint64_t)-1;
+ g_state.target = NULL;
}
-static uint32_t complete_commands(struct completion_context ctx,
- void *userdata) {
+bool completion_active(void) {
+ return popup_window_visible() &&
+ window_buffer(popup_window()) == g_state.completions_buffer;
+}
- struct commands *commands = (struct commands *)userdata;
- if (commands == NULL) {
- return 0;
- }
- struct text_chunk txt = {0};
- if (ctx.buffer == minibuffer_buffer()) {
- txt = minibuffer_content();
- } else {
- struct match_result start =
- buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
- if (!start.found) {
- start.at = (struct location){.line = ctx.location.line, .col = 0};
- return 0;
+static void do_nothing(void *userdata) { (void)userdata; }
+
+static void cleanup_buffer_completion(struct buffer_completion *comp) {
+ buffer_remove_delete_hook(comp->buffer, comp->remove_hook_id, do_nothing);
+ buffer_remove_insert_hook(comp->buffer, comp->insert_hook_id, do_nothing);
+
+ VEC_FOR_EACH(&comp->providers, struct completion_provider * provider) {
+ if (provider->cleanup != NULL) {
+ provider->cleanup(provider->userdata);
}
- txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
}
- char *needle = calloc(txt.nbytes + 1, sizeof(char));
- memcpy(needle, txt.text, txt.nbytes);
- needle[txt.nbytes] = '\0';
+ VEC_DESTROY(&comp->providers);
+}
- if (txt.allocated) {
- free(txt.text);
+void disable_completion(struct buffer *buffer) {
+ VEC_FOR_EACH_INDEXED(&g_state.buffer_completions,
+ struct buffer_completion * comp, i) {
+ if (buffer == comp->buffer) {
+ VEC_SWAP(&g_state.buffer_completions, i,
+ VEC_SIZE(&g_state.buffer_completions) - 1);
+ VEC_POP(&g_state.buffer_completions, struct buffer_completion removed);
+ cleanup_buffer_completion(&removed);
+ }
}
+}
- struct needle_match_ctx match_ctx = (struct needle_match_ctx){
- .needle = needle,
- .max_ncompletions = ctx.max_ncompletions,
- .completions = ctx.completions,
- .ncompletions = 0,
- };
- commands_for_each(commands, command_matches, &match_ctx);
-
- free(needle);
- return match_ctx.ncompletions;
+void destroy_completion(void) {
+ clear_completions(&g_state);
+ // clean up any active completions we might have
+ VEC_FOR_EACH(&g_state.buffer_completions, struct buffer_completion * comp) {
+ cleanup_buffer_completion(comp);
+ }
+ VEC_DESTROY(&g_state.buffer_completions);
+ VEC_DESTROY(&g_state.completions);
}
+
+void pause_completion() { g_state.paused = true; }
+
+void resume_completion() { g_state.paused = false; }
diff --git a/src/main/completion.h b/src/main/completion.h
index f2ce186..25f1ea2 100644
--- a/src/main/completion.h
+++ b/src/main/completion.h
@@ -1,6 +1,8 @@
#ifndef _COMPLETION_H
#define _COMPLETION_H
+#include <stddef.h>
+
#include "dged/location.h"
/** @file completion.h
@@ -9,29 +11,22 @@
struct buffer;
struct buffers;
+struct buffer_view;
struct commands;
-/**
- * A single completion.
- */
+typedef struct region (*completion_render_fn)(void *, struct buffer *);
+typedef void (*completion_selected_fn)(void *, struct buffer_view *);
+typedef void (*completion_cleanup_fn)(void *);
+
struct completion {
- /** The display text for the completion. */
- const char *display;
-
- /** The text to insert for this completion. */
- const char *insert;
-
- /**
- * True if this completion item represent a fully expanded value.
- *
- * One example might be when the file completion represents a
- * file (and not a directory) which means that there is not
- * going to be more to complete after picking this completion
- * item.
- */
- bool complete;
+ void *data;
+ completion_render_fn render;
+ completion_selected_fn selected;
+ completion_cleanup_fn cleanup;
};
+typedef void (*add_completions)(struct completion *, size_t);
+
/**
* Context for calculating completions.
*/
@@ -40,20 +35,19 @@ struct completion_context {
struct buffer *buffer;
/** The current location in the buffer. */
- const struct location location;
+ struct location location;
- /** The capacity of @ref completion_context.completions. */
- const uint32_t max_ncompletions;
-
- /** The resulting completions */
- struct completion *completions;
+ /** Callback for adding items to the completion list */
+ add_completions add_completions;
};
/**
* A function that provides completions.
*/
-typedef uint32_t (*completion_fn)(struct completion_context ctx,
- void *userdata);
+typedef void (*completion_fn)(struct completion_context ctx, bool deletion,
+ void *userdata);
+
+typedef void (*provider_cleanup_fn)(void *);
/**
* A completion provider.
@@ -62,54 +56,21 @@ struct completion_provider {
/** Name of the completion provider */
char name[16];
- /** Completion function. Called to get new completions. */
+ /** Completion function. Called to trigger retreival of new completions. */
completion_fn complete;
+ /** Cleanup function called when provider is destroyed. */
+ provider_cleanup_fn cleanup;
+
/** Userdata sent to @ref completion_provider.complete */
void *userdata;
};
/**
- * Type of event that triggers a completion.
- */
-enum completion_trigger_kind {
- /** Completion is triggered on any input. */
- CompletionTrigger_Input = 0,
-
- /** Completion is triggered on a specific char. */
- CompletionTrigger_Char = 1,
-};
-
-/**
- * Description for @c CompletionTrigger_Input.
- */
-struct completion_trigger_input {
- /** Trigger completion after this many chars */
- uint32_t nchars;
-
- /** Trigger an initial complete? */
- bool trigger_initially;
-};
-
-/**
- * Completion trigger descriptor.
- */
-struct completion_trigger {
- /** Type of trigger. */
- enum completion_trigger_kind kind;
- union completion_trigger_data {
- uint32_t c;
- struct completion_trigger_input input;
- } data;
-};
-
-/**
* Initialize the completion system.
*
- * @param buffers The buffer list to complete from.
- * @param commands The command list to complete from.
*/
-void init_completion(struct buffers *buffers, struct commands *commands);
+void init_completion(struct buffers *buffers);
/**
* Tear down the completion system.
@@ -117,48 +78,23 @@ void init_completion(struct buffers *buffers, struct commands *commands);
void destroy_completion(void);
/**
- * Callback for completion inserted.
- */
-typedef void (*insert_cb)(void);
-
-/**
* Enable completions in the buffer @p source.
*
* @param source [in] The buffer to provide completions for.
- * @param trigger [in] The completion trigger to use for this completion.
* @param providers [in] The completion providers to use.
* @param nproviders [in] The number of providers in @p providers.
- * @param on_completion_inserted [in] Callback to be called when a completion
- * has been inserted.
- */
-void enable_completion(struct buffer *source, struct completion_trigger trigger,
- struct completion_provider *providers,
- uint32_t nproviders, insert_cb on_completion_inserted);
-
-/**
- * Create a new path completion provider.
- *
- * This provider completes filesystem paths.
- * @returns A filesystem path @ref completion_provider.
*/
-struct completion_provider path_provider(void);
+void add_completion_providers(struct buffer *source,
+ struct completion_provider *providers,
+ uint32_t nproviders);
/**
- * Create a new buffer completion provider.
+ * Trigger a completion at @ref at in @ref buffer.
*
- * This provider completes buffer names from the
- * buffer list.
- * @returns A buffer name @ref completion_provider.
+ * @param buffer [in] Buffer to complete in.
+ * @param at [in] The location in @ref buffer to provide completions at.
*/
-struct completion_provider buffer_provider(void);
-
-/**
- * Create a new command completion provider.
- *
- * This provider completes registered command names.
- * @returns A command name @ref completion_provider.
- */
-struct completion_provider commands_provider(void);
+void complete(struct buffer *buffer, struct location at);
/**
* Abort any active completion.
@@ -173,10 +109,20 @@ void abort_completion(void);
bool completion_active(void);
/**
- * Disable completion for @ref buffer.
+ * Get a pointer to the buffer used to hold completion items.
+ *
+ * @returns A pointer to the buffer holding completions.
+ */
+struct buffer *completion_buffer(void);
+
+/**
+ * Disable completion for @ref buffer, removing all providers.
*
* @param buffer [in] Buffer to disable completions for.
*/
void disable_completion(struct buffer *buffer);
+void pause_completion();
+void resume_completion();
+
#endif
diff --git a/src/main/completion/buffer.c b/src/main/completion/buffer.c
new file mode 100644
index 0000000..8074414
--- /dev/null
+++ b/src/main/completion/buffer.c
@@ -0,0 +1,148 @@
+#include "buffer.h"
+
+#include <string.h>
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/buffers.h"
+#include "dged/minibuffer.h"
+
+#include "main/completion.h"
+
+static bool is_space(const struct codepoint *c) {
+ // TODO: utf8 whitespace and other whitespace
+ return c->codepoint == ' ';
+}
+
+typedef void (*on_buffer_selected_cb)(struct buffer *);
+
+struct buffer_completion {
+ struct buffer *buffer;
+ on_buffer_selected_cb on_buffer_selected;
+};
+
+struct buffer_provider_data {
+ struct buffers *buffers;
+ on_buffer_selected_cb on_buffer_selected;
+};
+
+static void buffer_comp_selected(void *data, struct buffer_view *target) {
+ struct buffer_completion *bc = (struct buffer_completion *)data;
+ buffer_set_text(target->buffer, (uint8_t *)bc->buffer->name,
+ strlen(bc->buffer->name));
+
+ abort_completion();
+ bc->on_buffer_selected(bc->buffer);
+}
+
+static struct region buffer_comp_render(void *data,
+ struct buffer *comp_buffer) {
+ struct buffer *buffer = ((struct buffer_completion *)data)->buffer;
+ struct location begin = buffer_end(comp_buffer);
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)buffer->name,
+ strlen(buffer->name));
+
+ struct location end = buffer_end(comp_buffer);
+ buffer_newline(comp_buffer, buffer_end(comp_buffer));
+ return region_new(begin, end);
+}
+
+static void buffer_comp_cleanup(void *data) {
+ struct buffer_completion *bc = (struct buffer_completion *)data;
+ free(bc);
+}
+
+struct needle_match_ctx {
+ const char *needle;
+ struct completion *completions;
+ uint32_t max_ncompletions;
+ uint32_t ncompletions;
+ on_buffer_selected_cb on_buffer_selected;
+};
+
+static void buffer_matches(struct buffer *buffer, void *userdata) {
+ struct needle_match_ctx *ctx = (struct needle_match_ctx *)userdata;
+
+ if (strncmp(ctx->needle, buffer->name, strlen(ctx->needle)) == 0 &&
+ ctx->ncompletions < ctx->max_ncompletions) {
+
+ struct buffer_completion *comp_data =
+ calloc(1, sizeof(struct buffer_completion));
+ comp_data->buffer = buffer;
+ comp_data->on_buffer_selected = ctx->on_buffer_selected;
+ ctx->completions[ctx->ncompletions] = (struct completion){
+ .render = buffer_comp_render,
+ .selected = buffer_comp_selected,
+ .cleanup = buffer_comp_cleanup,
+ .data = comp_data,
+ };
+ ++ctx->ncompletions;
+ }
+}
+
+static void buffer_complete(struct completion_context ctx, bool deletion,
+ void *userdata) {
+ (void)deletion;
+ struct buffer_provider_data *pd = (struct buffer_provider_data *)userdata;
+ struct buffers *buffers = pd->buffers;
+ if (buffers == NULL) {
+ return;
+ }
+
+ struct text_chunk txt = {0};
+ if (ctx.buffer == minibuffer_buffer()) {
+ txt = minibuffer_content();
+ } else {
+ struct match_result start =
+ buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
+ if (!start.found) {
+ start.at = (struct location){.line = ctx.location.line, .col = 0};
+ return;
+ }
+ txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
+ }
+
+ char *needle = calloc(txt.nbytes + 1, sizeof(char));
+ memcpy(needle, txt.text, txt.nbytes);
+ needle[txt.nbytes] = '\0';
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+
+ struct completion *completions = calloc(50, sizeof(struct completion));
+
+ struct needle_match_ctx match_ctx = (struct needle_match_ctx){
+ .needle = needle,
+ .max_ncompletions = 50,
+ .completions = completions,
+ .ncompletions = 0,
+ .on_buffer_selected = pd->on_buffer_selected,
+ };
+
+ buffers_for_each(buffers, buffer_matches, &match_ctx);
+ ctx.add_completions(match_ctx.completions, match_ctx.ncompletions);
+ free(completions);
+ free(needle);
+}
+
+static void cleanup_provider(void *data) {
+ struct buffer_provider_data *bpd = (struct buffer_provider_data *)data;
+ free(bpd);
+}
+
+struct completion_provider
+create_buffer_provider(struct buffers *buffers,
+ on_buffer_selected_cb on_buffer_selected) {
+ struct buffer_provider_data *data =
+ calloc(1, sizeof(struct buffer_provider_data));
+ data->buffers = buffers;
+ data->on_buffer_selected = on_buffer_selected;
+
+ return (struct completion_provider){
+ .name = "buffers",
+ .complete = buffer_complete,
+ .userdata = data,
+ .cleanup = cleanup_provider,
+ };
+}
diff --git a/src/main/completion/buffer.h b/src/main/completion/buffer.h
new file mode 100644
index 0000000..c2b6d42
--- /dev/null
+++ b/src/main/completion/buffer.h
@@ -0,0 +1,18 @@
+#ifndef _MAIN_COMPLETION_BUFFER_H
+#define _MAIN_COMPLETION_BUFFER_H
+
+struct buffer;
+struct buffers;
+
+/**
+ * Create a new buffer completion provider.
+ *
+ * This provider completes buffer names from the
+ * buffer list.
+ * @returns A buffer name @ref completion_provider.
+ */
+struct completion_provider
+create_buffer_provider(struct buffers *buffers,
+ void (*on_buffer_selected)(struct buffer *));
+
+#endif
diff --git a/src/main/completion/command.c b/src/main/completion/command.c
new file mode 100644
index 0000000..e4900ed
--- /dev/null
+++ b/src/main/completion/command.c
@@ -0,0 +1,151 @@
+#include "command.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/command.h"
+#include "dged/minibuffer.h"
+#include "dged/utf8.h"
+
+#include "main/completion.h"
+
+static bool is_space(const struct codepoint *c) {
+ // TODO: utf8 whitespace and other whitespace
+ return c->codepoint == ' ';
+}
+
+typedef void (*on_command_selected_cb)(struct command *);
+
+struct command_completion {
+ struct command *command;
+ on_command_selected_cb on_command_selected;
+};
+
+struct command_provider_data {
+ struct commands *commands;
+ on_command_selected_cb on_command_selected;
+};
+
+static void command_comp_selected(void *data, struct buffer_view *target) {
+ struct command_completion *cc = (struct command_completion *)data;
+ buffer_set_text(target->buffer, (uint8_t *)cc->command->name,
+ strlen(cc->command->name));
+
+ abort_completion();
+ cc->on_command_selected(cc->command);
+}
+
+static struct region command_comp_render(void *data,
+ struct buffer *comp_buffer) {
+ struct command *command = ((struct command_completion *)data)->command;
+ struct location begin = buffer_end(comp_buffer);
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)command->name,
+ strlen(command->name));
+
+ struct location end = buffer_end(comp_buffer);
+ buffer_newline(comp_buffer, buffer_end(comp_buffer));
+
+ return region_new(begin, end);
+}
+
+static void command_comp_cleanup(void *data) {
+ struct command_completion *cc = (struct command_completion *)data;
+ free(cc);
+}
+
+struct needle_match_ctx {
+ const char *needle;
+ struct completion *completions;
+ uint32_t max_ncompletions;
+ uint32_t ncompletions;
+ on_command_selected_cb on_command_selected;
+};
+
+static void command_matches(struct command *command, void *userdata) {
+ struct needle_match_ctx *ctx = (struct needle_match_ctx *)userdata;
+
+ if (strncmp(ctx->needle, command->name, strlen(ctx->needle)) == 0 &&
+ ctx->ncompletions < ctx->max_ncompletions) {
+
+ struct command_completion *comp_data =
+ calloc(1, sizeof(struct command_completion));
+ comp_data->command = command;
+ comp_data->on_command_selected = ctx->on_command_selected;
+ ctx->completions[ctx->ncompletions] = (struct completion){
+ .render = command_comp_render,
+ .selected = command_comp_selected,
+ .cleanup = command_comp_cleanup,
+ .data = comp_data,
+ };
+ ++ctx->ncompletions;
+ }
+}
+
+static void command_complete(struct completion_context ctx, bool deletion,
+ void *userdata) {
+ (void)deletion;
+ struct command_provider_data *pd = (struct command_provider_data *)userdata;
+ struct commands *commands = pd->commands;
+ if (commands == NULL) {
+ return;
+ }
+
+ struct text_chunk txt = {0};
+ if (ctx.buffer == minibuffer_buffer()) {
+ txt = minibuffer_content();
+ } else {
+ struct match_result start =
+ buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
+ if (!start.found) {
+ start.at = (struct location){.line = ctx.location.line, .col = 0};
+ return;
+ }
+ txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
+ }
+
+ char *needle = calloc(txt.nbytes + 1, sizeof(char));
+ memcpy(needle, txt.text, txt.nbytes);
+ needle[txt.nbytes] = '\0';
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+
+ struct completion *completions = calloc(50, sizeof(struct completion));
+
+ struct needle_match_ctx match_ctx = (struct needle_match_ctx){
+ .needle = needle,
+ .max_ncompletions = 50,
+ .completions = completions,
+ .ncompletions = 0,
+ .on_command_selected = pd->on_command_selected,
+ };
+
+ commands_for_each(commands, command_matches, &match_ctx);
+ ctx.add_completions(match_ctx.completions, match_ctx.ncompletions);
+ free(completions);
+ free(needle);
+}
+
+static void cleanup_provider(void *data) {
+ struct command_provider_data *cpd = (struct command_provider_data *)data;
+ free(cpd);
+}
+
+struct completion_provider
+create_commands_provider(struct commands *commands,
+ on_command_selected_cb on_command_selected) {
+ struct command_provider_data *data =
+ calloc(1, sizeof(struct command_provider_data));
+ data->commands = commands;
+ data->on_command_selected = on_command_selected;
+
+ return (struct completion_provider){
+ .name = "commands",
+ .complete = command_complete,
+ .userdata = data,
+ .cleanup = cleanup_provider,
+ };
+}
diff --git a/src/main/completion/command.h b/src/main/completion/command.h
new file mode 100644
index 0000000..c25df57
--- /dev/null
+++ b/src/main/completion/command.h
@@ -0,0 +1,17 @@
+#ifndef _MAIN_COMPLETION_COMMAND_H
+#define _MAIN_COMPLETION_COMMAND_H
+
+struct command;
+struct commands;
+
+/**
+ * Create a new command completion provider.
+ *
+ * This provider completes registered command names.
+ * @returns A command name @ref completion_provider.
+ */
+struct completion_provider
+create_commands_provider(struct commands *,
+ void (*on_command_selected)(struct command *));
+
+#endif
diff --git a/src/main/completion/path.c b/src/main/completion/path.c
new file mode 100644
index 0000000..708da3d
--- /dev/null
+++ b/src/main/completion/path.c
@@ -0,0 +1,268 @@
+#define _DEFAULT_SOURCE
+#include "path.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/display.h"
+#include "dged/minibuffer.h"
+#include "dged/path.h"
+#include "dged/s8.h"
+#include "dged/utf8.h"
+
+static bool is_space(const struct codepoint *c) {
+ // TODO: utf8 whitespace and other whitespace
+ return c->codepoint == ' ';
+}
+
+typedef void (*on_complete_path_cb)(void);
+
+struct path_completion {
+ struct s8 name;
+ struct region replace;
+ unsigned char type;
+ on_complete_path_cb on_complete_path;
+};
+
+static void path_selected(void *data, struct buffer_view *target) {
+ struct path_completion *comp_path = (struct path_completion *)data;
+ struct location loc = buffer_delete(target->buffer, comp_path->replace);
+ loc = buffer_add(target->buffer, loc, (uint8_t *)comp_path->name.s,
+ comp_path->name.l);
+ buffer_view_goto(target, loc);
+ switch (comp_path->type) {
+ case DT_DIR:
+ if (s8eq(comp_path->name, s8("."))) {
+ // trigger "dired" in this case
+ abort_completion();
+ comp_path->on_complete_path();
+ return;
+ }
+
+ buffer_view_add(target, (uint8_t *)"/", 1);
+ break;
+ default:
+ break;
+ }
+
+ // if the user selected a "normal" file,
+ // the completion is finished
+ if (comp_path->type == DT_REG) {
+ abort_completion();
+ comp_path->on_complete_path();
+ } else {
+ complete(target->buffer, target->dot);
+ }
+}
+
+static struct region path_render(void *data, struct buffer *comp_buffer) {
+ struct path_completion *comp_path = (struct path_completion *)data;
+
+ struct location start = buffer_end(comp_buffer);
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)comp_path->name.s,
+ comp_path->name.l);
+ switch (comp_path->type) {
+ case DT_DIR:
+ if (!(s8eq(comp_path->name, s8(".")) || s8eq(comp_path->name, s8("..")))) {
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)"/", 1);
+ struct location end = buffer_end(comp_buffer);
+ buffer_add_text_property(comp_buffer, start, end,
+ (struct text_property){
+ .start = start,
+ .end = end,
+ .type = TextProperty_Colors,
+ .data.colors =
+ (struct text_property_colors){
+ .set_fg = true,
+ .fg = Color_Magenta,
+ },
+ });
+ }
+ break;
+ case DT_LNK: {
+ struct location end = buffer_end(comp_buffer);
+ buffer_add_text_property(comp_buffer, start, end,
+ (struct text_property){
+ .start = start,
+ .end = end,
+ .type = TextProperty_Colors,
+ .data.colors =
+ (struct text_property_colors){
+ .set_fg = true,
+ .fg = Color_Green,
+ },
+ });
+ } break;
+ default:
+ break;
+ }
+
+ struct location end = buffer_end(comp_buffer);
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)"\n", 1);
+
+ return region_new(start, end);
+}
+
+static void path_cleanup(void *data) {
+ struct path_completion *comp_path = (struct path_completion *)data;
+ s8delete(comp_path->name);
+ free(comp_path);
+}
+
+static int cmp_path_completions(const void *comp_a, const void *comp_b) {
+ struct completion *ca = (struct completion *)comp_a;
+ struct completion *cb = (struct completion *)comp_b;
+ struct path_completion *a = (struct path_completion *)ca->data;
+ struct path_completion *b = (struct path_completion *)cb->data;
+ return s8cmp(a->name, b->name);
+}
+
+static bool is_hidden(const char *filename) {
+ return filename[0] == '.' && filename[1] != '\0' && filename[1] != '.';
+}
+
+static bool fuzzy_match_filename(const char *haystack, const char *needle) {
+ for (; *haystack; ++haystack) {
+ const char *h = haystack;
+ const char *n = needle;
+
+ while (*h && *n && *h == *n) {
+ ++h;
+ ++n;
+ }
+
+ // if we reached the end of needle, we found a match
+ if (!*n) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void path_complete(struct completion_context ctx, bool deletion,
+ void *on_complete_path) {
+ (void)deletion;
+
+ // obtain path from the buffer
+ struct text_chunk txt = {0};
+ struct location needle_end = ctx.location;
+ if (ctx.buffer == minibuffer_buffer()) {
+ txt = minibuffer_content();
+ needle_end = buffer_end(minibuffer_buffer());
+ } else {
+ struct match_result start =
+ buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
+ if (!start.found) {
+ start.at = (struct location){.line = ctx.location.line, .col = 0};
+ return;
+ }
+ txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
+ }
+
+ char *path = calloc(txt.nbytes + 1, sizeof(char));
+ memcpy(path, txt.text, txt.nbytes);
+ path[txt.nbytes] = '\0';
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+
+ uint32_t n = 0;
+ char *p1 = to_abspath(path);
+ char *p2 = strdup(p1);
+
+ size_t inlen = strlen(path);
+
+ const char *dir = p1;
+ const char *file = "";
+
+ // check the input path here since
+ // to_abspath removes trailing slashes
+ if (inlen > 0 && path[inlen - 1] != '/') {
+ dir = dirname(p1);
+ file = basename(p2);
+ }
+
+ struct completion *completions = calloc(50, sizeof(struct completion));
+
+ DIR *d = opendir(dir);
+ if (d == NULL) {
+ goto done;
+ }
+
+ errno = 0;
+ size_t filelen = strlen(file);
+ size_t file_nchars = utf8_nchars((uint8_t *)file, filelen);
+ struct location needle_start = (struct location){
+ .line = needle_end.line,
+ .col = needle_end.col - file_nchars,
+ };
+
+ bool file_is_curdir = filelen == 1 && file[0] == '.';
+ while (n < 50) {
+ struct dirent *de = readdir(d);
+ if (de == NULL && errno != 0) {
+ // skip the erroring entry
+ errno = 0;
+ continue;
+ } else if (de == NULL && errno == 0) {
+ break;
+ }
+
+ switch (de->d_type) {
+ case DT_DIR:
+ case DT_REG:
+ case DT_LNK:
+ if (!is_hidden(de->d_name) && (filelen == 0 || file_is_curdir ||
+ fuzzy_match_filename(de->d_name, file))) {
+
+ struct path_completion *comp_data =
+ calloc(1, sizeof(struct path_completion));
+ comp_data->name = s8new(de->d_name, strlen(de->d_name));
+ comp_data->replace = region_new(needle_start, needle_end);
+ comp_data->type = de->d_type;
+ comp_data->on_complete_path = on_complete_path;
+
+ completions[n] = (struct completion){
+ .data = comp_data,
+ .render = path_render,
+ .selected = path_selected,
+ .cleanup = path_cleanup,
+ };
+
+ ++n;
+ }
+ break;
+ }
+ }
+
+ closedir(d);
+
+done:
+ free(path);
+ free(p1);
+ free(p2);
+
+ qsort(completions, n, sizeof(struct completion), cmp_path_completions);
+ ctx.add_completions(completions, n);
+
+ free(completions);
+}
+
+struct completion_provider
+create_path_provider(void (*on_complete_path)(void)) {
+ return (struct completion_provider){
+ .name = "path",
+ .complete = path_complete,
+ .userdata = on_complete_path,
+ };
+}
diff --git a/src/main/completion/path.h b/src/main/completion/path.h
new file mode 100644
index 0000000..407cae7
--- /dev/null
+++ b/src/main/completion/path.h
@@ -0,0 +1,14 @@
+#ifndef _MAIN_COMPLETION_PATH_H
+#define _MAIN_COMPLETION_PATH_H
+
+#include "main/completion.h"
+
+/**
+ * Create a new path completion provider.
+ *
+ * This provider completes filesystem paths.
+ * @returns A filesystem path @ref completion_provider.
+ */
+struct completion_provider create_path_provider(void (*on_complete_path)(void));
+
+#endif
diff --git a/src/main/frame-hooks.c b/src/main/frame-hooks.c
new file mode 100644
index 0000000..ae7bc1e
--- /dev/null
+++ b/src/main/frame-hooks.c
@@ -0,0 +1,27 @@
+#include "frame-hooks.h"
+
+#include "dged/hook.h"
+
+HOOK_IMPL_NO_REMOVE(next_frame, next_frame_cb);
+
+static next_frame_hook_vec g_next_frame_hooks;
+static uint32_t g_next_frame_hook_id;
+
+void init_frame_hooks(void) { VEC_INIT(&g_next_frame_hooks, 16); }
+
+void teardown_frame_hooks(void) { VEC_DESTROY(&g_next_frame_hooks); }
+
+void run_next_frame(next_frame_cb callback, void *userdata) {
+ insert_next_frame_hook(&g_next_frame_hooks, &g_next_frame_hook_id, callback,
+ userdata);
+}
+
+size_t dispatch_next_frame_hooks() {
+ size_t nhooks = VEC_SIZE(&g_next_frame_hooks);
+ if (nhooks > 0) {
+ dispatch_hook_no_args(&g_next_frame_hooks, struct next_frame_hook);
+ VEC_CLEAR(&g_next_frame_hooks);
+ }
+
+ return nhooks;
+}
diff --git a/src/main/frame-hooks.h b/src/main/frame-hooks.h
new file mode 100644
index 0000000..fc382fc
--- /dev/null
+++ b/src/main/frame-hooks.h
@@ -0,0 +1,13 @@
+#ifndef _FRAME_HOOKS_H
+#define _FRAME_HOOKS_H
+
+#include <stddef.h>
+
+typedef void (*next_frame_cb)(void *);
+
+void init_frame_hooks(void);
+void teardown_frame_hooks(void);
+void run_next_frame(next_frame_cb callback, void *userdata);
+size_t dispatch_next_frame_hooks(void);
+
+#endif
diff --git a/src/main/lsp.c b/src/main/lsp.c
index d56ca07..5886ea7 100644
--- a/src/main/lsp.c
+++ b/src/main/lsp.c
@@ -1,38 +1,556 @@
#include "lsp.h"
+#include "dged/binding.h"
#include "dged/buffer.h"
+#include "dged/buffer_view.h"
#include "dged/buffers.h"
+#include "dged/command.h"
+#include "dged/display.h"
#include "dged/hash.h"
#include "dged/hashmap.h"
-#include "dged/lsp.h"
+#include "dged/lang.h"
#include "dged/minibuffer.h"
#include "dged/reactor.h"
#include "dged/settings.h"
+#include "dged/window.h"
-HASHMAP_ENTRY_TYPE(lsp_entry, struct lsp *);
+#include "lsp/references.h"
+#include "main/bindings.h"
+#include "main/completion.h"
-HASHMAP(struct lsp_entry) g_lsp_clients;
+#include "lsp/actions.h"
+#include "lsp/choice-buffer.h"
+#include "lsp/completion.h"
+#include "lsp/diagnostics.h"
+#include "lsp/format.h"
+#include "lsp/goto.h"
+#include "lsp/help.h"
+#include "lsp/rename.h"
+
+struct lsp_pending_request {
+ uint64_t request_id;
+ response_handler handler;
+ void *userdata;
+};
+
+struct lsp_server {
+ struct lsp *lsp;
+ uint32_t restarts;
+ struct s8 lang_id;
+ struct lsp_pending_request pending_requests[16];
+
+ bool initialized;
+
+ enum text_document_sync_kind sync_kind;
+ bool send_open_close;
+ bool send_save;
+
+ enum position_encoding_kind position_encoding;
+
+ struct lsp_diagnostics *diagnostics;
+ struct completion_ctx *completion_ctx;
+};
+
+HASHMAP_ENTRY_TYPE(lsp_entry, struct lsp_server);
+
+static struct lsp_data {
+ HASHMAP(struct lsp_entry) clients;
+ struct keymap keymap;
+ struct keymap all_keymap;
-static struct create_data {
struct reactor *reactor;
struct buffers *buffers;
-} g_create_data;
-static void log_message(int type, struct s8 msg) {
- (void)type;
- message("%s", msg);
+ struct buffer *current_diagnostic_buffer;
+ uint64_t current_request_id;
+} g_lsp_data;
+
+struct lsp *lsp_backend(struct lsp_server *server) { return server->lsp; }
+
+struct lsp_server *lsp_server_for_lang_id(const char *id) {
+ HASHMAP_GET(&g_lsp_data.clients, struct lsp_entry, id,
+ struct lsp_server * server);
+ return server;
}
-static void create_lsp_client(struct buffer *buffer, void *userdata) {
+static uint32_t bytepos_to_column(struct text_chunk *text, uint32_t bytecol) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(text->text, text->nbytes, 0);
+ uint32_t ncols = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL && col < bytecol) {
+ col += codepoint->nbytes;
+ ncols += unicode_visual_char_width(codepoint);
+ }
+
+ return ncols;
+}
+
+static uint32_t codepoint_pos_to_column(struct text_chunk *text,
+ uint32_t codepoint_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(text->text, text->nbytes, 0);
+ uint32_t ncols = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL &&
+ col < codepoint_col) {
+ ++col;
+ ncols += unicode_visual_char_width(codepoint);
+ }
+
+ return ncols;
+}
+
+static uint32_t codeunit_pos_to_column(struct text_chunk *text,
+ uint32_t codeunit_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(text->text, text->nbytes, 0);
+ uint32_t ncols = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL &&
+ col < codeunit_col) {
+ col += codepoint->codepoint >= 0x010000 ? 2 : 1;
+ ncols += unicode_visual_char_width(codepoint);
+ }
+
+ return ncols;
+}
+
+struct region lsp_range_to_coordinates(struct lsp_server *server,
+ struct buffer *buffer,
+ struct region range) {
+
+ uint32_t (*col_converter)(struct text_chunk *, uint32_t) =
+ codeunit_pos_to_column;
+
+ switch (server->position_encoding) {
+ case PositionEncoding_Utf8:
+ col_converter = bytepos_to_column;
+ break;
+
+ case PositionEncoding_Utf16:
+ col_converter = codeunit_pos_to_column;
+ break;
+
+ case PositionEncoding_Utf32:
+ col_converter = codepoint_pos_to_column;
+ break;
+ }
+
+ struct region reg = range;
+ struct text_chunk beg_line = buffer_line(buffer, range.begin.line);
+ reg.begin.col = col_converter(&beg_line, range.begin.col);
+
+ struct text_chunk end_line = beg_line;
+ if (range.begin.line != range.end.line) {
+ end_line = buffer_line(buffer, range.end.line);
+ }
+ reg.end.col = col_converter(&end_line, range.end.col);
+
+ if (beg_line.allocated) {
+ free(beg_line.text);
+ }
+
+ if (range.begin.line != range.end.line && end_line.allocated) {
+ free(end_line.text);
+ }
+
+ return reg;
+}
+
+uint64_t new_pending_request(struct lsp_server *server,
+ response_handler handler, void *userdata) {
+ for (int i = 0; i < 16; ++i) {
+ if (server->pending_requests[i].request_id == (uint64_t)-1) {
+ ++g_lsp_data.current_request_id;
+ server->pending_requests[i].request_id = g_lsp_data.current_request_id;
+ server->pending_requests[i].handler = handler;
+ server->pending_requests[i].userdata = userdata;
+ return g_lsp_data.current_request_id;
+ }
+ }
+
+ return -1;
+}
+
+static bool
+request_response_received(struct lsp_server *server, uint64_t id,
+ struct lsp_pending_request **pending_request) {
+ for (int i = 0; i < 16; ++i) {
+ if (server->pending_requests[i].request_id == id) {
+ server->pending_requests[i].request_id = (uint64_t)-1;
+ *pending_request = &server->pending_requests[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void buffer_updated(struct buffer *buffer, void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+
+ diagnostic_vec *diagnostics =
+ diagnostics_for_buffer(server->diagnostics, buffer);
+ if (diagnostics == NULL) {
+ return;
+ }
+
+ VEC_FOR_EACH(diagnostics, struct diagnostic * diag) {
+ struct text_property prop;
+ prop.type = TextProperty_Colors;
+ uint32_t color = diag_severity_color(diag->severity);
+ prop.data.colors = (struct text_property_colors){
+ .set_bg = true,
+ .set_fg = true,
+ .fg = Color_White,
+ .bg = color,
+ .underline = true,
+ };
+
+ struct region reg = region_new(
+ diag->region.begin, buffer_previous_char(buffer, diag->region.end));
+
+ buffer_add_text_property(buffer, reg.begin, reg.end, prop);
+
+ if (window_buffer(windows_get_active()) == buffer) {
+ struct buffer_view *bv = window_buffer_view(windows_get_active());
+
+ if (region_is_inside(diag->region, bv->dot)) {
+ size_t len = 0;
+ for (size_t i = 0; i < diag->message.l && diag->message.s[i] != '\n';
+ ++i) {
+ ++len;
+ }
+ minibuffer_display_timeout(1, "%s: %.*s",
+ diag_severity_to_str(diag->severity), len,
+ diag->message.s);
+ }
+ }
+ }
+}
+
+static void buffer_pre_save(struct buffer *buffer, void *userdata) {
+ (void)buffer;
(void)userdata;
+}
+
+static void format_on_save(struct buffer *buffer, struct lsp_server *server) {
+ struct setting *glob_fmt_on_save = settings_get("editor.format-on-save");
+ struct setting *fmt_on_save =
+ lang_setting(&buffer->lang, "language-server.format-on-save");
+
+ if ((glob_fmt_on_save != NULL && glob_fmt_on_save->value.data.bool_value) ||
+ (glob_fmt_on_save == NULL && fmt_on_save != NULL &&
+ fmt_on_save->value.data.bool_value)) {
+ format_document_save(server, buffer);
+ }
+}
+
+static void buffer_post_save(struct buffer *buffer, void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+ if (server->send_save) {
+ struct versioned_text_document_identifier text_document =
+ versioned_identifier_from_buffer(buffer);
- struct create_data *data = &g_create_data;
+ struct did_save_text_document_params params = {
+ .text_document =
+ (struct text_document_identifier){
+ .uri = text_document.uri,
+ },
+ };
+
+ struct s8 json_payload = did_save_text_document_params_to_json(&params);
+
+ lsp_send(server->lsp,
+ lsp_create_notification(s8("textDocument/didSave"), json_payload));
+
+ versioned_text_document_identifier_free(&text_document);
+ s8delete(json_payload);
+ }
+
+ format_on_save(buffer, server);
+}
+
+static uint32_t count_codepoints(struct text_chunk *chunk,
+ uint32_t target_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(chunk->text, chunk->nbytes, 0);
+ uint32_t ncodepoints = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL && col < target_col) {
+ col += unicode_visual_char_width(codepoint);
+ ++ncodepoints;
+ }
+
+ return ncodepoints;
+}
+
+static uint32_t count_codeunits(struct text_chunk *chunk, uint32_t target_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(chunk->text, chunk->nbytes, 0);
+ uint32_t ncodeunits = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL && col < target_col) {
+ col += unicode_visual_char_width(codepoint);
+ ncodeunits += codepoint->codepoint >= 0x010000 ? 2 : 1;
+ }
+
+ return ncodeunits;
+}
+
+static uint32_t count_bytes(struct text_chunk *chunk, uint32_t target_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(chunk->text, chunk->nbytes, 0);
+ uint32_t nbytes = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL && col < target_col) {
+ col += unicode_visual_char_width(codepoint);
+ nbytes += codepoint->nbytes;
+ }
+
+ return nbytes;
+}
+
+static struct region edit_location_to_lsp(struct buffer *buffer,
+ struct edit_location edit,
+ struct lsp_server *server) {
+
+ struct region res = edit.coordinates;
+ if (server->position_encoding == PositionEncoding_Utf8) {
+ /* In this case, the buffer hook has already
+ * done the job for us. */
+ res.begin.col = edit.bytes.begin.col;
+ res.end.col = edit.bytes.end.col;
+ return res;
+ }
+
+ return region_to_lsp(buffer, res, server);
+}
+
+struct region region_to_lsp(struct buffer *buffer, struct region region,
+ struct lsp_server *server) {
+ struct region res = region;
+
+ uint32_t (*col_counter)(struct text_chunk *, uint32_t) = count_codeunits;
+
+ switch (server->position_encoding) {
+ case PositionEncoding_Utf8:
+ col_counter = count_bytes;
+ return res;
+
+ case PositionEncoding_Utf16:
+ col_counter = count_codeunits;
+ break;
+
+ case PositionEncoding_Utf32:
+ col_counter = count_codepoints;
+ break;
+ }
+
+ struct text_chunk beg_line = buffer_line(buffer, region.begin.line);
+ res.begin.col = col_counter(&beg_line, region.begin.col);
+
+ struct text_chunk end_line = beg_line;
+ if (region.begin.line != region.end.line) {
+ end_line = buffer_line(buffer, region.end.line);
+ }
+
+ res.end.col = col_counter(&end_line, region.end.col);
+
+ if (beg_line.allocated) {
+ free(beg_line.text);
+ }
+
+ if (end_line.allocated && region.begin.line != region.end.line) {
+ free(end_line.text);
+ }
+
+ return res;
+}
+
+static void buffer_text_changed(struct buffer *buffer,
+ struct edit_location range, bool delete,
+ void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+
+ struct text_chunk new_text = {0};
+ switch (server->sync_kind) {
+ case TextDocumentSync_None:
+ return;
+
+ case TextDocumentSync_Full:
+ new_text =
+ buffer_region(buffer, region_new((struct location){.line = 0, .col = 0},
+ buffer_end(buffer)));
+ break;
+
+ case TextDocumentSync_Incremental:
+ if (!delete) {
+ new_text = buffer_region(buffer, range.coordinates);
+ }
+ break;
+ }
+
+ struct region reg = edit_location_to_lsp(buffer, range, server);
+ reg = delete ? reg : region_new(reg.begin, reg.begin);
+ struct text_document_content_change_event evt = {
+ .full_document = server->sync_kind == TextDocumentSync_Full,
+ .text = s8new((const char *)new_text.text, new_text.nbytes),
+ .range = reg,
+ };
+
+ struct versioned_text_document_identifier text_document =
+ versioned_identifier_from_buffer(buffer);
+ struct did_change_text_document_params params = {
+ .text_document = text_document,
+ .content_changes = &evt,
+ .ncontent_changes = 1,
+ };
+
+ struct s8 json_payload = did_change_text_document_params_to_json(&params);
+
+ lsp_send(server->lsp,
+ lsp_create_notification(s8("textDocument/didChange"), json_payload));
+
+ versioned_text_document_identifier_free(&text_document);
+ s8delete(json_payload);
+ s8delete(evt.text);
+
+ if (new_text.allocated) {
+ free(new_text.text);
+ }
+}
+
+static void buffer_text_inserted(struct buffer *buffer,
+ struct edit_location inserted,
+ void *userdata) {
+ buffer_text_changed(buffer, inserted, false, userdata);
+}
+
+static void buffer_text_deleted(struct buffer *buffer,
+ struct edit_location deleted, void *userdata) {
+ buffer_text_changed(buffer, deleted, true, userdata);
+}
+
+static void send_did_open(struct lsp_server *server, struct buffer *buffer) {
+ if (!server->send_open_close) {
+ return;
+ }
+
+ struct text_document_item doc = text_document_item_from_buffer(buffer);
+ struct did_open_text_document_params params = {
+ .text_document = doc,
+ };
+
+ struct s8 json_payload = did_open_text_document_params_to_json(&params);
+ lsp_send(server->lsp,
+ lsp_create_notification(s8("textDocument/didOpen"), json_payload));
+
+ text_document_item_free(&doc);
+ s8delete(json_payload);
+}
+
+static void setup_completion(struct lsp_server *server, struct buffer *buffer) {
+ if (server->completion_ctx != NULL) {
+ enable_completion_for_buffer(server->completion_ctx, buffer);
+ }
+}
+
+static void lsp_buffer_initialized(struct lsp_server *server,
+ struct buffer *buffer) {
+ if (s8eq(server->lang_id, s8(buffer->lang.id))) {
+ /* Needs to be a pre-delete hook since we need
+ * access to the deleted content to derive the
+ * correct UTF-8/16/32 position.
+ */
+ buffer_add_pre_delete_hook(buffer, buffer_text_deleted, server);
+ buffer_add_insert_hook(buffer, buffer_text_inserted, server);
+ buffer_add_update_hook(buffer, buffer_updated, server);
+ buffer_add_pre_save_hook(buffer, buffer_pre_save, server);
+ buffer_add_post_save_hook(buffer, buffer_post_save, server);
+
+ send_did_open(server, buffer);
+ setup_completion(server, buffer);
+ }
+}
+
+static void apply_initialized(struct buffer *buffer, void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+ lsp_buffer_initialized(server, buffer);
+}
+
+static void handle_initialize(struct lsp_server *server,
+ struct lsp_response *response, void *userdata) {
+ (void)userdata;
+
+ struct initialize_result res =
+ initialize_result_from_json(&response->value.result);
+ message("lsp server initialized: %.*s (%.*s)", res.server_info.name.l,
+ res.server_info.name.s, res.server_info.version.l,
+ res.server_info.version.s);
+
+ lsp_send(server->lsp, lsp_create_notification(s8("initialized"), s8("")));
+
+ struct text_document_sync *tsync = &res.capabilities.text_document_sync;
+ server->sync_kind = tsync->kind;
+ server->send_open_close = tsync->open_close;
+ server->send_save = tsync->save;
+ server->position_encoding = res.capabilities.position_encoding;
+
+ if (res.capabilities.supports_completion) {
+ struct completion_options *comp_opts = &res.capabilities.completion_options;
+ server->completion_ctx = create_completion_ctx(
+ server, (triggerchar_vec *)&comp_opts->trigger_characters);
+ }
+
+ initialize_result_free(&res);
+ buffers_for_each(g_lsp_data.buffers, apply_initialized, server);
+
+ server->initialized = true;
+}
+
+static void init_lsp_client(struct lsp_server *server) {
+ if (lsp_restart_server(server->lsp) < 0) {
+ minibuffer_echo("failed to start language server %s process.",
+ lsp_server_name(server->lsp));
+ return;
+ }
+
+ // send some init info
+ struct initialize_params params = {
+ .process_id = lsp_server_pid(server->lsp),
+ .client_info =
+ {
+ .name = s8("dged"),
+ .version = s8("dev"),
+ },
+ .client_capabilities = {},
+ .workspace_folders = NULL,
+ .nworkspace_folders = 0,
+ };
+
+ uint64_t id = new_pending_request(server, handle_initialize, NULL);
+ struct s8 json_payload = initialize_params_to_json(&params);
+ lsp_send(server->lsp, lsp_create_request(id, s8("initialize"), json_payload));
+
+ s8delete(json_payload);
+}
+
+static void create_lsp_client(struct buffer *buffer, void *userdata) {
+ (void)userdata;
const char *id = buffer->lang.id;
- HASHMAP_GET(&g_lsp_clients, struct lsp_entry, id, struct lsp * *lsp);
- if (lsp == NULL) {
+ HASHMAP_GET(&g_lsp_data.clients, struct lsp_entry, id,
+ struct lsp_server * server);
+ if (server == NULL) {
// we need to start a new server
- struct setting *s = lang_setting(&buffer->lang, "language-server");
- if (!s) { // no language server set
+ struct setting *s = lang_setting(&buffer->lang, "language-server.command");
+ if (!s) {
+ if (!lang_is_fundamental(&buffer->lang)) {
+ message("No language server set for %s. Set with "
+ "`languages.%s.language-server`.",
+ buffer->lang.id, buffer->lang.id);
+ }
return;
}
@@ -40,69 +558,388 @@ static void create_lsp_client(struct buffer *buffer, void *userdata) {
char bufname[1024] = {0};
snprintf(bufname, 1024, "*%s-lsp-stderr*", command[0]);
- struct buffer *stderr_buf = buffers_find(data->buffers, bufname);
+ struct buffer *stderr_buf = buffers_find(g_lsp_data.buffers, bufname);
if (stderr_buf == NULL) {
struct buffer buf = buffer_create(bufname);
buf.lazy_row_add = false;
- stderr_buf = buffers_add(data->buffers, buf);
+ stderr_buf = buffers_add(g_lsp_data.buffers, buf);
buffer_set_readonly(stderr_buf, true);
}
- struct lsp_client client_impl = {
- .log_message = log_message,
- };
struct lsp *new_lsp =
- lsp_create(command, data->reactor, stderr_buf, client_impl, NULL);
+ lsp_create(command, g_lsp_data.reactor, stderr_buf, command[0]);
if (new_lsp == NULL) {
minibuffer_echo("failed to create language server %s", command[0]);
- buffers_remove(data->buffers, bufname);
+ buffers_remove(g_lsp_data.buffers, bufname);
return;
}
- HASHMAP_APPEND(&g_lsp_clients, struct lsp_entry, id,
+ HASHMAP_APPEND(&g_lsp_data.clients, struct lsp_entry, id,
struct lsp_entry * new);
- new->value = new_lsp;
-
- if (lsp_start_server(new_lsp) < 0) {
- minibuffer_echo("failed to start language server %s process.",
- lsp_server_name(new_lsp));
- return;
+ new->value = (struct lsp_server){
+ .lsp = new_lsp,
+ .lang_id = s8new(id, strlen(id)),
+ .restarts = 0,
+ };
+ for (int i = 0; i < 16; ++i) {
+ new->value.pending_requests[i].request_id = (uint64_t)-1;
}
+
+ new->value.diagnostics = diagnostics_create();
+
+ // support for this is determined later
+ new->value.completion_ctx = NULL;
+
+ init_lsp_client(&new->value);
+ server = &new->value;
+ }
+
+ /* An lsp for the language for this buffer is already started.
+ * if server is not initialized, it will get picked
+ * up anyway when handling the initialize response. */
+ if (server->initialized) {
+ lsp_buffer_initialized(server, buffer);
}
}
-static void set_default_lsp(const char *lang_id, const char *server) {
+static void set_default_lsp(const char *lang_id, const char *command) {
struct language l = lang_from_id(lang_id);
if (!lang_is_fundamental(&l)) {
lang_setting_set_default(
- &l, "language-server",
+ &l, "language-server.command",
(struct setting_value){.type = Setting_String,
- .data.string_value = (char *)server});
+ .data.string_value = (char *)command});
+ lang_setting_set_default(
+ &l, "language-server.format-on-save",
+ (struct setting_value){.type = Setting_Bool, .data.bool_value = false});
+
lang_destroy(&l);
}
}
-void lang_servers_init(struct reactor *reactor, struct buffers *buffers) {
- HASHMAP_INIT(&g_lsp_clients, 32, hash_name);
+static struct s8 lsp_modeline(struct buffer_view *view, void *userdata) {
+ (void)userdata;
+ struct lsp_server *server = lsp_server_for_lang_id(view->buffer->lang.id);
+ if (server == NULL) {
+ return s8("");
+ }
+
+ return s8from_fmt(
+ "lsp: %s:%d", lsp_server_name(server->lsp),
+ lsp_server_running(server->lsp) ? lsp_server_pid(server->lsp) : 0);
+}
+
+static uint32_t lsp_keymap_hook(struct buffer *buffer, struct keymap *keymaps[],
+ uint32_t max_nkeymaps, void *userdata) {
+ (void)userdata;
+
+ if (max_nkeymaps < 2) {
+ return 0;
+ }
+
+ uint32_t nadded = 1;
+ keymaps[0] = &g_lsp_data.all_keymap;
+
+ if (lsp_server_for_lang_id(buffer->lang.id) != NULL) {
+ keymaps[1] = &g_lsp_data.keymap;
+ nadded = 2;
+ }
+
+ return nadded;
+}
+
+static int32_t lsp_restart_cmd(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)ctx;
+ (void)argc;
+ (void)argv;
+ struct buffer *b = window_buffer(windows_get_active());
+
+ struct lsp_server *server = lsp_server_for_buffer(b);
+ if (server == NULL) {
+ return 0;
+ }
+
+ lsp_restart_server(server->lsp);
+ return 0;
+}
+
+void lang_servers_init(struct reactor *reactor, struct buffers *buffers,
+ struct commands *commands) {
+ HASHMAP_INIT(&g_lsp_data.clients, 32, hash_name);
set_default_lsp("c", "clangd");
+ set_default_lsp("cxx", "clangd");
set_default_lsp("rs", "rust-analyzer");
set_default_lsp("python", "pylsp");
- g_create_data.reactor = reactor;
- g_create_data.buffers = buffers;
- buffer_add_create_hook(create_lsp_client, NULL);
+ g_lsp_data.current_request_id = 0;
+ g_lsp_data.reactor = reactor;
+ g_lsp_data.buffers = buffers;
+ buffers_add_add_hook(buffers, create_lsp_client, NULL);
+
+ struct command lsp_commands[] = {
+ {.name = "lsp-goto-definition", .fn = lsp_goto_def_cmd},
+ {.name = "lsp-goto-declaration", .fn = lsp_goto_decl_cmd},
+ {.name = "lsp-goto-implementation", .fn = lsp_goto_impl_cmd},
+ {.name = "lsp-goto", .fn = lsp_goto_cmd},
+ {.name = "lsp-goto-previous", .fn = lsp_goto_previous_cmd},
+ {.name = "lsp-references", .fn = lsp_references_cmd},
+ {.name = "lsp-restart", .fn = lsp_restart_cmd},
+ {.name = "lsp-diagnostics", .fn = diagnostics_cmd},
+ {.name = "lsp-next-diagnostic", .fn = next_diagnostic_cmd},
+ {.name = "lsp-prev-diagnostic", .fn = prev_diagnostic_cmd},
+ {.name = "lsp-code-actions", .fn = code_actions_cmd},
+ {.name = "lsp-format", .fn = format_cmd},
+ {.name = "lsp-rename", .fn = lsp_rename_cmd},
+ {.name = "lsp-help", .fn = lsp_help_cmd},
+ };
+
+ register_commands(commands, lsp_commands,
+ sizeof(lsp_commands) / sizeof(lsp_commands[0]));
+
+ struct binding lsp_binds[] = {
+ BINDING(Meta, '.', "lsp-goto-definition"),
+ BINDING(Meta, '/', "lsp-goto"),
+ BINDING(Meta, '[', "lsp-prev-diagnostic"),
+ BINDING(Meta, ']', "lsp-next-diagnostic"),
+ BINDING(Meta, 'a', "lsp-code-actions"),
+ BINDING(Meta, '=', "lsp-format"),
+ BINDING(Meta, 'r', "lsp-rename"),
+ BINDING(Meta, 'h', "lsp-help"),
+ };
+
+ struct binding global_binds[] = {
+ BINDING(Meta, ',', "lsp-goto-previous"),
+ };
+
+ g_lsp_data.keymap = keymap_create("lsp", 32);
+ keymap_bind_keys(&g_lsp_data.keymap, lsp_binds,
+ sizeof(lsp_binds) / sizeof(lsp_binds[0]));
+ g_lsp_data.all_keymap = keymap_create("lsp-global", 32);
+ keymap_bind_keys(&g_lsp_data.all_keymap, global_binds,
+ sizeof(global_binds) / sizeof(global_binds[0]));
+ buffer_add_keymaps_hook(lsp_keymap_hook, NULL);
+
+ buffer_view_add_modeline_hook(lsp_modeline, NULL);
+
+ init_goto(32, buffers);
+}
+
+void apply_edits_buffer(struct lsp_server *server, struct buffer *buffer,
+ text_edit_vec edits, struct location *point) {
+ VEC_FOR_EACH_REVERSE(&edits, struct text_edit * edit) {
+ struct region reg = lsp_range_to_coordinates(server, buffer, edit->range);
+ struct location at = reg.end;
+ if (region_has_size(reg)) {
+ if (point != NULL) {
+
+ if (reg.end.line == point->line) {
+ point->col -= reg.end.col > point->col ? 0 : point->col - reg.end.col;
+ }
+
+ uint64_t lines_deleted = reg.end.line - reg.begin.line;
+ if (lines_deleted > 0 && reg.end.line <= point->line) {
+ point->line -= lines_deleted;
+ }
+ }
+ at = buffer_delete(buffer, reg);
+ }
+
+ struct location after =
+ buffer_add(buffer, at, edit->new_text.s, edit->new_text.l);
+ if (point != NULL) {
+ if (after.line == point->line) {
+ point->col += after.col;
+ }
+
+ uint64_t lines_added = after.line - at.line;
+ if (lines_added > 0 && after.line <= point->line) {
+ point->line += lines_added;
+ }
+ }
+ }
+}
+
+bool apply_edits(struct lsp_server *server,
+ const struct workspace_edit *ws_edit) {
+ pause_completion();
+
+ VEC_FOR_EACH(&ws_edit->changes, struct text_edit_pair * pair) {
+ if (VEC_EMPTY(&pair->edits)) {
+ continue;
+ }
+
+ const char *p = s8tocstr(pair->uri);
+ struct buffer *b = buffers_find_by_filename(g_lsp_data.buffers, &p[7]);
+
+ if (b == NULL) {
+ struct buffer new_buf = buffer_from_file(&p[7]);
+ b = buffers_add(g_lsp_data.buffers, new_buf);
+ }
+
+ free((void *)p);
+ buffer_push_undo_boundary(b);
+ apply_edits_buffer(server, b, pair->edits, NULL);
+ buffer_push_undo_boundary(b);
+ }
+
+ resume_completion();
+ return true;
+}
+
+static void handle_request(struct lsp_server *server,
+ struct lsp_request request) {
+
+ struct s8 method = unescape_json_string(request.method);
+ if (s8eq(method, s8("workspace/applyEdit"))) {
+ struct workspace_edit ws_edit = workspace_edit_from_json(&request.params);
+ apply_edits(server, &ws_edit);
+ workspace_edit_free(&ws_edit);
+ } else {
+ message("unhandled lsp request (%s): id %d: %.*s",
+ lsp_server_name(server->lsp), request.id, request.method.l,
+ request.method.s);
+ }
+
+ s8delete(method);
+}
+
+static void handle_response(struct lsp_server *server,
+ struct lsp_response response) {
+ if (response.ok) {
+ struct lsp_pending_request *pending = NULL;
+ if (!request_response_received(server, response.id, &pending)) {
+ message("received response for id %d, server %s, which has no handler "
+ "registered",
+ response.id, lsp_server_name(server->lsp));
+ }
+
+ if (pending->handler != NULL) {
+ pending->handler(server, &response, pending->userdata);
+ }
+ } else {
+ struct s8 errmsg = response.value.error.message;
+ minibuffer_echo("lsp error (%s), id %d: %.*s", lsp_server_name(server->lsp),
+ response.id, errmsg.l, errmsg.s);
+ }
+}
+
+static void handle_notification(struct lsp_server *server,
+ struct lsp_notification notification) {
+ struct s8 method = unescape_json_string(notification.method);
+ if (s8eq(method, s8("textDocument/publishDiagnostics"))) {
+ handle_publish_diagnostics(server, g_lsp_data.buffers, &notification);
+ }
+
+ s8delete(method);
+}
+
+#define MAX_RESTARTS 10
+
+static void restart_if_needed(struct lsp_server *server) {
+ // if we successfully initialized the server, we can be sure
+ // it is up and running
+ if (lsp_server_running(server->lsp) && server->initialized) {
+ server->restarts = 0;
+ return;
+ }
+
+ if (!lsp_server_running(server->lsp)) {
+ if (server->restarts < MAX_RESTARTS) {
+ message("restarting \"%s\" (%d/%d)...", lsp_server_name(server->lsp),
+ server->restarts + 1, MAX_RESTARTS);
+ init_lsp_client(server);
+ ++server->restarts;
+
+ if (server->restarts == MAX_RESTARTS) {
+ minibuffer_echo("lsp \"%s\" has crashed %d times, giving up...",
+ lsp_server_name(server->lsp), MAX_RESTARTS);
+ }
+ } else {
+ // server is crashed and can only be restarted manually now
+ lsp_stop_server(server->lsp);
+ }
+ }
}
void lang_servers_update(void) {
- HASHMAP_FOR_EACH(&g_lsp_clients, struct lsp_entry * e) {
- lsp_update(e->value, NULL, 0);
+
+ HASHMAP_FOR_EACH(&g_lsp_data.clients, struct lsp_entry * e) {
+ restart_if_needed(&e->value);
+
+ struct lsp_message msgs[128];
+ uint32_t msgs_received = lsp_update(e->value.lsp, msgs, 128);
+
+ if (msgs_received == 0 || msgs_received == (uint32_t)-1) {
+ continue;
+ }
+
+ char bufname[1024] = {0};
+ snprintf(bufname, 1024, "*%s-lsp-messages*", lsp_server_name(e->value.lsp));
+ struct buffer *output_buf = buffers_find(g_lsp_data.buffers, bufname);
+ if (output_buf == NULL) {
+ struct buffer buf = buffer_create(bufname);
+ buf.lazy_row_add = false;
+ output_buf = buffers_add(g_lsp_data.buffers, buf);
+ }
+
+ buffer_set_readonly(output_buf, false);
+ for (uint32_t mi = 0; mi < msgs_received; ++mi) {
+ struct lsp_message *msg = &msgs[mi];
+ buffer_add(output_buf, buffer_end(output_buf), msg->payload.s,
+ msg->payload.l);
+ buffer_add(output_buf, buffer_end(output_buf), (uint8_t *)"\n", 1);
+
+ switch (msg->type) {
+ case Lsp_Response:
+ handle_response(&e->value, msg->message.response);
+ break;
+
+ case Lsp_Request:
+ handle_request(&e->value, msg->message.request);
+ break;
+
+ case Lsp_Notification:
+ handle_notification(&e->value, msg->message.notification);
+ break;
+ }
+
+ lsp_message_destroy(msg);
+ }
+
+ buffer_set_readonly(output_buf, true);
}
}
+static void lang_server_teardown(struct lsp_server *server) {
+ destroy_goto();
+ lsp_stop_server(server->lsp);
+ lsp_destroy(server->lsp);
+ s8delete(server->lang_id);
+}
+
void lang_servers_teardown(void) {
- HASHMAP_FOR_EACH(&g_lsp_clients, struct lsp_entry * e) {
- lsp_stop_server(e->value);
+ HASHMAP_FOR_EACH(&g_lsp_data.clients, struct lsp_entry * e) {
+ diagnostics_destroy(e->value.diagnostics);
+
+ if (e->value.completion_ctx != NULL) {
+ destroy_completion_ctx(e->value.completion_ctx);
+ }
+
+ lang_server_teardown(&e->value);
}
+
+ keymap_destroy(&g_lsp_data.keymap);
+ keymap_destroy(&g_lsp_data.all_keymap);
+ HASHMAP_DESTROY(&g_lsp_data.clients);
+}
+
+struct lsp_server *lsp_server_for_buffer(struct buffer *buffer) {
+ return lsp_server_for_lang_id(buffer->lang.id);
+}
+
+struct lsp_diagnostics *lsp_server_diagnostics(struct lsp_server *server) {
+ return server->diagnostics;
}
diff --git a/src/main/lsp.h b/src/main/lsp.h
index 736282d..27d8c93 100644
--- a/src/main/lsp.h
+++ b/src/main/lsp.h
@@ -1,11 +1,53 @@
#ifndef _MAIN_LSP_H
#define _MAIN_LSP_H
+#include <stddef.h>
+
+#include "dged/location.h"
+#include "dged/lsp.h"
+#include "dged/s8.h"
+#include "dged/vec.h"
+
+#include "lsp/types.h"
+
struct reactor;
struct buffers;
+struct commands;
-void lang_servers_init(struct reactor *reactor, struct buffers *buffers);
+void lang_servers_init(struct reactor *reactor, struct buffers *buffers,
+ struct commands *commands);
void lang_servers_update(void);
void lang_servers_teardown(void);
+struct lsp_server;
+struct buffer;
+struct workspace_edit;
+
+struct lsp_server *lsp_server_for_lang_id(const char *id);
+struct lsp_server *lsp_server_for_buffer(struct buffer *buffer);
+
+void lsp_server_reload(struct lsp_server *server);
+void lsp_server_shutdown(struct lsp_server *server);
+struct lsp *lsp_backend(struct lsp_server *server);
+
+bool apply_edits(struct lsp_server *server,
+ const struct workspace_edit *ws_edit);
+
+void apply_edits_buffer(struct lsp_server *, struct buffer *, text_edit_vec,
+ struct location *);
+
+typedef void (*response_handler)(struct lsp_server *, struct lsp_response *,
+ void *);
+uint64_t new_pending_request(struct lsp_server *server,
+ response_handler handler, void *userdata);
+
+struct region lsp_range_to_coordinates(struct lsp_server *server,
+ struct buffer *buffer,
+ struct region range);
+
+struct region region_to_lsp(struct buffer *buffer, struct region region,
+ struct lsp_server *server);
+
+struct lsp_diagnostics *lsp_server_diagnostics(struct lsp_server *server);
+
#endif
diff --git a/src/main/lsp/actions.c b/src/main/lsp/actions.c
new file mode 100644
index 0000000..ea792a1
--- /dev/null
+++ b/src/main/lsp/actions.c
@@ -0,0 +1,129 @@
+#include "actions.h"
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/lsp.h"
+#include "dged/minibuffer.h"
+#include "dged/window.h"
+#include "main/lsp.h"
+#include "main/lsp/diagnostics.h"
+
+#include "choice-buffer.h"
+#include "types.h"
+
+static struct code_actions g_code_actions_result = {};
+
+static void code_action_command_selected(void *selected, void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+ struct lsp_command *command = (struct lsp_command *)selected;
+ struct s8 json_payload = lsp_command_to_json(command);
+
+ uint64_t id = new_pending_request(server, NULL, NULL);
+ lsp_send(
+ lsp_backend(server),
+ lsp_create_request(id, s8("workspace/executeCommand"), json_payload));
+
+ s8delete(json_payload);
+}
+
+static void code_action_selected(void *selected, void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+ struct code_action *action = (struct code_action *)selected;
+
+ if (action->has_edit) {
+ apply_edits(server, &action->edit);
+ }
+
+ if (action->has_command) {
+ struct s8 json_payload = lsp_command_to_json(&action->command);
+
+ uint64_t id = new_pending_request(server, NULL, NULL);
+ lsp_send(
+ lsp_backend(server),
+ lsp_create_request(id, s8("workspace/executeCommand"), json_payload));
+ s8delete(json_payload);
+ }
+}
+
+static void code_action_closed(void *userdata) {
+ (void)userdata;
+ lsp_code_actions_free(&g_code_actions_result);
+}
+
+static void handle_code_actions_response(struct lsp_server *server,
+ struct lsp_response *response,
+ void *userdata) {
+ struct code_actions actions =
+ lsp_code_actions_from_json(&response->value.result);
+
+ struct buffers *buffers = (struct buffers *)userdata;
+
+ if (VEC_SIZE(&actions.commands) == 0 &&
+ VEC_SIZE(&actions.code_actions) == 0) {
+ minibuffer_echo_timeout(4, "no code actions available");
+ lsp_code_actions_free(&actions);
+ } else {
+ g_code_actions_result = actions;
+ struct choice_buffer *buf =
+ choice_buffer_create(s8("Code Actions"), buffers, code_action_selected,
+ code_action_closed, NULL, server);
+
+ VEC_FOR_EACH(&actions.code_actions, struct code_action * action) {
+ struct s8 line =
+ s8from_fmt("%.*s, (%.*s)", action->title.l, action->title.s,
+ action->kind.l, action->kind.s);
+ choice_buffer_add_choice_with_callback(buf, line, action,
+ code_action_selected);
+ s8delete(line);
+ }
+
+ VEC_FOR_EACH(&actions.commands, struct lsp_command * command) {
+ struct s8 line = s8from_fmt("%.*s", command->title.l, command->title.s);
+ choice_buffer_add_choice_with_callback(buf, line, command,
+ code_action_command_selected);
+ s8delete(line);
+ }
+ }
+}
+
+int32_t code_actions_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(windows_get_active());
+
+ struct lsp_server *server = lsp_server_for_lang_id(bv->buffer->lang.id);
+ if (server == NULL) {
+ return 0;
+ }
+
+ uint64_t id =
+ new_pending_request(server, handle_code_actions_response, ctx.buffers);
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(bv->buffer);
+ struct code_action_params params = {
+ .text_document.uri = doc.uri,
+ .range = region_new(bv->dot, bv->dot),
+ };
+
+ VEC_INIT(&params.context.diagnostics, 8);
+
+ diagnostic_vec *d =
+ diagnostics_for_buffer(lsp_server_diagnostics(server), bv->buffer);
+ if (d != NULL) {
+ VEC_FOR_EACH(d, struct diagnostic * diag) {
+ if (location_is_between(bv->dot, diag->region.begin, diag->region.end)) {
+ VEC_PUSH(&params.context.diagnostics, *diag);
+ }
+ }
+ }
+
+ struct s8 json_payload = code_action_params_to_json(&params);
+ lsp_send(lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/codeAction"), json_payload));
+
+ VEC_DESTROY(&params.context.diagnostics);
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+ return 0;
+}
diff --git a/src/main/lsp/actions.h b/src/main/lsp/actions.h
new file mode 100644
index 0000000..59b4d36
--- /dev/null
+++ b/src/main/lsp/actions.h
@@ -0,0 +1,10 @@
+#ifndef _ACTIONS_H
+#define _ACTIONS_H
+
+#include <stdint.h>
+
+#include "dged/command.h"
+
+int32_t code_actions_cmd(struct command_ctx, int, const char **);
+
+#endif
diff --git a/src/main/lsp/choice-buffer.c b/src/main/lsp/choice-buffer.c
new file mode 100644
index 0000000..44186bd
--- /dev/null
+++ b/src/main/lsp/choice-buffer.c
@@ -0,0 +1,201 @@
+#include "choice-buffer.h"
+
+#include "dged/binding.h"
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/buffers.h"
+#include "dged/command.h"
+#include "dged/display.h"
+#include "dged/location.h"
+
+#include "main/bindings.h"
+
+struct choice {
+ struct region region;
+ void *data;
+ select_callback callback;
+};
+
+struct choice_buffer {
+ struct buffers *buffers;
+ struct buffer *buffer;
+ VEC(struct choice) choices;
+
+ abort_callback abort_cb;
+ select_callback select_cb;
+ update_callback update_cb;
+ void *userdata;
+
+ uint32_t buffer_removed_hook;
+
+ struct command enter_pressed;
+ struct command q_pressed;
+};
+
+static void delete_choice_buffer(struct choice_buffer *buffer,
+ bool delete_underlying);
+
+static void underlying_buffer_destroyed(struct buffer *buffer,
+ void *choice_buffer) {
+ (void)buffer;
+ struct choice_buffer *cb = (struct choice_buffer *)choice_buffer;
+
+ // run this with false since the underlying buffer is already
+ // being deleted
+ delete_choice_buffer(cb, false);
+}
+
+static int32_t enter_pressed_fn(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)argc;
+ (void)argv;
+ struct choice_buffer *cb = (struct choice_buffer *)ctx.userdata;
+ struct window *w = window_find_by_buffer(cb->buffer);
+ if (w == NULL) {
+ return 0;
+ }
+
+ struct buffer_view *bv = window_buffer_view(w);
+
+ VEC_FOR_EACH(&cb->choices, struct choice * choice) {
+ if (location_is_between(bv->dot, choice->region.begin,
+ choice->region.end)) {
+ if (choice->callback != NULL) {
+ choice->callback(choice->data, cb->userdata);
+ } else {
+ cb->select_cb(choice->data, cb->userdata);
+ }
+
+ delete_choice_buffer(cb, true);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int32_t choice_buffer_close_fn(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct choice_buffer *cb = (struct choice_buffer *)ctx.userdata;
+ delete_choice_buffer(cb, true);
+ return 0;
+}
+
+struct choice_buffer *
+choice_buffer_create(struct s8 title, struct buffers *buffers,
+ select_callback selected, abort_callback aborted,
+ update_callback update, void *userdata) {
+
+ struct choice_buffer *b = calloc(1, sizeof(struct choice_buffer));
+ VEC_INIT(&b->choices, 16);
+ b->select_cb = selected;
+ b->abort_cb = aborted;
+ b->update_cb = update;
+ b->userdata = userdata;
+ b->buffers = buffers;
+
+ // set up
+ struct buffer buf = buffer_create("*something-choices*");
+ buf.lazy_row_add = false;
+ buf.retain_properties = true;
+ b->buffer = buffers_add(b->buffers, buf);
+ // TODO: error?
+ b->buffer_removed_hook =
+ buffer_add_destroy_hook(b->buffer, underlying_buffer_destroyed, b);
+
+ b->enter_pressed = (struct command){
+ .name = "choice-buffer-enter",
+ .fn = enter_pressed_fn,
+ .userdata = b,
+ };
+
+ b->q_pressed = (struct command){
+ .name = "choice-buffer-close",
+ .fn = choice_buffer_close_fn,
+ .userdata = b,
+ };
+
+ struct binding bindings[] = {
+ ANONYMOUS_BINDING(ENTER, &b->enter_pressed),
+ ANONYMOUS_BINDING(None, 'q', &b->q_pressed),
+ };
+
+ struct keymap km = keymap_create("choice_buffer", 8);
+ keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ buffer_add_keymap(b->buffer, km);
+
+ struct location begin = buffer_end(b->buffer);
+ buffer_add(b->buffer, buffer_end(b->buffer), title.s, title.l);
+ buffer_newline(b->buffer, buffer_end(b->buffer));
+ buffer_add(b->buffer, buffer_end(b->buffer), (uint8_t *)"----------------",
+ 16);
+ struct location end = buffer_end(b->buffer);
+ buffer_add_text_property(b->buffer, begin, end,
+ (struct text_property){
+ .type = TextProperty_Colors,
+ .data.colors =
+ (struct text_property_colors){
+ .set_fg = true,
+ .fg = Color_Cyan,
+ },
+ });
+ buffer_newline(b->buffer, buffer_end(b->buffer));
+ buffer_newline(b->buffer, buffer_end(b->buffer));
+
+ struct window *w = windows_get_active();
+
+ window_set_buffer(w, b->buffer);
+ struct buffer_view *bv = window_buffer_view(w);
+ bv->dot = buffer_end(b->buffer);
+
+ buffer_set_readonly(b->buffer, true);
+
+ return b;
+}
+
+void choice_buffer_add_choice(struct choice_buffer *buffer, struct s8 text,
+ void *data) {
+ buffer_set_readonly(buffer->buffer, false);
+ VEC_APPEND(&buffer->choices, struct choice * new_choice);
+
+ new_choice->data = data;
+ new_choice->callback = NULL;
+ new_choice->region.begin = buffer_end(buffer->buffer);
+ buffer_add(buffer->buffer, buffer_end(buffer->buffer), (uint8_t *)"- ", 2);
+ buffer_add(buffer->buffer, buffer_end(buffer->buffer), text.s, text.l);
+ new_choice->region.end = buffer_end(buffer->buffer);
+ buffer_newline(buffer->buffer, buffer_end(buffer->buffer));
+ buffer_set_readonly(buffer->buffer, false);
+}
+
+void choice_buffer_add_choice_with_callback(struct choice_buffer *buffer,
+ struct s8 text, void *data,
+ select_callback callback) {
+ buffer_set_readonly(buffer->buffer, false);
+ VEC_APPEND(&buffer->choices, struct choice * new_choice);
+
+ new_choice->data = data;
+ new_choice->callback = callback;
+ new_choice->region.begin = buffer_end(buffer->buffer);
+ buffer_add(buffer->buffer, buffer_end(buffer->buffer), (uint8_t *)"- ", 2);
+ buffer_add(buffer->buffer, buffer_end(buffer->buffer), text.s, text.l);
+ new_choice->region.end = buffer_end(buffer->buffer);
+ buffer_newline(buffer->buffer, buffer_end(buffer->buffer));
+ buffer_set_readonly(buffer->buffer, false);
+}
+
+static void delete_choice_buffer(struct choice_buffer *buffer,
+ bool delete_underlying) {
+ buffer->abort_cb(buffer->userdata);
+ VEC_DESTROY(&buffer->choices);
+ if (delete_underlying) {
+ buffer_remove_destroy_hook(buffer->buffer, buffer->buffer_removed_hook,
+ NULL);
+ buffers_remove(buffer->buffers, buffer->buffer->name);
+ }
+
+ free(buffer);
+}
diff --git a/src/main/lsp/choice-buffer.h b/src/main/lsp/choice-buffer.h
new file mode 100644
index 0000000..c2a7c33
--- /dev/null
+++ b/src/main/lsp/choice-buffer.h
@@ -0,0 +1,23 @@
+#ifndef _CHOICE_BUFFER_H
+#define _CHOICE_BUFFER_H
+
+#include "dged/s8.h"
+
+typedef void (*abort_callback)(void *);
+typedef void (*select_callback)(void *, void *);
+typedef void (*update_callback)(void *);
+
+struct choice_buffer;
+struct buffers;
+
+struct choice_buffer *
+choice_buffer_create(struct s8 title, struct buffers *buffers,
+ select_callback selected, abort_callback aborted,
+ update_callback update, void *userdata);
+void choice_buffer_add_choice(struct choice_buffer *buffer, struct s8 text,
+ void *data);
+void choice_buffer_add_choice_with_callback(struct choice_buffer *buffer,
+ struct s8 text, void *data,
+ select_callback callback);
+
+#endif
diff --git a/src/main/lsp/completion.c b/src/main/lsp/completion.c
new file mode 100644
index 0000000..df89255
--- /dev/null
+++ b/src/main/lsp/completion.c
@@ -0,0 +1,405 @@
+#include "completion.h"
+
+#include <stddef.h>
+
+#include "dged/s8.h"
+#include "dged/vec.h"
+#include "types.h"
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/minibuffer.h"
+#include "main/completion.h"
+#include "main/lsp.h"
+
+struct completion_ctx {
+ struct lsp_server *server;
+ struct completion_context comp_ctx;
+ struct completion_list completions;
+ struct s8 cached_with;
+ struct completion *completion_data;
+ uint64_t last_request;
+
+ triggerchar_vec trigger_chars;
+};
+
+struct symbol {
+ struct s8 symbol;
+ struct region region;
+};
+
+static struct symbol current_symbol(struct buffer *buffer, struct location at) {
+ struct region word = buffer_word_at(buffer, at);
+ if (!region_has_size(word)) {
+ return (struct symbol){
+ .symbol =
+ (struct s8){
+ .s = NULL,
+ .l = 0,
+ },
+ .region = word,
+ };
+ };
+
+ struct text_chunk line = buffer_region(buffer, region_new(word.begin, at));
+ struct s8 symbol = s8new((const char *)line.text, line.nbytes);
+
+ if (line.allocated) {
+ free(line.text);
+ }
+
+ return (struct symbol){.symbol = symbol, .region = word};
+}
+
+struct completion_ctx *create_completion_ctx(struct lsp_server *server,
+ triggerchar_vec *trigger_chars) {
+ struct completion_ctx *ctx =
+ (struct completion_ctx *)calloc(1, sizeof(struct completion_ctx));
+
+ ctx->server = server;
+ ctx->completion_data = NULL;
+ ctx->completions.incomplete = false;
+ ctx->cached_with.s = NULL;
+ ctx->cached_with.l = 0;
+ VEC_INIT(&ctx->completions.items, 0);
+
+ VEC_INIT(&ctx->trigger_chars, VEC_SIZE(trigger_chars));
+ VEC_FOR_EACH(trigger_chars, struct s8 * s) {
+ VEC_PUSH(&ctx->trigger_chars, s8dup(*s));
+ }
+
+ return ctx;
+}
+
+void destroy_completion_ctx(struct completion_ctx *ctx) {
+ completion_list_free(&ctx->completions);
+
+ if (ctx->completion_data != NULL) {
+ free(ctx->completion_data);
+ }
+
+ s8delete(ctx->cached_with);
+
+ VEC_FOR_EACH(&ctx->trigger_chars, struct s8 * s) { s8delete(*s); }
+ VEC_DESTROY(&ctx->trigger_chars);
+
+ free(ctx);
+}
+
+static char *item_kind_to_str(enum completion_item_kind kind) {
+ switch (kind) {
+ case CompletionItem_Text:
+ return "tx";
+
+ case CompletionItem_Method:
+ return "mth";
+
+ case CompletionItem_Function:
+ return "fn";
+
+ case CompletionItem_Constructor:
+ return "cons";
+
+ case CompletionItem_Field:
+ return "field";
+
+ case CompletionItem_Variable:
+ return "var";
+
+ case CompletionItem_Class:
+ return "cls";
+
+ case CompletionItem_Interface:
+ return "iface";
+
+ case CompletionItem_Module:
+ return "mod";
+
+ case CompletionItem_Property:
+ return "prop";
+
+ case CompletionItem_Unit:
+ return "unit";
+
+ case CompletionItem_Value:
+ return "val";
+
+ case CompletionItem_Enum:
+ return "enum";
+
+ case CompletionItem_Keyword:
+ return "kw";
+
+ case CompletionItem_Snippet:
+ return "snp";
+
+ case CompletionItem_Color:
+ return "col";
+
+ case CompletionItem_File:
+ return "file";
+
+ case CompletionItem_Reference:
+ return "ref";
+
+ case CompletionItem_Folder:
+ return "fld";
+
+ case CompletionItem_EnumMember:
+ return "em";
+
+ case CompletionItem_Constant:
+ return "const";
+
+ case CompletionItem_Struct:
+ return "struct";
+
+ case CompletionItem_Event:
+ return "ev";
+
+ case CompletionItem_Operator:
+ return "op";
+
+ case CompletionItem_TypeParameter:
+ return "tp";
+
+ default:
+ return "";
+ }
+}
+
+static struct region lsp_item_render(void *data, struct buffer *buffer) {
+ struct lsp_completion_item *item = (struct lsp_completion_item *)data;
+ struct location begin = buffer_end(buffer);
+ struct s8 kind_str = s8from_fmt("(%s)", item_kind_to_str(item->kind));
+ struct s8 txt = s8from_fmt("%-8.*s%.*s", kind_str.l, kind_str.s,
+ item->label.l, item->label.s);
+ struct location end = buffer_add(buffer, begin, txt.s, txt.l);
+ s8delete(txt);
+ s8delete(kind_str);
+ buffer_newline(buffer, buffer_end(buffer));
+
+ return region_new(begin, end);
+}
+
+static void lsp_item_selected(void *data, struct buffer_view *view) {
+ struct lsp_completion_item *item = (struct lsp_completion_item *)data;
+ struct buffer *buffer = view->buffer;
+ struct lsp_server *lsp_server = lsp_server_for_buffer(buffer);
+
+ abort_completion();
+
+ if (lsp_server == NULL) {
+ return;
+ }
+
+ switch (item->edit_type) {
+ case TextEdit_None: {
+ struct symbol symbol = current_symbol(buffer, view->dot);
+ struct s8 insert = item->insert_text;
+
+ // FIXME: why does this happen?
+ if (symbol.symbol.l >= insert.l) {
+ s8delete(symbol.symbol);
+ return;
+ }
+
+ if (symbol.symbol.l > 0) {
+ insert.s += symbol.symbol.l;
+ insert.l -= symbol.symbol.l;
+ }
+
+ s8delete(symbol.symbol);
+
+ struct location at = buffer_add(buffer, view->dot, insert.s, insert.l);
+ buffer_view_goto(view, at);
+
+ } break;
+ case TextEdit_TextEdit: {
+ struct text_edit *ed = &item->edit.text_edit;
+ struct region reg = lsp_range_to_coordinates(lsp_server, buffer, ed->range);
+ struct location at = reg.begin;
+ if (!region_is_inside(reg, view->dot)) {
+ reg.end = view->dot;
+ }
+
+ if (region_has_size(reg)) {
+ at = buffer_delete(buffer, reg);
+ }
+
+ at = buffer_add(buffer, at, ed->new_text.s, ed->new_text.l);
+ buffer_view_goto(view, at);
+ } break;
+
+ case TextEdit_InsertReplaceEdit: {
+ struct insert_replace_edit *ed = &item->edit.insert_replace_edit;
+ struct region reg =
+ lsp_range_to_coordinates(lsp_server, buffer, ed->replace);
+
+ if (!region_is_inside(reg, view->dot)) {
+ reg.end = view->dot;
+ }
+
+ if (region_has_size(reg)) {
+ buffer_delete(buffer, reg);
+ }
+
+ struct location at =
+ buffer_add(buffer, ed->insert.begin, ed->new_text.s, ed->new_text.l);
+ buffer_view_goto(view, at);
+ } break;
+ }
+
+ if (!VEC_EMPTY(&item->additional_text_edits)) {
+ apply_edits_buffer(lsp_server, view->buffer, item->additional_text_edits,
+ &view->dot);
+ }
+}
+
+static void lsp_item_cleanup(void *data) { (void)data; }
+
+static struct s8 get_filter_text(struct lsp_completion_item *item) {
+ return item->filter_text.l > 0 ? item->filter_text : item->label;
+}
+
+static void fill_completions(struct completion_ctx *lsp_ctx, struct s8 needle) {
+ if (lsp_ctx->completion_data != NULL) {
+ free(lsp_ctx->completion_data);
+ lsp_ctx->completion_data = NULL;
+ }
+
+ size_t ncomps = VEC_SIZE(&lsp_ctx->completions.items);
+
+ // if there is more than a single item or the user has not typed that
+ // single item exactly, then add to the list of completions.
+ lsp_ctx->completion_data = calloc(ncomps, sizeof(struct completion));
+
+ ncomps = 0;
+ VEC_FOR_EACH(&lsp_ctx->completions.items,
+ struct lsp_completion_item * lsp_item) {
+ struct s8 filter_text = get_filter_text(lsp_item);
+ if (needle.l == 0 || s8startswith(filter_text, needle)) {
+ struct completion *c = &lsp_ctx->completion_data[ncomps];
+
+ c->data = lsp_item;
+ c->render = lsp_item_render;
+ c->selected = lsp_item_selected;
+ c->cleanup = lsp_item_cleanup;
+ ++ncomps;
+ }
+ }
+
+ // if there is only a single item that matches the needle exactly,
+ // don't add it to the list since the user has already won
+ if (ncomps == 1 && needle.l > 0 &&
+ s8eq(get_filter_text(lsp_ctx->completion_data[0].data), needle)) {
+ return;
+ }
+
+ if (ncomps > 0) {
+ lsp_ctx->comp_ctx.add_completions(lsp_ctx->completion_data, ncomps);
+ }
+}
+
+static void handle_completion_response(struct lsp_server *server,
+ struct lsp_response *response,
+ void *userdata) {
+ (void)server;
+ struct completion_ctx *lsp_ctx = (struct completion_ctx *)userdata;
+
+ if (response->id != lsp_ctx->last_request) {
+ // discard any old requests
+ return;
+ }
+
+ completion_list_free(&lsp_ctx->completions);
+ lsp_ctx->completions = completion_list_from_json(&response->value.result);
+
+ fill_completions(lsp_ctx, lsp_ctx->cached_with);
+}
+
+static void complete_with_lsp(struct completion_context ctx, bool deletion,
+ void *userdata) {
+ (void)deletion;
+ struct completion_ctx *lsp_ctx = (struct completion_ctx *)userdata;
+ lsp_ctx->comp_ctx = ctx;
+
+ struct symbol sym = current_symbol(ctx.buffer, ctx.location);
+ struct s8 symbol = sym.symbol;
+
+ // check if the symbol is too short for triggering completion
+ bool should_activate =
+ (symbol.l >= 3 || completion_active()) && !s8onlyws(symbol);
+
+ // use trigger chars as an alternative activation condition
+ if (!should_activate) {
+ struct location begin = buffer_previous_char(ctx.buffer, ctx.location);
+ struct location end = begin;
+ end.col += 4;
+ struct text_chunk txt = buffer_region(ctx.buffer, region_new(begin, end));
+ struct s8 t = {
+ .s = txt.text,
+ .l = txt.nbytes,
+ };
+
+ VEC_FOR_EACH(&lsp_ctx->trigger_chars, struct s8 * tc) {
+ if (s8startswith(t, *tc)) {
+ should_activate = true;
+ goto done;
+ }
+ }
+ done:
+ if (txt.allocated) {
+ free(txt.text);
+ }
+ }
+
+ // if we still should not activate, we give up
+ if (!should_activate) {
+ s8delete(symbol);
+ return;
+ }
+
+ bool has_completions = !VEC_EMPTY(&lsp_ctx->completions.items);
+ if (completion_active() && has_completions &&
+ !lsp_ctx->completions.incomplete && !s8empty(lsp_ctx->cached_with) &&
+ s8startswith(symbol, lsp_ctx->cached_with)) {
+ fill_completions(lsp_ctx, symbol);
+ } else {
+ uint64_t id = new_pending_request(lsp_ctx->server,
+ handle_completion_response, lsp_ctx);
+ lsp_ctx->last_request = id;
+
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(ctx.buffer);
+ struct text_document_position params = {
+ .uri = doc.uri,
+ .position = ctx.location,
+ };
+
+ s8delete(lsp_ctx->cached_with);
+ lsp_ctx->cached_with = s8dup(symbol);
+
+ struct s8 json_payload = document_position_to_json(&params);
+ lsp_send(
+ lsp_backend(lsp_ctx->server),
+ lsp_create_request(id, s8("textDocument/completion"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+ }
+
+ s8delete(symbol);
+}
+
+void enable_completion_for_buffer(struct completion_ctx *ctx,
+ struct buffer *buffer) {
+ struct completion_provider prov = {
+ .name = "lsp",
+ .complete = complete_with_lsp,
+ .userdata = ctx,
+ };
+ struct completion_provider providers[] = {prov};
+
+ add_completion_providers(buffer, providers, 1);
+}
diff --git a/src/main/lsp/completion.h b/src/main/lsp/completion.h
new file mode 100644
index 0000000..f3c51c0
--- /dev/null
+++ b/src/main/lsp/completion.h
@@ -0,0 +1,18 @@
+#ifndef _LSP_COMPLETION_H
+#define _LSP_COMPLETION_H
+
+#include "dged/vec.h"
+
+struct completion_ctx;
+struct buffer;
+struct lsp_server;
+
+typedef VEC(struct s8) triggerchar_vec;
+
+struct completion_ctx *create_completion_ctx(struct lsp_server *server,
+ triggerchar_vec *trigger_chars);
+void destroy_completion_ctx(struct completion_ctx *);
+
+void enable_completion_for_buffer(struct completion_ctx *, struct buffer *);
+
+#endif
diff --git a/src/main/lsp/diagnostics.c b/src/main/lsp/diagnostics.c
new file mode 100644
index 0000000..fbab4c0
--- /dev/null
+++ b/src/main/lsp/diagnostics.c
@@ -0,0 +1,386 @@
+#include "diagnostics.h"
+
+#include "dged/binding.h"
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/buffers.h"
+#include "dged/location.h"
+#include "dged/lsp.h"
+#include "dged/minibuffer.h"
+#include "dged/vec.h"
+#include "main/bindings.h"
+#include "main/lsp.h"
+
+struct lsp_buffer_diagnostics {
+ struct buffer *buffer;
+ diagnostic_vec diagnostics;
+};
+
+#define DIAGNOSTIC_BUFNAME "*lsp-diagnostics*"
+
+typedef VEC(struct lsp_buffer_diagnostics) buffer_diagnostics_vec;
+
+struct lsp_diagnostics {
+ buffer_diagnostics_vec buffer_diagnostics;
+};
+
+struct diagnostic_region {
+ struct diagnostic *diagnostic;
+ struct region region;
+};
+
+struct active_diagnostics {
+ struct buffer *buffer;
+ VEC(struct diagnostic_region) diag_regions;
+};
+
+static struct active_diagnostics g_active_diagnostic;
+
+static struct s8 diagnostics_modeline(struct buffer_view *view,
+ void *userdata) {
+ struct lsp_diagnostics *diag = (struct lsp_diagnostics *)userdata;
+
+ diagnostic_vec *diags = diagnostics_for_buffer(diag, view->buffer);
+
+ size_t nerrs = 0, nwarn = 0;
+ if (diags != NULL) {
+ VEC_FOR_EACH(diags, struct diagnostic * d) {
+ if (d->severity == LspDiagnostic_Error) {
+ ++nerrs;
+ } else if (d->severity == LspDiagnostic_Warning) {
+ ++nwarn;
+ }
+ }
+
+ return s8from_fmt("E: %d, W: %d", nerrs, nwarn);
+ }
+
+ return s8("");
+}
+
+struct lsp_diagnostics *diagnostics_create(void) {
+ struct lsp_diagnostics *d = calloc(1, sizeof(struct lsp_diagnostics));
+
+ VEC_INIT(&d->buffer_diagnostics, 16);
+ buffer_view_add_modeline_hook(diagnostics_modeline, d);
+ VEC_INIT(&g_active_diagnostic.diag_regions, 0);
+ g_active_diagnostic.buffer = NULL;
+ return d;
+}
+
+void diagnostics_destroy(struct lsp_diagnostics *d) {
+ VEC_FOR_EACH(&d->buffer_diagnostics, struct lsp_buffer_diagnostics * diag) {
+ VEC_FOR_EACH(&diag->diagnostics, struct diagnostic * d) {
+ diagnostic_free(d);
+ }
+ VEC_DESTROY(&diag->diagnostics);
+ }
+
+ VEC_DESTROY(&d->buffer_diagnostics);
+ VEC_DESTROY(&g_active_diagnostic.diag_regions);
+ free(d);
+}
+
+diagnostic_vec *diagnostics_for_buffer(struct lsp_diagnostics *d,
+ struct buffer *buffer) {
+ VEC_FOR_EACH(&d->buffer_diagnostics, struct lsp_buffer_diagnostics * diag) {
+ if (diag->buffer == buffer) {
+ return &diag->diagnostics;
+ }
+ }
+
+ return NULL;
+}
+
+static int32_t diagnostics_goto_fn(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct buffer *db = buffers_find(ctx.buffers, DIAGNOSTIC_BUFNAME);
+ if (db == NULL) {
+ return 0;
+ }
+
+ struct window *w = window_find_by_buffer(db);
+ if (w == NULL) {
+ return 0;
+ }
+
+ if (g_active_diagnostic.buffer == NULL) {
+ return 0;
+ }
+
+ struct buffer_view *bv = window_buffer_view(w);
+
+ VEC_FOR_EACH(&g_active_diagnostic.diag_regions,
+ struct diagnostic_region * reg) {
+ if (region_is_inside(reg->region, bv->dot)) {
+ struct window *target_win =
+ window_find_by_buffer(g_active_diagnostic.buffer);
+ if (target_win == NULL) {
+ // if the buffer is not open, reuse the diagnostic buffer
+ target_win = w;
+ window_set_buffer(target_win, g_active_diagnostic.buffer);
+ }
+
+ buffer_view_goto(window_buffer_view(target_win),
+ reg->diagnostic->region.begin);
+ windows_set_active(target_win);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int32_t diagnostics_close_fn(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ if (window_has_prev_buffer_view(ctx.active_window)) {
+ window_set_buffer(ctx.active_window,
+ window_prev_buffer_view(ctx.active_window)->buffer);
+ }
+
+ return 0;
+}
+
+static struct buffer *update_diagnostics_buffer(struct lsp_server *server,
+ struct buffers *buffers,
+ diagnostic_vec diagnostics,
+ struct buffer *buffer) {
+ char buf[2048];
+ struct buffer *db = buffers_find(buffers, DIAGNOSTIC_BUFNAME);
+ if (db == NULL) {
+ struct buffer buf = buffer_create(DIAGNOSTIC_BUFNAME);
+ buf.lazy_row_add = false;
+ buf.retain_properties = true;
+ db = buffers_add(buffers, buf);
+
+ static struct command diagnostics_goto = {
+ .name = "diagnostics-goto",
+ .fn = diagnostics_goto_fn,
+ };
+
+ static struct command diagnostics_close = {
+ .name = "diagnostics-close",
+ .fn = diagnostics_close_fn,
+ };
+
+ struct binding bindings[] = {
+ ANONYMOUS_BINDING(ENTER, &diagnostics_goto),
+ ANONYMOUS_BINDING(None, 'q', &diagnostics_close),
+ };
+ struct keymap km = keymap_create("diagnostics", 8);
+ keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ buffer_add_keymap(db, km);
+ }
+ buffer_set_readonly(db, false);
+ buffer_clear(db);
+ buffer_clear_text_properties(db);
+
+ g_active_diagnostic.buffer = buffer;
+ ssize_t len = snprintf(buf, 2048, "Diagnostics for %s:\n\n", buffer->name);
+ if (len != -1) {
+ buffer_add(db, buffer_end(db), (uint8_t *)buf, len);
+ buffer_add_text_property(
+ db, (struct location){.line = 0, .col = 0},
+ (struct location){.line = 1, .col = 0},
+ (struct text_property){.type = TextProperty_Colors,
+ .data.colors.underline = true});
+ }
+
+ VEC_DESTROY(&g_active_diagnostic.diag_regions);
+ VEC_INIT(&g_active_diagnostic.diag_regions, VEC_SIZE(&diagnostics));
+ VEC_FOR_EACH(&diagnostics, struct diagnostic * diag) {
+ struct location start = buffer_end(db);
+ char src[128];
+ size_t srclen = snprintf(src, 128, "%.*s%s", diag->source.l, diag->source.s,
+ diag->source.l > 0 ? ": " : "");
+ const char *severity_str = diag_severity_to_str(diag->severity);
+ size_t severity_str_len = strlen(severity_str);
+ struct region reg = lsp_range_to_coordinates(server, buffer, diag->region);
+ len = snprintf(buf, 2048,
+ "%s%s [%d, %d]: %.*s\n-------------------------------", src,
+ severity_str, reg.begin.line + 1, reg.begin.col,
+ diag->message.l, diag->message.s);
+
+ if (len != -1) {
+ buffer_add(db, buffer_end(db), (uint8_t *)buf, len);
+
+ struct location srcend = start;
+ srcend.col += srclen - 3;
+ buffer_add_text_property(
+ db, start, srcend,
+ (struct text_property){.type = TextProperty_Colors,
+ .data.colors.underline = true});
+
+ uint32_t color = diag_severity_color(diag->severity);
+ struct location sevstart = start;
+ sevstart.col += srclen;
+ struct location sevend = sevstart;
+ sevend.col += severity_str_len;
+ buffer_add_text_property(
+ db, sevstart, sevend,
+ (struct text_property){.type = TextProperty_Colors,
+ .data.colors.set_fg = true,
+ .data.colors.fg = color});
+
+ VEC_PUSH(&g_active_diagnostic.diag_regions,
+ ((struct diagnostic_region){
+ .diagnostic = diag,
+ .region = region_new(start, buffer_end(db)),
+ }));
+
+ buffer_newline(db, buffer_end(db));
+ }
+ }
+
+ buffer_set_readonly(db, true);
+ return db;
+}
+
+void handle_publish_diagnostics(struct lsp_server *server,
+ struct buffers *buffers,
+ struct lsp_notification *notification) {
+ struct publish_diagnostics_params params =
+ diagnostics_from_json(&notification->params);
+ if (s8startswith(params.uri, s8("file://"))) {
+ const char *p = s8tocstr(params.uri);
+ struct buffer *b = buffers_find_by_filename(buffers, &p[7]);
+ free((void *)p);
+
+ if (b != NULL) {
+ struct lsp_diagnostics *ld = lsp_server_diagnostics(server);
+ diagnostic_vec *diagnostics = diagnostics_for_buffer(ld, b);
+ if (diagnostics == NULL) {
+ VEC_APPEND(&ld->buffer_diagnostics,
+ struct lsp_buffer_diagnostics * new_diag);
+ new_diag->buffer = b;
+ new_diag->diagnostics.nentries = 0;
+ new_diag->diagnostics.capacity = 0;
+ new_diag->diagnostics.temp = NULL;
+ new_diag->diagnostics.entries = NULL;
+
+ diagnostics = &new_diag->diagnostics;
+ }
+
+ VEC_FOR_EACH(diagnostics, struct diagnostic * diag) {
+ diagnostic_free(diag);
+ }
+ VEC_DESTROY(diagnostics);
+
+ *diagnostics = params.diagnostics;
+ update_diagnostics_buffer(server, buffers, *diagnostics, b);
+ } else {
+ VEC_FOR_EACH(&params.diagnostics, struct diagnostic * diag) {
+ diagnostic_free(diag);
+ }
+ VEC_DESTROY(&params.diagnostics);
+ message("failed to find buffer with URI: %.*s", params.uri.l,
+ params.uri.s);
+ }
+ } else {
+ message("warning: unsupported LSP URI: %.*s", params.uri.l, params.uri.s);
+ }
+
+ s8delete(params.uri);
+}
+
+static struct lsp_diagnostics *
+lsp_diagnostics_from_server(struct lsp_server *server) {
+ return lsp_server_diagnostics(server);
+}
+
+int32_t next_diagnostic_cmd(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)ctx;
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(windows_get_active());
+
+ struct lsp_server *server = lsp_server_for_lang_id(bv->buffer->lang.id);
+ if (server == NULL) {
+ return 0;
+ }
+
+ diagnostic_vec *diagnostics =
+ diagnostics_for_buffer(lsp_diagnostics_from_server(server), bv->buffer);
+ if (diagnostics == NULL) {
+ return 0;
+ }
+
+ if (VEC_EMPTY(diagnostics)) {
+ minibuffer_echo_timeout(4, "no more diagnostics");
+ return 0;
+ }
+
+ VEC_FOR_EACH(diagnostics, struct diagnostic * diag) {
+ if (location_compare(bv->dot, diag->region.begin) < 0) {
+ buffer_view_goto(bv, diag->region.begin);
+ return 0;
+ }
+ }
+
+ buffer_view_goto(bv, VEC_FRONT(diagnostics)->region.begin);
+ return 0;
+}
+
+int32_t prev_diagnostic_cmd(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)ctx;
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(windows_get_active());
+
+ struct lsp_server *server = lsp_server_for_lang_id(bv->buffer->lang.id);
+ if (server == NULL) {
+ return 0;
+ }
+
+ diagnostic_vec *diagnostics =
+ diagnostics_for_buffer(lsp_diagnostics_from_server(server), bv->buffer);
+
+ if (diagnostics == NULL) {
+ return 0;
+ }
+
+ if (VEC_EMPTY(diagnostics)) {
+ minibuffer_echo_timeout(4, "no more diagnostics");
+ return 0;
+ }
+
+ VEC_FOR_EACH(diagnostics, struct diagnostic * diag) {
+ if (location_compare(bv->dot, diag->region.begin) > 0) {
+ buffer_view_goto(bv, diag->region.begin);
+ return 0;
+ }
+ }
+
+ buffer_view_goto(bv, VEC_BACK(diagnostics)->region.begin);
+ return 0;
+}
+
+int32_t diagnostics_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct buffer *b = window_buffer(ctx.active_window);
+ struct lsp_server *server = lsp_server_for_buffer(b);
+
+ if (server == NULL) {
+ minibuffer_echo_timeout(2, "buffer %s does not have lsp enabled", b->name);
+ return 0;
+ }
+
+ diagnostic_vec *d =
+ diagnostics_for_buffer(lsp_diagnostics_from_server(server), b);
+ struct buffer *db = update_diagnostics_buffer(server, ctx.buffers, *d, b);
+ window_set_buffer(ctx.active_window, db);
+
+ return 0;
+}
diff --git a/src/main/lsp/diagnostics.h b/src/main/lsp/diagnostics.h
new file mode 100644
index 0000000..4357b8e
--- /dev/null
+++ b/src/main/lsp/diagnostics.h
@@ -0,0 +1,26 @@
+#ifndef _DIAGNOSTICS_H
+#define _DIAGNOSTICS_H
+
+#include "dged/command.h"
+#include "main/lsp/types.h"
+
+struct lsp_server;
+struct buffers;
+struct lsp_notification;
+
+struct lsp_diagnostics;
+
+struct lsp_diagnostics *diagnostics_create(void);
+void diagnostics_destroy(struct lsp_diagnostics *);
+
+diagnostic_vec *diagnostics_for_buffer(struct lsp_diagnostics *,
+ struct buffer *);
+void handle_publish_diagnostics(struct lsp_server *, struct buffers *,
+ struct lsp_notification *);
+
+/* COMMANDS */
+int32_t diagnostics_cmd(struct command_ctx, int, const char **);
+int32_t next_diagnostic_cmd(struct command_ctx, int, const char **);
+int32_t prev_diagnostic_cmd(struct command_ctx, int, const char **);
+
+#endif
diff --git a/src/main/lsp/format.c b/src/main/lsp/format.c
new file mode 100644
index 0000000..2019a90
--- /dev/null
+++ b/src/main/lsp/format.c
@@ -0,0 +1,149 @@
+#include "format.h"
+
+#include "completion.h"
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/minibuffer.h"
+#include "dged/settings.h"
+#include "dged/window.h"
+#include "main/completion.h"
+#include "main/lsp.h"
+
+struct formatted_buffer {
+ struct buffer *buffer;
+ bool save;
+};
+
+static uint32_t get_tab_width(struct buffer *buffer) {
+ struct setting *tw = lang_setting(&buffer->lang, "tab-width");
+ if (tw == NULL) {
+ tw = settings_get("editor.tab-width");
+ }
+
+ uint32_t tab_width = 4;
+ if (tw != NULL && tw->value.type == Setting_Number) {
+ tab_width = tw->value.data.number_value;
+ }
+ return tab_width;
+}
+
+static bool use_tabs(struct buffer *buffer) {
+ struct setting *ut = lang_setting(&buffer->lang, "use-tabs");
+ if (ut == NULL) {
+ ut = settings_get("editor.use-tabs");
+ }
+
+ bool use_tabs = false;
+ if (ut != NULL && ut->value.type == Setting_Bool) {
+ use_tabs = ut->value.data.bool_value;
+ }
+
+ return use_tabs;
+}
+
+static struct formatting_options options_from_lang(struct buffer *buffer) {
+ return (struct formatting_options){
+ .tab_size = get_tab_width(buffer),
+ .use_spaces = !use_tabs(buffer),
+ };
+}
+
+void handle_format_response(struct lsp_server *server,
+ struct lsp_response *response, void *userdata) {
+
+ text_edit_vec edits = text_edits_from_json(&response->value.result);
+ struct formatted_buffer *buffer = (struct formatted_buffer *)userdata;
+
+ pause_completion();
+ if (!VEC_EMPTY(&edits)) {
+ apply_edits_buffer(server, buffer->buffer, edits, NULL);
+
+ if (buffer->save) {
+ buffer_to_file(buffer->buffer);
+ }
+ }
+ resume_completion();
+
+ text_edits_free(edits);
+ free(buffer);
+}
+
+static void format_buffer(struct lsp_server *server, struct buffer *buffer,
+ bool save) {
+ struct formatted_buffer *b =
+ (struct formatted_buffer *)calloc(1, sizeof(struct formatted_buffer));
+ b->buffer = buffer;
+ b->save = save;
+
+ uint64_t id = new_pending_request(server, handle_format_response, b);
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(buffer);
+
+ struct document_formatting_params params = {
+ .text_document.uri = doc.uri,
+ .options = options_from_lang(buffer),
+ };
+
+ struct s8 json_payload = document_formatting_params_to_json(&params);
+ lsp_send(lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/formatting"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+}
+
+void format_document(struct lsp_server *server, struct buffer *buffer) {
+ format_buffer(server, buffer, false);
+}
+
+void format_document_save(struct lsp_server *server, struct buffer *buffer) {
+ format_buffer(server, buffer, true);
+}
+
+void format_region(struct lsp_server *server, struct buffer *buffer,
+ struct region region) {
+ struct formatted_buffer *b =
+ (struct formatted_buffer *)calloc(1, sizeof(struct formatted_buffer));
+ b->buffer = buffer;
+ b->save = false;
+
+ uint64_t id = new_pending_request(server, handle_format_response, b);
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(buffer);
+
+ struct document_range_formatting_params params = {
+ .text_document.uri = doc.uri,
+ .range = region_to_lsp(buffer, region, server),
+ .options = options_from_lang(buffer),
+ };
+
+ struct s8 json_payload = document_range_formatting_params_to_json(&params);
+ lsp_send(lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/formatting"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+}
+
+int32_t format_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ (void)ctx;
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(windows_get_active());
+
+ struct lsp_server *server = lsp_server_for_lang_id(bv->buffer->lang.id);
+ if (server == NULL) {
+ return 0;
+ }
+
+ struct region reg = region_new(bv->dot, bv->mark);
+ if (bv->mark_set && region_has_size(reg)) {
+ buffer_view_clear_mark(bv);
+ format_region(server, bv->buffer, reg);
+ } else {
+ format_document(server, bv->buffer);
+ }
+
+ return 0;
+}
diff --git a/src/main/lsp/format.h b/src/main/lsp/format.h
new file mode 100644
index 0000000..8e90ab3
--- /dev/null
+++ b/src/main/lsp/format.h
@@ -0,0 +1,18 @@
+#ifndef _FORMAT_H
+#define _FORMAT_H
+
+#include "dged/command.h"
+#include "dged/location.h"
+
+struct buffer;
+struct lsp_server;
+struct lsp_response;
+
+void format_document(struct lsp_server *, struct buffer *);
+void format_document_save(struct lsp_server *, struct buffer *);
+void format_region(struct lsp_server *, struct buffer *, struct region);
+
+/* COMMANDS */
+int32_t format_cmd(struct command_ctx, int, const char **);
+
+#endif
diff --git a/src/main/lsp/goto.c b/src/main/lsp/goto.c
new file mode 100644
index 0000000..7d2d228
--- /dev/null
+++ b/src/main/lsp/goto.c
@@ -0,0 +1,297 @@
+#include "goto.h"
+
+#include "dged/binding.h"
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/buffers.h"
+#include "dged/location.h"
+#include "dged/minibuffer.h"
+#include "dged/window.h"
+#include "main/bindings.h"
+#include "main/lsp.h"
+
+#include "choice-buffer.h"
+
+static struct jump_stack {
+ buffer_keymap_id goto_keymap_id;
+ struct buffer_location *stack;
+ uint32_t top;
+ uint32_t size;
+ struct buffers *buffers;
+} g_jump_stack;
+
+static struct location_result g_location_result = {};
+
+struct buffer_location {
+ struct buffer *buffer;
+ struct location location;
+};
+
+void init_goto(size_t jump_stack_depth, struct buffers *buffers) {
+ g_jump_stack.size = jump_stack_depth;
+ g_jump_stack.top = 0;
+ g_jump_stack.goto_keymap_id = (buffer_keymap_id)-1;
+ g_jump_stack.stack = calloc(g_jump_stack.size, sizeof(struct jump_stack));
+ g_jump_stack.buffers = buffers;
+}
+
+void destroy_goto(void) {
+ free(g_jump_stack.stack);
+ g_jump_stack.stack = NULL;
+ g_jump_stack.top = 0;
+ g_jump_stack.size = 0;
+}
+
+void lsp_jump_to(struct text_document_location loc) {
+ if (s8startswith(loc.uri, s8("file://"))) {
+ const char *p = s8tocstr(loc.uri);
+ struct buffer *b = buffers_find_by_filename(g_jump_stack.buffers, &p[7]);
+
+ if (b == NULL) {
+ struct buffer new_buf = buffer_from_file(&p[7]);
+ b = buffers_add(g_jump_stack.buffers, new_buf);
+ }
+
+ free((void *)p);
+
+ struct window *w = windows_get_active();
+
+ struct buffer_view *old_bv = window_buffer_view(w);
+ g_jump_stack.stack[g_jump_stack.top] = (struct buffer_location){
+ .buffer = old_bv->buffer,
+ .location = old_bv->dot,
+ };
+ g_jump_stack.top = (g_jump_stack.top + 1) % g_jump_stack.size;
+
+ if (old_bv->buffer != b) {
+ struct window *tw = window_find_by_buffer(b);
+ if (tw == NULL) {
+ window_set_buffer(w, b);
+ } else {
+ w = tw;
+ windows_set_active(w);
+ }
+ }
+
+ struct buffer_view *bv = window_buffer_view(w);
+ buffer_view_goto(bv, loc.range.begin);
+ } else {
+ message("warning: unsupported LSP URI: %.*s", loc.uri.l, loc.uri.s);
+ }
+}
+
+static void location_selected(void *location, void *userdata) {
+ (void)userdata;
+ struct text_document_location *loc =
+ (struct text_document_location *)location;
+ lsp_jump_to(*loc);
+}
+
+static void location_buffer_close(void *userdata) {
+ (void)userdata;
+ location_result_free(&g_location_result);
+}
+
+static void handle_location_result(struct lsp_server *server,
+ struct lsp_response *response,
+ void *userdata) {
+ struct s8 title = s8((const char *)userdata);
+ struct location_result res =
+ location_result_from_json(&response->value.result);
+
+ if (res.type == Location_Null ||
+ (res.type == Location_Array && VEC_EMPTY(&res.location.array))) {
+ minibuffer_echo_timeout(2, "nothing found");
+ location_result_free(&res);
+ return;
+ }
+
+ if (res.type == Location_Single) {
+ lsp_jump_to(res.location.single);
+ location_result_free(&res);
+ } else if (res.type == Location_Array && VEC_SIZE(&res.location.array) == 1) {
+ lsp_jump_to(*VEC_FRONT(&res.location.array));
+ location_result_free(&res);
+ } else if (res.type == Location_Array) {
+
+ g_location_result = res;
+ struct choice_buffer *buf =
+ choice_buffer_create(title, g_jump_stack.buffers, location_selected,
+ location_buffer_close, NULL, server);
+
+ VEC_FOR_EACH(&res.location.array, struct text_document_location * loc) {
+ choice_buffer_add_choice(buf,
+ s8from_fmt("%.*s: %d, %d", loc->uri.l,
+ loc->uri.s, loc->range.begin.line,
+ loc->range.begin.col),
+ loc);
+ }
+ }
+}
+
+int32_t lsp_goto_def_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(ctx.active_window);
+ struct buffer *b = bv->buffer;
+ struct lsp_server *server = lsp_server_for_buffer(b);
+
+ if (server == NULL) {
+ minibuffer_echo_timeout(2, "buffer %s does not have lsp enabled", b->name);
+ return 0;
+ }
+
+ uint64_t id = new_pending_request(server, handle_location_result,
+ (void *)"Definitions");
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(b);
+ struct text_document_position params = {
+ .uri = doc.uri,
+ .position = bv->dot,
+ };
+
+ struct s8 json_payload = document_position_to_json(&params);
+ lsp_send(lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/definition"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+
+ return 0;
+}
+
+int32_t lsp_goto_decl_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(ctx.active_window);
+ struct buffer *b = bv->buffer;
+ struct lsp_server *server = lsp_server_for_buffer(b);
+
+ if (server == NULL) {
+ minibuffer_echo_timeout(2, "buffer %s does not have lsp enabled", b->name);
+ return 0;
+ }
+
+ uint64_t id = new_pending_request(server, handle_location_result,
+ (void *)"Declarations");
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(b);
+ struct text_document_position params = {
+ .uri = doc.uri,
+ .position = bv->dot,
+ };
+
+ struct s8 json_payload = document_position_to_json(&params);
+ lsp_send(
+ lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/declaration"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+
+ return 0;
+}
+
+int32_t lsp_goto_impl_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(ctx.active_window);
+ struct buffer *b = bv->buffer;
+ struct lsp_server *server = lsp_server_for_buffer(b);
+
+ if (server == NULL) {
+ minibuffer_echo_timeout(2, "buffer %s does not have lsp enabled", b->name);
+ return 0;
+ }
+
+ uint64_t id = new_pending_request(server, handle_location_result,
+ (void *)"Implementations");
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(b);
+ struct text_document_position params = {
+ .uri = doc.uri,
+ .position = bv->dot,
+ };
+
+ struct s8 json_payload = document_position_to_json(&params);
+ lsp_send(
+ lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/implementation"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+
+ return 0;
+}
+
+static int32_t handle_lsp_goto_key(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ buffer_remove_keymap(g_jump_stack.goto_keymap_id);
+ minibuffer_abort_prompt();
+
+ struct command *cmd = lookup_command(ctx.commands, (char *)ctx.userdata);
+ if (cmd == NULL) {
+ return 0;
+ }
+
+ return execute_command(cmd, ctx.commands, windows_get_active(), ctx.buffers,
+ 0, NULL);
+}
+
+COMMAND_FN("lsp-goto-definition", goto_d_pressed, handle_lsp_goto_key,
+ "lsp-goto-definition");
+COMMAND_FN("lsp-goto-declaration", goto_f_pressed, handle_lsp_goto_key,
+ "lsp-goto-declaration");
+
+int32_t lsp_goto_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct binding bindings[] = {
+ ANONYMOUS_BINDING(None, 'd', &goto_d_pressed_command),
+ ANONYMOUS_BINDING(None, 'f', &goto_f_pressed_command),
+ };
+ struct keymap m = keymap_create("lsp-goto", 8);
+ keymap_bind_keys(&m, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ g_jump_stack.goto_keymap_id = buffer_add_keymap(minibuffer_buffer(), m);
+ return minibuffer_keymap_prompt(ctx, "lsp-goto: ", &m);
+}
+
+int32_t lsp_goto_previous_cmd(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)ctx;
+ (void)argc;
+ (void)argv;
+
+ uint32_t index =
+ g_jump_stack.top == 0 ? g_jump_stack.size - 1 : g_jump_stack.top - 1;
+
+ struct buffer_location *loc = &g_jump_stack.stack[index];
+ if (loc->buffer == NULL) {
+ return 0;
+ }
+
+ struct window *w = windows_get_active();
+ if (window_buffer(w) != loc->buffer) {
+ struct window *tw = window_find_by_buffer(loc->buffer);
+ if (tw == NULL) {
+ window_set_buffer(w, loc->buffer);
+ } else {
+ w = tw;
+ windows_set_active(w);
+ }
+ }
+
+ buffer_view_goto(window_buffer_view(w), loc->location);
+
+ loc->buffer = NULL;
+ g_jump_stack.top = index;
+
+ return 0;
+}
diff --git a/src/main/lsp/goto.h b/src/main/lsp/goto.h
new file mode 100644
index 0000000..524772d
--- /dev/null
+++ b/src/main/lsp/goto.h
@@ -0,0 +1,23 @@
+#ifndef _GOTO_H
+#define _GOTO_H
+
+#include "dged/command.h"
+
+#include "types.h"
+
+struct lsp_server;
+struct buffers;
+
+void init_goto(size_t jump_stack_depth, struct buffers *);
+void destroy_goto(void);
+
+void lsp_jump_to(struct text_document_location loc);
+
+/* COMMANDS */
+int32_t lsp_goto_def_cmd(struct command_ctx, int, const char **);
+int32_t lsp_goto_decl_cmd(struct command_ctx, int, const char **);
+int32_t lsp_goto_impl_cmd(struct command_ctx, int, const char **);
+int32_t lsp_goto_cmd(struct command_ctx, int, const char **);
+int32_t lsp_goto_previous_cmd(struct command_ctx, int, const char **);
+
+#endif
diff --git a/src/main/lsp/help.c b/src/main/lsp/help.c
new file mode 100644
index 0000000..e5bcc28
--- /dev/null
+++ b/src/main/lsp/help.c
@@ -0,0 +1,101 @@
+#include "help.h"
+
+#include "dged/binding.h"
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/buffers.h"
+#include "dged/minibuffer.h"
+#include "dged/s8.h"
+#include "dged/window.h"
+
+#include "bindings.h"
+#include "lsp.h"
+
+static int32_t close_help(struct command_ctx ctx, int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ if (window_has_prev_buffer_view(ctx.active_window)) {
+ window_set_buffer(ctx.active_window,
+ window_prev_buffer_view(ctx.active_window)->buffer);
+ } else {
+ minibuffer_echo_timeout(4, "no previous buffer to go to");
+ }
+
+ return 0;
+}
+
+static void handle_help_response(struct lsp_server *server,
+ struct lsp_response *response,
+ void *userdata) {
+ (void)server;
+
+ struct buffers *buffers = (struct buffers *)userdata;
+ if (response->value.result.type == Json_Null) {
+ minibuffer_echo_timeout(4, "help: no help found");
+ return;
+ }
+
+ struct buffer *b = buffers_find(buffers, "*lsp-help*");
+ if (b == NULL) {
+ b = buffers_add(buffers, buffer_create("*lsp-help*"));
+ static struct command help_close = {
+ .name = "help_close",
+ .fn = close_help,
+ };
+
+ struct binding bindings[] = {
+ ANONYMOUS_BINDING(None, 'q', &help_close),
+ };
+ struct keymap km = keymap_create("help", 2);
+ keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ buffer_add_keymap(b, km);
+ }
+
+ struct hover help = hover_from_json(&response->value.result);
+
+ buffer_set_readonly(b, false);
+ buffer_clear(b);
+ buffer_add(b, buffer_end(b), help.contents.s, help.contents.l);
+ buffer_set_readonly(b, true);
+
+ if (window_find_by_buffer(b) == NULL) {
+ window_set_buffer(windows_get_active(), b);
+ }
+ hover_free(&help);
+}
+
+void lsp_help(struct lsp_server *server, struct buffer *buffer,
+ struct location at, struct buffers *buffers) {
+ uint64_t id = new_pending_request(server, handle_help_response, buffers);
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(buffer);
+
+ struct text_document_position pos = {
+ .uri = doc.uri,
+ .position = at,
+ };
+
+ struct s8 json_payload = document_position_to_json(&pos);
+ lsp_send(lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/hover"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+}
+
+int32_t lsp_help_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(ctx.active_window);
+ struct lsp_server *server = lsp_server_for_lang_id(bv->buffer->lang.id);
+ if (server == NULL) {
+ minibuffer_echo_timeout(4, "no lsp server associated with %s",
+ bv->buffer->name);
+ return 0;
+ }
+
+ lsp_help(server, bv->buffer, bv->dot, ctx.buffers);
+ return 0;
+}
diff --git a/src/main/lsp/help.h b/src/main/lsp/help.h
new file mode 100644
index 0000000..98a4478
--- /dev/null
+++ b/src/main/lsp/help.h
@@ -0,0 +1,16 @@
+#ifndef _LSP_HELP_H
+#define _LSP_HELP_H
+
+#include "dged/command.h"
+#include "dged/location.h"
+
+struct buffer;
+struct buffers;
+struct lsp_server;
+
+void lsp_help(struct lsp_server *, struct buffer *, struct location,
+ struct buffers *);
+
+int32_t lsp_help_cmd(struct command_ctx, int, const char **);
+
+#endif
diff --git a/src/main/lsp/references.c b/src/main/lsp/references.c
new file mode 100644
index 0000000..c2438fa
--- /dev/null
+++ b/src/main/lsp/references.c
@@ -0,0 +1,248 @@
+#include "references.h"
+
+#include "dged/binding.h"
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/buffers.h"
+#include "dged/command.h"
+#include "dged/display.h"
+#include "dged/location.h"
+#include "dged/minibuffer.h"
+#include "dged/s8.h"
+#include "dged/text.h"
+#include "dged/vec.h"
+#include "dged/window.h"
+
+#include "bindings.h"
+#include "lsp.h"
+#include "lsp/goto.h"
+#include "lsp/types.h"
+#include "unistd.h"
+
+struct link {
+ struct s8 uri;
+ struct region target_region;
+ struct region region;
+};
+
+typedef VEC(struct link) link_vec;
+
+static link_vec g_links;
+static struct buffer *g_prev_buffer = NULL;
+static struct location g_prev_location;
+
+static int32_t references_close(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ if (g_prev_buffer != NULL) {
+ // validate that it is still a valid buffer
+ struct buffer *b =
+ buffers_find_by_filename(ctx.buffers, g_prev_buffer->filename);
+ window_set_buffer(ctx.active_window, b);
+ buffer_view_goto(window_buffer_view(ctx.active_window), g_prev_location);
+ } else if (window_has_prev_buffer_view(ctx.active_window)) {
+ window_set_buffer(ctx.active_window,
+ window_prev_buffer_view(ctx.active_window)->buffer);
+ } else {
+ minibuffer_echo_timeout(4, "no previous buffer to go to");
+ }
+
+ return 0;
+}
+
+static int32_t references_visit(struct command_ctx ctx, int argc,
+ const char **argv) {
+
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *view = window_buffer_view(ctx.active_window);
+
+ VEC_FOR_EACH(&g_links, struct link * link) {
+ if (region_is_inside(link->region, view->dot)) {
+ lsp_jump_to((struct text_document_location){
+ .range = link->target_region,
+ .uri = link->uri,
+ });
+
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static void reference_buffer_closed(struct buffer *buffer, void *userdata) {
+ (void)buffer;
+ link_vec *vec = (link_vec *)userdata;
+ VEC_FOR_EACH(vec, struct link * link) { s8delete(link->uri); }
+ VEC_DESTROY(vec);
+}
+
+static void handle_references_response(struct lsp_server *server,
+ struct lsp_response *response,
+ void *userdata) {
+ (void)server;
+
+ struct buffers *buffers = (struct buffers *)userdata;
+ if (response->value.result.type == Json_Null) {
+ minibuffer_echo_timeout(4, "references: no references found");
+ return;
+ }
+
+ struct location_result locations =
+ location_result_from_json(&response->value.result);
+
+ if (locations.type != Location_Array) {
+ minibuffer_echo_timeout(4, "references: expected location array");
+ return;
+ }
+
+ struct buffer *b = buffers_find(buffers, "*lsp-references*");
+ if (b == NULL) {
+ b = buffers_add(buffers, buffer_create("*lsp-references*"));
+ b->lazy_row_add = false;
+ b->retain_properties = true;
+ static struct command ref_close = {
+ .name = "ref_close",
+ .fn = references_close,
+ };
+
+ static struct command ref_visit_cmd = {
+ .name = "ref_visit",
+ .fn = references_visit,
+ };
+
+ struct binding bindings[] = {
+ ANONYMOUS_BINDING(None, 'q', &ref_close),
+ ANONYMOUS_BINDING(ENTER, &ref_visit_cmd),
+ };
+ struct keymap km = keymap_create("references", 2);
+ keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ buffer_add_keymap(b, km);
+ buffer_add_destroy_hook(b, reference_buffer_closed, &g_links);
+ VEC_INIT(&g_links, 16);
+ }
+
+ buffer_set_readonly(b, false);
+ buffer_clear(b);
+
+ VEC_FOR_EACH(&g_links, struct link * link) { s8delete(link->uri); }
+ VEC_CLEAR(&g_links);
+
+ buffer_clear_text_properties(b);
+ VEC_FOR_EACH(&locations.location.array, struct text_document_location * loc) {
+ uint32_t found = 0, found_at = (uint32_t)-1;
+ for (uint32_t i = 0; i < loc->uri.l; ++i) {
+ uint8_t b = loc->uri.s[i];
+ if (b == ':') {
+ ++found;
+ } else if (b == '/' && found == 1) {
+ ++found;
+ } else if (b == '/' && found == 2) {
+ found_at = i;
+ } else {
+ found = 0;
+ }
+ }
+
+ struct s8 path = loc->uri;
+ if (found_at != (uint32_t)-1) {
+ path.s += found_at;
+ path.l -= found_at;
+ }
+
+ struct s8 relpath = path;
+ char *cwd = getcwd(NULL, 0);
+ if (s8startswith(relpath, s8(cwd))) {
+ size_t l = strlen(cwd);
+ // cwd does not end in /
+ relpath.s += l + 1;
+ relpath.l -= l + 1;
+ }
+ free(cwd);
+
+ struct location start = buffer_end(b);
+ struct location fileend = buffer_add(b, start, relpath.s, relpath.l);
+ buffer_add_text_property(b, start,
+ (struct location){
+ .line = fileend.line,
+ .col = fileend.col > 0 ? fileend.col - 1 : 0,
+ },
+ (struct text_property){
+ .type = TextProperty_Colors,
+ .data.colors =
+ (struct text_property_colors){
+ .set_bg = false,
+ .set_fg = true,
+ .fg = Color_Magenta,
+ .underline = true,
+ },
+ });
+
+ struct s8 line = s8from_fmt(":%d", loc->range.begin.line);
+ struct location end = buffer_add(b, buffer_end(b), line.s, line.l);
+ s8delete(line);
+
+ VEC_PUSH(&g_links, ((struct link){
+ .target_region = loc->range,
+ .region = region_new(start, end),
+ .uri = s8dup(loc->uri),
+ }));
+
+ buffer_newline(b, end);
+ }
+
+ buffer_set_readonly(b, true);
+
+ if (window_find_by_buffer(b) == NULL) {
+ struct window *w = windows_get_active();
+ g_prev_buffer = window_buffer(w);
+ g_prev_location = window_buffer_view(w)->dot;
+ window_set_buffer(w, b);
+ }
+ location_result_free(&locations);
+}
+
+void lsp_references(struct lsp_server *server, struct buffer *buffer,
+ struct location at, struct buffers *buffers) {
+ uint64_t id =
+ new_pending_request(server, handle_references_response, buffers);
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(buffer);
+
+ struct reference_params params = {
+ .position =
+ {
+ .uri = doc.uri,
+ .position = at,
+ },
+ .include_declaration = true,
+ };
+
+ struct s8 json_payload = reference_params_to_json(&params);
+ lsp_send(lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/references"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+}
+
+int32_t lsp_references_cmd(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)argc;
+ (void)argv;
+
+ struct buffer_view *bv = window_buffer_view(ctx.active_window);
+ struct lsp_server *server = lsp_server_for_lang_id(bv->buffer->lang.id);
+ if (server == NULL) {
+ minibuffer_echo_timeout(4, "no lsp server associated with %s",
+ bv->buffer->name);
+ return 0;
+ }
+
+ lsp_references(server, bv->buffer, bv->dot, ctx.buffers);
+ return 0;
+}
diff --git a/src/main/lsp/references.h b/src/main/lsp/references.h
new file mode 100644
index 0000000..ea51987
--- /dev/null
+++ b/src/main/lsp/references.h
@@ -0,0 +1,19 @@
+#ifndef _LSP_REFERENCES_H
+#define _LSP_REFERENCES_H
+
+#include <stdint.h>
+
+#include "dged/command.h"
+#include "dged/location.h"
+
+struct lsp_server;
+struct buffer;
+struct buffers;
+
+void lsp_references(struct lsp_server *server, struct buffer *buffer,
+ struct location at, struct buffers *buffers);
+
+int32_t lsp_references_cmd(struct command_ctx ctx, int argc,
+ const char *argv[]);
+
+#endif
diff --git a/src/main/lsp/rename.c b/src/main/lsp/rename.c
new file mode 100644
index 0000000..6adc9a1
--- /dev/null
+++ b/src/main/lsp/rename.c
@@ -0,0 +1,61 @@
+#include "rename.h"
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/minibuffer.h"
+#include "dged/window.h"
+
+#include "lsp.h"
+
+static void handle_rename_response(struct lsp_server *server,
+ struct lsp_response *response,
+ void *userdata) {
+ (void)userdata;
+ if (response->value.result.type == Json_Null) {
+ minibuffer_echo_timeout(4, "rename: no edits");
+ return;
+ }
+
+ struct workspace_edit edit =
+ workspace_edit_from_json(&response->value.result);
+ apply_edits(server, &edit);
+ workspace_edit_free(&edit);
+}
+
+void lsp_rename(struct lsp_server *server, struct buffer *buffer,
+ struct location location, struct s8 new_name) {
+ uint64_t id = new_pending_request(server, handle_rename_response, NULL);
+ struct versioned_text_document_identifier doc =
+ versioned_identifier_from_buffer(buffer);
+
+ struct rename_params params = {
+ .position.uri = doc.uri,
+ .position.position = location,
+ .new_name = new_name,
+ };
+
+ struct s8 json_payload = rename_params_to_json(&params);
+ lsp_send(lsp_backend(server),
+ lsp_create_request(id, s8("textDocument/rename"), json_payload));
+
+ versioned_text_document_identifier_free(&doc);
+ s8delete(json_payload);
+}
+
+int32_t lsp_rename_cmd(struct command_ctx ctx, int argc, const char **argv) {
+ if (argc == 0) {
+ return minibuffer_prompt(ctx, "rename to: ");
+ }
+
+ struct buffer_view *bv = window_buffer_view(ctx.active_window);
+ struct lsp_server *server = lsp_server_for_lang_id(bv->buffer->lang.id);
+ if (server == NULL) {
+ minibuffer_echo_timeout(4, "no lsp server associated with %s",
+ bv->buffer->name);
+ return 0;
+ }
+
+ lsp_rename(server, bv->buffer, bv->dot, s8(argv[0]));
+
+ return 0;
+}
diff --git a/src/main/lsp/rename.h b/src/main/lsp/rename.h
new file mode 100644
index 0000000..4fb8396
--- /dev/null
+++ b/src/main/lsp/rename.h
@@ -0,0 +1,16 @@
+#ifndef _LSP_RENAME_H
+#define _LSP_RENAME_H
+
+#include "dged/command.h"
+#include "dged/location.h"
+#include "dged/s8.h"
+
+struct lsp_server;
+struct buffer;
+
+void lsp_rename(struct lsp_server *, struct buffer *, struct location,
+ struct s8);
+
+int32_t lsp_rename_cmd(struct command_ctx, int, const char **);
+
+#endif
diff --git a/src/main/lsp/types.c b/src/main/lsp/types.c
new file mode 100644
index 0000000..bd87377
--- /dev/null
+++ b/src/main/lsp/types.c
@@ -0,0 +1,1081 @@
+#include "types.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "dged/buffer.h"
+#include "dged/display.h"
+#include "dged/path.h"
+#include "dged/s8.h"
+
+struct s8 initialize_params_to_json(struct initialize_params *params) {
+ char *cwd = getcwd(NULL, 0);
+ const char *fmt =
+ "{ \"processId\": %d, \"clientInfo\": { \"name\": "
+ "\"%.*s\", \"version\": \"%.*s\" },"
+ "\"capabilities\": { \"textDocument\": { "
+ "\"publishDiagnostics\": { },"
+ "\"hover\": { \"dynamicRegistration\": false, \"contentFormat\": [ "
+ "\"plaintext\", \"markdown\" ] },"
+ "\"signatureHelp\" : { \"dynamicRegistration\": false, "
+ "\"signatureInformation\": {"
+ " \"documentationFormat\": [ \"plaintext\", \"markdown\" ], "
+ "\"activeParameterSupport\": true } },"
+ "\"codeAction\": { \"codeActionLiteralSupport\": { \"codeActionKind\":"
+ "{ \"valueSet\": [ \"quickfix\", \"refactor\", \"source\", "
+ "\"refactor.extract\", "
+ "\"refactor.inline\", \"refactor.rewrite\", \"source.organizeImports\" ] "
+ "} } } },"
+ "\"general\": { \"positionEncodings\": [ \"utf-8\", "
+ "\"utf-32\", \"utf-16\" ] },"
+ "\"offsetEncoding\": [ \"utf-8\", \"utf-32\" ,\"utf-16\" ]"
+ "},"
+ "\"workspaceFolders\": [ { \"uri\": \"file://%s\", "
+ "\"name\": \"cwd\" } ] }";
+
+ struct s8 s =
+ s8from_fmt(fmt, params->process_id, params->client_info.name.l,
+ params->client_info.name.s, params->client_info.version.l,
+ params->client_info.version.s, cwd);
+
+ free(cwd);
+ return s;
+}
+
+static enum position_encoding_kind
+position_encoding_from_str(struct s8 encoding_kind) {
+ if (s8eq(encoding_kind, s8("utf-8"))) {
+ return PositionEncoding_Utf8;
+ } else if (s8eq(encoding_kind, s8("utf-32"))) {
+ return PositionEncoding_Utf32;
+ }
+
+ return PositionEncoding_Utf16;
+}
+
+struct s8 position_encoding_kind_str(enum position_encoding_kind kind) {
+ switch (kind) {
+ case PositionEncoding_Utf8:
+ return s8("utf-8");
+
+ case PositionEncoding_Utf32:
+ return s8("utf-32");
+
+ default:
+ break;
+ }
+
+ return s8("utf-16");
+}
+
+static struct server_capabilities
+parse_capabilities(struct json_object *root, struct json_value *capabilities) {
+ struct server_capabilities caps = {
+ .text_document_sync.kind = TextDocumentSync_Full,
+ .text_document_sync.open_close = false,
+ .text_document_sync.save = false,
+ .position_encoding = PositionEncoding_Utf16,
+ };
+
+ // clang has this legacy attribute for positionEncoding
+ // use with a lower prio than positionEncoding in capabilities
+ struct json_value *offset_encoding = json_get(root, s8("offsetEncoding"));
+ if (offset_encoding != NULL && offset_encoding->type == Json_String) {
+ caps.position_encoding =
+ position_encoding_from_str(offset_encoding->value.string);
+ }
+
+ if (capabilities == NULL || capabilities->type != Json_Object) {
+ return caps;
+ }
+
+ struct json_object *obj = capabilities->value.object;
+ // text document sync caps
+ struct json_value *text_doc_sync = json_get(obj, s8("textDocumentSync"));
+ if (text_doc_sync != NULL) {
+ if (text_doc_sync->type == Json_Number) {
+ caps.text_document_sync.kind =
+ (enum text_document_sync_kind)text_doc_sync->value.number;
+ } else {
+ struct json_object *tsync = text_doc_sync->value.object;
+ caps.text_document_sync.kind =
+ (enum text_document_sync_kind)json_get(tsync, s8("change"))
+ ->value.number;
+
+ struct json_value *open_close = json_get(tsync, s8("openClose"));
+ caps.text_document_sync.open_close =
+ open_close != NULL ? open_close->value.boolean : false;
+
+ struct json_value *save = json_get(tsync, s8("save"));
+ caps.text_document_sync.save =
+ save != NULL ? open_close->value.boolean : false;
+ }
+ }
+
+ // position encoding
+ struct json_value *pos_enc = json_get(obj, s8("positionEncoding"));
+ if (pos_enc != NULL && pos_enc->type == Json_String) {
+ caps.position_encoding = position_encoding_from_str(pos_enc->value.string);
+ }
+
+ struct json_value *completion_opts = json_get(obj, s8("completionProvider"));
+ caps.supports_completion = false;
+ if (completion_opts != NULL && completion_opts->type == Json_Object) {
+ caps.supports_completion = true;
+
+ // trigger chars
+ struct json_value *trigger_chars =
+ json_get(completion_opts->value.object, s8("triggerCharacters"));
+ if (trigger_chars != NULL && trigger_chars->type == Json_Array) {
+ uint64_t arrlen = json_array_len(trigger_chars->value.array);
+ VEC_INIT(&caps.completion_options.trigger_characters, arrlen);
+ for (uint32_t i = 0; i < arrlen; ++i) {
+ struct json_value *val = json_array_get(trigger_chars->value.array, i);
+ VEC_PUSH(&caps.completion_options.trigger_characters,
+ s8dup(val->value.string));
+ }
+ }
+
+ // all commit characters
+ struct json_value *commit_chars =
+ json_get(completion_opts->value.object, s8("allCommitCharacters"));
+ if (commit_chars != NULL && commit_chars->type == Json_Array) {
+ uint64_t arrlen = json_array_len(commit_chars->value.array);
+ VEC_INIT(&caps.completion_options.all_commit_characters, arrlen);
+ for (uint32_t i = 0; i < arrlen; ++i) {
+ struct json_value *val = json_array_get(commit_chars->value.array, i);
+ VEC_PUSH(&caps.completion_options.all_commit_characters,
+ s8dup(val->value.string));
+ }
+ }
+
+ // resolve provider
+ struct json_value *resolve_provider =
+ json_get(completion_opts->value.object, s8("resolveProvider"));
+ if (resolve_provider != NULL && resolve_provider->type == Json_Bool) {
+ caps.completion_options.resolve_provider =
+ resolve_provider->value.boolean;
+ }
+ }
+
+ return caps;
+}
+
+struct initialize_result initialize_result_from_json(struct json_value *json) {
+ struct json_object *obj = json->value.object;
+ struct json_object *server_info =
+ json_get(obj, s8("serverInfo"))->value.object;
+ return (struct initialize_result){
+ .capabilities =
+ parse_capabilities(obj, json_get(obj, s8("capabilities"))),
+ .server_info.name =
+ s8dup(json_get(server_info, s8("name"))->value.string),
+ .server_info.version =
+ s8dup(json_get(server_info, s8("version"))->value.string),
+ };
+}
+
+void initialize_result_free(struct initialize_result *res) {
+ s8delete(res->server_info.name);
+ s8delete(res->server_info.version);
+ if (res->capabilities.supports_completion) {
+ VEC_FOR_EACH(&res->capabilities.completion_options.trigger_characters,
+ struct s8 * s) {
+ s8delete(*s);
+ }
+
+ VEC_DESTROY(&res->capabilities.completion_options.trigger_characters);
+
+ VEC_FOR_EACH(&res->capabilities.completion_options.all_commit_characters,
+ struct s8 * s) {
+ s8delete(*s);
+ }
+
+ VEC_DESTROY(&res->capabilities.completion_options.all_commit_characters);
+ }
+}
+
+static struct s8 uri_from_buffer(struct buffer *buffer) {
+ if (buffer->filename != NULL) {
+ char *abspath = to_abspath(buffer->filename);
+ struct s8 ret = s8from_fmt("file://%s", abspath);
+ free(abspath);
+ return ret;
+ }
+
+ return s8from_fmt("file://invalid-file");
+}
+
+struct text_document_item
+text_document_item_from_buffer(struct buffer *buffer) {
+ struct text_chunk buffer_text =
+ buffer_region(buffer, region_new((struct location){.line = 0, .col = 0},
+ buffer_end(buffer)));
+ struct text_document_item item = {
+ .uri = uri_from_buffer(buffer),
+ .language_id = s8new(buffer->lang.id, strlen(buffer->lang.id)),
+ .version = buffer->version,
+ .text =
+ (struct s8){
+ .s = buffer_text.text,
+ .l = buffer_text.nbytes,
+ },
+ };
+
+ return item;
+}
+
+void text_document_item_free(struct text_document_item *item) {
+ s8delete(item->uri);
+ s8delete(item->language_id);
+ s8delete(item->text);
+}
+
+struct versioned_text_document_identifier
+versioned_identifier_from_buffer(struct buffer *buffer) {
+ struct versioned_text_document_identifier identifier = {
+ .uri = uri_from_buffer(buffer),
+ .version = buffer->version,
+ };
+
+ return identifier;
+}
+
+void versioned_text_document_identifier_free(
+ struct versioned_text_document_identifier *identifier) {
+ s8delete(identifier->uri);
+}
+
+struct s8 did_change_text_document_params_to_json(
+ struct did_change_text_document_params *params) {
+ size_t event_buf_size = 0;
+ for (size_t i = 0; i < params->ncontent_changes; ++i) {
+ struct text_document_content_change_event *ev = &params->content_changes[i];
+ struct s8 escaped = escape_json_string(ev->text);
+ if (!ev->full_document) {
+ const char *item_fmt =
+ "{ \"range\": { \"start\": { \"line\": %d, \"character\": %d}, "
+ "\"end\": { \"line\": %d, \"character\": %d } }, "
+ "\"text\": \"%.*s\" }%s";
+
+ ssize_t num =
+ snprintf(NULL, 0, item_fmt, ev->range.begin.line, ev->range.begin.col,
+ ev->range.end.line, ev->range.end.col, escaped.l, escaped.s,
+ i == params->ncontent_changes - 1 ? "" : ", ");
+
+ if (num < 0) {
+ return s8("");
+ }
+
+ event_buf_size += num;
+ } else {
+ const char *item_fmt = "{ \"text\", \"%.*s\" }%s";
+ ssize_t num = snprintf(NULL, 0, item_fmt, escaped.l, escaped.s,
+ i == params->ncontent_changes - 1 ? "" : ", ");
+
+ if (num < 0) {
+ return s8("");
+ }
+
+ event_buf_size += num;
+ }
+
+ s8delete(escaped);
+ }
+
+ ++event_buf_size;
+ char *buf = calloc(event_buf_size, 1);
+ size_t offset = 0;
+ for (size_t i = 0; i < params->ncontent_changes; ++i) {
+ struct text_document_content_change_event *ev = &params->content_changes[i];
+ struct s8 escaped = escape_json_string(ev->text);
+ if (!ev->full_document) {
+ const char *item_fmt =
+ "{ \"range\": { \"start\": { \"line\": %d, \"character\": %d}, "
+ "\"end\": { \"line\": %d, \"character\": %d } }, "
+ "\"text\": \"%.*s\" }%s";
+
+ ssize_t num = snprintf(
+ &buf[offset], event_buf_size - offset, item_fmt, ev->range.begin.line,
+ ev->range.begin.col, ev->range.end.line, ev->range.end.col, escaped.l,
+ escaped.s, i == params->ncontent_changes - 1 ? "" : ", ");
+
+ if (num < 0) {
+ return s8("");
+ }
+
+ offset += num;
+ } else {
+ const char *item_fmt = "{ \"text\", \"%.*s\" }%s";
+ ssize_t num =
+ snprintf(&buf[offset], event_buf_size - offset, item_fmt, escaped.l,
+ escaped.s, i == params->ncontent_changes - 1 ? "" : ", ");
+
+ if (num < 0) {
+ return s8("");
+ }
+
+ offset += num;
+ }
+
+ s8delete(escaped);
+ }
+
+ const char *fmt =
+ "{ \"textDocument\": { \"uri\": \"%.*s\", \"version\": %d }, "
+ "\"contentChanges\": [ %s ]"
+ "}";
+
+ struct versioned_text_document_identifier *doc = &params->text_document;
+ struct s8 json = s8from_fmt(fmt, doc->uri.l, doc->uri.s, doc->version, buf);
+
+ free(buf);
+ return json;
+}
+
+struct s8 did_open_text_document_params_to_json(
+ struct did_open_text_document_params *params) {
+ const char *fmt =
+ "{ \"textDocument\": { \"uri\": \"%.*s\", \"languageId\": \"%.*s\", "
+ "\"version\": %d, \"text\": \"%.*s\" }}";
+
+ struct text_document_item *item = &params->text_document;
+
+ struct s8 escaped_content = escape_json_string(item->text);
+ struct s8 json = s8from_fmt(
+ fmt, item->uri.l, item->uri.s, item->language_id.l, item->language_id.s,
+ item->version, escaped_content.l, escaped_content.s);
+
+ s8delete(escaped_content);
+ return json;
+}
+
+struct s8 did_save_text_document_params_to_json(
+ struct did_save_text_document_params *params) {
+ const char *fmt = "{ \"textDocument\": { \"uri\": \"%.*s\" } }";
+
+ struct text_document_identifier *item = &params->text_document;
+ struct s8 json = s8from_fmt(fmt, item->uri.l, item->uri.s);
+ return json;
+}
+
+static struct region parse_region(struct json_object *obj) {
+ struct json_object *start = json_get(obj, s8("start"))->value.object;
+ struct json_object *end = json_get(obj, s8("end"))->value.object;
+
+ return region_new(
+ (struct location){.line = json_get(start, s8("line"))->value.number,
+ .col = json_get(start, s8("character"))->value.number},
+ (struct location){.line = json_get(end, s8("line"))->value.number,
+ .col = json_get(end, s8("character"))->value.number});
+}
+
+static void parse_diagnostic(uint64_t id, struct json_value *elem,
+ void *userdata) {
+ (void)id;
+ diagnostic_vec *vec = (diagnostic_vec *)userdata;
+ struct json_object *obj = elem->value.object;
+ struct json_value *severity = json_get(obj, s8("severity"));
+ struct json_value *source = json_get(obj, s8("source"));
+
+ struct diagnostic diag;
+ diag.message =
+ unescape_json_string(json_get(obj, s8("message"))->value.string);
+ diag.region = parse_region(json_get(obj, s8("range"))->value.object);
+ diag.severity = severity != NULL
+ ? (enum diagnostic_severity)severity->value.number
+ : LspDiagnostic_Error;
+ diag.source = source != NULL ? unescape_json_string(source->value.string)
+ : (struct s8){.l = 0, .s = NULL};
+
+ VEC_PUSH(vec, diag);
+}
+
+const char *diag_severity_to_str(enum diagnostic_severity severity) {
+
+ switch (severity) {
+ case LspDiagnostic_Error:
+ return "error";
+ case LspDiagnostic_Warning:
+ return "warning";
+ case LspDiagnostic_Information:
+ return "info";
+ case LspDiagnostic_Hint:
+ return "hint";
+ }
+
+ return "";
+}
+
+struct publish_diagnostics_params
+diagnostics_from_json(struct json_value *json) {
+ struct json_object *obj = json->value.object;
+ struct json_value *version = json_get(obj, s8("version"));
+ struct publish_diagnostics_params params = {
+ .uri = unescape_json_string(json_get(obj, s8("uri"))->value.string),
+ .version = version != NULL ? version->value.number : 0,
+ };
+
+ struct json_array *diagnostics =
+ json_get(obj, s8("diagnostics"))->value.array;
+ VEC_INIT(&params.diagnostics, json_array_len(diagnostics));
+ json_array_foreach(diagnostics, &params.diagnostics, parse_diagnostic);
+
+ return params;
+}
+
+void diagnostic_free(struct diagnostic *diag) {
+ s8delete(diag->message);
+ s8delete(diag->source);
+}
+
+struct s8 document_position_to_json(struct text_document_position *position) {
+ const char *fmt = "{ \"textDocument\": { \"uri\": \"%.*s\" }, "
+ "\"position\": { \"line\": %d, \"character\": %d } }";
+
+ struct s8 json = s8from_fmt(fmt, position->uri.l, position->uri.s,
+ position->position.line, position->position.col);
+ return json;
+}
+
+static struct text_document_location
+location_from_json(struct json_value *json) {
+ struct text_document_location loc = {0};
+ if (json->type != Json_Object) {
+ return loc;
+ }
+
+ struct json_object *obj = json->value.object;
+ loc.uri = unescape_json_string(json_get(obj, s8("uri"))->value.string);
+ loc.range = parse_region(json_get(obj, s8("range"))->value.object);
+
+ return loc;
+}
+
+static void parse_text_doc_location(uint64_t id, struct json_value *elem,
+ void *userdata) {
+ (void)id;
+ location_vec *vec = (location_vec *)userdata;
+ VEC_PUSH(vec, location_from_json(elem));
+}
+
+struct location_result location_result_from_json(struct json_value *json) {
+ if (json->type == Json_Null) {
+ return (struct location_result){
+ .type = Location_Null,
+ };
+ } else if (json->type == Json_Object) {
+ return (struct location_result){
+ .type = Location_Single,
+ .location.single = location_from_json(json),
+ };
+ } else if (json->type == Json_Array) {
+ // location link or location
+ struct location_result res = {};
+ res.type = Location_Array;
+ struct json_array *locations = json->value.array;
+ VEC_INIT(&res.location.array, json_array_len(locations));
+ json_array_foreach(locations, &res.location.array, parse_text_doc_location);
+ return res;
+ }
+
+ return (struct location_result){.type = Location_Null};
+}
+
+void location_result_free(struct location_result *res) {
+ switch (res->type) {
+ case Location_Null:
+ break;
+ case Location_Single:
+ s8delete(res->location.single.uri);
+ break;
+ case Location_Array:
+ VEC_FOR_EACH(&res->location.array, struct text_document_location * loc) {
+ s8delete(loc->uri);
+ }
+ VEC_DESTROY(&res->location.array);
+ break;
+ case Location_Link:
+ // TODO
+ break;
+ }
+}
+
+static uint32_t severity_to_json(enum diagnostic_severity severity) {
+ return (uint32_t)severity;
+}
+
+static struct s8 region_to_json(struct region region) {
+ const char *fmt = "{ \"start\": { \"line\": %d, \"character\": %d }, "
+ "\"end\": { \"line\": %d, \"character\": %d } }";
+ return s8from_fmt(fmt, region.begin.line, region.begin.col, region.end.line,
+ region.end.col);
+}
+
+static struct s8 diagnostic_to_json(struct diagnostic *diag) {
+ const char *fmt =
+ "{ \"range\": %.*s, \"message\": \"%.*s\", \"severity\": %d }";
+
+ struct s8 range = region_to_json(diag->region);
+ struct s8 json =
+ s8from_fmt(fmt, range.l, range.s, diag->message.l, diag->message.s,
+ severity_to_json(diag->severity));
+
+ s8delete(range);
+ return json;
+}
+
+static struct s8 diagnostic_vec_to_json(diagnostic_vec diagnostics) {
+ size_t ndiags = VEC_SIZE(&diagnostics);
+ if (ndiags == 0) {
+ return s8new("[]", 2);
+ }
+
+ struct s8 *strings = calloc(ndiags, sizeof(struct s8));
+
+ size_t len = 1;
+ VEC_FOR_EACH_INDEXED(&diagnostics, struct diagnostic * diag, i) {
+ strings[i] = diagnostic_to_json(diag);
+ len += strings[i].l + 1;
+ }
+
+ uint8_t *final = (uint8_t *)calloc(len, 1);
+ struct s8 json = {
+ .s = final,
+ .l = len,
+ };
+
+ final[0] = '[';
+
+ size_t offset = 1;
+ for (uint32_t i = 0; i < ndiags; ++i) {
+ memcpy(&final[offset], strings[i].s, strings[i].l);
+ offset += strings[i].l;
+
+ s8delete(strings[i]);
+ }
+
+ final[len - 1] = ']';
+
+ free(strings);
+
+ return json;
+}
+
+struct s8 code_action_params_to_json(struct code_action_params *params) {
+ const char *fmt = "{ \"textDocument\": { \"uri\": \"%.*s\" }, "
+ " \"range\": %.*s, "
+ " \"context\": { \"diagnostics\": %.*s } }";
+
+ struct s8 json_diags = diagnostic_vec_to_json(params->context.diagnostics);
+ struct s8 range = region_to_json(params->range);
+
+ struct s8 json =
+ s8from_fmt(fmt, params->text_document.uri.l, params->text_document.uri.s,
+ range.l, range.s, json_diags.l, json_diags.s);
+
+ s8delete(json_diags);
+ s8delete(range);
+ return json;
+}
+
+static struct lsp_command lsp_command_from_json(struct json_value *json) {
+ struct json_object *obj = json->value.object;
+ struct lsp_command command = {
+ .title = unescape_json_string(json_get(obj, s8("title"))->value.string),
+ .command =
+ unescape_json_string(json_get(obj, s8("command"))->value.string),
+ .arguments = s8(""),
+ };
+
+ struct json_value *arguments = json_get(obj, s8("arguments"));
+ if (arguments != NULL && arguments->type == Json_Array) {
+ size_t len = arguments->end - arguments->start;
+ command.arguments = s8new((const char *)arguments->start, len);
+ }
+
+ return command;
+}
+
+static void lsp_action_from_json(uint64_t id, struct json_value *json,
+ void *userdata) {
+ (void)id;
+ struct code_actions *actions = (struct code_actions *)userdata;
+
+ struct json_object *obj = json->value.object;
+ struct json_value *command_val = json_get(obj, s8("command"));
+ if (command_val != NULL && command_val->type == Json_String) {
+ VEC_PUSH(&actions->commands, lsp_command_from_json(json));
+ } else {
+ VEC_APPEND(&actions->code_actions, struct code_action * action);
+ action->title =
+ unescape_json_string(json_get(obj, s8("title"))->value.string);
+ action->kind = s8("");
+ action->has_edit = false;
+ action->has_command = false;
+
+ struct json_value *kind_val = json_get(obj, s8("kind"));
+ if (kind_val != NULL && kind_val->type == Json_String) {
+ action->kind = unescape_json_string(kind_val->value.string);
+ }
+
+ struct json_value *edit_val = json_get(obj, s8("edit"));
+ if (edit_val != NULL && edit_val->type == Json_Object) {
+ action->has_edit = true;
+ action->edit = workspace_edit_from_json(edit_val);
+ }
+
+ command_val = json_get(obj, s8("command"));
+ if (command_val != NULL && command_val->type == Json_Object) {
+ action->has_command = true;
+ action->command = lsp_command_from_json(command_val);
+ }
+ }
+}
+
+struct code_actions lsp_code_actions_from_json(struct json_value *json) {
+ struct code_actions actions;
+
+ if (json->type == Json_Array) {
+ struct json_array *jcmds = json->value.array;
+ VEC_INIT(&actions.commands, json_array_len(jcmds));
+ VEC_INIT(&actions.code_actions, json_array_len(jcmds));
+ json_array_foreach(jcmds, &actions, lsp_action_from_json);
+ } else { /* NULL or wrong type */
+ VEC_INIT(&actions.commands, 0);
+ VEC_INIT(&actions.code_actions, 0);
+ }
+
+ return actions;
+}
+
+static void lsp_command_free(struct lsp_command *command) {
+ s8delete(command->title);
+ s8delete(command->command);
+
+ if (command->arguments.l > 0) {
+ s8delete(command->arguments);
+ }
+}
+
+void lsp_code_actions_free(struct code_actions *actions) {
+ VEC_FOR_EACH(&actions->commands, struct lsp_command * command) {
+ lsp_command_free(command);
+ }
+
+ VEC_DESTROY(&actions->commands);
+
+ VEC_FOR_EACH(&actions->code_actions, struct code_action * action) {
+ s8delete(action->title);
+ s8delete(action->kind);
+
+ if (action->has_edit) {
+ workspace_edit_free(&action->edit);
+ }
+
+ if (action->has_command) {
+ lsp_command_free(&action->command);
+ }
+ }
+
+ VEC_DESTROY(&actions->code_actions);
+}
+
+struct s8 lsp_command_to_json(struct lsp_command *command) {
+ const char *fmt = "{ \"command\": \"%.*s\", \"arguments\": %.*s }";
+
+ return s8from_fmt(fmt, command->command.l, command->command.s,
+ command->arguments.l, command->arguments.s);
+}
+
+static void text_edit_from_json(uint64_t id, struct json_value *val,
+ void *userdata) {
+ (void)id;
+ text_edit_vec *vec = (text_edit_vec *)userdata;
+ struct json_object *obj = val->value.object;
+ struct text_edit edit = {
+ .range = parse_region(json_get(obj, s8("range"))->value.object),
+ .new_text =
+ unescape_json_string(json_get(obj, s8("newText"))->value.string),
+ };
+ VEC_PUSH(vec, edit);
+}
+
+text_edit_vec text_edits_from_json(struct json_value *json) {
+ text_edit_vec vec = {0};
+
+ if (json->type == Json_Array) {
+ struct json_array *arr = json->value.array;
+
+ VEC_INIT(&vec, json_array_len(arr));
+ json_array_foreach(arr, &vec, text_edit_from_json);
+ }
+
+ return vec;
+}
+
+static void changes_from_json(struct s8 key, struct json_value *json,
+ void *userdata) {
+ change_vec *vec = (change_vec *)userdata;
+
+ struct text_edit_pair pair = {
+ .uri = s8dup(key),
+ };
+
+ // pick out the edits for this key and create array
+ struct json_array *edits = json->value.array;
+ VEC_INIT(&pair.edits, json_array_len(edits));
+ json_array_foreach(edits, &pair.edits, text_edit_from_json);
+ VEC_PUSH(vec, pair);
+}
+
+struct workspace_edit workspace_edit_from_json(struct json_value *json) {
+ struct workspace_edit edit;
+ struct json_object *obj = json->value.object;
+ struct json_value *edit_container = json_get(obj, s8("edit"));
+ if (edit_container != NULL && edit_container->type == Json_Object) {
+ obj = edit_container->value.object;
+ }
+
+ struct json_value *changes = json_get(obj, s8("changes"));
+ if (changes != NULL) {
+ struct json_object *changes_obj = changes->value.object;
+ VEC_INIT(&edit.changes, json_len(changes_obj));
+ json_foreach(changes_obj, changes_from_json, &edit.changes);
+ } else {
+ VEC_INIT(&edit.changes, 0);
+ }
+
+ return edit;
+}
+
+void workspace_edit_free(struct workspace_edit *edit) {
+ VEC_FOR_EACH(&edit->changes, struct text_edit_pair * pair) {
+ s8delete(pair->uri);
+ VEC_FOR_EACH(&pair->edits, struct text_edit * edit) {
+ s8delete(edit->new_text);
+ }
+ VEC_DESTROY(&pair->edits);
+ }
+ VEC_DESTROY(&edit->changes);
+}
+
+uint32_t diag_severity_color(enum diagnostic_severity severity) {
+ switch (severity) {
+ case LspDiagnostic_Error:
+ return Color_BrightRed;
+ case LspDiagnostic_Warning:
+ return Color_BrightYellow;
+ default:
+ return Color_BrightBlack;
+ }
+
+ return Color_BrightBlack;
+}
+
+struct s8
+document_formatting_params_to_json(struct document_formatting_params *params) {
+ const char *fmt = "{ \"textDocument\": { \"uri\": \"%.*s\" }, \"options\": { "
+ "\"tabSize\": %d, \"insertSpaces\": %s } }";
+
+ return s8from_fmt(fmt, params->text_document.uri.l,
+ params->text_document.uri.s, params->options.tab_size,
+ params->options.use_spaces ? "true" : "false");
+}
+
+struct s8 document_range_formatting_params_to_json(
+ struct document_range_formatting_params *params) {
+ const char *fmt = "{ \"textDocument\": { \"uri\": \"%.*s\" }, \"range\": "
+ "%.*s, \"options\": { "
+ "\"tabSize\": %d, \"insertSpaces\": %s } }";
+
+ struct s8 range = region_to_json(params->range);
+ struct s8 json =
+ s8from_fmt(fmt, params->text_document.uri.l, params->text_document.uri.s,
+ range.l, range.s, params->options.tab_size,
+ params->options.use_spaces ? "true" : "false");
+
+ s8delete(range);
+ return json;
+}
+
+void text_edits_free(text_edit_vec edits) {
+ VEC_FOR_EACH(&edits, struct text_edit * edit) { s8delete(edit->new_text); }
+ VEC_DESTROY(&edits);
+}
+
+static void parse_completion_item(uint64_t id, struct json_value *json,
+ void *userdata) {
+
+ (void)id;
+ completions_vec *vec = (completions_vec *)userdata;
+
+ struct json_object *obj = json->value.object;
+
+ struct lsp_completion_item item = {0};
+ item.label = s8dup(json_get(obj, s8("label"))->value.string);
+
+ struct json_value *kind_val = json_get(obj, s8("kind"));
+ if (kind_val != NULL && kind_val->type == Json_Number) {
+ item.kind = (enum completion_item_kind)kind_val->value.number;
+ }
+
+ struct json_value *detail_val = json_get(obj, s8("detail"));
+ if (detail_val != NULL && detail_val->type == Json_String) {
+ item.detail = s8dup(detail_val->value.string);
+ }
+
+ struct json_value *sort_txt_val = json_get(obj, s8("sortText"));
+ if (sort_txt_val != NULL && sort_txt_val->type == Json_String) {
+ item.sort_text = s8dup(sort_txt_val->value.string);
+ }
+
+ struct json_value *filter_txt_val = json_get(obj, s8("filterText"));
+ if (filter_txt_val != NULL && filter_txt_val->type == Json_String) {
+ item.filter_text = s8dup(filter_txt_val->value.string);
+ }
+
+ struct json_value *insert_txt_val = json_get(obj, s8("insertText"));
+ if (insert_txt_val != NULL && insert_txt_val->type == Json_String) {
+ item.insert_text = s8dup(insert_txt_val->value.string);
+ }
+
+ // determine type of edit
+ struct json_value *edit_val = json_get(obj, s8("textEdit"));
+ item.edit_type = TextEdit_None;
+ if (edit_val != NULL && edit_val->type == Json_Object) {
+ struct json_object *edit_obj = edit_val->value.object;
+
+ struct json_value *insert_val = json_get(edit_obj, s8("insert"));
+
+ if (insert_val != NULL) {
+ item.edit_type = TextEdit_InsertReplaceEdit;
+ item.edit.insert_replace_edit = (struct insert_replace_edit){
+ .insert =
+ parse_region(json_get(edit_obj, s8("insert"))->value.object),
+ .replace =
+ parse_region(json_get(edit_obj, s8("replace"))->value.object),
+ .new_text = unescape_json_string(
+ json_get(edit_obj, s8("newText"))->value.string),
+ };
+ } else {
+ item.edit_type = TextEdit_TextEdit;
+ item.edit.text_edit = (struct text_edit){
+ .range = parse_region(json_get(edit_obj, s8("range"))->value.object),
+ .new_text = unescape_json_string(
+ json_get(edit_obj, s8("newText"))->value.string),
+ };
+ }
+ }
+
+ struct json_value *additional_txt_edits_val =
+ json_get(obj, s8("additionalTextEdits"));
+ if (additional_txt_edits_val != NULL &&
+ additional_txt_edits_val->type == Json_Array) {
+ item.additional_text_edits = text_edits_from_json(additional_txt_edits_val);
+ }
+
+ struct json_value *command_val = json_get(obj, s8("command"));
+ if (command_val != NULL && command_val->type == Json_Object) {
+ item.command = lsp_command_from_json(command_val);
+ }
+
+ VEC_PUSH(vec, item);
+}
+
+struct completion_list completion_list_from_json(struct json_value *json) {
+
+ if (json->type == Json_Null) {
+ return (struct completion_list){
+ .incomplete = false,
+ };
+ }
+
+ struct completion_list complist;
+ complist.incomplete = false;
+
+ struct json_array *js_items = NULL;
+ if (json->type == Json_Object) {
+ struct json_object *obj = json->value.object;
+ complist.incomplete = json_get(obj, s8("isIncomplete"))->value.boolean;
+ js_items = json_get(obj, s8("items"))->value.array;
+ } else if (json->type == Json_Array) {
+ js_items = json->value.array;
+ } else {
+ return (struct completion_list){
+ .incomplete = false,
+ };
+ }
+
+ // parse the list
+ VEC_INIT(&complist.items, json_array_len(js_items));
+ json_array_foreach(js_items, &complist.items, parse_completion_item);
+
+ return complist;
+}
+
+void completion_list_free(struct completion_list *complist) {
+ VEC_FOR_EACH(&complist->items, struct lsp_completion_item * item) {
+ s8delete(item->label);
+ s8delete(item->detail);
+ s8delete(item->sort_text);
+ s8delete(item->filter_text);
+ s8delete(item->insert_text);
+
+ if (item->edit_type == TextEdit_TextEdit) {
+ s8delete(item->edit.text_edit.new_text);
+ } else {
+ s8delete(item->edit.insert_replace_edit.new_text);
+ }
+
+ text_edits_free(item->additional_text_edits);
+ lsp_command_free(&item->command);
+ }
+
+ VEC_DESTROY(&complist->items);
+}
+
+struct s8 rename_params_to_json(struct rename_params *params) {
+
+ const char *fmt = "{ \"textDocument\": { \"uri\": \"%.*s\" }, "
+ "\"position\": { \"line\": %d, \"character\": %d }, "
+ "\"newName\": \"%.*s\" }";
+
+ struct text_document_position *position = &params->position;
+ struct s8 escaped = escape_json_string(params->new_name);
+ struct s8 json =
+ s8from_fmt(fmt, position->uri.l, position->uri.s, position->position.line,
+ position->position.col, escaped.l, escaped.s);
+
+ s8delete(escaped);
+ return json;
+}
+
+static void parse_parameter(uint64_t id, struct json_value *json,
+ void *userdata) {
+
+ (void)id;
+ param_info_vec *vec = (param_info_vec *)userdata;
+ struct json_object *obj = json->value.object;
+
+ struct parameter_information info;
+ struct json_value *label = json_get(obj, s8("label"));
+ if (label != NULL && label->type == Json_String) {
+ info.label = s8dup(label->value.string);
+ }
+
+ struct json_value *doc = json_get(obj, s8("documentation"));
+ if (doc != NULL && doc->type == Json_String) {
+ info.documentation = s8dup(doc->value.string);
+ }
+ VEC_PUSH(vec, info);
+}
+
+static void parse_signature(uint64_t id, struct json_value *json,
+ void *userdata) {
+
+ (void)id;
+ signature_info_vec *vec = (signature_info_vec *)userdata;
+
+ struct json_object *obj = json->value.object;
+
+ struct signature_information info;
+ struct json_value *label = json_get(obj, s8("label"));
+ if (label != NULL && label->type == Json_String) {
+ info.label = s8dup(label->value.string);
+ }
+
+ struct json_value *doc = json_get(obj, s8("documentation"));
+ if (doc != NULL && doc->type == Json_String) {
+ info.documentation = s8dup(doc->value.string);
+ }
+
+ struct json_value *params = json_get(obj, s8("parameters"));
+ if (params != NULL && params->type == Json_Array) {
+ struct json_array *arr = params->value.array;
+ VEC_INIT(&info.parameters, json_array_len(arr));
+ json_array_foreach(arr, &info.parameters, parse_parameter);
+ }
+
+ VEC_PUSH(vec, info);
+}
+
+struct signature_help signature_help_from_json(struct json_value *value) {
+ struct signature_help help = {0};
+ struct json_object *obj = value->value.object;
+
+ struct json_value *active_sig = json_get(obj, s8("activeSignature"));
+ if (active_sig != NULL && active_sig->type == Json_Number) {
+ help.active_signature = active_sig->value.number;
+ }
+
+ struct json_value *sigs = json_get(obj, s8("signatures"));
+ if (sigs != NULL && sigs->type == Json_Array) {
+ struct json_array *arr = sigs->value.array;
+ VEC_INIT(&help.signatures, json_array_len(arr));
+ json_array_foreach(arr, &help.signatures, parse_signature);
+ }
+
+ return help;
+}
+
+void signature_help_free(struct signature_help *help) {
+ VEC_FOR_EACH(&help->signatures, struct signature_information * info) {
+ s8delete(info->label);
+ s8delete(info->documentation);
+
+ VEC_FOR_EACH(&info->parameters, struct parameter_information * pinfo) {
+ s8delete(pinfo->label);
+ s8delete(pinfo->documentation);
+ }
+
+ VEC_DESTROY(&info->parameters);
+ }
+
+ VEC_DESTROY(&help->signatures);
+}
+
+struct hover hover_from_json(struct json_value *value) {
+ struct hover hover = {0};
+ struct json_object *obj = value->value.object;
+
+ struct json_value *contents = json_get(obj, s8("contents"));
+ if (contents != NULL) {
+ switch (contents->type) {
+ case Json_String:
+ hover.contents = unescape_json_string(contents->value.string);
+ break;
+ case Json_Object: {
+ struct json_value *val = json_get(contents->value.object, s8("value"));
+ if (val != NULL && val->type == Json_String) {
+ hover.contents = unescape_json_string(val->value.string);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+
+ struct json_value *range = json_get(obj, s8("range"));
+ if (range != NULL && range->type == Json_Object) {
+ hover.range = parse_region(range->value.object);
+ }
+
+ return hover;
+}
+
+void hover_free(struct hover *hover) { s8delete(hover->contents); }
+
+struct s8 reference_params_to_json(struct reference_params *params) {
+ const char *fmt = "{ \"textDocument\": { \"uri\": \"%.*s\" }, "
+ "\"position\": { \"line\": %d, \"character\": %d }, "
+ "\"includeDeclaration\": \"%s\" }";
+
+ struct text_document_position *position = &params->position;
+ struct s8 json = s8from_fmt(fmt, position->uri.l, position->uri.s,
+ position->position.line, position->position.col,
+ params->include_declaration ? "true" : "false");
+
+ return json;
+}
diff --git a/src/main/lsp/types.h b/src/main/lsp/types.h
new file mode 100644
index 0000000..7b6ba1a
--- /dev/null
+++ b/src/main/lsp/types.h
@@ -0,0 +1,385 @@
+#ifndef _LSP_TYPES_H
+#define _LSP_TYPES_H
+
+#include "dged/json.h"
+#include "dged/location.h"
+#include "dged/s8.h"
+#include "dged/vec.h"
+
+struct buffer;
+
+struct client_capabilities {};
+
+struct workspace_folder {
+ struct s8 uri;
+ struct s8 name;
+};
+
+struct initialize_params {
+ int process_id;
+ struct client_info {
+ struct s8 name;
+ struct s8 version;
+ } client_info;
+
+ struct client_capabilities client_capabilities;
+
+ struct workspace_folder *workspace_folders;
+ size_t nworkspace_folders;
+};
+
+enum text_document_sync_kind {
+ TextDocumentSync_None = 0,
+ TextDocumentSync_Full = 1,
+ TextDocumentSync_Incremental = 2,
+};
+
+struct text_document_sync {
+ enum text_document_sync_kind kind;
+ bool open_close;
+ bool save;
+};
+
+enum position_encoding_kind {
+ PositionEncoding_Utf8,
+ PositionEncoding_Utf16,
+ PositionEncoding_Utf32,
+};
+
+struct completion_options {
+ VEC(struct s8) trigger_characters;
+ VEC(struct s8) all_commit_characters;
+ bool resolve_provider;
+};
+
+struct server_capabilities {
+ struct text_document_sync text_document_sync;
+ enum position_encoding_kind position_encoding;
+ bool supports_completion;
+ struct completion_options completion_options;
+};
+
+struct initialize_result {
+ struct server_capabilities capabilities;
+ struct server_info {
+ struct s8 name;
+ struct s8 version;
+ } server_info;
+};
+
+struct s8 initialize_params_to_json(struct initialize_params *params);
+struct initialize_result initialize_result_from_json(struct json_value *json);
+void initialize_result_free(struct initialize_result *);
+struct s8 position_encoding_kind_str(enum position_encoding_kind);
+
+struct text_document_item {
+ struct s8 uri;
+ struct s8 language_id;
+ uint64_t version;
+ struct s8 text;
+};
+
+struct text_document_identifier {
+ struct s8 uri;
+};
+
+struct text_document_position {
+ struct s8 uri;
+ struct location position;
+};
+
+struct text_document_location {
+ struct s8 uri;
+ struct region range;
+};
+
+struct versioned_text_document_identifier {
+ struct s8 uri;
+ uint64_t version;
+};
+
+struct did_open_text_document_params {
+ struct text_document_item text_document;
+};
+
+enum location_type {
+ Location_Single,
+ Location_Array,
+ Location_Link,
+ Location_Null,
+};
+
+typedef VEC(struct text_document_location) location_vec;
+
+struct location_result {
+ enum location_type type;
+ union location_data {
+ struct text_document_location single;
+ location_vec array;
+ } location;
+};
+
+struct did_change_text_document_params {
+ struct versioned_text_document_identifier text_document;
+ struct text_document_content_change_event *content_changes;
+ size_t ncontent_changes;
+};
+
+struct did_save_text_document_params {
+ struct text_document_identifier text_document;
+};
+
+struct text_document_content_change_event {
+ struct region range;
+ struct s8 text;
+ bool full_document;
+};
+
+enum diagnostic_severity {
+ LspDiagnostic_Error = 1,
+ LspDiagnostic_Warning = 2,
+ LspDiagnostic_Information = 3,
+ LspDiagnostic_Hint = 4,
+};
+
+struct diagnostic {
+ struct s8 message;
+ struct s8 source;
+ struct region region;
+ enum diagnostic_severity severity;
+};
+
+typedef VEC(struct diagnostic) diagnostic_vec;
+
+struct publish_diagnostics_params {
+ struct s8 uri;
+ uint64_t version;
+ diagnostic_vec diagnostics;
+};
+
+struct code_action_context {
+ diagnostic_vec diagnostics;
+};
+
+struct code_action_params {
+ struct text_document_identifier text_document;
+ struct region range;
+ struct code_action_context context;
+};
+
+struct text_edit {
+ struct region range;
+ struct s8 new_text;
+};
+
+typedef VEC(struct text_edit) text_edit_vec;
+
+struct text_edit_pair {
+ struct s8 uri;
+ text_edit_vec edits;
+};
+
+typedef VEC(struct text_edit_pair) change_vec;
+
+struct workspace_edit {
+ change_vec changes;
+};
+
+struct lsp_command {
+ struct s8 title;
+ struct s8 command;
+ struct s8 arguments;
+};
+
+struct code_action {
+ struct s8 title;
+ struct s8 kind;
+
+ bool has_edit;
+ struct workspace_edit edit;
+
+ bool has_command;
+ struct lsp_command command;
+};
+
+typedef VEC(struct lsp_command) lsp_command_vec;
+typedef VEC(struct code_action) code_action_vec;
+
+struct code_actions {
+ lsp_command_vec commands;
+ code_action_vec code_actions;
+};
+
+struct formatting_options {
+ size_t tab_size;
+ bool use_spaces;
+};
+
+struct document_formatting_params {
+ struct text_document_identifier text_document;
+ struct formatting_options options;
+};
+
+struct document_range_formatting_params {
+ struct text_document_identifier text_document;
+ struct region range;
+ struct formatting_options options;
+};
+
+enum completion_item_kind {
+ CompletionItem_Text = 1,
+ CompletionItem_Method = 2,
+ CompletionItem_Function = 3,
+ CompletionItem_Constructor = 4,
+ CompletionItem_Field = 5,
+ CompletionItem_Variable = 6,
+ CompletionItem_Class = 7,
+ CompletionItem_Interface = 8,
+ CompletionItem_Module = 9,
+ CompletionItem_Property = 10,
+ CompletionItem_Unit = 11,
+ CompletionItem_Value = 12,
+ CompletionItem_Enum = 13,
+ CompletionItem_Keyword = 14,
+ CompletionItem_Snippet = 15,
+ CompletionItem_Color = 16,
+ CompletionItem_File = 17,
+ CompletionItem_Reference = 18,
+ CompletionItem_Folder = 19,
+ CompletionItem_EnumMember = 20,
+ CompletionItem_Constant = 21,
+ CompletionItem_Struct = 22,
+ CompletionItem_Event = 23,
+ CompletionItem_Operator = 24,
+ CompletionItem_TypeParameter = 25,
+};
+
+enum text_edit_type {
+ TextEdit_None,
+ TextEdit_TextEdit,
+ TextEdit_InsertReplaceEdit,
+};
+
+struct insert_replace_edit {
+ struct s8 new_text;
+ struct region insert;
+ struct region replace;
+};
+
+struct lsp_completion_item {
+ struct s8 label;
+ enum completion_item_kind kind;
+ struct s8 detail;
+ struct s8 sort_text;
+ struct s8 filter_text;
+ struct s8 insert_text;
+
+ enum text_edit_type edit_type;
+ union edit_ {
+ struct text_edit text_edit;
+ struct insert_replace_edit insert_replace_edit;
+ } edit;
+
+ text_edit_vec additional_text_edits;
+
+ struct lsp_command command;
+};
+
+typedef VEC(struct lsp_completion_item) completions_vec;
+
+struct completion_list {
+ bool incomplete;
+ completions_vec items;
+};
+
+struct rename_params {
+ struct text_document_position position;
+ struct s8 new_name;
+};
+
+struct parameter_information {
+ struct s8 label;
+ struct s8 documentation;
+};
+
+typedef VEC(struct parameter_information) param_info_vec;
+
+struct signature_information {
+ struct s8 label;
+ struct s8 documentation;
+ param_info_vec parameters;
+};
+
+typedef VEC(struct signature_information) signature_info_vec;
+
+struct signature_help {
+ uint32_t active_signature;
+ signature_info_vec signatures;
+};
+
+struct hover {
+ struct s8 contents;
+ struct region range;
+};
+
+struct reference_params {
+ struct text_document_position position;
+ bool include_declaration;
+};
+
+struct text_document_item text_document_item_from_buffer(struct buffer *buffer);
+struct versioned_text_document_identifier
+versioned_identifier_from_buffer(struct buffer *buffer);
+
+void versioned_text_document_identifier_free(
+ struct versioned_text_document_identifier *);
+void text_document_item_free(struct text_document_item *);
+
+struct s8 did_change_text_document_params_to_json(
+ struct did_change_text_document_params *);
+struct s8
+did_open_text_document_params_to_json(struct did_open_text_document_params *);
+struct s8
+did_save_text_document_params_to_json(struct did_save_text_document_params *);
+
+struct publish_diagnostics_params
+diagnostics_from_json(struct json_value *json);
+
+const char *diag_severity_to_str(enum diagnostic_severity severity);
+uint32_t diag_severity_color(enum diagnostic_severity severity);
+void diagnostic_free(struct diagnostic *);
+
+struct s8 document_position_to_json(struct text_document_position *position);
+struct location_result location_result_from_json(struct json_value *json);
+void location_result_free(struct location_result *res);
+
+struct s8 code_action_params_to_json(struct code_action_params *);
+
+struct code_actions lsp_code_actions_from_json(struct json_value *);
+void lsp_code_actions_free(struct code_actions *);
+struct s8 lsp_command_to_json(struct lsp_command *);
+
+text_edit_vec text_edits_from_json(struct json_value *);
+void text_edits_free(text_edit_vec);
+struct workspace_edit workspace_edit_from_json(struct json_value *);
+void workspace_edit_free(struct workspace_edit *);
+
+struct s8
+document_formatting_params_to_json(struct document_formatting_params *);
+struct s8 document_range_formatting_params_to_json(
+ struct document_range_formatting_params *);
+
+struct completion_list completion_list_from_json(struct json_value *);
+void completion_list_free(struct completion_list *);
+
+struct s8 rename_params_to_json(struct rename_params *);
+
+struct signature_help signature_help_from_json(struct json_value *);
+void signature_help_free(struct signature_help *);
+
+struct hover hover_from_json(struct json_value *);
+void hover_free(struct hover *);
+
+struct s8 reference_params_to_json(struct reference_params *);
+
+#endif
diff --git a/src/main/main.c b/src/main/main.c
index fa740e8..12ed1ec 100644
--- a/src/main/main.c
+++ b/src/main/main.c
@@ -38,6 +38,7 @@
#include "bindings.h"
#include "cmds.h"
#include "completion.h"
+#include "frame-hooks.h"
#include "version.h"
/* welcome.h is generated from welcome.inc with
@@ -86,12 +87,21 @@ void segfault(int sig) {
abort();
}
+/* void __asan_on_error() {
+ if (display != NULL) {
+ display_clear(display);
+ display_destroy(display);
+ }
+} */
+
#define INVALID_WATCH (uint32_t) - 1
static void clear_buffer_props(struct buffer *buffer, void *userdata) {
(void)userdata;
- buffer_clear_text_properties(buffer);
+ if (!buffer->retain_properties) {
+ buffer_clear_text_properties(buffer);
+ }
}
struct watched_file {
@@ -275,6 +285,10 @@ int main(int argc, char *argv[]) {
buffers_add_add_hook(&buflist, watch_file, (void *)reactor);
+ init_bindings();
+
+ init_completion(&buflist);
+
#ifdef SYNTAX_ENABLE
char *treesitter_path_env = getenv("TREESITTER_GRAMMARS");
struct setting *path_setting = settings_get("editor.grammars-path");
@@ -324,7 +338,7 @@ int main(int argc, char *argv[]) {
#endif
#ifdef LSP_ENABLE
- lang_servers_init(reactor, &buflist);
+ lang_servers_init(reactor, &buflist, &commands);
#endif
struct buffer initial_buffer = buffer_create("welcome");
@@ -361,20 +375,21 @@ int main(int argc, char *argv[]) {
register_settings_commands(&commands);
struct keymap *current_keymap = NULL;
- init_bindings();
-
- init_completion(&buflist, &commands);
timers_init();
+ init_frame_hooks();
float frame_time = 0.f;
static char keyname[64] = {0};
static uint32_t nkeychars = 0;
+ bool needs_render = true;
+
while (running) {
timers_start_frame();
if (display_resized) {
windows_resize(display_height(display), display_width(display));
display_resized = false;
+ needs_render = true;
}
// TODO: maybe this should be hidden behind something
@@ -383,7 +398,7 @@ int main(int argc, char *argv[]) {
/* Update all windows together with the buffers in them. */
struct timer *update_windows = timer_start("update-windows");
- windows_update(frame_alloc, frame_time);
+ needs_render |= windows_update(frame_alloc, frame_time);
timer_stop(update_windows);
struct window *active_window = windows_get_active();
@@ -392,26 +407,36 @@ int main(int argc, char *argv[]) {
* from updating the buffers.
*/
struct timer *update_display = timer_start("display");
- display_begin_render(display);
- windows_render(display);
- struct buffer_view *view = window_buffer_view(active_window);
- struct location cursor = buffer_view_dot_to_visual(view);
- struct window_position winpos = window_position(active_window);
- display_move_cursor(display, winpos.y + cursor.line, winpos.x + cursor.col);
- display_end_render(display);
+ if (needs_render) {
+ display_begin_render(display);
+ windows_render(display);
+ struct buffer_view *view = window_buffer_view(active_window);
+ struct location cursor = buffer_view_dot_to_visual(view);
+ struct window_position winpos = window_position(active_window);
+ display_move_cursor(display, winpos.y + cursor.line,
+ winpos.x + cursor.col);
+ display_end_render(display);
+ needs_render = false;
+ }
timer_stop(update_display);
- /* This blocks for events, so if nothing has happened we block here and let
- * the CPU do something more useful than updating this editor for no reason.
- * This is also the reason that there is no timed scope around this, it
- * simply makes no sense.
+ /* if we have dispatched frame hooks, they need a
+ * full cycle of updates.
*/
- reactor_update(reactor);
+ if (dispatch_next_frame_hooks() == 0) {
+ /* This blocks for events, so if nothing has happened we block here and
+ * let the CPU do something more useful than updating this editor for no
+ * reason. This is also the reason that there is no timed scope around
+ * this, it simply makes no sense.
+ */
+ reactor_update(reactor);
+ }
struct timer *update_keyboard = timer_start("update-keyboard");
struct keyboard_update kbd_upd =
keyboard_update(&kbd, reactor, frame_alloc);
+ needs_render |= kbd_upd.nkeys > 0;
for (uint32_t ki = 0; ki < kbd_upd.nkeys; ++ki) {
struct key *k = &kbd_upd.keys[ki];
@@ -457,7 +482,7 @@ int main(int argc, char *argv[]) {
if (nkeychars < 64) {
nkeychars += key_name(k, keyname + nkeychars, 64 - nkeychars);
- minibuffer_echo("%s", keyname);
+ minibuffer_display("%s", keyname);
}
current_keymap = res.data.keymap;
@@ -472,10 +497,10 @@ int main(int argc, char *argv[]) {
char keyname[16];
key_name(k, keyname, 16);
if (current_keymap == NULL) {
- minibuffer_echo_timeout(4, "key \"%s\" is not bound!", keyname);
+ minibuffer_display_timeout(4, "key \"%s\" is not bound!", keyname);
} else {
- minibuffer_echo_timeout(4, "key \"%s %s\" is not bound!",
- current_keymap->name, keyname);
+ minibuffer_display_timeout(4, "key \"%s %s\" is not bound!",
+ current_keymap->name, keyname);
}
current_keymap = NULL;
nkeychars = 0;
@@ -498,13 +523,10 @@ int main(int argc, char *argv[]) {
frame_allocator_clear(&frame_allocator);
}
+ teardown_frame_hooks();
timers_destroy();
teardown_global_commands();
- destroy_completion();
windows_destroy();
- minibuffer_destroy();
- buffer_destroy(&minibuffer);
- buffers_destroy(&buflist);
#ifdef SYNTAX_ENABLE
syntax_teardown();
@@ -514,6 +536,11 @@ int main(int argc, char *argv[]) {
lang_servers_teardown();
#endif
+ destroy_completion();
+ minibuffer_destroy();
+ buffer_destroy(&minibuffer);
+ buffers_destroy(&buflist);
+
display_clear(display);
display_destroy(display);
destroy_bindings();