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 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 bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len);
|
||||||
void bufferFreeRow(row_t *row);
|
void bufferFreeRow(row_t *row);
|
||||||
|
char* bufferToText(struct buffer_t* buf);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ typedef enum
|
|||||||
LSP_NOT_STARTED = 0,
|
LSP_NOT_STARTED = 0,
|
||||||
LSP_INITIALIZING,
|
LSP_INITIALIZING,
|
||||||
LSP_READY,
|
LSP_READY,
|
||||||
|
LSP_SHUTTING_DOWN,
|
||||||
LSP_SHUTDOWN,
|
LSP_SHUTDOWN,
|
||||||
} LspState;
|
} LspState;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
project('beluga', 'c',
|
project('beluga', 'c',
|
||||||
version : '2.3',
|
version : '2.3',
|
||||||
default_options : [
|
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");
|
appDebug("Creating new buffer");
|
||||||
char* filename = basename((char*)path);
|
char* filename = basename((char*)path);
|
||||||
|
char* fullname;
|
||||||
// Check if file is already open
|
// Check if file is already open
|
||||||
const int existing_id = bufferFindByFilename(path);
|
const int existing_id = bufferFindByFilename(path);
|
||||||
if (existing_id != -1)
|
if (existing_id != -1)
|
||||||
@@ -91,8 +92,10 @@ int bufferCreate(const char* path, enum bufferStatus_e state)
|
|||||||
free(new_buf->filename);
|
free(new_buf->filename);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
realpath(path, new_buf->fullname);
|
fullname = malloc(PATH_MAX * sizeof(char));
|
||||||
new_buf->path = dirname(new_buf->fullname);
|
realpath(path, fullname);
|
||||||
|
new_buf->fullname = strdup(fullname);
|
||||||
|
new_buf->path = dirname(fullname);
|
||||||
new_buf->type = FILE_BUFF;
|
new_buf->type = FILE_BUFF;
|
||||||
new_buf->state = state;
|
new_buf->state = state;
|
||||||
new_buf->x = 0;
|
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->dirty = 0; // New file starts clean
|
||||||
new_buf->b_lsp_open = 0;
|
new_buf->b_lsp_open = 0;
|
||||||
|
|
||||||
|
free(fullname);
|
||||||
|
|
||||||
// Load file content using existing editorOpen
|
// Load file content using existing editorOpen
|
||||||
editorOpen(new_buf);
|
editorOpen(new_buf);
|
||||||
E.number_of_buffer++;
|
E.number_of_buffer++;
|
||||||
@@ -495,7 +500,6 @@ void bufferInsertNewLine(void)
|
|||||||
EditorPane* active = splitScreenGetActivePane();
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||||
|
|
||||||
appDebug("buf x %d\n", buf->x);
|
|
||||||
|
|
||||||
if (buf->y >= buf->numrows)
|
if (buf->y >= buf->numrows)
|
||||||
{
|
{
|
||||||
@@ -522,3 +526,20 @@ void bufferInsertNewLine(void)
|
|||||||
buf->x = 0;
|
buf->x = 0;
|
||||||
appDebug("Insert new line done\n");
|
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/input.h"
|
||||||
#include "../include/terminal.h"
|
#include "../include/terminal.h"
|
||||||
#include "../include/split_screen.h"
|
#include "../include/split_screen.h"
|
||||||
|
#include "../include/lisp.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -185,13 +186,13 @@ Lisp editorQuit(Lisp args, LispError* e, LispContext ctx)
|
|||||||
--E.quit_times_buffer;
|
--E.quit_times_buffer;
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
disableRawMode();
|
||||||
bFree_structs();
|
bFree_structs();
|
||||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||||
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
||||||
lspShutdown(E.lsp_client);
|
lspShutdown(E.lsp_client);
|
||||||
lisp_shutdown(E.ctx);
|
lisp_shutdown(E.ctx);
|
||||||
// deInitEditor();
|
// deInitEditor();
|
||||||
disableRawMode();
|
|
||||||
exit(0);
|
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 "../include/completion.h"
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/syslimits.h>
|
#include <sys/syslimits.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#include "include/append_buffer.h"
|
#include "../include/append_buffer.h"
|
||||||
#include "include/cJSON.h"
|
#include "../include/cJSON.h"
|
||||||
#include "include/data.h"
|
#include "../include/data.h"
|
||||||
#include "include/lsp_ui.h"
|
#include "../include/lsp_ui.h"
|
||||||
#include "include/split_screen.h"
|
#include "../include/split_screen.h"
|
||||||
#include "include/terminal.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);
|
int body_len = strlen(json);
|
||||||
char header[1024];
|
char header[1024];
|
||||||
int header_len = snprintf(header, sizeof(header),
|
int header_len = snprintf(header, sizeof(header),
|
||||||
"Content-Length: %d\r\n\r\n", body_len);
|
"Content-Length: %d\r\n\r\n", body_len);
|
||||||
|
|
||||||
// Write header + body atomically in two writes, no dprintf mixing
|
// Write header + body atomically
|
||||||
write(fd, header, header_len);
|
write(lsp->read_fd, header, header_len);
|
||||||
write(fd, json, body_len);
|
write(lsp->read_fd, json, body_len);
|
||||||
|
|
||||||
// Log to stderr for debugging
|
// Log to stderr for debugging
|
||||||
appDebug("[LSP →] Content-Length: %d | %s\n", body_len, json);
|
appDebug("[LSP →] Content-Length: %d | %s\n", body_len, json);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* lsp_recv(int fd)
|
static char *lsp_recv(LspClient *lsp)
|
||||||
{
|
{
|
||||||
char header[1024];
|
char header[1024];
|
||||||
int content_length = 0;
|
int content_length = 0;
|
||||||
|
|
||||||
while (1)
|
while (1) {
|
||||||
{
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
char c;
|
char c;
|
||||||
int n;
|
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;
|
header[i++] = c;
|
||||||
if (n <= 0) return NULL;
|
}
|
||||||
header[i] = '\0';
|
header[i] = '\0';
|
||||||
// Strip \r explicitly
|
|
||||||
while (i > 0 && (header[i - 1] == '\r' || header[i - 1] == ' '))
|
// Skip empty lines
|
||||||
header[--i] = '\0';
|
if (i == 0)
|
||||||
if (i == 0) break; // blank line = end of headers
|
break;
|
||||||
if (strncmp(header, "Content-Length: ", 16) == 0)
|
|
||||||
|
// Parse Content-Length header
|
||||||
|
if (strncmp(header, "Content-Length: ", 16) == 0) {
|
||||||
content_length = atoi(header + 16);
|
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);
|
char *body = malloc(content_length + 1);
|
||||||
int total = 0;
|
if (!body)
|
||||||
while (total < content_length)
|
return NULL;
|
||||||
{
|
|
||||||
int n = read(fd, body + total, content_length - total);
|
int total_read = 0;
|
||||||
if (n <= 0)
|
while (total_read < content_length) {
|
||||||
{
|
int n = read(lsp->write_fd, body + total_read, content_length - total_read);
|
||||||
|
if (n <= 0) {
|
||||||
free(body);
|
free(body);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
total += n;
|
total_read += n;
|
||||||
}
|
}
|
||||||
body[content_length] = '\0';
|
body[content_length] = '\0';
|
||||||
|
|
||||||
|
appDebug("[LSP ←] %s\n", body);
|
||||||
return 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);
|
// Create pipes for communication with LSP server
|
||||||
if (!root)
|
int stdin_pipe[2];
|
||||||
{
|
int stdout_pipe[2];
|
||||||
appDebug("[LSP ←] Failed to parse JSON: %.120s\n", json);
|
|
||||||
return;
|
if (pipe(stdin_pipe) == -1 || pipe(stdout_pipe) == -1) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON* method = cJSON_GetObjectItem(root, "method");
|
// Create wake pipe for interrupting select()
|
||||||
cJSON* id = cJSON_GetObjectItem(root, "id");
|
if (pipe(client->wake_pipe) == -1) {
|
||||||
cJSON* result = cJSON_GetObjectItem(root, "result");
|
close(stdin_pipe[0]);
|
||||||
cJSON* error = cJSON_GetObjectItem(root, "error");
|
close(stdin_pipe[1]);
|
||||||
|
close(stdout_pipe[0]);
|
||||||
// ── Error response ────────────────────────────────────────────────────────
|
close(stdout_pipe[1]);
|
||||||
if (error)
|
return -1;
|
||||||
{
|
|
||||||
cJSON* msg = cJSON_GetObjectItem(error, "message");
|
|
||||||
appDebug("[LSP ←] ERROR: %s\n",
|
|
||||||
msg ? msg->valuestring : "(no message)");
|
|
||||||
cJSON_Delete(root);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Notification (no id, has method) ─────────────────────────────────────
|
// Fork LSP server process
|
||||||
if (method && !id)
|
pid_t pid = fork();
|
||||||
{
|
if (pid == 0) {
|
||||||
const char* m = method->valuestring;
|
// Child process - exec clangd
|
||||||
appDebug("[LSP ←] NOTIF: %s\n", m);
|
close(stdin_pipe[1]);
|
||||||
|
close(stdout_pipe[0]);
|
||||||
|
close(client->wake_pipe[0]);
|
||||||
|
close(client->wake_pipe[1]);
|
||||||
|
|
||||||
if (strcmp(m, "textDocument/publishDiagnostics") == 0)
|
dup2(stdin_pipe[0], STDIN_FILENO);
|
||||||
{
|
dup2(stdout_pipe[1], STDOUT_FILENO);
|
||||||
// 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;
|
|
||||||
|
|
||||||
appDebug("[LSP ←] Diagnostics for buffer %d\n", buf_id);
|
close(stdin_pipe[0]);
|
||||||
|
close(stdout_pipe[1]);
|
||||||
|
|
||||||
pthread_mutex_lock(&lsp->lock);
|
// Find clangd in PATH
|
||||||
lspParseDiagnostics(json, &E.lsp_diagnostics, buf_id);
|
execlp("clangd", "clangd", NULL);
|
||||||
pthread_mutex_unlock(&lsp->lock);
|
|
||||||
write(lsp->wake_pipe[1], "d", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (strcmp(m, "window/logMessage") == 0)
|
// If execlp fails
|
||||||
{
|
exit(1);
|
||||||
cJSON* params = cJSON_GetObjectItem(root, "params");
|
} else if (pid > 0) {
|
||||||
cJSON* message = cJSON_GetObjectItem(params, "message");
|
// Parent process
|
||||||
appDebug("[LSP ←] LOG: %s\n",
|
close(stdin_pipe[0]);
|
||||||
message ? message->valuestring : "");
|
close(stdout_pipe[1]);
|
||||||
}
|
|
||||||
E.lsp_client->completion_just_arrived = 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);
|
// Send initialize request
|
||||||
return;
|
char root_uri[PATH_MAX + 8];
|
||||||
}
|
snprintf(root_uri, sizeof(root_uri), "file://%s", root_dir);
|
||||||
|
|
||||||
// ── 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);
|
|
||||||
|
|
||||||
cJSON *req = cJSON_CreateObject();
|
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 *params = cJSON_CreateObject();
|
||||||
|
cJSON_AddNumberToObject(params, "processId", getpid());
|
||||||
|
cJSON_AddStringToObject(params, "rootUri", root_uri);
|
||||||
|
|
||||||
|
// Capabilities
|
||||||
cJSON *caps = cJSON_CreateObject();
|
cJSON *caps = cJSON_CreateObject();
|
||||||
cJSON *td_caps = cJSON_CreateObject();
|
cJSON *td_caps = cJSON_CreateObject();
|
||||||
cJSON *comp_caps = cJSON_CreateObject();
|
cJSON *comp_caps = cJSON_CreateObject();
|
||||||
cJSON *comp_item = 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, "snippetSupport", 0);
|
||||||
cJSON_AddBoolToObject(comp_item, "commitCharactersSupport", 0);
|
cJSON_AddBoolToObject(comp_item, "commitCharactersSupport", 0);
|
||||||
cJSON_AddItemToObject(comp_caps, "completionItem", comp_item);
|
cJSON_AddItemToObject(comp_caps, "completionItem", comp_item);
|
||||||
@@ -332,151 +181,128 @@ int lspStart(LspClient *lsp, const char *project_root)
|
|||||||
cJSON_AddItemToObject(req, "params", params);
|
cJSON_AddItemToObject(req, "params", params);
|
||||||
|
|
||||||
char *msg = cJSON_PrintUnformatted(req);
|
char *msg = cJSON_PrintUnformatted(req);
|
||||||
lsp_send(lsp->write_fd, msg);
|
lsp_send(client, msg);
|
||||||
|
|
||||||
free(msg);
|
free(msg);
|
||||||
cJSON_Delete(req);
|
cJSON_Delete(req);
|
||||||
|
|
||||||
// ── Wait for LSP_READY ────────────────────────────────────────────────────
|
return 0;
|
||||||
// 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 -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends textDocument/didOpen notification
|
||||||
|
* @param client LSP client
|
||||||
|
* @param buf Buffer to open
|
||||||
|
*/
|
||||||
void lspDidOpen(LspClient* lsp, struct buffer_t* buf)
|
void lspDidOpen(LspClient* lsp, struct buffer_t* buf)
|
||||||
{
|
{
|
||||||
appDebug("[LSP] opening file");
|
if (!lsp || !buf)
|
||||||
if (lsp->state != LSP_READY || buf->b_lsp_open) return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
char uri[PATH_MAX + 8];
|
char uri[PATH_MAX + 8];
|
||||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
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* root = cJSON_CreateObject();
|
||||||
cJSON* params = cJSON_CreateObject();
|
|
||||||
cJSON* td = cJSON_CreateObject();
|
|
||||||
|
|
||||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||||
cJSON_AddStringToObject(root, "method", "textDocument/didOpen");
|
cJSON_AddStringToObject(root, "method", "textDocument/didOpen");
|
||||||
|
|
||||||
cJSON_AddStringToObject(td, "uri", uri);
|
cJSON* params = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(td, "languageId", lang);
|
cJSON* textDoc = cJSON_CreateObject();
|
||||||
cJSON_AddNumberToObject(td, "version", 1);
|
cJSON_AddStringToObject(textDoc, "uri", uri);
|
||||||
cJSON_AddStringToObject(td, "text", raw); // cJSON escapes this
|
cJSON_AddStringToObject(textDoc, "languageId", "c");
|
||||||
|
cJSON_AddNumberToObject(textDoc, "version", 1);
|
||||||
cJSON_AddItemToObject(params, "textDocument", td);
|
cJSON_AddStringToObject(textDoc, "text", "");
|
||||||
|
cJSON_AddItemToObject(params, "textDocument", textDoc);
|
||||||
cJSON_AddItemToObject(root, "params", params);
|
cJSON_AddItemToObject(root, "params", params);
|
||||||
|
|
||||||
char* msg = cJSON_PrintUnformatted(root);
|
char* msg = cJSON_PrintUnformatted(root);
|
||||||
lsp_send(lsp->write_fd, msg);
|
lsp_send(lsp, msg);
|
||||||
|
|
||||||
free(msg);
|
free(msg);
|
||||||
free(raw);
|
|
||||||
cJSON_Delete(root);
|
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)
|
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];
|
char uri[PATH_MAX + 8];
|
||||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||||
|
|
||||||
char* raw = buffer_to_text(buf);
|
char* raw = bufferToText(buf);
|
||||||
|
|
||||||
cJSON* root = cJSON_CreateObject();
|
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, "jsonrpc", "2.0");
|
||||||
cJSON_AddStringToObject(root, "method", "textDocument/didChange");
|
cJSON_AddStringToObject(root, "method", "textDocument/didChange");
|
||||||
|
|
||||||
cJSON_AddStringToObject(td, "uri", uri);
|
cJSON* params = cJSON_CreateObject();
|
||||||
cJSON_AddNumberToObject(td, "version", buf->dirty);
|
|
||||||
|
|
||||||
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_AddItemToArray(changes, change);
|
||||||
|
|
||||||
cJSON_AddItemToObject(params, "textDocument", td);
|
|
||||||
cJSON_AddItemToObject(params, "contentChanges", changes);
|
cJSON_AddItemToObject(params, "contentChanges", changes);
|
||||||
|
|
||||||
cJSON_AddItemToObject(root, "params", params);
|
cJSON_AddItemToObject(root, "params", params);
|
||||||
|
|
||||||
char* msg = cJSON_PrintUnformatted(root);
|
char* msg = cJSON_PrintUnformatted(root);
|
||||||
lsp_send(lsp->write_fd, msg);
|
lsp_send(lsp, msg);
|
||||||
|
|
||||||
free(msg);
|
free(msg);
|
||||||
free(raw);
|
|
||||||
cJSON_Delete(root);
|
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)
|
void lspDidClose(LspClient* lsp, struct buffer_t* buf)
|
||||||
{
|
{
|
||||||
|
if (!lsp || !buf)
|
||||||
|
return;
|
||||||
|
|
||||||
char uri[PATH_MAX + 8];
|
char uri[PATH_MAX + 8];
|
||||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||||
|
|
||||||
cJSON* root = cJSON_CreateObject();
|
cJSON* root = cJSON_CreateObject();
|
||||||
cJSON* params = cJSON_CreateObject();
|
|
||||||
cJSON* td = cJSON_CreateObject();
|
|
||||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||||
cJSON_AddStringToObject(root, "method", "textDocument/didClose");
|
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);
|
cJSON_AddItemToObject(root, "params", params);
|
||||||
|
|
||||||
char* msg = cJSON_PrintUnformatted(root);
|
char* msg = cJSON_PrintUnformatted(root);
|
||||||
lsp_send(lsp->write_fd, msg);
|
lsp_send(lsp, msg);
|
||||||
|
|
||||||
free(msg);
|
free(msg);
|
||||||
cJSON_Delete(root);
|
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,
|
void lspRequestCompletion(LspClient *lsp, struct buffer_t *buf,
|
||||||
int line, int col,
|
int line, int col,
|
||||||
int screen_x, int screen_y)
|
int screen_x, int screen_y)
|
||||||
@@ -511,84 +337,228 @@ void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf,
|
|||||||
cJSON_AddItemToObject(req, "params", params);
|
cJSON_AddItemToObject(req, "params", params);
|
||||||
|
|
||||||
msg = cJSON_PrintUnformatted(req);
|
msg = cJSON_PrintUnformatted(req);
|
||||||
lsp_send(lsp->write_fd, msg);
|
lsp_send(lsp, msg);
|
||||||
E.lsp_client->completion_requested = 1;
|
E.lsp_client->completion_requested = 1;
|
||||||
cJSON_Delete(req);
|
cJSON_Delete(req);
|
||||||
free(msg);
|
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;
|
if (!lsp || lsp->state != LSP_READY || !buf)
|
||||||
char* msg;
|
return;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lspShutdown(LspClient *lsp)
|
char uri[PATH_MAX + 8];
|
||||||
{
|
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||||
if (!lsp || lsp->state == LSP_SHUTDOWN) return;
|
|
||||||
|
|
||||||
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* req = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
|
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
|
||||||
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
|
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
|
||||||
cJSON_AddStringToObject(req, "method", "shutdown");
|
cJSON_AddStringToObject(req, "method", "textDocument/definition");
|
||||||
cJSON_AddNullToObject (req, "params");
|
|
||||||
|
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);
|
char* msg = cJSON_PrintUnformatted(req);
|
||||||
lsp_send(lsp->write_fd, msg);
|
lsp_send(lsp, msg);
|
||||||
|
|
||||||
free(msg);
|
free(msg);
|
||||||
cJSON_Delete(req);
|
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,
|
* @brief Reads and processes LSP messages (non-blocking)
|
||||||
"{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}");
|
* @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
|
char *msg = lsp_recv(client);
|
||||||
close(lsp->write_fd);
|
if (!msg)
|
||||||
lsp->write_fd = -1;
|
return 0;
|
||||||
|
|
||||||
// 6. Wake the main loop so it doesn't stay blocked in select()
|
// Parse response
|
||||||
write(lsp->wake_pipe[1], "q", 1);
|
cJSON *root = cJSON_Parse(msg);
|
||||||
|
if (!root) {
|
||||||
// 7. Wait for reader thread to finish
|
free(msg);
|
||||||
pthread_join(lsp->reader_thread, NULL);
|
return 0;
|
||||||
|
}
|
||||||
// 8. Close remaining fds
|
|
||||||
close(lsp->read_fd);
|
// Handle different message types
|
||||||
lsp->read_fd = -1;
|
cJSON *id = cJSON_GetObjectItem(root, "id");
|
||||||
close(lsp->wake_pipe[0]);
|
cJSON *method = cJSON_GetObjectItem(root, "method");
|
||||||
close(lsp->wake_pipe[1]);
|
cJSON *result = cJSON_GetObjectItem(root, "result");
|
||||||
|
|
||||||
// 9. Destroy synchronization primitives
|
if (id) {
|
||||||
pthread_mutex_destroy(&lsp->lock);
|
// This is a response - check if it's shutdown response
|
||||||
pthread_cond_destroy (&lsp->ready_cond);
|
int msg_id = id->valueint;
|
||||||
|
if (msg_id == 0) { // Our shutdown request ID
|
||||||
// 10. Reap the clangd process
|
client->state = LSP_SHUTDOWN;
|
||||||
waitpid(lsp->pid, NULL, 0);
|
}
|
||||||
|
}
|
||||||
free(lsp);
|
|
||||||
|
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