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/diagnostics.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/diagnostics.c')
| -rw-r--r-- | src/main/lsp/diagnostics.c | 386 |
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(¬ification->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(¶ms.diagnostics, struct diagnostic * diag) { + diagnostic_free(diag); + } + VEC_DESTROY(¶ms.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; +} |
