summaryrefslogtreecommitdiff
path: root/src/main/lsp/goto.c
diff options
context:
space:
mode:
authorAlbert Cervin <albert@acervin.com>2024-09-17 08:47:03 +0200
committerAlbert Cervin <albert@acervin.com>2025-11-01 22:11:14 +0100
commit4459b8b3aa9d73895391785a99dcc87134e80601 (patch)
treea5204f447a0b2b05f63504c7fe958ef9bbf1918a /src/main/lsp/goto.c
parent4689f3f38277bb64981fc960e8e384e2d065d659 (diff)
downloaddged-4459b8b3aa9d73895391785a99dcc87134e80601.tar.gz
dged-4459b8b3aa9d73895391785a99dcc87134e80601.tar.xz
dged-4459b8b3aa9d73895391785a99dcc87134e80601.zip
More lsp support
This makes the LSP support complete for now: - Completion - Diagnostics - Goto implementation/declaration - Rename - Documentation - Find references
Diffstat (limited to 'src/main/lsp/goto.c')
-rw-r--r--src/main/lsp/goto.c297
1 files changed, 297 insertions, 0 deletions
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;
+}