386 lines
12 KiB
C
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;
|
|
}
|