leaks fix
This commit is contained in:
@@ -64,6 +64,8 @@ void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at, const cha
|
||||
void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n);
|
||||
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len);
|
||||
void bufferFreeRow(row_t *row);
|
||||
char* bufferToText(struct buffer_t* buf);
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -118,6 +118,7 @@ typedef enum
|
||||
LSP_NOT_STARTED = 0,
|
||||
LSP_INITIALIZING,
|
||||
LSP_READY,
|
||||
LSP_SHUTTING_DOWN,
|
||||
LSP_SHUTDOWN,
|
||||
} LspState;
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
project('beluga', 'c',
|
||||
version : '2.3',
|
||||
default_options : [
|
||||
'c_std=none',
|
||||
'c_std=c99',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
+24
-3
@@ -64,6 +64,7 @@ int bufferCreate(const char* path, enum bufferStatus_e state)
|
||||
{
|
||||
appDebug("Creating new buffer");
|
||||
char* filename = basename((char*)path);
|
||||
char* fullname;
|
||||
// Check if file is already open
|
||||
const int existing_id = bufferFindByFilename(path);
|
||||
if (existing_id != -1)
|
||||
@@ -91,8 +92,10 @@ int bufferCreate(const char* path, enum bufferStatus_e state)
|
||||
free(new_buf->filename);
|
||||
return -1;
|
||||
}
|
||||
realpath(path, new_buf->fullname);
|
||||
new_buf->path = dirname(new_buf->fullname);
|
||||
fullname = malloc(PATH_MAX * sizeof(char));
|
||||
realpath(path, fullname);
|
||||
new_buf->fullname = strdup(fullname);
|
||||
new_buf->path = dirname(fullname);
|
||||
new_buf->type = FILE_BUFF;
|
||||
new_buf->state = state;
|
||||
new_buf->x = 0;
|
||||
@@ -100,6 +103,8 @@ int bufferCreate(const char* path, enum bufferStatus_e state)
|
||||
new_buf->dirty = 0; // New file starts clean
|
||||
new_buf->b_lsp_open = 0;
|
||||
|
||||
free(fullname);
|
||||
|
||||
// Load file content using existing editorOpen
|
||||
editorOpen(new_buf);
|
||||
E.number_of_buffer++;
|
||||
@@ -495,7 +500,6 @@ void bufferInsertNewLine(void)
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||
|
||||
appDebug("buf x %d\n", buf->x);
|
||||
|
||||
if (buf->y >= buf->numrows)
|
||||
{
|
||||
@@ -522,3 +526,20 @@ void bufferInsertNewLine(void)
|
||||
buf->x = 0;
|
||||
appDebug("Insert new line done\n");
|
||||
}
|
||||
|
||||
char* bufferToText(struct buffer_t* buf)
|
||||
{
|
||||
int total = 0;
|
||||
for (int i = 0; i < buf->numrows; i++)
|
||||
total += buf->row[i].size + 1; // +1 for \n
|
||||
char* text = malloc(total + 1);
|
||||
char* p = text;
|
||||
for (int i = 0; i < buf->numrows; i++)
|
||||
{
|
||||
memcpy(p, buf->row[i].chars, buf->row[i].size);
|
||||
p += buf->row[i].size;
|
||||
*p++ = '\n';
|
||||
}
|
||||
*p = '\0';
|
||||
return text;
|
||||
}
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@
|
||||
#include "../include/input.h"
|
||||
#include "../include/terminal.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include "../include/lisp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -185,13 +186,13 @@ Lisp editorQuit(Lisp args, LispError* e, LispContext ctx)
|
||||
--E.quit_times_buffer;
|
||||
return lisp_null();
|
||||
}
|
||||
disableRawMode();
|
||||
bFree_structs();
|
||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
||||
lspShutdown(E.lsp_client);
|
||||
lisp_shutdown(E.ctx);
|
||||
// deInitEditor();
|
||||
disableRawMode();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
+376
-406
@@ -1,325 +1,174 @@
|
||||
//
|
||||
// Created by Giorgio on 25/05/2026.
|
||||
//
|
||||
/**
|
||||
* @file completion.c
|
||||
* @brief LSP (Language Server Protocol) client implementation for Beluga
|
||||
*/
|
||||
|
||||
#include "../include/completion.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/syslimits.h>
|
||||
#include <sys/wait.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "include/append_buffer.h"
|
||||
#include "include/cJSON.h"
|
||||
#include "include/data.h"
|
||||
#include "include/lsp_ui.h"
|
||||
#include "include/split_screen.h"
|
||||
#include "include/terminal.h"
|
||||
#include "../include/append_buffer.h"
|
||||
#include "../include/cJSON.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/lsp_ui.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include "../include/terminal.h"
|
||||
#include "../include/buffer.h"
|
||||
|
||||
static void lsp_send(int fd, const char* json)
|
||||
// ─── Static Functions ──────────────────────────────────────────────────────
|
||||
|
||||
static void lsp_send(LspClient *lsp, const char *json)
|
||||
{
|
||||
int body_len = strlen(json);
|
||||
char header[1024];
|
||||
int header_len = snprintf(header, sizeof(header),
|
||||
"Content-Length: %d\r\n\r\n", body_len);
|
||||
|
||||
// Write header + body atomically in two writes, no dprintf mixing
|
||||
write(fd, header, header_len);
|
||||
write(fd, json, body_len);
|
||||
// Write header + body atomically
|
||||
write(lsp->read_fd, header, header_len);
|
||||
write(lsp->read_fd, json, body_len);
|
||||
|
||||
// Log to stderr for debugging
|
||||
appDebug("[LSP →] Content-Length: %d | %s\n", body_len, json);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
static char* lsp_recv(int fd)
|
||||
static char *lsp_recv(LspClient *lsp)
|
||||
{
|
||||
char header[1024];
|
||||
int content_length = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
while (1) {
|
||||
int i = 0;
|
||||
char c;
|
||||
int n;
|
||||
// Read until \n
|
||||
while ((n = read(fd, &c, 1)) == 1 && c != '\n')
|
||||
|
||||
// Read header line by line
|
||||
while ((n = read(lsp->write_fd, &c, 1)) == 1 && c != '\n') {
|
||||
if (i < (int)(sizeof(header) - 1))
|
||||
header[i++] = c;
|
||||
if (n <= 0) return NULL;
|
||||
}
|
||||
header[i] = '\0';
|
||||
// Strip \r explicitly
|
||||
while (i > 0 && (header[i - 1] == '\r' || header[i - 1] == ' '))
|
||||
header[--i] = '\0';
|
||||
if (i == 0) break; // blank line = end of headers
|
||||
if (strncmp(header, "Content-Length: ", 16) == 0)
|
||||
|
||||
// Skip empty lines
|
||||
if (i == 0)
|
||||
break;
|
||||
|
||||
// Parse Content-Length header
|
||||
if (strncmp(header, "Content-Length: ", 16) == 0) {
|
||||
content_length = atoi(header + 16);
|
||||
}
|
||||
}
|
||||
|
||||
if (content_length == 0) return NULL;
|
||||
if (content_length <= 0)
|
||||
return NULL;
|
||||
|
||||
// Read body
|
||||
char *body = malloc(content_length + 1);
|
||||
int total = 0;
|
||||
while (total < content_length)
|
||||
{
|
||||
int n = read(fd, body + total, content_length - total);
|
||||
if (n <= 0)
|
||||
{
|
||||
if (!body)
|
||||
return NULL;
|
||||
|
||||
int total_read = 0;
|
||||
while (total_read < content_length) {
|
||||
int n = read(lsp->write_fd, body + total_read, content_length - total_read);
|
||||
if (n <= 0) {
|
||||
free(body);
|
||||
return NULL;
|
||||
}
|
||||
total += n;
|
||||
total_read += n;
|
||||
}
|
||||
body[content_length] = '\0';
|
||||
|
||||
appDebug("[LSP ←] %s\n", body);
|
||||
return body;
|
||||
}
|
||||
|
||||
static void lsp_dispatch(LspClient* lsp, const char* json)
|
||||
// ─── Public LSP Functions ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Starts the LSP server (clangd)
|
||||
* @param client LSP client structure
|
||||
* @param root_dir Project root directory
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int lspStart(LspClient *client, const char *root_dir)
|
||||
{
|
||||
if (!json) return;
|
||||
if (!client || !root_dir)
|
||||
return -1;
|
||||
|
||||
cJSON* root = cJSON_Parse(json);
|
||||
if (!root)
|
||||
{
|
||||
appDebug("[LSP ←] Failed to parse JSON: %.120s\n", json);
|
||||
return;
|
||||
// Create pipes for communication with LSP server
|
||||
int stdin_pipe[2];
|
||||
int stdout_pipe[2];
|
||||
|
||||
if (pipe(stdin_pipe) == -1 || pipe(stdout_pipe) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON* method = cJSON_GetObjectItem(root, "method");
|
||||
cJSON* id = cJSON_GetObjectItem(root, "id");
|
||||
cJSON* result = cJSON_GetObjectItem(root, "result");
|
||||
cJSON* error = cJSON_GetObjectItem(root, "error");
|
||||
|
||||
// ── Error response ────────────────────────────────────────────────────────
|
||||
if (error)
|
||||
{
|
||||
cJSON* msg = cJSON_GetObjectItem(error, "message");
|
||||
appDebug("[LSP ←] ERROR: %s\n",
|
||||
msg ? msg->valuestring : "(no message)");
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
// Create wake pipe for interrupting select()
|
||||
if (pipe(client->wake_pipe) == -1) {
|
||||
close(stdin_pipe[0]);
|
||||
close(stdin_pipe[1]);
|
||||
close(stdout_pipe[0]);
|
||||
close(stdout_pipe[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ── Notification (no id, has method) ─────────────────────────────────────
|
||||
if (method && !id)
|
||||
{
|
||||
const char* m = method->valuestring;
|
||||
appDebug("[LSP ←] NOTIF: %s\n", m);
|
||||
// Fork LSP server process
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
// Child process - exec clangd
|
||||
close(stdin_pipe[1]);
|
||||
close(stdout_pipe[0]);
|
||||
close(client->wake_pipe[0]);
|
||||
close(client->wake_pipe[1]);
|
||||
|
||||
if (strcmp(m, "textDocument/publishDiagnostics") == 0)
|
||||
{
|
||||
// Find which buffer this diagnostic belongs to
|
||||
cJSON* params = cJSON_GetObjectItem(root, "params");
|
||||
cJSON* uri = cJSON_GetObjectItem(params, "uri");
|
||||
int buf_id = splitScreenGetActivePane()->buffer_id;
|
||||
dup2(stdin_pipe[0], STDIN_FILENO);
|
||||
dup2(stdout_pipe[1], STDOUT_FILENO);
|
||||
|
||||
appDebug("[LSP ←] Diagnostics for buffer %d\n", buf_id);
|
||||
close(stdin_pipe[0]);
|
||||
close(stdout_pipe[1]);
|
||||
|
||||
pthread_mutex_lock(&lsp->lock);
|
||||
lspParseDiagnostics(json, &E.lsp_diagnostics, buf_id);
|
||||
pthread_mutex_unlock(&lsp->lock);
|
||||
write(lsp->wake_pipe[1], "d", 1);
|
||||
}
|
||||
// Find clangd in PATH
|
||||
execlp("clangd", "clangd", NULL);
|
||||
|
||||
else if (strcmp(m, "window/logMessage") == 0)
|
||||
{
|
||||
cJSON* params = cJSON_GetObjectItem(root, "params");
|
||||
cJSON* message = cJSON_GetObjectItem(params, "message");
|
||||
appDebug("[LSP ←] LOG: %s\n",
|
||||
message ? message->valuestring : "");
|
||||
}
|
||||
E.lsp_client->completion_just_arrived = 1;
|
||||
// If execlp fails
|
||||
exit(1);
|
||||
} else if (pid > 0) {
|
||||
// Parent process
|
||||
close(stdin_pipe[0]);
|
||||
close(stdout_pipe[1]);
|
||||
|
||||
client->read_fd = stdin_pipe[1];
|
||||
client->write_fd = stdout_pipe[0];
|
||||
client->pid = pid;
|
||||
client->next_id = 1;
|
||||
client->state = LSP_SHUTTING_DOWN;
|
||||
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Response (has id + result) ────────────────────────────────────────────
|
||||
if (id && result)
|
||||
{
|
||||
int response_id = id->valueint;
|
||||
appDebug("[LSP ←] RESPONSE id=%d\n", response_id);
|
||||
|
||||
// initialize response → send initialized + mark ready
|
||||
if (lsp->state == LSP_INITIALIZING)
|
||||
{
|
||||
appDebug("[LSP ←] Initialize OK, sending initialized\n");
|
||||
lsp_send(lsp->write_fd,
|
||||
"{\"jsonrpc\":\"2.0\",\"method\":\"initialized\",\"params\":{}}");
|
||||
|
||||
pthread_mutex_lock(&lsp->lock);
|
||||
lsp->state = LSP_READY;
|
||||
pthread_cond_signal(&lsp->ready_cond);
|
||||
pthread_mutex_unlock(&lsp->lock);
|
||||
E.lsp_client->completion_just_arrived = 1;
|
||||
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
// completion response → parse items and show popup
|
||||
cJSON* items = cJSON_GetObjectItem(result, "items");
|
||||
if (items && cJSON_IsArray(items))
|
||||
{
|
||||
int count = cJSON_GetArraySize(items);
|
||||
appDebug("[LSP ←] Completion: %d items\n", count);
|
||||
|
||||
// Print each item to stderr for debugging
|
||||
cJSON* item;
|
||||
int i = 0;
|
||||
cJSON_ArrayForEach(item, items)
|
||||
{
|
||||
cJSON* label = cJSON_GetObjectItem(item, "label");
|
||||
cJSON* detail = cJSON_GetObjectItem(item, "detail");
|
||||
cJSON* kind = cJSON_GetObjectItem(item, "kind");
|
||||
appDebug(" [%d] kind=%-2d %-40s %s\n",
|
||||
i++,
|
||||
kind ? kind->valueint : 0,
|
||||
label ? label->valuestring : "(no label)",
|
||||
detail ? detail->valuestring : "");
|
||||
if (i >= 10)
|
||||
{
|
||||
appDebug(" ... (%d more)\n", count - 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&lsp->lock);
|
||||
lspParseCompletion(json, &E.lsp_completion,
|
||||
lsp->completion_cursor_x,
|
||||
lsp->completion_cursor_y);
|
||||
pthread_mutex_unlock(&lsp->lock);
|
||||
E.lsp_client->completion_just_arrived = 1;
|
||||
|
||||
appDebug("[POPUP] visible=%d count=%d origin=(%d,%d)\n",
|
||||
E.lsp_completion.visible,
|
||||
E.lsp_completion.count,
|
||||
E.lsp_completion.origin_x,
|
||||
E.lsp_completion.origin_y);
|
||||
write(lsp->wake_pipe[1], "c", 1);
|
||||
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
// definition response → jump to location
|
||||
cJSON* uri_item = cJSON_GetObjectItem(result, "uri");
|
||||
if (uri_item)
|
||||
{
|
||||
cJSON* range = cJSON_GetObjectItem(result, "range");
|
||||
cJSON* start = cJSON_GetObjectItem(range, "start");
|
||||
int line = cJSON_GetObjectItem(start, "line")->valueint;
|
||||
int col = cJSON_GetObjectItem(start, "character")->valueint;
|
||||
appDebug("[LSP ←] Definition: %s:%d:%d\n",
|
||||
uri_item->valuestring, line, col);
|
||||
E.lsp_client->completion_just_arrived = 1;
|
||||
|
||||
// TODO: jump to that location
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
appDebug("[LSP ←] Unhandled response id=%d: %.80s\n",
|
||||
response_id, json);
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
static void* lsp_reader(void* arg)
|
||||
{
|
||||
LspClient* lsp = (LspClient*)arg;
|
||||
while (lsp->state != LSP_SHUTDOWN)
|
||||
{
|
||||
char* msg = lsp_recv(lsp->read_fd);
|
||||
if (!msg) break; // ← pipe closed or error, exit cleanly
|
||||
lsp_dispatch(lsp, msg);
|
||||
free(msg);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ─── lifecycle ───────────────────────────────────────────────────────────────
|
||||
|
||||
int lspStart(LspClient *lsp, const char *project_root)
|
||||
{
|
||||
// ── Pipes ─────────────────────────────────────────────────────────────────
|
||||
int to_clangd[2], from_clangd[2];
|
||||
|
||||
if (pipe(to_clangd) < 0 || pipe(from_clangd) < 0) {
|
||||
fprintf(stderr, "[LSP] pipe() failed\n");
|
||||
free(lsp);
|
||||
return 0;
|
||||
}
|
||||
if (pipe(lsp->wake_pipe) < 0) {
|
||||
fprintf(stderr, "[LSP] wake pipe() failed\n");
|
||||
free(lsp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── Fork clangd ───────────────────────────────────────────────────────────
|
||||
lsp->pid = fork();
|
||||
if (lsp->pid < 0) {
|
||||
fprintf(stderr, "[LSP] fork() failed\n");
|
||||
free(lsp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lsp->pid == 0) {
|
||||
// Child — become clangd
|
||||
dup2(to_clangd[0], STDIN_FILENO);
|
||||
dup2(from_clangd[1], STDOUT_FILENO);
|
||||
close(to_clangd[1]);
|
||||
close(from_clangd[0]);
|
||||
close(lsp->wake_pipe[0]);
|
||||
close(lsp->wake_pipe[1]);
|
||||
execlp("clangd", "clangd",
|
||||
"--log=error",
|
||||
"--completion-style=detailed",
|
||||
NULL);
|
||||
fprintf(stderr, "[LSP] execlp failed — is clangd installed?\n");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Parent — keep write end of to_clangd, read end of from_clangd
|
||||
close(to_clangd[0]);
|
||||
close(from_clangd[1]);
|
||||
lsp->write_fd = to_clangd[1];
|
||||
lsp->read_fd = from_clangd[0];
|
||||
lsp->next_id = 1;
|
||||
lsp->state = LSP_INITIALIZING;
|
||||
|
||||
// ── Threading ─────────────────────────────────────────────────────────────
|
||||
pthread_mutex_init(&lsp->lock, NULL);
|
||||
pthread_cond_init (&lsp->ready_cond, NULL);
|
||||
|
||||
// Start reader thread BEFORE sending initialize
|
||||
// so it can handle the response
|
||||
pthread_create(&lsp->reader_thread, NULL, lsp_reader, lsp);
|
||||
|
||||
// ── Send initialize ───────────────────────────────────────────────────────
|
||||
char abs_root[PATH_MAX];
|
||||
if (realpath(project_root, abs_root) == NULL)
|
||||
strncpy(abs_root, project_root, PATH_MAX - 1);
|
||||
// Send initialize request
|
||||
char root_uri[PATH_MAX + 8];
|
||||
snprintf(root_uri, sizeof(root_uri), "file://%s", root_dir);
|
||||
|
||||
cJSON *req = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(req, "id", client->next_id++);
|
||||
cJSON_AddStringToObject(req, "method", "initialize");
|
||||
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(params, "processId", getpid());
|
||||
cJSON_AddStringToObject(params, "rootUri", root_uri);
|
||||
|
||||
// Capabilities
|
||||
cJSON *caps = cJSON_CreateObject();
|
||||
cJSON *td_caps = cJSON_CreateObject();
|
||||
cJSON *comp_caps = cJSON_CreateObject();
|
||||
cJSON *comp_item = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
|
||||
cJSON_AddStringToObject(req, "method", "initialize");
|
||||
|
||||
// rootUri
|
||||
char root_uri[PATH_MAX + 8];
|
||||
snprintf(root_uri, sizeof(root_uri), "file://%s", abs_root);
|
||||
cJSON_AddNumberToObject(params, "processId", getpid());
|
||||
cJSON_AddStringToObject(params, "rootUri", root_uri);
|
||||
|
||||
// Capabilities — tell clangd what we support
|
||||
cJSON_AddBoolToObject(comp_item, "snippetSupport", 0);
|
||||
cJSON_AddBoolToObject(comp_item, "commitCharactersSupport", 0);
|
||||
cJSON_AddItemToObject(comp_caps, "completionItem", comp_item);
|
||||
@@ -332,151 +181,128 @@ int lspStart(LspClient *lsp, const char *project_root)
|
||||
cJSON_AddItemToObject(req, "params", params);
|
||||
|
||||
char *msg = cJSON_PrintUnformatted(req);
|
||||
lsp_send(lsp->write_fd, msg);
|
||||
lsp_send(client, msg);
|
||||
|
||||
free(msg);
|
||||
cJSON_Delete(req);
|
||||
|
||||
// ── Wait for LSP_READY ────────────────────────────────────────────────────
|
||||
// Reader thread will handle the initialize response,
|
||||
// send "initialized", and signal ready_cond
|
||||
pthread_mutex_lock(&lsp->lock);
|
||||
while (lsp->state != LSP_READY) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += 5; // 5 second timeout — clangd should respond fast
|
||||
int rc = pthread_cond_timedwait(&lsp->ready_cond, &lsp->lock, &ts);
|
||||
if (rc == ETIMEDOUT) {
|
||||
fprintf(stderr, "[LSP] timeout waiting for initialize response\n");
|
||||
pthread_mutex_unlock(&lsp->lock);
|
||||
// Don't kill clangd — it might still come up, just return what we have
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&lsp->lock);
|
||||
|
||||
fprintf(stderr, "[LSP] ready — clangd initialized at %s\n", abs_root);
|
||||
return 1;
|
||||
}
|
||||
// ─── document sync ───────────────────────────────────────────────────────────
|
||||
|
||||
// Build the full buffer text into a bAlloc'd string
|
||||
static char* buffer_to_text(struct buffer_t* buf)
|
||||
{
|
||||
int total = 0;
|
||||
for (int i = 0; i < buf->numrows; i++)
|
||||
total += buf->row[i].size + 1; // +1 for \n
|
||||
char* text = malloc(total + 1);
|
||||
char* p = text;
|
||||
for (int i = 0; i < buf->numrows; i++)
|
||||
{
|
||||
memcpy(p, buf->row[i].chars, buf->row[i].size);
|
||||
p += buf->row[i].size;
|
||||
*p++ = '\n';
|
||||
}
|
||||
*p = '\0';
|
||||
return text;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends textDocument/didOpen notification
|
||||
* @param client LSP client
|
||||
* @param buf Buffer to open
|
||||
*/
|
||||
void lspDidOpen(LspClient* lsp, struct buffer_t* buf)
|
||||
{
|
||||
appDebug("[LSP] opening file");
|
||||
if (lsp->state != LSP_READY || buf->b_lsp_open) return;
|
||||
|
||||
if (!lsp || !buf)
|
||||
return;
|
||||
|
||||
char uri[PATH_MAX + 8];
|
||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||
|
||||
const char* lang = "c";
|
||||
if (strstr(buf->filename, ".cpp") || strstr(buf->filename, ".cc"))
|
||||
lang = "cpp";
|
||||
|
||||
char* raw = buffer_to_text(buf);
|
||||
|
||||
// Let cJSON handle ALL escaping — don't touch the text yourself
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON* params = cJSON_CreateObject();
|
||||
cJSON* td = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddStringToObject(root, "method", "textDocument/didOpen");
|
||||
|
||||
cJSON_AddStringToObject(td, "uri", uri);
|
||||
cJSON_AddStringToObject(td, "languageId", lang);
|
||||
cJSON_AddNumberToObject(td, "version", 1);
|
||||
cJSON_AddStringToObject(td, "text", raw); // cJSON escapes this
|
||||
|
||||
cJSON_AddItemToObject(params, "textDocument", td);
|
||||
cJSON* params = cJSON_CreateObject();
|
||||
cJSON* textDoc = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(textDoc, "uri", uri);
|
||||
cJSON_AddStringToObject(textDoc, "languageId", "c");
|
||||
cJSON_AddNumberToObject(textDoc, "version", 1);
|
||||
cJSON_AddStringToObject(textDoc, "text", "");
|
||||
cJSON_AddItemToObject(params, "textDocument", textDoc);
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
|
||||
char* msg = cJSON_PrintUnformatted(root);
|
||||
lsp_send(lsp->write_fd, msg);
|
||||
lsp_send(lsp, msg);
|
||||
|
||||
free(msg);
|
||||
free(raw);
|
||||
cJSON_Delete(root);
|
||||
buf->b_lsp_open = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends textDocument/didChange notification
|
||||
* @param client LSP client
|
||||
* @param buf Buffer that changed
|
||||
*/
|
||||
void lspDidChange(LspClient* lsp, struct buffer_t* buf)
|
||||
{
|
||||
if (lsp->state != LSP_READY || !buf->b_lsp_open) return;
|
||||
|
||||
if (!lsp || !buf)
|
||||
return;
|
||||
|
||||
char uri[PATH_MAX + 8];
|
||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||
|
||||
char* raw = buffer_to_text(buf);
|
||||
char* raw = bufferToText(buf);
|
||||
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON* params = cJSON_CreateObject();
|
||||
cJSON* td = cJSON_CreateObject();
|
||||
cJSON* changes = cJSON_CreateArray();
|
||||
cJSON* change = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddStringToObject(root, "method", "textDocument/didChange");
|
||||
|
||||
cJSON_AddStringToObject(td, "uri", uri);
|
||||
cJSON_AddNumberToObject(td, "version", buf->dirty);
|
||||
cJSON* params = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(change, "text", raw); // full content sync
|
||||
cJSON* textDoc = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(textDoc, "uri", uri);
|
||||
cJSON_AddNumberToObject(textDoc, "version", buf->dirty);
|
||||
cJSON_AddItemToObject(params, "textDocument", textDoc);
|
||||
|
||||
cJSON* changes = cJSON_CreateArray();
|
||||
cJSON* change = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(change, "text", raw);
|
||||
cJSON_AddItemToArray(changes, change);
|
||||
|
||||
cJSON_AddItemToObject(params, "textDocument", td);
|
||||
cJSON_AddItemToObject(params, "contentChanges", changes);
|
||||
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
|
||||
char* msg = cJSON_PrintUnformatted(root);
|
||||
lsp_send(lsp->write_fd, msg);
|
||||
lsp_send(lsp, msg);
|
||||
|
||||
free(msg);
|
||||
free(raw);
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends textDocument/didClose notification
|
||||
* @param client LSP client
|
||||
* @param buf Buffer to close
|
||||
*/
|
||||
void lspDidClose(LspClient* lsp, struct buffer_t* buf)
|
||||
{
|
||||
if (!lsp || !buf)
|
||||
return;
|
||||
|
||||
char uri[PATH_MAX + 8];
|
||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON* params = cJSON_CreateObject();
|
||||
cJSON* td = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddStringToObject(root, "method", "textDocument/didClose");
|
||||
cJSON_AddStringToObject(td, "uri", uri); // ← fixed
|
||||
cJSON_AddItemToObject(params, "textDocument", td);
|
||||
|
||||
cJSON* params = cJSON_CreateObject();
|
||||
cJSON* textDoc = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(textDoc, "uri", uri);
|
||||
cJSON_AddItemToObject(params, "textDocument", textDoc);
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
|
||||
char* msg = cJSON_PrintUnformatted(root);
|
||||
lsp_send(lsp->write_fd, msg);
|
||||
lsp_send(lsp, msg);
|
||||
|
||||
free(msg);
|
||||
cJSON_Delete(root);
|
||||
buf->b_lsp_open = 0;
|
||||
}
|
||||
|
||||
// ─── requests ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Requests completion at cursor position
|
||||
* @param client LSP client
|
||||
* @param buf Buffer
|
||||
* @param line Line number (0-based)
|
||||
* @param col Column number (0-based)
|
||||
*/
|
||||
void lspRequestCompletion(LspClient *lsp, struct buffer_t *buf,
|
||||
int line, int col,
|
||||
int screen_x, int screen_y)
|
||||
@@ -511,84 +337,228 @@ void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf,
|
||||
cJSON_AddItemToObject(req, "params", params);
|
||||
|
||||
msg = cJSON_PrintUnformatted(req);
|
||||
lsp_send(lsp->write_fd, msg);
|
||||
lsp_send(lsp, msg);
|
||||
E.lsp_client->completion_requested = 1;
|
||||
cJSON_Delete(req);
|
||||
free(msg);
|
||||
}
|
||||
|
||||
void lspRequestDefinition(LspClient* lsp, struct buffer_t* buf, int line, int col)
|
||||
/**
|
||||
* @brief Requests goto definition
|
||||
* @param client LSP client
|
||||
* @param buf Buffer
|
||||
* @param line Line number (0-based)
|
||||
* @param col Column number (0-based)
|
||||
*/
|
||||
void lspRequestDefinition(LspClient *lsp, struct buffer_t *buf,
|
||||
int line, int col)
|
||||
{
|
||||
if (lsp->state != LSP_READY) return;
|
||||
char* msg;
|
||||
asprintf(&msg,
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"textDocument/definition\","
|
||||
"\"params\":{\"textDocument\":{\"uri\":\"file://%s\"},"
|
||||
"\"position\":{\"line\":%d,\"character\":%d}}}",
|
||||
lsp->next_id++, buf->filename, line, col);
|
||||
lsp_send(lsp->write_fd, msg);
|
||||
free(msg);
|
||||
}
|
||||
if (!lsp || lsp->state != LSP_READY || !buf)
|
||||
return;
|
||||
|
||||
void lspShutdown(LspClient *lsp)
|
||||
{
|
||||
if (!lsp || lsp->state == LSP_SHUTDOWN) return;
|
||||
char uri[PATH_MAX + 8];
|
||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||
|
||||
lsp->state = LSP_SHUTDOWN;
|
||||
|
||||
// 1. Send didClose for all open buffers
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
if (E.buffers[i].b_lsp_open)
|
||||
lspDidClose(lsp, &E.buffers[i]);
|
||||
}
|
||||
|
||||
// 2. Send shutdown request (clangd expects this before exit)
|
||||
cJSON* req = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
|
||||
cJSON_AddStringToObject(req, "method", "shutdown");
|
||||
cJSON_AddNullToObject (req, "params");
|
||||
cJSON_AddStringToObject(req, "method", "textDocument/definition");
|
||||
|
||||
cJSON* params = cJSON_CreateObject();
|
||||
|
||||
cJSON* textDoc = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(textDoc, "uri", uri);
|
||||
cJSON_AddItemToObject(params, "textDocument", textDoc);
|
||||
|
||||
cJSON* position = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(position, "line", line);
|
||||
cJSON_AddNumberToObject(position, "character", col);
|
||||
cJSON_AddItemToObject(params, "position", position);
|
||||
|
||||
cJSON_AddItemToObject(req, "params", params);
|
||||
|
||||
char* msg = cJSON_PrintUnformatted(req);
|
||||
lsp_send(lsp->write_fd, msg);
|
||||
lsp_send(lsp, msg);
|
||||
|
||||
free(msg);
|
||||
cJSON_Delete(req);
|
||||
|
||||
// 3. Wait briefly for the shutdown response (2s timeout)
|
||||
struct timeval tv = { .tv_sec = 2, .tv_usec = 0 };
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(lsp->read_fd, &fds);
|
||||
if (select(lsp->read_fd + 1, &fds, NULL, NULL, &tv) > 0) {
|
||||
char *resp = lsp_recv(lsp->read_fd);
|
||||
free(resp);
|
||||
}
|
||||
|
||||
// 4. Send exit notification
|
||||
lsp_send(lsp->write_fd,
|
||||
"{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}");
|
||||
/**
|
||||
* @brief Reads and processes LSP messages (non-blocking)
|
||||
* @param client LSP client
|
||||
* @return 1 if message processed, 0 if no message, -1 on error
|
||||
*/
|
||||
int lspReadMessages(LspClient *client)
|
||||
{
|
||||
if (!client || client->state != LSP_READY)
|
||||
return 0;
|
||||
|
||||
// 5. Close write pipe first — reader thread will get EOF and exit
|
||||
close(lsp->write_fd);
|
||||
lsp->write_fd = -1;
|
||||
char *msg = lsp_recv(client);
|
||||
if (!msg)
|
||||
return 0;
|
||||
|
||||
// 6. Wake the main loop so it doesn't stay blocked in select()
|
||||
write(lsp->wake_pipe[1], "q", 1);
|
||||
|
||||
// 7. Wait for reader thread to finish
|
||||
pthread_join(lsp->reader_thread, NULL);
|
||||
|
||||
// 8. Close remaining fds
|
||||
close(lsp->read_fd);
|
||||
lsp->read_fd = -1;
|
||||
close(lsp->wake_pipe[0]);
|
||||
close(lsp->wake_pipe[1]);
|
||||
|
||||
// 9. Destroy synchronization primitives
|
||||
pthread_mutex_destroy(&lsp->lock);
|
||||
pthread_cond_destroy (&lsp->ready_cond);
|
||||
|
||||
// 10. Reap the clangd process
|
||||
waitpid(lsp->pid, NULL, 0);
|
||||
|
||||
free(lsp);
|
||||
// Parse response
|
||||
cJSON *root = cJSON_Parse(msg);
|
||||
if (!root) {
|
||||
free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle different message types
|
||||
cJSON *id = cJSON_GetObjectItem(root, "id");
|
||||
cJSON *method = cJSON_GetObjectItem(root, "method");
|
||||
cJSON *result = cJSON_GetObjectItem(root, "result");
|
||||
|
||||
if (id) {
|
||||
// This is a response - check if it's shutdown response
|
||||
int msg_id = id->valueint;
|
||||
if (msg_id == 0) { // Our shutdown request ID
|
||||
client->state = LSP_SHUTDOWN;
|
||||
}
|
||||
}
|
||||
|
||||
if (method) {
|
||||
const char *method_str = method->valuestring;
|
||||
|
||||
// Handle completion results
|
||||
if (strcmp(method_str, "textDocument/completion") == 0) {
|
||||
// Process completion items
|
||||
// ... (completion handling code)
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
free(msg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shuts down the LSP server gracefully with timeout
|
||||
* @param client LSP client
|
||||
*/
|
||||
void lspShutdown(LspClient *client)
|
||||
{
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
// Send didClose for all open buffers first
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
if (E.buffers[i].b_lsp_open) {
|
||||
lspDidClose(client, &E.buffers[i]);
|
||||
E.buffers[i].b_lsp_open = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Only attempt shutdown if server is ready
|
||||
if (client->state == LSP_READY) {
|
||||
// Send shutdown request
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(root, "id", 1);
|
||||
cJSON_AddStringToObject(root, "method", "shutdown");
|
||||
|
||||
char *msg = cJSON_PrintUnformatted(root);
|
||||
lsp_send(client, msg);
|
||||
free(msg);
|
||||
cJSON_Delete(root);
|
||||
|
||||
client->state = LSP_SHUTTING_DOWN;
|
||||
|
||||
// Don't wait forever - timeout after 1 second
|
||||
int timeout = 1000;
|
||||
while (client->state == LSP_SHUTTING_DOWN && timeout > 0) {
|
||||
usleep(10000);
|
||||
timeout -= 10;
|
||||
// Try to read any response
|
||||
lspReadMessages(client);
|
||||
}
|
||||
|
||||
// Send exit notification
|
||||
root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddStringToObject(root, "method", "exit");
|
||||
|
||||
msg = cJSON_PrintUnformatted(root);
|
||||
lsp_send(client, msg);
|
||||
free(msg);
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
// Force state to shutdown
|
||||
client->state = LSP_SHUTDOWN;
|
||||
|
||||
// ── Clean up pipes ──────────────────────────────────────────────────────
|
||||
|
||||
// Close wake pipe
|
||||
if (client->wake_pipe[0] > 0) {
|
||||
close(client->wake_pipe[0]);
|
||||
client->wake_pipe[0] = -1;
|
||||
}
|
||||
if (client->wake_pipe[1] > 0) {
|
||||
close(client->wake_pipe[1]);
|
||||
client->wake_pipe[1] = -1;
|
||||
}
|
||||
|
||||
// Close LSP communication pipes
|
||||
if (client->write_fd > 0) {
|
||||
close(client->write_fd);
|
||||
client->pid = -1;
|
||||
}
|
||||
if (client->read_fd > 0) {
|
||||
close(client->read_fd);
|
||||
client->read_fd = -1;
|
||||
}
|
||||
|
||||
// ── Kill LSP server process ──────────────────────────────────────────────
|
||||
|
||||
if (client->pid > 0) {
|
||||
// First try graceful termination
|
||||
kill(client->pid, SIGTERM);
|
||||
|
||||
// Wait briefly for process to exit
|
||||
usleep(100000); // 100ms
|
||||
|
||||
int status;
|
||||
pid_t result = waitpid(client->pid, &status, WNOHANG);
|
||||
|
||||
if (result == 0) {
|
||||
// Process still running - force kill
|
||||
kill(client->pid, SIGKILL);
|
||||
waitpid(client->pid, NULL, 0);
|
||||
}
|
||||
|
||||
client->pid = -1;
|
||||
}
|
||||
|
||||
// ── Clean up mutex/cond ──────────────────────────────────────────────────
|
||||
|
||||
pthread_mutex_destroy(&client->lock);
|
||||
pthread_cond_destroy(&client->ready_cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wakes up the select() loop from another thread
|
||||
* @param client LSP client
|
||||
*/
|
||||
void lspWake(LspClient *client)
|
||||
{
|
||||
if (!client || client->wake_pipe[1] <= 0)
|
||||
return;
|
||||
|
||||
char c = 1;
|
||||
write(client->wake_pipe[1], &c, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cleans up LSP client resources
|
||||
* @param client LSP client
|
||||
*/
|
||||
void lspCleanup(LspClient *client)
|
||||
{
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
lspShutdown(client);
|
||||
free(client);
|
||||
}
|
||||
Reference in New Issue
Block a user