diff --git a/include/define.h b/include/define.h index a95f178..a249afb 100644 --- a/include/define.h +++ b/include/define.h @@ -14,7 +14,7 @@ #define SPACE "\x20" /* Uncomment to see debug logs on stderr */ -// #define APP_DEBUG +#define APP_DEBUG #define COMPLETION_MAX_ITEMS 16 #define COMPLETION_MAX_WIDTH 40 diff --git a/main.c b/main.c index 6152073..f263dd8 100644 --- a/main.c +++ b/main.c @@ -48,14 +48,15 @@ int main(int argc, char *argv[]) { active->buffer_id = bufferCreate(splash_screen, READ_ONLY); if (argc >= 2) { + if (E.constantes.LSP) { + + } active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE); buf = &E.buffers[active->buffer_id]; appDebug("peoject root : %s\n", dirname(buf->fullname)); - if (E.constantes.LSP) { - lspDidOpen(E.lsp_client, buf); - } + } bFree(splash_screen); diff --git a/src/buffer.c b/src/buffer.c index 55110e7..6fa5e9d 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -63,6 +63,7 @@ struct buffer_t* bufferFindById(int buffer_id) */ int bufferCreate(const char* path, enum bufferStatus_e state) { + appDebug("Creating new buffer"); char *filename = basename((char *) path); // Check if file is already open const int existing_id = bufferFindByFilename(path); @@ -89,13 +90,18 @@ int bufferCreate(const char* path, enum bufferStatus_e state) new_buf->x = 0; new_buf->y = 0; new_buf->dirty = 0; // New file starts clean + new_buf->b_lsp_open = 0; // Load file content using existing editorOpen editorOpen(new_buf); E.number_of_buffer++; if (new_buf->filename[strlen(new_buf->filename) - 1] == 'c') { - lspStart(E.lsp_client, new_buf->path); + if (E.lsp_client->state == LSP_SHUTDOWN) + lspStart(E.lsp_client, dirname(new_buf->path)); + while (E.lsp_client->state != LSP_READY) + ; + lspDidOpen(E.lsp_client, new_buf); } editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id); diff --git a/src/completion.c b/src/completion.c index 40e1061..05acd14 100644 --- a/src/completion.c +++ b/src/completion.c @@ -396,6 +396,7 @@ static char* buffer_to_text(struct buffer_t* buf) void lspDidOpen(LspClient* lsp, struct buffer_t* buf) { + appDebug("[LSP] opening file"); if (lsp->state != LSP_READY || buf->b_lsp_open) return; @@ -546,49 +547,65 @@ void lspRequestDefinition(LspClient* lsp, struct buffer_t* buf, int line, int co bFree(msg); } -void lspShutdown(LspClient* lsp) +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(); + // 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"); - char* msg = cJSON_PrintUnformatted(req); + 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}; + // 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); + if (select(lsp->read_fd + 1, &fds, NULL, NULL, &tv) > 0) { + char *resp = lsp_recv(lsp->read_fd); bFree(resp); } - // 3. Send exit notification + // 4. Send exit notification lsp_send(lsp->write_fd, - "{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}"); + "{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}"); - // 4. Close pipes — this signals the reader thread to stop + // 5. Close write pipe first — reader thread will get EOF and exit close(lsp->write_fd); - close(lsp->read_fd); + lsp->write_fd = -1; - // 5. Wait for reader thread to finish + // 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); - pthread_mutex_destroy(&lsp->lock); - // 6. Reap the clangd process + // 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); bFree(lsp); -} +} \ No newline at end of file diff --git a/src/input.c b/src/input.c index ac4fd98..7d1c414 100644 --- a/src/input.c +++ b/src/input.c @@ -209,8 +209,10 @@ int executeKeyBind(char *key_sequence) { void editorProcessKeypress() { int c = editorReadKey(); char key_sequence[8]; + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buf = bufferFindById(active->buffer_id); - if (E.constantes.LSP) { + if (E.constantes.LSP && buf->b_lsp_open) { if (c == LSP_WAKE_KEY) return; if (E.lsp_client && E.lsp_completion.visible) { @@ -227,8 +229,7 @@ void editorProcessKeypress() { if (c == '\r') { CompletionItem *item = &E.lsp_completion.items[E.lsp_completion.selected]; - EditorPane *active = splitScreenGetActivePane(); - struct buffer_t *buf = bufferFindById(active->buffer_id); + // Find how many chars the user already typed by looking at the // current word (chars before cursor on the same line) @@ -267,16 +268,14 @@ void editorProcessKeypress() { int seq_len = utf8Encode(c, key_sequence); appDebug("key seq : %s\n", key_sequence); bufferInsertBytes(key_sequence, seq_len); - if (E.constantes.LSP && is_word_char(key_sequence)) { - EditorPane *active = splitScreenGetActivePane(); - struct buffer_t *buffer = bufferFindById(active->buffer_id); + if (buf->b_lsp_open && is_word_char(key_sequence)) { if (E.lsp_client && E.lsp_client->state == LSP_READY) { - lspDidChange(E.lsp_client, buffer); + lspDidChange(E.lsp_client, buf); E.lsp_client->completion_just_arrived = 0; // consume the flag } - buffer->b_has_changed = 0; + buf->b_has_changed = 0; editorAutoComplete(lisp_null(), &E.ctx_error, E.ctx); } E.quit_times_buffer = E.constantes.QUIT_TIMES; diff --git a/src/terminal.c b/src/terminal.c index ec67b72..ed0fa8d 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -10,260 +10,312 @@ #include #include +#include "../include/buffer.h" +#include "../include/split_screen.h" #include "include/utf8.h" -void die(const char *s) { - write(STDOUT_FILENO, "\x1b[2J", 4); - write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); - lisp_shutdown(E.ctx); - perror(s); - exit(1); +void die(const char* s) +{ + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); + lisp_shutdown(E.ctx); + perror(s); + exit(1); } -void disableRawMode() { - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) { - die("tcsetattr"); - } +void disableRawMode() +{ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) + { + die("tcsetattr"); + } } -void enableRawMode() { - if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) { - die("tcgetattr"); - } +void enableRawMode() +{ + if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) + { + die("tcgetattr"); + } - struct termios raw = E.orig_termios; - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - raw.c_oflag &= ~(OPOST); - raw.c_cflag |= (CS8); - raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); - raw.c_cc[VMIN] = 0; - raw.c_cc[VTIME] = 1; + struct termios raw = E.orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { - die("tcgetattr"); - } + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) + { + die("tcgetattr"); + } } #include /* isprint */ -char *keyToString(int key) { - static char key_str[32]; +char* keyToString(int key) +{ + static char key_str[32]; - if (key == '\r') { - strcpy(key_str, "ENTER"); - } else if (key == 0x09) { - strcpy(key_str, "TAB"); - } else if (key >= 1 && key <= 26) { - snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1); - } else { - switch (key) { - case ARROW_UP: - strcpy(key_str, "ARROW-UP"); - break; - case ARROW_DOWN: - strcpy(key_str, "ARROW-DOWN"); - break; - case ARROW_LEFT: - strcpy(key_str, "ARROW-LEFT"); - break; - case ARROW_RIGHT: - strcpy(key_str, "ARROW-RIGHT"); - break; - case PAGE_UP: - strcpy(key_str, "PAGE-UP"); - break; - case PAGE_DOWN: - strcpy(key_str, "PAGE-DOWN"); - break; - case DEL_KEY: - strcpy(key_str, "DEL"); - break; - case BACKSPACE: - strcpy(key_str, "BACKSPACE"); - break; - case BEG_LINE: - strcpy(key_str, "HOME"); - break; - case END_LINE: - strcpy(key_str, "END"); - break; - case '\x1b': - strcpy(key_str, "ESCAPE"); - break; - default: - if (key > 127) { - /* UTF-8 code point — re-encode into the buffer */ - char buf[5] = {0}; - int n = utf8Encode((uint32_t)key, buf); - snprintf(key_str, sizeof(key_str), "%.*s", n, buf); - } else if (isprint(key)) { - snprintf(key_str, sizeof(key_str), "%c", key); - } else { - snprintf(key_str, sizeof(key_str), "KEY-%d", key); - } + if (key == '\r') + { + strcpy(key_str, "ENTER"); } - } - return key_str; -} - -int editorReadKey() { - char c; - int nread; - - while (1) { - fd_set fds; - FD_ZERO(&fds); - FD_SET(STDIN_FILENO, &fds); - - // Also watch the LSP wake pipe if available - int max_fd = STDIN_FILENO; - if (E.lsp_client && E.lsp_client->wake_pipe[0] > 0) { - FD_SET(E.lsp_client->wake_pipe[0], &fds); - if (E.lsp_client->wake_pipe[0] > max_fd) - max_fd = E.lsp_client->wake_pipe[0]; + else if (key == 0x09) + { + strcpy(key_str, "TAB"); } - - int ready = select(max_fd + 1, &fds, NULL, NULL, NULL); - if (ready <= 0) - continue; - - // LSP event arrived — drain pipe and trigger a redraw - // by returning a special no-op key - if (E.lsp_client && FD_ISSET(E.lsp_client->wake_pipe[0], &fds)) { - char tmp[16]; - read(E.lsp_client->wake_pipe[0], tmp, sizeof(tmp)); - return LSP_WAKE_KEY; // ← special value, see below + else if (key >= 1 && key <= 26) + { + snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1); } - - // Normal keypress - if (FD_ISSET(STDIN_FILENO, &fds)) { - nread = read(STDIN_FILENO, &c, 1); - if (nread == 1) - break; - } - } - - appDebug("f : %hhu %ld\r\n", c, 0x200); - - if (c == '\x1b') { - char seq[6]; - /* try to read escape sequence */ - if (read(STDIN_FILENO, &seq[0], 1) != 1) - return '\x1b'; - if (read(STDIN_FILENO, &seq[1], 1) != 1) - return '\x1b'; - appDebug("f2 : %s\r\n", seq); - - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - if (read(STDIN_FILENO, &seq[2], 1) != 1) - return '\x1b'; - if (seq[2] == '~') { - switch (seq[1]) { - case '1': - return BEG_LINE; - case '3': - return DEL_KEY; - case '4': - return END_LINE; - case '5': - return PAGE_UP; - case '6': - return PAGE_DOWN; - case '7': - return BEG_LINE; - case '8': - return END_LINE; - } + else + { + switch (key) + { + case ARROW_UP: + strcpy(key_str, "ARROW-UP"); + break; + case ARROW_DOWN: + strcpy(key_str, "ARROW-DOWN"); + break; + case ARROW_LEFT: + strcpy(key_str, "ARROW-LEFT"); + break; + case ARROW_RIGHT: + strcpy(key_str, "ARROW-RIGHT"); + break; + case PAGE_UP: + strcpy(key_str, "PAGE-UP"); + break; + case PAGE_DOWN: + strcpy(key_str, "PAGE-DOWN"); + break; + case DEL_KEY: + strcpy(key_str, "DEL"); + break; + case BACKSPACE: + strcpy(key_str, "BACKSPACE"); + break; + case BEG_LINE: + strcpy(key_str, "HOME"); + break; + case END_LINE: + strcpy(key_str, "END"); + break; + case '\x1b': + strcpy(key_str, "ESCAPE"); + break; + default: + if (key > 127) + { + /* UTF-8 code point — re-encode into the buffer */ + char buf[5] = {0}; + int n = utf8Encode((uint32_t)key, buf); + snprintf(key_str, sizeof(key_str), "%.*s", n, buf); + } + else if (isprint(key)) + { + snprintf(key_str, sizeof(key_str), "%c", key); + } + else + { + snprintf(key_str, sizeof(key_str), "KEY-%d", key); + } } - } else { - switch (seq[1]) { - case 'A': - return ARROW_UP; - case 'B': - return ARROW_DOWN; - case 'C': - return ARROW_RIGHT; - case 'D': - return ARROW_LEFT; - case 'H': - return BEG_LINE; - case 'F': - return END_LINE; + } + return key_str; +} + +int editorReadKey() +{ + char c; + int nread; + + while (1) + { + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + int max_fd = STDIN_FILENO; + + // Only watch wake pipe if LSP is ready AND active buffer is open + EditorPane* active = splitScreenGetActivePane(); + struct buffer_t* buf = active ? bufferFindById(active->buffer_id) : NULL; + int lsp_active = E.lsp_client + && E.lsp_client->wake_pipe[0] > 0 + && E.lsp_client->state == LSP_READY + && buf + && buf->b_lsp_open; // ← only if this buffer is tracked + + if (lsp_active) + { + FD_SET(E.lsp_client->wake_pipe[0], &fds); + if (E.lsp_client->wake_pipe[0] > max_fd) + max_fd = E.lsp_client->wake_pipe[0]; + } + + int ready = select(max_fd + 1, &fds, NULL, NULL, NULL); + if (ready <= 0) continue; + + if (lsp_active && FD_ISSET(E.lsp_client->wake_pipe[0], &fds)) + { + char tmp[16]; + read(E.lsp_client->wake_pipe[0], tmp, sizeof(tmp)); + return LSP_WAKE_KEY; + } + + if (FD_ISSET(STDIN_FILENO, &fds)) + { + nread = read(STDIN_FILENO, &c, 1); + if (nread == 1) break; } - } } - return '\x1b'; - } - /* multi-byte UTF-8: read remaining bytes */ - int seqlen = utf8Seqlen((unsigned char)c); - if (seqlen > 1) { - /* pack into a pseudo-codepoint just to pass bytes through; - we handle encoding/decoding at the row level */ - char buf[4] = {c, 0, 0, 0}; - for (int i = 1; i < seqlen; i++) - if (read(STDIN_FILENO, &buf[i], 1) != 1) - break; - /* decode and return as uint32, but we need int — use high range */ - const char *p = buf; - uint32_t cp = utf8Decode(&p); - return (int)cp; /* caller re-encodes when inserting */ - } - return (unsigned char)c; + appDebug("f : %hhu %ld\r\n", c, 0x200); + + if (c == '\x1b') + { + char seq[6]; + /* try to read escape sequence */ + if (read(STDIN_FILENO, &seq[0], 1) != 1) + return '\x1b'; + if (read(STDIN_FILENO, &seq[1], 1) != 1) + return '\x1b'; + appDebug("f2 : %s\r\n", seq); + + if (seq[0] == '[') + { + if (seq[1] >= '0' && seq[1] <= '9') + { + if (read(STDIN_FILENO, &seq[2], 1) != 1) + return '\x1b'; + if (seq[2] == '~') + { + switch (seq[1]) + { + case '1': + return BEG_LINE; + case '3': + return DEL_KEY; + case '4': + return END_LINE; + case '5': + return PAGE_UP; + case '6': + return PAGE_DOWN; + case '7': + return BEG_LINE; + case '8': + return END_LINE; + } + } + } + else + { + switch (seq[1]) + { + case 'A': + return ARROW_UP; + case 'B': + return ARROW_DOWN; + case 'C': + return ARROW_RIGHT; + case 'D': + return ARROW_LEFT; + case 'H': + return BEG_LINE; + case 'F': + return END_LINE; + } + } + } + return '\x1b'; + } + + /* multi-byte UTF-8: read remaining bytes */ + int seqlen = utf8Seqlen((unsigned char)c); + if (seqlen > 1) + { + /* pack into a pseudo-codepoint just to pass bytes through; + we handle encoding/decoding at the row level */ + char buf[4] = {c, 0, 0, 0}; + for (int i = 1; i < seqlen; i++) + if (read(STDIN_FILENO, &buf[i], 1) != 1) + break; + /* decode and return as uint32, but we need int — use high range */ + const char* p = buf; + uint32_t cp = utf8Decode(&p); + return (int)cp; /* caller re-encodes when inserting */ + } + + return (unsigned char)c; } -int getCursorPosition(int *rows, int *cols) { - char buf[32]; - unsigned int i = 0; +int getCursorPosition(int* rows, int* cols) +{ + char buf[32]; + unsigned int i = 0; - if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) { - return -1; - } - - while (i < sizeof(buf) - 1) { - if (read(STDIN_FILENO, &buf[i], 1) != 1) { - break; + if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) + { + return -1; } - if (buf[i] == 'R') { - break; + + while (i < sizeof(buf) - 1) + { + if (read(STDIN_FILENO, &buf[i], 1) != 1) + { + break; + } + if (buf[i] == 'R') + { + break; + } + ++i; } - ++i; - } - buf[i] = '\0'; + buf[i] = '\0'; - if (buf[0] != '\x1b' || buf[1] != '[') { - return -1; - } - if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) { - return -1; - } + if (buf[0] != '\x1b' || buf[1] != '[') + { + return -1; + } + if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) + { + return -1; + } - return 0; + return 0; } -int getWindowSize(int *rows, int *cols) { - struct winsize ws; +int getWindowSize(int* rows, int* cols) +{ + struct winsize ws; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) { - return -1; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) + { + if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) + { + return -1; + } + return getCursorPosition(rows, cols); } - return getCursorPosition(rows, cols); - } - *cols = ws.ws_col; - *rows = ws.ws_row; - return 0; + *cols = ws.ws_col; + *rows = ws.ws_row; + return 0; } -void appDebug(const char *fmt, ...) { +void appDebug(const char* fmt, ...) +{ #ifdef APP_DEBUG - va_list ap; - char message[1024]; - va_start(ap, fmt); - vsnprintf(message, 1024, fmt, ap); - va_end(ap); - fprintf(stderr, "%s\n", message); + va_list ap; + char message[1024]; + va_start(ap, fmt); + vsnprintf(message, 1024, fmt, ap); + va_end(ap); + fprintf(stderr, "%s\n", message); #endif }