595 lines
20 KiB
C
595 lines
20 KiB
C
//
|
|
// Created by Giorgio on 25/05/2026.
|
|
//
|
|
|
|
#include "../include/completion.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/syslimits.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/utils.h"
|
|
|
|
static void lsp_send(int fd, 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);
|
|
|
|
// Log to stderr for debugging
|
|
appDebug("[LSP →] Content-Length: %d | %s\n", body_len, json);
|
|
fflush(stderr);
|
|
}
|
|
|
|
static int lsp_uri_to_buffer_id(const char* uri)
|
|
{
|
|
const char *path = uri;
|
|
if (strncmp(uri, "file://", 7) == 0)
|
|
path = uri + 7;
|
|
// path is now "/absolute/path" — realpath output matches this directly
|
|
|
|
for (int i = 0; i < E.number_of_buffer; i++) {
|
|
if (E.buffers[i].filename == NULL) continue;
|
|
appDebug("[URI MATCH] comparing '%s' vs '%s'\n", E.buffers[i].fullname, path);
|
|
if (strcmp(E.buffers[i].fullname, path) == 0)
|
|
return E.buffers[i].buffer_id;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static char* lsp_recv(int fd)
|
|
{
|
|
char header[1024];
|
|
int content_length = 0;
|
|
|
|
while (1)
|
|
{
|
|
int i = 0;
|
|
char c;
|
|
int n;
|
|
// Read until \n
|
|
while ((n = read(fd, &c, 1)) == 1 && c != '\n')
|
|
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)
|
|
content_length = atoi(header + 16);
|
|
}
|
|
|
|
if (content_length == 0) return NULL;
|
|
|
|
char* body = bAlloc(content_length + 1);
|
|
int total = 0;
|
|
while (total < content_length)
|
|
{
|
|
int n = read(fd, body + total, content_length - total);
|
|
if (n <= 0)
|
|
{
|
|
bFree(body);
|
|
return NULL;
|
|
}
|
|
total += n;
|
|
}
|
|
body[content_length] = '\0';
|
|
return body;
|
|
}
|
|
|
|
static void lsp_dispatch(LspClient* lsp, const char* json)
|
|
{
|
|
if (!json) return;
|
|
|
|
cJSON* root = cJSON_Parse(json);
|
|
if (!root)
|
|
{
|
|
appDebug("[LSP ←] Failed to parse JSON: %.120s\n", json);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// ── Notification (no id, has method) ─────────────────────────────────────
|
|
if (method && !id)
|
|
{
|
|
const char* m = method->valuestring;
|
|
appDebug("[LSP ←] NOTIF: %s\n", m);
|
|
|
|
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;
|
|
|
|
appDebug("[LSP ←] Diagnostics for buffer %d\n", buf_id);
|
|
|
|
pthread_mutex_lock(&lsp->lock);
|
|
lspParseDiagnostics(json, &E.lsp_diagnostics, buf_id);
|
|
pthread_mutex_unlock(&lsp->lock);
|
|
write(lsp->wake_pipe[1], "d", 1);
|
|
}
|
|
|
|
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;
|
|
|
|
|
|
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);
|
|
bFree(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 *params = cJSON_CreateObject();
|
|
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);
|
|
cJSON_AddItemToObject (td_caps, "completion", comp_caps);
|
|
cJSON_AddItemToObject (td_caps, "hover", cJSON_CreateObject());
|
|
cJSON_AddItemToObject (td_caps, "definition", cJSON_CreateObject());
|
|
cJSON_AddItemToObject (td_caps, "publishDiagnostics", cJSON_CreateObject());
|
|
cJSON_AddItemToObject (caps, "textDocument", td_caps);
|
|
cJSON_AddItemToObject (params, "capabilities", caps);
|
|
cJSON_AddItemToObject (req, "params", params);
|
|
|
|
char *msg = cJSON_PrintUnformatted(req);
|
|
lsp_send(lsp->write_fd, 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 = bAlloc(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;
|
|
}
|
|
|
|
void lspDidOpen(LspClient* lsp, struct buffer_t* buf)
|
|
{
|
|
if (lsp->state != LSP_READY || buf->b_lsp_open) 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_AddItemToObject(root, "params", params);
|
|
|
|
char* msg = cJSON_PrintUnformatted(root);
|
|
lsp_send(lsp->write_fd, msg);
|
|
|
|
bFree(msg);
|
|
bFree(raw);
|
|
cJSON_Delete(root);
|
|
buf->b_lsp_open = 1;
|
|
}
|
|
|
|
void lspDidChange(LspClient* lsp, struct buffer_t* buf)
|
|
{
|
|
if (lsp->state != LSP_READY || !buf->b_lsp_open) return;
|
|
|
|
|
|
char uri[PATH_MAX + 8];
|
|
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
|
|
|
char* raw = buffer_to_text(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_AddStringToObject(change, "text", raw); // full content sync
|
|
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);
|
|
|
|
bFree(msg);
|
|
bFree(raw);
|
|
cJSON_Delete(root);
|
|
}
|
|
|
|
void lspDidClose(LspClient* lsp, struct buffer_t* buf)
|
|
{
|
|
|
|
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_AddItemToObject(root, "params", params);
|
|
char* msg = cJSON_PrintUnformatted(root);
|
|
lsp_send(lsp->write_fd, msg);
|
|
bFree(msg);
|
|
cJSON_Delete(root);
|
|
buf->b_lsp_open = 0;
|
|
}
|
|
|
|
// ─── requests ────────────────────────────────────────────────────────────────
|
|
|
|
void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf,
|
|
int line, int col,
|
|
int screen_x, int screen_y)
|
|
{
|
|
if (lsp->state != LSP_READY) return;
|
|
lsp->completion_cursor_x = screen_x;
|
|
lsp->completion_cursor_y = screen_y;
|
|
|
|
appDebug("LSP REQUEST COMP");
|
|
|
|
char* msg;
|
|
char uri[PATH_MAX + 8];
|
|
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
|
appDebug("FULLNAME : %s\n", buf->fullname);
|
|
|
|
cJSON* req = cJSON_CreateObject();
|
|
cJSON* params = cJSON_CreateObject();
|
|
cJSON* td = cJSON_CreateObject();
|
|
cJSON* position = cJSON_CreateObject();
|
|
|
|
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
|
|
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
|
|
cJSON_AddStringToObject(req, "method", "textDocument/completion");
|
|
|
|
cJSON_AddStringToObject(td, "uri", uri);
|
|
|
|
cJSON_AddNumberToObject(position, "line", line);
|
|
cJSON_AddNumberToObject(position, "character", col);
|
|
cJSON_AddItemToObject(params, "position", position);
|
|
|
|
cJSON_AddItemToObject(params, "textDocument", td);
|
|
cJSON_AddItemToObject(req, "params", params);
|
|
|
|
msg = cJSON_PrintUnformatted(req);
|
|
lsp_send(lsp->write_fd, msg);
|
|
E.lsp_client->completion_requested = 1;
|
|
cJSON_Delete(req);
|
|
bFree(msg);
|
|
}
|
|
|
|
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);
|
|
bFree(msg);
|
|
}
|
|
|
|
void lspShutdown(LspClient* lsp)
|
|
{
|
|
if (!lsp || lsp->state == LSP_SHUTDOWN) return;
|
|
|
|
lsp->state = LSP_SHUTDOWN;
|
|
|
|
// 1. 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");
|
|
char* msg = cJSON_PrintUnformatted(req);
|
|
lsp_send(lsp->write_fd, msg);
|
|
bFree(msg);
|
|
cJSON_Delete(req);
|
|
|
|
// 2. Wait briefly for the shutdown response
|
|
// (don't block forever — clangd has 2s to reply)
|
|
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);
|
|
bFree(resp);
|
|
}
|
|
|
|
// 3. Send exit notification
|
|
lsp_send(lsp->write_fd,
|
|
"{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}");
|
|
|
|
// 4. Close pipes — this signals the reader thread to stop
|
|
close(lsp->write_fd);
|
|
close(lsp->read_fd);
|
|
|
|
// 5. Wait for reader thread to finish
|
|
pthread_join(lsp->reader_thread, NULL);
|
|
pthread_mutex_destroy(&lsp->lock);
|
|
|
|
// 6. Reap the clangd process
|
|
waitpid(lsp->pid, NULL, 0);
|
|
|
|
bFree(lsp);
|
|
}
|