summaryrefslogtreecommitdiff
path: root/src/main/lsp.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.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.c')
-rw-r--r--src/main/lsp.c917
1 files changed, 877 insertions, 40 deletions
diff --git a/src/main/lsp.c b/src/main/lsp.c
index d56ca07..5886ea7 100644
--- a/src/main/lsp.c
+++ b/src/main/lsp.c
@@ -1,38 +1,556 @@
#include "lsp.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/hash.h"
#include "dged/hashmap.h"
-#include "dged/lsp.h"
+#include "dged/lang.h"
#include "dged/minibuffer.h"
#include "dged/reactor.h"
#include "dged/settings.h"
+#include "dged/window.h"
-HASHMAP_ENTRY_TYPE(lsp_entry, struct lsp *);
+#include "lsp/references.h"
+#include "main/bindings.h"
+#include "main/completion.h"
-HASHMAP(struct lsp_entry) g_lsp_clients;
+#include "lsp/actions.h"
+#include "lsp/choice-buffer.h"
+#include "lsp/completion.h"
+#include "lsp/diagnostics.h"
+#include "lsp/format.h"
+#include "lsp/goto.h"
+#include "lsp/help.h"
+#include "lsp/rename.h"
+
+struct lsp_pending_request {
+ uint64_t request_id;
+ response_handler handler;
+ void *userdata;
+};
+
+struct lsp_server {
+ struct lsp *lsp;
+ uint32_t restarts;
+ struct s8 lang_id;
+ struct lsp_pending_request pending_requests[16];
+
+ bool initialized;
+
+ enum text_document_sync_kind sync_kind;
+ bool send_open_close;
+ bool send_save;
+
+ enum position_encoding_kind position_encoding;
+
+ struct lsp_diagnostics *diagnostics;
+ struct completion_ctx *completion_ctx;
+};
+
+HASHMAP_ENTRY_TYPE(lsp_entry, struct lsp_server);
+
+static struct lsp_data {
+ HASHMAP(struct lsp_entry) clients;
+ struct keymap keymap;
+ struct keymap all_keymap;
-static struct create_data {
struct reactor *reactor;
struct buffers *buffers;
-} g_create_data;
-static void log_message(int type, struct s8 msg) {
- (void)type;
- message("%s", msg);
+ struct buffer *current_diagnostic_buffer;
+ uint64_t current_request_id;
+} g_lsp_data;
+
+struct lsp *lsp_backend(struct lsp_server *server) { return server->lsp; }
+
+struct lsp_server *lsp_server_for_lang_id(const char *id) {
+ HASHMAP_GET(&g_lsp_data.clients, struct lsp_entry, id,
+ struct lsp_server * server);
+ return server;
}
-static void create_lsp_client(struct buffer *buffer, void *userdata) {
+static uint32_t bytepos_to_column(struct text_chunk *text, uint32_t bytecol) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(text->text, text->nbytes, 0);
+ uint32_t ncols = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL && col < bytecol) {
+ col += codepoint->nbytes;
+ ncols += unicode_visual_char_width(codepoint);
+ }
+
+ return ncols;
+}
+
+static uint32_t codepoint_pos_to_column(struct text_chunk *text,
+ uint32_t codepoint_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(text->text, text->nbytes, 0);
+ uint32_t ncols = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL &&
+ col < codepoint_col) {
+ ++col;
+ ncols += unicode_visual_char_width(codepoint);
+ }
+
+ return ncols;
+}
+
+static uint32_t codeunit_pos_to_column(struct text_chunk *text,
+ uint32_t codeunit_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(text->text, text->nbytes, 0);
+ uint32_t ncols = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL &&
+ col < codeunit_col) {
+ col += codepoint->codepoint >= 0x010000 ? 2 : 1;
+ ncols += unicode_visual_char_width(codepoint);
+ }
+
+ return ncols;
+}
+
+struct region lsp_range_to_coordinates(struct lsp_server *server,
+ struct buffer *buffer,
+ struct region range) {
+
+ uint32_t (*col_converter)(struct text_chunk *, uint32_t) =
+ codeunit_pos_to_column;
+
+ switch (server->position_encoding) {
+ case PositionEncoding_Utf8:
+ col_converter = bytepos_to_column;
+ break;
+
+ case PositionEncoding_Utf16:
+ col_converter = codeunit_pos_to_column;
+ break;
+
+ case PositionEncoding_Utf32:
+ col_converter = codepoint_pos_to_column;
+ break;
+ }
+
+ struct region reg = range;
+ struct text_chunk beg_line = buffer_line(buffer, range.begin.line);
+ reg.begin.col = col_converter(&beg_line, range.begin.col);
+
+ struct text_chunk end_line = beg_line;
+ if (range.begin.line != range.end.line) {
+ end_line = buffer_line(buffer, range.end.line);
+ }
+ reg.end.col = col_converter(&end_line, range.end.col);
+
+ if (beg_line.allocated) {
+ free(beg_line.text);
+ }
+
+ if (range.begin.line != range.end.line && end_line.allocated) {
+ free(end_line.text);
+ }
+
+ return reg;
+}
+
+uint64_t new_pending_request(struct lsp_server *server,
+ response_handler handler, void *userdata) {
+ for (int i = 0; i < 16; ++i) {
+ if (server->pending_requests[i].request_id == (uint64_t)-1) {
+ ++g_lsp_data.current_request_id;
+ server->pending_requests[i].request_id = g_lsp_data.current_request_id;
+ server->pending_requests[i].handler = handler;
+ server->pending_requests[i].userdata = userdata;
+ return g_lsp_data.current_request_id;
+ }
+ }
+
+ return -1;
+}
+
+static bool
+request_response_received(struct lsp_server *server, uint64_t id,
+ struct lsp_pending_request **pending_request) {
+ for (int i = 0; i < 16; ++i) {
+ if (server->pending_requests[i].request_id == id) {
+ server->pending_requests[i].request_id = (uint64_t)-1;
+ *pending_request = &server->pending_requests[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void buffer_updated(struct buffer *buffer, void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+
+ diagnostic_vec *diagnostics =
+ diagnostics_for_buffer(server->diagnostics, buffer);
+ if (diagnostics == NULL) {
+ return;
+ }
+
+ VEC_FOR_EACH(diagnostics, struct diagnostic * diag) {
+ struct text_property prop;
+ prop.type = TextProperty_Colors;
+ uint32_t color = diag_severity_color(diag->severity);
+ prop.data.colors = (struct text_property_colors){
+ .set_bg = true,
+ .set_fg = true,
+ .fg = Color_White,
+ .bg = color,
+ .underline = true,
+ };
+
+ struct region reg = region_new(
+ diag->region.begin, buffer_previous_char(buffer, diag->region.end));
+
+ buffer_add_text_property(buffer, reg.begin, reg.end, prop);
+
+ if (window_buffer(windows_get_active()) == buffer) {
+ struct buffer_view *bv = window_buffer_view(windows_get_active());
+
+ if (region_is_inside(diag->region, bv->dot)) {
+ size_t len = 0;
+ for (size_t i = 0; i < diag->message.l && diag->message.s[i] != '\n';
+ ++i) {
+ ++len;
+ }
+ minibuffer_display_timeout(1, "%s: %.*s",
+ diag_severity_to_str(diag->severity), len,
+ diag->message.s);
+ }
+ }
+ }
+}
+
+static void buffer_pre_save(struct buffer *buffer, void *userdata) {
+ (void)buffer;
(void)userdata;
+}
+
+static void format_on_save(struct buffer *buffer, struct lsp_server *server) {
+ struct setting *glob_fmt_on_save = settings_get("editor.format-on-save");
+ struct setting *fmt_on_save =
+ lang_setting(&buffer->lang, "language-server.format-on-save");
+
+ if ((glob_fmt_on_save != NULL && glob_fmt_on_save->value.data.bool_value) ||
+ (glob_fmt_on_save == NULL && fmt_on_save != NULL &&
+ fmt_on_save->value.data.bool_value)) {
+ format_document_save(server, buffer);
+ }
+}
+
+static void buffer_post_save(struct buffer *buffer, void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+ if (server->send_save) {
+ struct versioned_text_document_identifier text_document =
+ versioned_identifier_from_buffer(buffer);
- struct create_data *data = &g_create_data;
+ struct did_save_text_document_params params = {
+ .text_document =
+ (struct text_document_identifier){
+ .uri = text_document.uri,
+ },
+ };
+
+ struct s8 json_payload = did_save_text_document_params_to_json(&params);
+
+ lsp_send(server->lsp,
+ lsp_create_notification(s8("textDocument/didSave"), json_payload));
+
+ versioned_text_document_identifier_free(&text_document);
+ s8delete(json_payload);
+ }
+
+ format_on_save(buffer, server);
+}
+
+static uint32_t count_codepoints(struct text_chunk *chunk,
+ uint32_t target_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(chunk->text, chunk->nbytes, 0);
+ uint32_t ncodepoints = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL && col < target_col) {
+ col += unicode_visual_char_width(codepoint);
+ ++ncodepoints;
+ }
+
+ return ncodepoints;
+}
+
+static uint32_t count_codeunits(struct text_chunk *chunk, uint32_t target_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(chunk->text, chunk->nbytes, 0);
+ uint32_t ncodeunits = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL && col < target_col) {
+ col += unicode_visual_char_width(codepoint);
+ ncodeunits += codepoint->codepoint >= 0x010000 ? 2 : 1;
+ }
+
+ return ncodeunits;
+}
+
+static uint32_t count_bytes(struct text_chunk *chunk, uint32_t target_col) {
+ struct utf8_codepoint_iterator iter =
+ create_utf8_codepoint_iterator(chunk->text, chunk->nbytes, 0);
+ uint32_t nbytes = 0, col = 0;
+ struct codepoint *codepoint = NULL;
+ while ((codepoint = utf8_next_codepoint(&iter)) != NULL && col < target_col) {
+ col += unicode_visual_char_width(codepoint);
+ nbytes += codepoint->nbytes;
+ }
+
+ return nbytes;
+}
+
+static struct region edit_location_to_lsp(struct buffer *buffer,
+ struct edit_location edit,
+ struct lsp_server *server) {
+
+ struct region res = edit.coordinates;
+ if (server->position_encoding == PositionEncoding_Utf8) {
+ /* In this case, the buffer hook has already
+ * done the job for us. */
+ res.begin.col = edit.bytes.begin.col;
+ res.end.col = edit.bytes.end.col;
+ return res;
+ }
+
+ return region_to_lsp(buffer, res, server);
+}
+
+struct region region_to_lsp(struct buffer *buffer, struct region region,
+ struct lsp_server *server) {
+ struct region res = region;
+
+ uint32_t (*col_counter)(struct text_chunk *, uint32_t) = count_codeunits;
+
+ switch (server->position_encoding) {
+ case PositionEncoding_Utf8:
+ col_counter = count_bytes;
+ return res;
+
+ case PositionEncoding_Utf16:
+ col_counter = count_codeunits;
+ break;
+
+ case PositionEncoding_Utf32:
+ col_counter = count_codepoints;
+ break;
+ }
+
+ struct text_chunk beg_line = buffer_line(buffer, region.begin.line);
+ res.begin.col = col_counter(&beg_line, region.begin.col);
+
+ struct text_chunk end_line = beg_line;
+ if (region.begin.line != region.end.line) {
+ end_line = buffer_line(buffer, region.end.line);
+ }
+
+ res.end.col = col_counter(&end_line, region.end.col);
+
+ if (beg_line.allocated) {
+ free(beg_line.text);
+ }
+
+ if (end_line.allocated && region.begin.line != region.end.line) {
+ free(end_line.text);
+ }
+
+ return res;
+}
+
+static void buffer_text_changed(struct buffer *buffer,
+ struct edit_location range, bool delete,
+ void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+
+ struct text_chunk new_text = {0};
+ switch (server->sync_kind) {
+ case TextDocumentSync_None:
+ return;
+
+ case TextDocumentSync_Full:
+ new_text =
+ buffer_region(buffer, region_new((struct location){.line = 0, .col = 0},
+ buffer_end(buffer)));
+ break;
+
+ case TextDocumentSync_Incremental:
+ if (!delete) {
+ new_text = buffer_region(buffer, range.coordinates);
+ }
+ break;
+ }
+
+ struct region reg = edit_location_to_lsp(buffer, range, server);
+ reg = delete ? reg : region_new(reg.begin, reg.begin);
+ struct text_document_content_change_event evt = {
+ .full_document = server->sync_kind == TextDocumentSync_Full,
+ .text = s8new((const char *)new_text.text, new_text.nbytes),
+ .range = reg,
+ };
+
+ struct versioned_text_document_identifier text_document =
+ versioned_identifier_from_buffer(buffer);
+ struct did_change_text_document_params params = {
+ .text_document = text_document,
+ .content_changes = &evt,
+ .ncontent_changes = 1,
+ };
+
+ struct s8 json_payload = did_change_text_document_params_to_json(&params);
+
+ lsp_send(server->lsp,
+ lsp_create_notification(s8("textDocument/didChange"), json_payload));
+
+ versioned_text_document_identifier_free(&text_document);
+ s8delete(json_payload);
+ s8delete(evt.text);
+
+ if (new_text.allocated) {
+ free(new_text.text);
+ }
+}
+
+static void buffer_text_inserted(struct buffer *buffer,
+ struct edit_location inserted,
+ void *userdata) {
+ buffer_text_changed(buffer, inserted, false, userdata);
+}
+
+static void buffer_text_deleted(struct buffer *buffer,
+ struct edit_location deleted, void *userdata) {
+ buffer_text_changed(buffer, deleted, true, userdata);
+}
+
+static void send_did_open(struct lsp_server *server, struct buffer *buffer) {
+ if (!server->send_open_close) {
+ return;
+ }
+
+ struct text_document_item doc = text_document_item_from_buffer(buffer);
+ struct did_open_text_document_params params = {
+ .text_document = doc,
+ };
+
+ struct s8 json_payload = did_open_text_document_params_to_json(&params);
+ lsp_send(server->lsp,
+ lsp_create_notification(s8("textDocument/didOpen"), json_payload));
+
+ text_document_item_free(&doc);
+ s8delete(json_payload);
+}
+
+static void setup_completion(struct lsp_server *server, struct buffer *buffer) {
+ if (server->completion_ctx != NULL) {
+ enable_completion_for_buffer(server->completion_ctx, buffer);
+ }
+}
+
+static void lsp_buffer_initialized(struct lsp_server *server,
+ struct buffer *buffer) {
+ if (s8eq(server->lang_id, s8(buffer->lang.id))) {
+ /* Needs to be a pre-delete hook since we need
+ * access to the deleted content to derive the
+ * correct UTF-8/16/32 position.
+ */
+ buffer_add_pre_delete_hook(buffer, buffer_text_deleted, server);
+ buffer_add_insert_hook(buffer, buffer_text_inserted, server);
+ buffer_add_update_hook(buffer, buffer_updated, server);
+ buffer_add_pre_save_hook(buffer, buffer_pre_save, server);
+ buffer_add_post_save_hook(buffer, buffer_post_save, server);
+
+ send_did_open(server, buffer);
+ setup_completion(server, buffer);
+ }
+}
+
+static void apply_initialized(struct buffer *buffer, void *userdata) {
+ struct lsp_server *server = (struct lsp_server *)userdata;
+ lsp_buffer_initialized(server, buffer);
+}
+
+static void handle_initialize(struct lsp_server *server,
+ struct lsp_response *response, void *userdata) {
+ (void)userdata;
+
+ struct initialize_result res =
+ initialize_result_from_json(&response->value.result);
+ message("lsp server initialized: %.*s (%.*s)", res.server_info.name.l,
+ res.server_info.name.s, res.server_info.version.l,
+ res.server_info.version.s);
+
+ lsp_send(server->lsp, lsp_create_notification(s8("initialized"), s8("")));
+
+ struct text_document_sync *tsync = &res.capabilities.text_document_sync;
+ server->sync_kind = tsync->kind;
+ server->send_open_close = tsync->open_close;
+ server->send_save = tsync->save;
+ server->position_encoding = res.capabilities.position_encoding;
+
+ if (res.capabilities.supports_completion) {
+ struct completion_options *comp_opts = &res.capabilities.completion_options;
+ server->completion_ctx = create_completion_ctx(
+ server, (triggerchar_vec *)&comp_opts->trigger_characters);
+ }
+
+ initialize_result_free(&res);
+ buffers_for_each(g_lsp_data.buffers, apply_initialized, server);
+
+ server->initialized = true;
+}
+
+static void init_lsp_client(struct lsp_server *server) {
+ if (lsp_restart_server(server->lsp) < 0) {
+ minibuffer_echo("failed to start language server %s process.",
+ lsp_server_name(server->lsp));
+ return;
+ }
+
+ // send some init info
+ struct initialize_params params = {
+ .process_id = lsp_server_pid(server->lsp),
+ .client_info =
+ {
+ .name = s8("dged"),
+ .version = s8("dev"),
+ },
+ .client_capabilities = {},
+ .workspace_folders = NULL,
+ .nworkspace_folders = 0,
+ };
+
+ uint64_t id = new_pending_request(server, handle_initialize, NULL);
+ struct s8 json_payload = initialize_params_to_json(&params);
+ lsp_send(server->lsp, lsp_create_request(id, s8("initialize"), json_payload));
+
+ s8delete(json_payload);
+}
+
+static void create_lsp_client(struct buffer *buffer, void *userdata) {
+ (void)userdata;
const char *id = buffer->lang.id;
- HASHMAP_GET(&g_lsp_clients, struct lsp_entry, id, struct lsp * *lsp);
- if (lsp == NULL) {
+ HASHMAP_GET(&g_lsp_data.clients, struct lsp_entry, id,
+ struct lsp_server * server);
+ if (server == NULL) {
// we need to start a new server
- struct setting *s = lang_setting(&buffer->lang, "language-server");
- if (!s) { // no language server set
+ struct setting *s = lang_setting(&buffer->lang, "language-server.command");
+ if (!s) {
+ if (!lang_is_fundamental(&buffer->lang)) {
+ message("No language server set for %s. Set with "
+ "`languages.%s.language-server`.",
+ buffer->lang.id, buffer->lang.id);
+ }
return;
}
@@ -40,69 +558,388 @@ static void create_lsp_client(struct buffer *buffer, void *userdata) {
char bufname[1024] = {0};
snprintf(bufname, 1024, "*%s-lsp-stderr*", command[0]);
- struct buffer *stderr_buf = buffers_find(data->buffers, bufname);
+ struct buffer *stderr_buf = buffers_find(g_lsp_data.buffers, bufname);
if (stderr_buf == NULL) {
struct buffer buf = buffer_create(bufname);
buf.lazy_row_add = false;
- stderr_buf = buffers_add(data->buffers, buf);
+ stderr_buf = buffers_add(g_lsp_data.buffers, buf);
buffer_set_readonly(stderr_buf, true);
}
- struct lsp_client client_impl = {
- .log_message = log_message,
- };
struct lsp *new_lsp =
- lsp_create(command, data->reactor, stderr_buf, client_impl, NULL);
+ lsp_create(command, g_lsp_data.reactor, stderr_buf, command[0]);
if (new_lsp == NULL) {
minibuffer_echo("failed to create language server %s", command[0]);
- buffers_remove(data->buffers, bufname);
+ buffers_remove(g_lsp_data.buffers, bufname);
return;
}
- HASHMAP_APPEND(&g_lsp_clients, struct lsp_entry, id,
+ HASHMAP_APPEND(&g_lsp_data.clients, struct lsp_entry, id,
struct lsp_entry * new);
- new->value = new_lsp;
-
- if (lsp_start_server(new_lsp) < 0) {
- minibuffer_echo("failed to start language server %s process.",
- lsp_server_name(new_lsp));
- return;
+ new->value = (struct lsp_server){
+ .lsp = new_lsp,
+ .lang_id = s8new(id, strlen(id)),
+ .restarts = 0,
+ };
+ for (int i = 0; i < 16; ++i) {
+ new->value.pending_requests[i].request_id = (uint64_t)-1;
}
+
+ new->value.diagnostics = diagnostics_create();
+
+ // support for this is determined later
+ new->value.completion_ctx = NULL;
+
+ init_lsp_client(&new->value);
+ server = &new->value;
+ }
+
+ /* An lsp for the language for this buffer is already started.
+ * if server is not initialized, it will get picked
+ * up anyway when handling the initialize response. */
+ if (server->initialized) {
+ lsp_buffer_initialized(server, buffer);
}
}
-static void set_default_lsp(const char *lang_id, const char *server) {
+static void set_default_lsp(const char *lang_id, const char *command) {
struct language l = lang_from_id(lang_id);
if (!lang_is_fundamental(&l)) {
lang_setting_set_default(
- &l, "language-server",
+ &l, "language-server.command",
(struct setting_value){.type = Setting_String,
- .data.string_value = (char *)server});
+ .data.string_value = (char *)command});
+ lang_setting_set_default(
+ &l, "language-server.format-on-save",
+ (struct setting_value){.type = Setting_Bool, .data.bool_value = false});
+
lang_destroy(&l);
}
}
-void lang_servers_init(struct reactor *reactor, struct buffers *buffers) {
- HASHMAP_INIT(&g_lsp_clients, 32, hash_name);
+static struct s8 lsp_modeline(struct buffer_view *view, void *userdata) {
+ (void)userdata;
+ struct lsp_server *server = lsp_server_for_lang_id(view->buffer->lang.id);
+ if (server == NULL) {
+ return s8("");
+ }
+
+ return s8from_fmt(
+ "lsp: %s:%d", lsp_server_name(server->lsp),
+ lsp_server_running(server->lsp) ? lsp_server_pid(server->lsp) : 0);
+}
+
+static uint32_t lsp_keymap_hook(struct buffer *buffer, struct keymap *keymaps[],
+ uint32_t max_nkeymaps, void *userdata) {
+ (void)userdata;
+
+ if (max_nkeymaps < 2) {
+ return 0;
+ }
+
+ uint32_t nadded = 1;
+ keymaps[0] = &g_lsp_data.all_keymap;
+
+ if (lsp_server_for_lang_id(buffer->lang.id) != NULL) {
+ keymaps[1] = &g_lsp_data.keymap;
+ nadded = 2;
+ }
+
+ return nadded;
+}
+
+static int32_t lsp_restart_cmd(struct command_ctx ctx, int argc,
+ const char **argv) {
+ (void)ctx;
+ (void)argc;
+ (void)argv;
+ struct buffer *b = window_buffer(windows_get_active());
+
+ struct lsp_server *server = lsp_server_for_buffer(b);
+ if (server == NULL) {
+ return 0;
+ }
+
+ lsp_restart_server(server->lsp);
+ return 0;
+}
+
+void lang_servers_init(struct reactor *reactor, struct buffers *buffers,
+ struct commands *commands) {
+ HASHMAP_INIT(&g_lsp_data.clients, 32, hash_name);
set_default_lsp("c", "clangd");
+ set_default_lsp("cxx", "clangd");
set_default_lsp("rs", "rust-analyzer");
set_default_lsp("python", "pylsp");
- g_create_data.reactor = reactor;
- g_create_data.buffers = buffers;
- buffer_add_create_hook(create_lsp_client, NULL);
+ g_lsp_data.current_request_id = 0;
+ g_lsp_data.reactor = reactor;
+ g_lsp_data.buffers = buffers;
+ buffers_add_add_hook(buffers, create_lsp_client, NULL);
+
+ struct command lsp_commands[] = {
+ {.name = "lsp-goto-definition", .fn = lsp_goto_def_cmd},
+ {.name = "lsp-goto-declaration", .fn = lsp_goto_decl_cmd},
+ {.name = "lsp-goto-implementation", .fn = lsp_goto_impl_cmd},
+ {.name = "lsp-goto", .fn = lsp_goto_cmd},
+ {.name = "lsp-goto-previous", .fn = lsp_goto_previous_cmd},
+ {.name = "lsp-references", .fn = lsp_references_cmd},
+ {.name = "lsp-restart", .fn = lsp_restart_cmd},
+ {.name = "lsp-diagnostics", .fn = diagnostics_cmd},
+ {.name = "lsp-next-diagnostic", .fn = next_diagnostic_cmd},
+ {.name = "lsp-prev-diagnostic", .fn = prev_diagnostic_cmd},
+ {.name = "lsp-code-actions", .fn = code_actions_cmd},
+ {.name = "lsp-format", .fn = format_cmd},
+ {.name = "lsp-rename", .fn = lsp_rename_cmd},
+ {.name = "lsp-help", .fn = lsp_help_cmd},
+ };
+
+ register_commands(commands, lsp_commands,
+ sizeof(lsp_commands) / sizeof(lsp_commands[0]));
+
+ struct binding lsp_binds[] = {
+ BINDING(Meta, '.', "lsp-goto-definition"),
+ BINDING(Meta, '/', "lsp-goto"),
+ BINDING(Meta, '[', "lsp-prev-diagnostic"),
+ BINDING(Meta, ']', "lsp-next-diagnostic"),
+ BINDING(Meta, 'a', "lsp-code-actions"),
+ BINDING(Meta, '=', "lsp-format"),
+ BINDING(Meta, 'r', "lsp-rename"),
+ BINDING(Meta, 'h', "lsp-help"),
+ };
+
+ struct binding global_binds[] = {
+ BINDING(Meta, ',', "lsp-goto-previous"),
+ };
+
+ g_lsp_data.keymap = keymap_create("lsp", 32);
+ keymap_bind_keys(&g_lsp_data.keymap, lsp_binds,
+ sizeof(lsp_binds) / sizeof(lsp_binds[0]));
+ g_lsp_data.all_keymap = keymap_create("lsp-global", 32);
+ keymap_bind_keys(&g_lsp_data.all_keymap, global_binds,
+ sizeof(global_binds) / sizeof(global_binds[0]));
+ buffer_add_keymaps_hook(lsp_keymap_hook, NULL);
+
+ buffer_view_add_modeline_hook(lsp_modeline, NULL);
+
+ init_goto(32, buffers);
+}
+
+void apply_edits_buffer(struct lsp_server *server, struct buffer *buffer,
+ text_edit_vec edits, struct location *point) {
+ VEC_FOR_EACH_REVERSE(&edits, struct text_edit * edit) {
+ struct region reg = lsp_range_to_coordinates(server, buffer, edit->range);
+ struct location at = reg.end;
+ if (region_has_size(reg)) {
+ if (point != NULL) {
+
+ if (reg.end.line == point->line) {
+ point->col -= reg.end.col > point->col ? 0 : point->col - reg.end.col;
+ }
+
+ uint64_t lines_deleted = reg.end.line - reg.begin.line;
+ if (lines_deleted > 0 && reg.end.line <= point->line) {
+ point->line -= lines_deleted;
+ }
+ }
+ at = buffer_delete(buffer, reg);
+ }
+
+ struct location after =
+ buffer_add(buffer, at, edit->new_text.s, edit->new_text.l);
+ if (point != NULL) {
+ if (after.line == point->line) {
+ point->col += after.col;
+ }
+
+ uint64_t lines_added = after.line - at.line;
+ if (lines_added > 0 && after.line <= point->line) {
+ point->line += lines_added;
+ }
+ }
+ }
+}
+
+bool apply_edits(struct lsp_server *server,
+ const struct workspace_edit *ws_edit) {
+ pause_completion();
+
+ VEC_FOR_EACH(&ws_edit->changes, struct text_edit_pair * pair) {
+ if (VEC_EMPTY(&pair->edits)) {
+ continue;
+ }
+
+ const char *p = s8tocstr(pair->uri);
+ struct buffer *b = buffers_find_by_filename(g_lsp_data.buffers, &p[7]);
+
+ if (b == NULL) {
+ struct buffer new_buf = buffer_from_file(&p[7]);
+ b = buffers_add(g_lsp_data.buffers, new_buf);
+ }
+
+ free((void *)p);
+ buffer_push_undo_boundary(b);
+ apply_edits_buffer(server, b, pair->edits, NULL);
+ buffer_push_undo_boundary(b);
+ }
+
+ resume_completion();
+ return true;
+}
+
+static void handle_request(struct lsp_server *server,
+ struct lsp_request request) {
+
+ struct s8 method = unescape_json_string(request.method);
+ if (s8eq(method, s8("workspace/applyEdit"))) {
+ struct workspace_edit ws_edit = workspace_edit_from_json(&request.params);
+ apply_edits(server, &ws_edit);
+ workspace_edit_free(&ws_edit);
+ } else {
+ message("unhandled lsp request (%s): id %d: %.*s",
+ lsp_server_name(server->lsp), request.id, request.method.l,
+ request.method.s);
+ }
+
+ s8delete(method);
+}
+
+static void handle_response(struct lsp_server *server,
+ struct lsp_response response) {
+ if (response.ok) {
+ struct lsp_pending_request *pending = NULL;
+ if (!request_response_received(server, response.id, &pending)) {
+ message("received response for id %d, server %s, which has no handler "
+ "registered",
+ response.id, lsp_server_name(server->lsp));
+ }
+
+ if (pending->handler != NULL) {
+ pending->handler(server, &response, pending->userdata);
+ }
+ } else {
+ struct s8 errmsg = response.value.error.message;
+ minibuffer_echo("lsp error (%s), id %d: %.*s", lsp_server_name(server->lsp),
+ response.id, errmsg.l, errmsg.s);
+ }
+}
+
+static void handle_notification(struct lsp_server *server,
+ struct lsp_notification notification) {
+ struct s8 method = unescape_json_string(notification.method);
+ if (s8eq(method, s8("textDocument/publishDiagnostics"))) {
+ handle_publish_diagnostics(server, g_lsp_data.buffers, &notification);
+ }
+
+ s8delete(method);
+}
+
+#define MAX_RESTARTS 10
+
+static void restart_if_needed(struct lsp_server *server) {
+ // if we successfully initialized the server, we can be sure
+ // it is up and running
+ if (lsp_server_running(server->lsp) && server->initialized) {
+ server->restarts = 0;
+ return;
+ }
+
+ if (!lsp_server_running(server->lsp)) {
+ if (server->restarts < MAX_RESTARTS) {
+ message("restarting \"%s\" (%d/%d)...", lsp_server_name(server->lsp),
+ server->restarts + 1, MAX_RESTARTS);
+ init_lsp_client(server);
+ ++server->restarts;
+
+ if (server->restarts == MAX_RESTARTS) {
+ minibuffer_echo("lsp \"%s\" has crashed %d times, giving up...",
+ lsp_server_name(server->lsp), MAX_RESTARTS);
+ }
+ } else {
+ // server is crashed and can only be restarted manually now
+ lsp_stop_server(server->lsp);
+ }
+ }
}
void lang_servers_update(void) {
- HASHMAP_FOR_EACH(&g_lsp_clients, struct lsp_entry * e) {
- lsp_update(e->value, NULL, 0);
+
+ HASHMAP_FOR_EACH(&g_lsp_data.clients, struct lsp_entry * e) {
+ restart_if_needed(&e->value);
+
+ struct lsp_message msgs[128];
+ uint32_t msgs_received = lsp_update(e->value.lsp, msgs, 128);
+
+ if (msgs_received == 0 || msgs_received == (uint32_t)-1) {
+ continue;
+ }
+
+ char bufname[1024] = {0};
+ snprintf(bufname, 1024, "*%s-lsp-messages*", lsp_server_name(e->value.lsp));
+ struct buffer *output_buf = buffers_find(g_lsp_data.buffers, bufname);
+ if (output_buf == NULL) {
+ struct buffer buf = buffer_create(bufname);
+ buf.lazy_row_add = false;
+ output_buf = buffers_add(g_lsp_data.buffers, buf);
+ }
+
+ buffer_set_readonly(output_buf, false);
+ for (uint32_t mi = 0; mi < msgs_received; ++mi) {
+ struct lsp_message *msg = &msgs[mi];
+ buffer_add(output_buf, buffer_end(output_buf), msg->payload.s,
+ msg->payload.l);
+ buffer_add(output_buf, buffer_end(output_buf), (uint8_t *)"\n", 1);
+
+ switch (msg->type) {
+ case Lsp_Response:
+ handle_response(&e->value, msg->message.response);
+ break;
+
+ case Lsp_Request:
+ handle_request(&e->value, msg->message.request);
+ break;
+
+ case Lsp_Notification:
+ handle_notification(&e->value, msg->message.notification);
+ break;
+ }
+
+ lsp_message_destroy(msg);
+ }
+
+ buffer_set_readonly(output_buf, true);
}
}
+static void lang_server_teardown(struct lsp_server *server) {
+ destroy_goto();
+ lsp_stop_server(server->lsp);
+ lsp_destroy(server->lsp);
+ s8delete(server->lang_id);
+}
+
void lang_servers_teardown(void) {
- HASHMAP_FOR_EACH(&g_lsp_clients, struct lsp_entry * e) {
- lsp_stop_server(e->value);
+ HASHMAP_FOR_EACH(&g_lsp_data.clients, struct lsp_entry * e) {
+ diagnostics_destroy(e->value.diagnostics);
+
+ if (e->value.completion_ctx != NULL) {
+ destroy_completion_ctx(e->value.completion_ctx);
+ }
+
+ lang_server_teardown(&e->value);
}
+
+ keymap_destroy(&g_lsp_data.keymap);
+ keymap_destroy(&g_lsp_data.all_keymap);
+ HASHMAP_DESTROY(&g_lsp_data.clients);
+}
+
+struct lsp_server *lsp_server_for_buffer(struct buffer *buffer) {
+ return lsp_server_for_lang_id(buffer->lang.id);
+}
+
+struct lsp_diagnostics *lsp_server_diagnostics(struct lsp_server *server) {
+ return server->diagnostics;
}