#include "lsp_handler.h" #include "../libs/cjson/cJSON.h" #include "constants.h" #include "helper_functions.h" #include "node.h" #include #include #include #include #include #include #include #include int lsp_stdin_fd = -1; int lsp_stdout_fd = -1; int lsp_pid = -1; int version = 1; int c_id = 1; pthread_mutex_t lsp_mutex = PTHREAD_MUTEX_INITIALIZER; LspQueue *lsp_head = NULL; void lsp_enqueue(LspRequestType type, int id, Node *buffer) { LspQueue *node = malloc(sizeof(LspQueue)); node->type = type; node->id = id; node->buffer = buffer; node->next = NULL; pthread_mutex_lock(&lsp_mutex); if (!lsp_head) { lsp_head = node; } else { LspQueue *cur = lsp_head; while (cur->next) cur = cur->next; cur->next = node; } pthread_mutex_unlock(&lsp_mutex); } void handle_hover_response(Node *buffer, cJSON *msg) { cJSON *result = cJSON_GetObjectItem(msg, "result"); cJSON *contents = cJSON_GetObjectItem(result, "contents"); cJSON *text_json = cJSON_GetObjectItem(contents, "value"); if (!text_json || text_json->type != cJSON_String) return; char *text = text_json->valuestring; wrap_text(text, 100); int lines, longest; measure_text(text, &lines, &longest); if (lines > 14) lines = 14; int x = buffer->Buffer.data->cursor_col + buffer->Buffer.calc_x; int y = buffer->Buffer.data->cursor_row + buffer->Buffer.calc_y; int width = longest + 1; int height = lines; if (x + width >= cols) x = cols - width - 1; if (y + height >= rows) y = rows - height - 1; char *f_text = strdup(text); Node *bubble = create_bubble(y, x, width, height, f_text); free(f_text); draw_bubble(bubble); for (int i = 0; i < bubble->Bubble.line_count; i++) { for (int j = 0; j < bubble->Bubble.lines[i].cluster_count; j++) { free(bubble->Bubble.lines[i].clusters[j].utf8); } free(bubble->Bubble.lines[i].clusters); free(bubble->Bubble.lines[i].highlights); } free(bubble->Bubble.lines); free(bubble); } void *lsp_thread(void *arg) { UNUSED(arg); while (1) { cJSON *msg = lsp_recv(); if (!msg) continue; cJSON *id_json = cJSON_GetObjectItem(msg, "id"); if (id_json) { int resp_id = id_json->valueint; pthread_mutex_lock(&lsp_mutex); LspQueue **cur = &lsp_head; while (*cur) { if ((*cur)->id == resp_id) { LspQueue *matched = *cur; *cur = matched->next; pthread_mutex_unlock(&lsp_mutex); if (matched->type == HOVER) handle_hover_response(matched->buffer, msg); else if (matched->type == INITIALIZED) handle_initialized(msg); free(matched); break; } cur = &(*cur)->next; } pthread_mutex_unlock(&lsp_mutex); } else { cJSON *method_json = cJSON_GetObjectItem(msg, "method"); if (method_json && cJSON_IsString(method_json)) { const char *method = method_json->valuestring; if (method) { if (strcmp(method, "textDocument/publishDiagnostics") == 0) { cJSON *params = cJSON_GetObjectItem(msg, "params"); cJSON *diagnostics = cJSON_GetObjectItem(params, "diagnostics"); cJSON *diag = NULL; const char *filename = cJSON_GetObjectItem(params, "uri")->valuestring; if (root.Root.focused && root.Root.focused->type == BUFFER) { free(root.Root.focused->Buffer.errors); root.Root.focused->Buffer.errors = NULL; root.Root.focused->Buffer.error_count = 0; cJSON_ArrayForEach(diag, diagnostics) handle_diagnostic(diag, root.Root.focused); } } } } } cJSON_Delete(msg); } } void handle_diagnostic(cJSON *diag, Node *buffer) { cJSON *range = cJSON_GetObjectItem(diag, "range"); cJSON *start = cJSON_GetObjectItem(range, "start"); int start_line = cJSON_GetObjectItem(start, "line")->valueint; int start_char = cJSON_GetObjectItem(start, "character")->valueint; cJSON *end = cJSON_GetObjectItem(range, "end"); int end_line = cJSON_GetObjectItem(end, "line")->valueint; int end_char = cJSON_GetObjectItem(end, "character")->valueint; int severity = cJSON_GetObjectItem(diag, "severity")->valueint; char *text = cJSON_GetObjectItem(diag, "message")->valuestring; buffer->Buffer.errors = realloc(buffer->Buffer.errors, (buffer->Buffer.error_count + 1) * sizeof(LSPErrors)); buffer->Buffer.errors[buffer->Buffer.error_count].start_line = start_line; buffer->Buffer.errors[buffer->Buffer.error_count].start_col = start_char; buffer->Buffer.errors[buffer->Buffer.error_count].end_line = end_line; buffer->Buffer.errors[buffer->Buffer.error_count].end_col = end_char; buffer->Buffer.errors[buffer->Buffer.error_count].severity = severity; buffer->Buffer.errors[buffer->Buffer.error_count].message = text; buffer->Buffer.error_count++; } void handle_initialized(cJSON *_msg) { cJSON *initialized = make_initialized_request(); lsp_send(initialized); cJSON_Delete(initialized); } void send_notification(cJSON *msg) { char *dump = cJSON_Print(msg); wrap_text(dump, 200); Node *bubble = create_bubble(0, 0, 200, 30, dump); draw_bubble(bubble); } void lsp_setup(Node *buffer, uint8_t lang) { if (lang == RUBY) { char *argv[] = {"solargraph", "stdio", NULL}; start_lsp("solargraph", argv); } else if (lang == C_LANG) { char *argv[] = {"clangd", NULL}; start_lsp("clangd", argv); } else { return; } cJSON *init = make_folder_init_request(1, buffer->Buffer.folder); lsp_send(init); lsp_enqueue(INITIALIZED, 1, buffer); cJSON_Delete(init); char *text = lines_to_string(buffer->Buffer.data); cJSON *req = make_open_file_request(buffer->Buffer.filename, "c", text, 1); lsp_send(req); cJSON_Delete(req); free(text); pthread_t lsp_recive_thread_id; if (pthread_create(&lsp_recive_thread_id, NULL, lsp_thread, NULL) != 0) die("pthread_create failed for lsp thread"); } void lsp_change(Node *buffer, size_t start_line, size_t start_col, size_t end_line, size_t end_col, const char *new_text) { cJSON *req = make_change_file_request(buffer->Buffer.filename, version++, start_line, start_col, end_line, end_col, new_text); lsp_send(req); cJSON_Delete(req); } void lsp_hover(Node *buffer, size_t line, size_t character) { cJSON *req = make_hover_request(++c_id, buffer->Buffer.filename, line, character); lsp_send(req); lsp_enqueue(HOVER, c_id, buffer); cJSON_Delete(req); } void handle_sigchld(int sig) { int status; pid_t pid; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { if (pid == lsp_pid) die("lsp exited"); } } void start_lsp(const char *cmd, char *const argv[]) { int in_pipe[2]; int out_pipe[2]; if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1) { perror("pipe"); exit(1); } pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(1); } if (pid == 0) { dup2(in_pipe[0], STDIN_FILENO); dup2(out_pipe[1], STDOUT_FILENO); int devnull = open("/dev/null", O_WRONLY); if (devnull != -1) { dup2(devnull, STDERR_FILENO); close(devnull); } close(in_pipe[1]); close(out_pipe[0]); execvp(cmd, argv); perror("execvp"); _exit(1); } lsp_pid = pid; close(in_pipe[0]); close(out_pipe[1]); signal(SIGCHLD, handle_sigchld); lsp_stdin_fd = in_pipe[1]; lsp_stdout_fd = out_pipe[0]; } void lsp_send(cJSON *msg) { char *dump = cJSON_PrintUnformatted(msg); int len = strlen(dump); dprintf(lsp_stdin_fd, "Content-Length: %d\r\n\r\n%s", len, dump); free(dump); } cJSON *lsp_recv() { char header[256]; int content_length = 0; char c; int pos = 0; int newlines = 0; while (read(lsp_stdout_fd, &c, 1) == 1) { header[pos++] = c; if (pos >= sizeof(header) - 1) break; if (c == '\n') { header[pos] = '\0'; if (strncmp(header, "Content-Length:", 15) == 0) content_length = atoi(header + 15); if (strcmp(header, "\r\n") == 0) break; pos = 0; } } if (content_length <= 0) return NULL; char *buf = malloc(content_length + 1); if (!buf) return NULL; int got = 0; while (got < content_length) { int n = read(lsp_stdout_fd, buf + got, content_length - got); if (n <= 0) break; got += n; } buf[got] = '\0'; cJSON *msg = cJSON_Parse(buf); free(buf); return msg; } cJSON *make_hover_request(int id, const char *uri, size_t line, size_t character) { cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "jsonrpc", "2.0"); cJSON_AddNumberToObject(root, "id", id); cJSON_AddStringToObject(root, "method", "textDocument/hover"); cJSON *params = cJSON_CreateObject(); cJSON *doc = cJSON_CreateObject(); char *f_uri = malloc(strlen(uri) + 9); sprintf(f_uri, "file:///%s", uri); cJSON_AddStringToObject(doc, "uri", f_uri); cJSON_AddItemToObject(params, "textDocument", doc); free(f_uri); cJSON *pos = cJSON_CreateObject(); cJSON_AddNumberToObject(pos, "line", line); cJSON_AddNumberToObject(pos, "character", character); cJSON_AddItemToObject(params, "position", pos); cJSON_AddItemToObject(root, "params", params); return root; } cJSON *make_folder_init_request(int id, const char *root_uri) { cJSON *init = cJSON_CreateObject(); cJSON_AddStringToObject(init, "jsonrpc", "2.0"); cJSON_AddNumberToObject(init, "id", id); cJSON_AddStringToObject(init, "method", "initialize"); cJSON *params = cJSON_CreateObject(); char *uri = malloc(strlen(root_uri) + 9); sprintf(uri, "file:///%s", root_uri); cJSON_AddStringToObject(params, "rootUri", uri); free(uri); cJSON_AddItemToObject(init, "params", params); return init; } cJSON *make_initialized_request() { cJSON *msg = cJSON_CreateObject(); cJSON_AddStringToObject(msg, "jsonrpc", "2.0"); cJSON_AddStringToObject(msg, "method", "initialized"); cJSON *params = cJSON_CreateObject(); cJSON_AddItemToObject(msg, "params", params); return msg; } cJSON *make_open_file_request(const char *uri, const char *languageId, const char *text, int version) { cJSON *msg = cJSON_CreateObject(); cJSON_AddStringToObject(msg, "jsonrpc", "2.0"); cJSON_AddStringToObject(msg, "method", "textDocument/didOpen"); cJSON *params = cJSON_CreateObject(); cJSON *doc = cJSON_CreateObject(); char *f_uri = malloc(strlen(uri) + 9); sprintf(f_uri, "file:///%s", uri); cJSON_AddStringToObject(doc, "uri", f_uri); free(f_uri); cJSON_AddStringToObject(doc, "languageId", languageId); cJSON_AddNumberToObject(doc, "version", version); cJSON_AddStringToObject(doc, "text", text); cJSON_AddItemToObject(params, "textDocument", doc); cJSON_AddItemToObject(msg, "params", params); return msg; } cJSON *make_change_file_request(const char *uri, int version, size_t start_line, size_t start_col, size_t end_line, size_t end_col, const char *new_text) { cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "jsonrpc", "2.0"); cJSON_AddStringToObject(root, "method", "textDocument/didChange"); cJSON *params = cJSON_CreateObject(); cJSON *doc = cJSON_CreateObject(); char *f_uri = malloc(strlen(uri) + 9); sprintf(f_uri, "file:///%s", uri); cJSON_AddStringToObject(doc, "uri", f_uri); free(f_uri); cJSON_AddNumberToObject(doc, "version", version); cJSON_AddItemToObject(params, "textDocument", doc); cJSON *changes = cJSON_CreateArray(); cJSON *change = cJSON_CreateObject(); cJSON *range = cJSON_CreateObject(); cJSON *start = cJSON_CreateObject(); cJSON_AddNumberToObject(start, "line", start_line); cJSON_AddNumberToObject(start, "character", start_col); cJSON *end = cJSON_CreateObject(); cJSON_AddNumberToObject(end, "line", end_line); cJSON_AddNumberToObject(end, "character", end_col); cJSON_AddItemToObject(range, "start", start); cJSON_AddItemToObject(range, "end", end); cJSON_AddItemToObject(change, "range", range); cJSON_AddStringToObject(change, "text", new_text); cJSON_AddItemToArray(changes, change); cJSON_AddItemToObject(params, "contentChanges", changes); cJSON_AddItemToObject(root, "params", params); return root; }