summaryrefslogtreecommitdiff
path: root/src/main/lsp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/lsp')
-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
20 files changed, 3612 insertions, 0 deletions
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