summaryrefslogtreecommitdiff
path: root/src/main/lsp/diagnostics.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/diagnostics.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/diagnostics.c')
-rw-r--r--src/main/lsp/diagnostics.c386
1 files changed, 386 insertions, 0 deletions
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;
+}