diff options
| author | Albert Cervin <albert@acervin.com> | 2024-09-17 08:47:03 +0200 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2025-11-01 22:11:14 +0100 |
| commit | 4459b8b3aa9d73895391785a99dcc87134e80601 (patch) | |
| tree | a5204f447a0b2b05f63504c7fe958ef9bbf1918a /src/main/lsp/references.c | |
| parent | 4689f3f38277bb64981fc960e8e384e2d065d659 (diff) | |
| download | dged-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/references.c')
| -rw-r--r-- | src/main/lsp/references.c | 248 |
1 files changed, 248 insertions, 0 deletions
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(¶ms); + 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; +} |
