Files
cubit/src/lsp_handler.c
2025-10-03 12:40:47 +01:00

386 lines
12 KiB
C

#include "lsp_handler.h"
#include "../libs/cjson/cJSON.h"
#include "constants.h"
#include "helper_functions.h"
#include "node.h"
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
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;
}