From 2738bc804225c30bc7482d36ad0974bd00eccb8b Mon Sep 17 00:00:00 2001 From: arthur barraux Date: Tue, 2 Jun 2026 13:00:13 +0200 Subject: [PATCH] popup and diagnose ui --- config/init.lisp | 1 + include/append_buffer.h | 2 +- include/completion.h | 19 ++- include/data.h | 304 ++++++++++++++++++++------------------- include/define.h | 5 +- include/init.h | 3 + include/lisp_lib.h | 4 +- include/split_screen.h | 4 + include/utf8.h | 1 + main.c | 39 +++-- src/append_buffer.c | 2 +- src/buffer.c | 14 +- src/builtins.c | 18 ++- src/cJSON.c | 1 + src/completion.c | 203 +++++++++++++++----------- src/editor_op.c | 2 + src/file_io.c | 10 +- src/init.c | 19 ++- src/input.c | 123 ++++++++-------- src/lsp_ui.c | 38 ++--- src/output.c | 122 +++++++--------- src/split_screen.c | 15 ++ src/syntax_highlighter.c | 49 ++++--- src/terminal.c | 49 +++++-- src/utils.c | 1 - 25 files changed, 587 insertions(+), 461 deletions(-) diff --git a/config/init.lisp b/config/init.lisp index 640fc9b..95aa753 100644 --- a/config/init.lisp +++ b/config/init.lisp @@ -3,6 +3,7 @@ (define TAB-LENGTH 4) (define QUIT-TIMES 1) (define THEME "dark") +(define LSP #t) ;; PACKAGES diff --git a/include/append_buffer.h b/include/append_buffer.h index 3f28992..778762e 100644 --- a/include/append_buffer.h +++ b/include/append_buffer.h @@ -7,6 +7,6 @@ void abAppend(struct abuf *ab, const char *s, int len); -void abFree(struct abuf *ab); +void abFree(const struct abuf *ab); #endif // APPEND_BUFFER_H_ diff --git a/include/completion.h b/include/completion.h index afce595..4fc94e9 100644 --- a/include/completion.h +++ b/include/completion.h @@ -7,23 +7,20 @@ #include "data.h" - -void createContextBuffer(const int x, const int y, const char * text); - -int lspStart(LspClient *lsp, const char *project_root); -void lspShutdown(LspClient *lsp); +int lspStart(LspClient* lsp, const char* project_root); +void lspShutdown(LspClient* lsp); // Document sync — call these from your buffer open/save/edit hooks -void lspDidOpen(LspClient *lsp, struct buffer_t *buf); -void lspDidChange(LspClient *lsp, struct buffer_t *buf); -void lspDidClose(LspClient *lsp, struct buffer_t *buf); +void lspDidOpen(LspClient* lsp, struct buffer_t* buf); +void lspDidChange(LspClient* lsp, struct buffer_t* buf); +void lspDidClose(LspClient* lsp, struct buffer_t* buf); // Requests -void lspRequestCompletion(LspClient *lsp, struct buffer_t *buf, +void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf, int line, int col, int screen_x, int screen_y); -void lspRequestHover(LspClient *lsp, struct buffer_t *buf, int line, int col); -void lspRequestDefinition(LspClient *lsp, struct buffer_t *buf, int line, int col); +void lspRequestHover(LspClient* lsp, struct buffer_t* buf, int line, int col); +void lspRequestDefinition(LspClient* lsp, struct buffer_t* buf, int line, int col); #endif //BELUGA_COMPLETION_H diff --git a/include/data.h b/include/data.h index ca1a529..c204a45 100644 --- a/include/data.h +++ b/include/data.h @@ -17,85 +17,94 @@ typedef struct lsp_client_t LspClient; * \param * */ -typedef struct row { - int size; /**< Size of the line */ - int cap; /**< Size of the render line */ - char *chars; /**< Characters of the line */ +typedef struct row +{ + int size; /**< Size of the line */ + int cap; /**< Size of the render line */ + char* chars; /**< Characters of the line */ } row_t; typedef struct context_buffer_t { - int editor_x, editor_y; - int width, height; - row_t *rows; + int editor_x, editor_y; + int width, height; + row_t* rows; } ContextBuffer; /** * @brief Split modes for screen layout */ -typedef enum { - SPLIT_NONE = 0, // Single buffer fullscreen - SPLIT_VERTICAL, // Left-right split - SPLIT_HORIZONTAL // Top-bottom split +typedef enum +{ + SPLIT_NONE = 0, // Single buffer fullscreen + SPLIT_VERTICAL, // Left-right split + SPLIT_HORIZONTAL // Top-bottom split } SplitMode; /** * @brief Represents an editor viewport/pane */ -typedef struct { - int buffer_id; // Which buffer this pane displays - int height; // Height of this pane - int width; // Width of this pane +typedef struct +{ + int buffer_id; // Which buffer this pane displays + int height; // Height of this pane + int width; // Width of this pane int origin_x, origin_y; - int cursor_x; // Local cursor x in this pane - int cursor_y; // Local cursor y in this pane + int cursor_x; // Local cursor x in this pane + int cursor_y; // Local cursor y in this pane int x_offset, y_offset; - int is_active; // Is this pane currently active + int is_active; // Is this pane currently active } EditorPane; /** * @brief Screen layout manager */ -typedef struct { +typedef struct +{ SplitMode mode; - EditorPane *panes; + EditorPane* panes; int num_panes; - int active_pane; // Index of active pane + int active_pane; // Index of active pane } ScreenLayout; -typedef struct theme { - char *BACKGROUND_COLOR; - char *COLOR_KEYWORD; - char *COLOR_TYPE; - char *COLOR_STRING; - char *COLOR_COMMENT; - char *COLOR_NUMBER; - char *COLOR_DEFAULT; - +typedef struct theme +{ + char* BACKGROUND_COLOR; + char* COLOR_KEYWORD; + char* COLOR_TYPE; + char* COLOR_STRING; + char* COLOR_COMMENT; + char* COLOR_NUMBER; + char* COLOR_DEFAULT; } theme_t; -enum bufferStatus_e { - IDLE, - READ_ONLY, - READ_AND_WRITE, +enum bufferStatus_e +{ + IDLE, + READ_ONLY, + READ_AND_WRITE, }; -struct const_t { - int TAB_LENGTH; - int QUIT_TIMES; - char *THEME; +struct const_t +{ + int TAB_LENGTH; + int QUIT_TIMES; + char* THEME; + int LSP; }; -struct prefix_t { - char prefix_name[64]; - int prefix_id; +struct prefix_t +{ + char prefix_name[64]; + int prefix_id; }; -struct keyBind_t { - char *key_sequence; - int prefix_id; - Lisp command; +struct keyBind_t +{ + char* key_sequence; + int prefix_id; + Lisp command; }; // In data.h — add these @@ -104,142 +113,144 @@ struct keyBind_t { #define LSP_MAX_PENDING 64 -typedef enum { - LSP_NOT_STARTED = 0, - LSP_INITIALIZING, - LSP_READY, - LSP_SHUTDOWN, +typedef enum +{ + LSP_NOT_STARTED = 0, + LSP_INITIALIZING, + LSP_READY, + LSP_SHUTDOWN, } LspState; -typedef struct { - int id; - void (*callback)(struct lsp_client_t *lsp, const char *json); +typedef struct +{ + int id; + void (*callback)(struct lsp_client_t* lsp, const char* json); } LspPending; -typedef struct lsp_client_t { - // ── Process ─────────────────────────────────────────────────────────────── - pid_t pid; - int write_fd; - int read_fd; - int completion_just_arrived; - int completion_requested; +typedef struct lsp_client_t +{ + // ── Process ─────────────────────────────────────────────────────────────── + pid_t pid; + int write_fd; + int read_fd; + int completion_just_arrived; + int completion_requested; + int wake_pipe[2]; // [0] = read end (main loop), [1] = write end (reader thread) + // ── State ───────────────────────────────────────────────────────────────── + LspState state; + int next_id; + // ── Pending requests ────────────────────────────────────────────────────── + LspPending pending[LSP_MAX_PENDING]; + int pending_count; - int wake_pipe[2]; // [0] = read end (main loop), [1] = write end (reader thread) + // ── Threading ───────────────────────────────────────────────────────────── + pthread_t reader_thread; + pthread_mutex_t lock; + pthread_cond_t ready_cond; // signaled when state → LSP_READY - - // ── State ───────────────────────────────────────────────────────────────── - LspState state; - int next_id; - - // ── Pending requests ────────────────────────────────────────────────────── - LspPending pending[LSP_MAX_PENDING]; - int pending_count; - - // ── Threading ───────────────────────────────────────────────────────────── - pthread_t reader_thread; - pthread_mutex_t lock; - pthread_cond_t ready_cond; // signaled when state → LSP_READY - - // ── Completion context ──────────────────────────────────────────────────── - int completion_cursor_x; // screen position when - int completion_cursor_y; // completion was requested + // ── Completion context ──────────────────────────────────────────────────── + int completion_cursor_x; // screen position when + int completion_cursor_y; // completion was requested } LspClient; -typedef struct { - char label[128]; // display text e.g. "printf" - char detail[64]; // type/sig hint e.g. "int (const char *, ...)" - int kind; // LSP CompletionItemKind (1=Text,2=Method,3=Function…) +typedef struct +{ + char label[128]; // display text e.g. "printf" + char detail[64]; // type/sig hint e.g. "int (const char *, ...)" + int kind; // LSP CompletionItemKind (1=Text,2=Method,3=Function…) } CompletionItem; -typedef struct { - CompletionItem items[COMPLETION_MAX_ITEMS]; - int count; - int selected; // currently highlighted row - int visible; // is the popup shown? - int origin_x; // screen col where popup appears - int origin_y; // screen row where popup appears +typedef struct +{ + CompletionItem items[COMPLETION_MAX_ITEMS]; + int count; + int selected; // currently highlighted row + int visible; // is the popup shown? + int origin_x; // screen col where popup appears + int origin_y; // screen row where popup appears } CompletionPopup; typedef enum { DIAG_ERROR = 1, DIAG_WARNING, DIAG_HINT } DiagSeverity; -typedef struct { - int buffer_id; - int line; // 0-based - int col_start; // 0-based - int col_end; - DiagSeverity severity; - char message[256]; +typedef struct +{ + int buffer_id; + int line; // 0-based + int col_start; // 0-based + int col_end; + DiagSeverity severity; + char message[256]; } Diagnostic; -typedef struct { - Diagnostic entries[DIAG_MAX]; - int count; +typedef struct +{ + Diagnostic entries[DIAG_MAX]; + int count; } DiagnosticList; enum buffer_type { FILE_BUFF, TERMINAL_BUFF }; -struct buffer_t { - enum buffer_type type; - int buffer_id; - int b_lsp_open; - int x, y; /**< Position in the file */ - row_t *row; - int numrows; - int b_has_changed; - char *filename; - char *path; - enum bufferStatus_e state; - int dirty; /**< Has this buffer been modified since last save */ +struct buffer_t +{ + enum buffer_type type; + int buffer_id; + int b_lsp_open; + int x, y; /**< Position in the file */ + row_t* row; + int numrows; + int b_has_changed; + char* filename; + char* path; + char * fullname; + enum bufferStatus_e state; + int dirty; /**< Has this buffer been modified since last save */ }; /** * \struct editorConfig * \brief Containing our editor state. */ -struct editorConfig { - int cursor_x, cursor_y; /**< Cursor position */ - int screenrows; /**< Terminal height*/ - int screencols; /**< Terminal width*/ +struct editorConfig +{ + int cursor_x, cursor_y; /**< Cursor position */ + int screenrows; /**< Terminal height*/ + int screencols; /**< Terminal width*/ - ScreenLayout layout; + ScreenLayout layout; - row_t *rows; /**< Store all the rows printed */ + LspClient* lsp_client; + CompletionPopup lsp_completion; + DiagnosticList lsp_diagnostics; - ContextBuffer* context_buffers; + int dirty; - LspClient *lsp_client; - CompletionPopup lsp_completion; - DiagnosticList lsp_diagnostics; + char* status_msg; + time_t status_msg_time; + struct termios orig_termios; /**< Terminal communication interface */ - int dirty; + struct const_t constantes; + int quit_times_buffer; - char *status_msg; - time_t status_msg_time; - struct termios orig_termios; /**< Terminal communication interface */ + char* init_file_path; + FILE* fd_init_file; + Lisp env; + LispContext ctx; /** Lisp context */ + Lisp ctx_data; /** Lisp data context */ + LispError ctx_error; /** Lisp ctx error */ - struct const_t constantes; - int quit_times_buffer; + struct keyBind_t* key_binds; + int number_of_keybinds; - char *init_file_path; - FILE *fd_init_file; - Lisp env; - LispContext ctx; /** Lisp context */ - Lisp ctx_data; /** Lisp data context */ - LispError ctx_error; /** Lisp ctx error */ + struct prefix_t* prefix; + int number_of_prefix; + int prefix_state; - struct keyBind_t *key_binds; - int number_of_keybinds; + struct buffer_t buffers[64]; + int number_of_buffer; - struct prefix_t *prefix; - int number_of_prefix; - int prefix_state; - - struct buffer_t buffers[64]; - int number_of_buffer; - - theme_t theme; + theme_t theme; }; /** @@ -247,9 +258,10 @@ struct editorConfig { * \brief Contains text to add before writing to screen. * */ -struct abuf { - char *b; /**< Text that will be printed */ - int len; /**< Length of the text */ +struct abuf +{ + char* b; /**< Text that will be printed */ + int len; /**< Length of the text */ }; extern struct editorConfig E; diff --git a/include/define.h b/include/define.h index 9dc7d6c..a95f178 100644 --- a/include/define.h +++ b/include/define.h @@ -12,7 +12,9 @@ #define ERASE_END_LINE "\x1b[K" #define TAB "\t" #define SPACE "\x20" -#define APP_DEBUG + +/* Uncomment to see debug logs on stderr */ +// #define APP_DEBUG #define COMPLETION_MAX_ITEMS 16 #define COMPLETION_MAX_WIDTH 40 @@ -32,6 +34,7 @@ enum editorKey_e { END_LINE, PAGE_UP, PAGE_DOWN, + LSP_WAKE_KEY = 2000 }; #define ABUF_INIT {NULL, 0} diff --git a/include/init.h b/include/init.h index 858e132..e56776c 100644 --- a/include/init.h +++ b/include/init.h @@ -11,4 +11,7 @@ void initBuiltins(); void initEditor(); +void deInitEditor(); + + #endif // INIT_H_ diff --git a/include/lisp_lib.h b/include/lisp_lib.h index a7d997f..8d9c7a3 100644 --- a/include/lisp_lib.h +++ b/include/lisp_lib.h @@ -1163,7 +1163,7 @@ static Lisp sch_string_ref(Lisp args, LispError* e, LispContext ctx) return lisp_null(); } - return lisp_make_char((int)lisp_string_ref(str, lisp_int(index))); + return lisp_make_char(lisp_string_ref(str, lisp_int(index))); } static Lisp sch_string_set(Lisp args, LispError* e, LispContext ctx) @@ -1737,7 +1737,7 @@ static Lisp sch_pseudo_rand(Lisp args, LispError* e, LispContext ctx) static Lisp sch_univeral_time(Lisp args, LispError* e, LispContext ctx) { - return lisp_make_int((LispInt)time(NULL)); + return lisp_make_int(time(NULL)); } static Lisp sch_is_table(Lisp args, LispError* e, LispContext ctx) diff --git a/include/split_screen.h b/include/split_screen.h index da7e555..eee37b2 100644 --- a/include/split_screen.h +++ b/include/split_screen.h @@ -59,4 +59,8 @@ ScreenLayout *splitScreenGetLayout(void); */ EditorPane *splitScreenGetActivePane(void); +void freePane(EditorPane *pane); +void freeScreenLayout(ScreenLayout *layout); + + #endif diff --git a/include/utf8.h b/include/utf8.h index 2c3f425..1391e4a 100644 --- a/include/utf8.h +++ b/include/utf8.h @@ -11,6 +11,7 @@ int utf8Encode(uint32_t cp, char *buf); int utf8Seqlen(unsigned char c); int codepointWidth(uint32_t codepoint); uint32_t utf8Decode(const char** s); +int is_word_char(const char *s); #endif //BELUGA_UTF8_H diff --git a/main.c b/main.c index f5080b5..6152073 100644 --- a/main.c +++ b/main.c @@ -9,6 +9,8 @@ #define _BSD_SOURCE #define _GNU_SOURCE +#include + #include "include/utils.h" #include "include/buffer.h" @@ -36,34 +38,27 @@ int main(int argc, char *argv[]) { enableRawMode(); initEditor(); + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buf; + + strcat(splash_screen, getenv("HOME")); + strcat(splash_screen, "/.beluga/assets/beluga.txt"); + + appDebug("splash : %s\n", splash_screen); + active->buffer_id = bufferCreate(splash_screen, READ_ONLY); + if (argc >= 2) { - EditorPane *active = splitScreenGetActivePane(); active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE); - char project_root[512]; - realpath(argv[1], project_root); - char *slash = strrchr(project_root, '/'); - if (slash) *slash = '\0'; + buf = &E.buffers[active->buffer_id]; - appDebug("peoject root : %s\n", project_root); - - lspStart(E.lsp_client, project_root); - struct buffer_t *buf = &E.buffers[active->buffer_id]; - lspDidOpen(E.lsp_client, buf); - - } else { - strcat(splash_screen, getenv("HOME")); - strcat(splash_screen, "/.beluga/assets/beluga.txt"); - - appDebug("splash : %s\n", splash_screen); - EditorPane *active = splitScreenGetActivePane(); - active->buffer_id = bufferCreate(splash_screen, READ_ONLY); - struct buffer_t *buf = &E.buffers[active->buffer_id]; - lspStart(E.lsp_client, splash_screen); - lspDidOpen(E.lsp_client, buf); + appDebug("peoject root : %s\n", dirname(buf->fullname)); + if (E.constantes.LSP) { + lspDidOpen(E.lsp_client, buf); + } } - free(splash_screen); + bFree(splash_screen); editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit"); diff --git a/src/append_buffer.c b/src/append_buffer.c index 1f46062..9695d6a 100644 --- a/src/append_buffer.c +++ b/src/append_buffer.c @@ -12,4 +12,4 @@ void abAppend(struct abuf *ab, const char *s, int len) { ab->len += len; } -void abFree(struct abuf *ab) { bFree(ab->b); } +void abFree(const struct abuf *ab) { bFree(ab->b); } diff --git a/src/buffer.c b/src/buffer.c index 7ac566f..55110e7 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -8,12 +8,14 @@ #include "../include/editor_op.h" #include "../include/data.h" #include "include/split_screen.h" +#include <_string.h> #include #include #include #include #include +#include "include/completion.h" #include "include/input.h" #include "include/utils.h" @@ -64,7 +66,6 @@ int bufferCreate(const char* path, enum bufferStatus_e state) char *filename = basename((char *) path); // Check if file is already open const int existing_id = bufferFindByFilename(path); - path = dirname((char *) path); if (existing_id != -1) { return bufferSwitch(existing_id); @@ -80,17 +81,22 @@ int bufferCreate(const char* path, enum bufferStatus_e state) struct buffer_t* new_buf = &E.buffers[E.number_of_buffer]; new_buf->buffer_id = E.number_of_buffer; new_buf->filename = strdup(filename); + new_buf->fullname = bAlloc(1024 * sizeof(char)); + realpath(path, new_buf->fullname); + new_buf->path = dirname(new_buf->fullname); new_buf->type = FILE_BUFF; new_buf->state = state; new_buf->x = 0; new_buf->y = 0; new_buf->dirty = 0; // New file starts clean - new_buf->path = strdup(path); // 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); + } editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id); return new_buf->buffer_id; @@ -478,4 +484,4 @@ void bufferInsertNewLine(void) { buf->y++; buf->x = 0; appDebug("Insert new line done\n"); -} \ No newline at end of file +} diff --git a/src/builtins.c b/src/builtins.c index 55206cf..416e457 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -21,6 +21,7 @@ #include #include "include/completion.h" +#include "include/init.h" #include "include/utils.h" /** @@ -68,7 +69,7 @@ Lisp mapKey(Lisp args, LispError* e, LispContext ctx) // second argument const Lisp func = lisp_car(args); - memory_temp = (void*)bRealloc( + memory_temp = bRealloc( E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t)); E.key_binds = (struct keyBind_t*)memory_temp; if (!E.key_binds) @@ -188,6 +189,7 @@ Lisp editorQuit(Lisp args, LispError* e, LispContext ctx) disableRawMode(); lspShutdown(E.lsp_client); lisp_shutdown(E.ctx); + deInitEditor(); appDebug("Rest alloc %d\n", beluga_alloc_counter); exit(0); } @@ -218,7 +220,6 @@ Lisp l_editorSplitScreenVertical(Lisp args, LispError* e, LispContext ctx) Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx) { editorSave(); - return lisp_null(); } @@ -415,7 +416,7 @@ Lisp addPackage(Lisp args, LispError* e, LispContext ctx) { const char* package_name = lisp_string(lisp_car(args)); appDebug("%s\n", package_name); - char* package_dir = (char*)calloc(256, sizeof(char)); + char* package_dir = calloc(256, sizeof(char)); FILE* fd_package = NULL; strcat(package_dir, getenv("HOME")); strcat(package_dir, "/.beluga/packages/"); @@ -557,7 +558,6 @@ Lisp editorSetPrefix(Lisp args, LispError* e, LispContext ctx) */ Lisp editorPrefix(Lisp args, LispError* e, LispContext ctx) { - void * memory_temp; E.prefix = (struct prefix_t*)bRealloc(E.prefix, (++(E.number_of_prefix) + 1) * sizeof(struct prefix_t)); E.prefix[E.number_of_prefix].prefix_id = E.number_of_prefix; @@ -610,6 +610,9 @@ Lisp editorMoveEndBuffer(Lisp args, LispError* e, LispContext ctx) Lisp editorAutoComplete(Lisp args, LispError* e, LispContext ctx) { + if (!E.constantes.LSP) { + return lisp_null(); + } // createContextBuffer(E.cursor_x - 2, E.cursor_y + 1, "hello"); appDebug("editor-auto-complete\n"); EditorPane* active = splitScreenGetActivePane(); @@ -618,8 +621,8 @@ Lisp editorAutoComplete(Lisp args, LispError* e, LispContext ctx) lspRequestCompletion( E.lsp_client, buffer, - active->cursor_y + active->y_offset, // file line - active->cursor_x + active->x_offset, // file col + buffer->y, // file line + buffer->x, // file col active->cursor_x + active->origin_x + GUTTER_WIDTH, // screen x active->cursor_y + active->origin_y // screen y ); @@ -628,6 +631,9 @@ Lisp editorAutoComplete(Lisp args, LispError* e, LispContext ctx) Lisp lspDefinition(Lisp args, LispError* e, LispContext ctx) { + if (!E.constantes.LSP) { + return lisp_null(); + } (void)args; (void)e; (void)ctx; diff --git a/src/cJSON.c b/src/cJSON.c index f861fb2..a2bb627 100644 --- a/src/cJSON.c +++ b/src/cJSON.c @@ -1906,6 +1906,7 @@ CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) return 0; } + child = array->child; while(child != NULL) diff --git a/src/completion.c b/src/completion.c index e1bb6c7..40e1061 100644 --- a/src/completion.c +++ b/src/completion.c @@ -13,23 +13,10 @@ #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" -void createContextBuffer(const int x, const int y, const char* text) -{ - E.context_buffers = bAlloc(sizeof(ContextBuffer)); - ContextBuffer* buffer = E.context_buffers; - buffer->editor_x = x; - buffer->editor_y = y; - buffer->height = 1; - buffer->rows = bAlloc(sizeof(struct row)); - if (!buffer->rows) return; - buffer->rows[0].chars = strdup(text); - buffer->rows[0].size = strlen(text); - buffer->width = strlen(text); -} - static void lsp_send(int fd, const char* json) { int body_len = strlen(json); @@ -42,7 +29,7 @@ static void lsp_send(int fd, const char* json) write(fd, json, body_len); // Log to stderr for debugging - fprintf(stderr, "[LSP →] Content-Length: %d | %s\n", body_len, json); + appDebug("[LSP →] Content-Length: %d | %s\n", body_len, json); fflush(stderr); } @@ -55,10 +42,8 @@ static int lsp_uri_to_buffer_id(const char* uri) for (int i = 0; i < E.number_of_buffer; i++) { if (E.buffers[i].filename == NULL) continue; - char abs[PATH_MAX]; - realpath(E.buffers[i].filename, abs); - fprintf(stderr, "[URI MATCH] comparing '%s' vs '%s'\n", abs, path); - if (strcmp(abs, path) == 0) + 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; @@ -112,7 +97,7 @@ static void lsp_dispatch(LspClient* lsp, const char* json) cJSON* root = cJSON_Parse(json); if (!root) { - fprintf(stderr, "[LSP ←] Failed to parse JSON: %.120s\n", json); + appDebug("[LSP ←] Failed to parse JSON: %.120s\n", json); return; } @@ -125,7 +110,7 @@ static void lsp_dispatch(LspClient* lsp, const char* json) if (error) { cJSON* msg = cJSON_GetObjectItem(error, "message"); - fprintf(stderr, "[LSP ←] ERROR: %s\n", + appDebug("[LSP ←] ERROR: %s\n", msg ? msg->valuestring : "(no message)"); cJSON_Delete(root); return; @@ -135,17 +120,16 @@ static void lsp_dispatch(LspClient* lsp, const char* json) if (method && !id) { const char* m = method->valuestring; - fprintf(stderr, "[LSP ←] NOTIF: %s\n", m); + 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 = lsp_uri_to_buffer_id( - uri ? uri->valuestring : ""); + int buf_id = splitScreenGetActivePane()->buffer_id; - fprintf(stderr, "[LSP ←] Diagnostics for buffer %d\n", buf_id); + appDebug("[LSP ←] Diagnostics for buffer %d\n", buf_id); pthread_mutex_lock(&lsp->lock); lspParseDiagnostics(json, &E.lsp_diagnostics, buf_id); @@ -157,7 +141,7 @@ static void lsp_dispatch(LspClient* lsp, const char* json) { cJSON* params = cJSON_GetObjectItem(root, "params"); cJSON* message = cJSON_GetObjectItem(params, "message"); - fprintf(stderr, "[LSP ←] LOG: %s\n", + appDebug("[LSP ←] LOG: %s\n", message ? message->valuestring : ""); } E.lsp_client->completion_just_arrived = 1; @@ -171,12 +155,12 @@ static void lsp_dispatch(LspClient* lsp, const char* json) if (id && result) { int response_id = id->valueint; - fprintf(stderr, "[LSP ←] RESPONSE id=%d\n", response_id); + appDebug("[LSP ←] RESPONSE id=%d\n", response_id); // initialize response → send initialized + mark ready if (lsp->state == LSP_INITIALIZING) { - fprintf(stderr, "[LSP ←] Initialize OK, sending initialized\n"); + appDebug("[LSP ←] Initialize OK, sending initialized\n"); lsp_send(lsp->write_fd, "{\"jsonrpc\":\"2.0\",\"method\":\"initialized\",\"params\":{}}"); @@ -195,7 +179,7 @@ static void lsp_dispatch(LspClient* lsp, const char* json) if (items && cJSON_IsArray(items)) { int count = cJSON_GetArraySize(items); - fprintf(stderr, "[LSP ←] Completion: %d items\n", count); + appDebug("[LSP ←] Completion: %d items\n", count); // Print each item to stderr for debugging cJSON* item; @@ -205,14 +189,14 @@ static void lsp_dispatch(LspClient* lsp, const char* json) cJSON* label = cJSON_GetObjectItem(item, "label"); cJSON* detail = cJSON_GetObjectItem(item, "detail"); cJSON* kind = cJSON_GetObjectItem(item, "kind"); - fprintf(stderr, " [%d] kind=%-2d %-40s %s\n", + appDebug(" [%d] kind=%-2d %-40s %s\n", i++, kind ? kind->valueint : 0, label ? label->valuestring : "(no label)", detail ? detail->valuestring : ""); if (i >= 10) { - fprintf(stderr, " ... (%d more)\n", count - 10); + appDebug(" ... (%d more)\n", count - 10); break; } } @@ -224,7 +208,7 @@ static void lsp_dispatch(LspClient* lsp, const char* json) pthread_mutex_unlock(&lsp->lock); E.lsp_client->completion_just_arrived = 1; - fprintf(stderr, "[POPUP] visible=%d count=%d origin=(%d,%d)\n", + appDebug("[POPUP] visible=%d count=%d origin=(%d,%d)\n", E.lsp_completion.visible, E.lsp_completion.count, E.lsp_completion.origin_x, @@ -243,7 +227,7 @@ static void lsp_dispatch(LspClient* lsp, const char* json) cJSON* start = cJSON_GetObjectItem(range, "start"); int line = cJSON_GetObjectItem(start, "line")->valueint; int col = cJSON_GetObjectItem(start, "character")->valueint; - fprintf(stderr, "[LSP ←] Definition: %s:%d:%d\n", + appDebug("[LSP ←] Definition: %s:%d:%d\n", uri_item->valuestring, line, col); E.lsp_client->completion_just_arrived = 1; @@ -252,7 +236,7 @@ static void lsp_dispatch(LspClient* lsp, const char* json) return; } - fprintf(stderr, "[LSP ←] Unhandled response id=%d: %.80s\n", + appDebug("[LSP ←] Unhandled response id=%d: %.80s\n", response_id, json); } @@ -274,63 +258,122 @@ static void* lsp_reader(void* arg) // ─── lifecycle ─────────────────────────────────────────────────────────────── -int lspStart(LspClient* lsp, const char* project_root) +int lspStart(LspClient *lsp, const char *project_root) { + // ── Pipes ───────────────────────────────────────────────────────────────── int to_clangd[2], from_clangd[2]; - pipe(to_clangd); - pipe(from_clangd); - pipe(lsp->wake_pipe); + 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) - { - // Child: become clangd - dup2(to_clangd[0], STDIN_FILENO); + 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]); - execlp("clangd", "clangd", "--log=error", "--completion-style=detailed", NULL); - _exit(1); // clangd not found + 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; - pthread_mutex_init(&lsp->lock, NULL); + lsp->read_fd = from_clangd[0]; + lsp->next_id = 1; + lsp->state = LSP_INITIALIZING; - // Send initialize - char buf[1024]; - snprintf(buf, sizeof(buf), - "{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"initialize\"," - "\"params\":{\"processId\":%d,\"rootUri\":\"file://%s\"," - "\"capabilities\":{" - "\"textDocument\":{" - "\"completion\":{\"completionItem\":{\"snippetSupport\":false}}," - "\"hover\":{}," - "\"definition\":{}," - "\"publishDiagnostics\":{}" - "}" - "}}}", - lsp->next_id++, getpid(), project_root); + // ── Threading ───────────────────────────────────────────────────────────── + pthread_mutex_init(&lsp->lock, NULL); + pthread_cond_init (&lsp->ready_cond, NULL); - 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); - lsp_send(lsp->write_fd, buf); + 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) - pthread_cond_wait(&lsp->ready_cond, &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); - return 0; -} + 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 @@ -355,10 +398,9 @@ void lspDidOpen(LspClient* lsp, struct buffer_t* buf) { if (lsp->state != LSP_READY || buf->b_lsp_open) return; - char abs[PATH_MAX]; - realpath(buf->filename, abs); + char uri[PATH_MAX + 8]; - snprintf(uri, sizeof(uri), "file://%s", abs); + snprintf(uri, sizeof(uri), "file://%s", buf->fullname); const char* lang = "c"; if (strstr(buf->filename, ".cpp") || strstr(buf->filename, ".cc")) @@ -395,10 +437,9 @@ void lspDidChange(LspClient* lsp, struct buffer_t* buf) { if (lsp->state != LSP_READY || !buf->b_lsp_open) return; - char abs[PATH_MAX]; - realpath(buf->filename, abs); + char uri[PATH_MAX + 8]; - snprintf(uri, sizeof(uri), "file://%s", abs); + snprintf(uri, sizeof(uri), "file://%s", buf->fullname); char* raw = buffer_to_text(buf); @@ -431,10 +472,9 @@ void lspDidChange(LspClient* lsp, struct buffer_t* buf) void lspDidClose(LspClient* lsp, struct buffer_t* buf) { - char abs[PATH_MAX]; - realpath(buf->filename, abs); + char uri[PATH_MAX + 8]; - snprintf(uri, sizeof(uri), "file://%s", abs); + snprintf(uri, sizeof(uri), "file://%s", buf->fullname); cJSON* root = cJSON_CreateObject(); cJSON* params = cJSON_CreateObject(); @@ -458,16 +498,15 @@ void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf, int screen_x, int screen_y) { if (lsp->state != LSP_READY) return; - lsp->completion_cursor_x = screen_x; // ← add + lsp->completion_cursor_x = screen_x; lsp->completion_cursor_y = screen_y; appDebug("LSP REQUEST COMP"); char* msg; - char abs[PATH_MAX]; - realpath(buf->filename, abs); char uri[PATH_MAX + 8]; - snprintf(uri, sizeof(uri), "file://%s", abs); + snprintf(uri, sizeof(uri), "file://%s", buf->fullname); + appDebug("FULLNAME : %s\n", buf->fullname); cJSON* req = cJSON_CreateObject(); cJSON* params = cJSON_CreateObject(); diff --git a/src/editor_op.c b/src/editor_op.c index 32ed498..e86a3c1 100644 --- a/src/editor_op.c +++ b/src/editor_op.c @@ -74,6 +74,8 @@ int editorMoveCursor(int key) { buf->x = buf->row[buf->y].size; } break; + default: + break; } return 1; } diff --git a/src/file_io.c b/src/file_io.c index 42feceb..4a36b56 100644 --- a/src/file_io.c +++ b/src/file_io.c @@ -13,18 +13,16 @@ #include "../include/data.h" #include "../include/split_screen.h" #include "../include/row_op.h" +#include <_string.h> #include #include #include #include -#include #include #include #include "../include/utils.h" -extern struct editorConfig E; - /** * @brief Closes the current file and resets editor state * @details Clears all rows, resets cursor position, scroll offsets, and file @@ -56,7 +54,7 @@ void editorCloseFile(void) { * @details Loads file content into editor rows, one line per row. If another * file is already open, it is closed first (without saving). File is opened in * a+ (read/append) mode to allow both reading and modification. - * @param filename Path to the file to open (relative or absolute) + * @param buffer Path to the file to open (relative or absolute) * @note Updates global editor state E * @note Calls die() on file open failure * @note Newline characters are stripped from loaded lines @@ -118,11 +116,11 @@ void editorSave() { return; } } - fd = open(buffer->filename, O_RDWR | O_CREAT, 0644); + fd = open(buffer->fullname, O_RDWR | O_CREAT, 0644); if (fd != -1) { for (int i = 0; i < buffer->numrows; ++i) { - len = strlen(buffer->row[i].chars); + len = (int) strlen(buffer->row[i].chars); if (write(fd, buffer->row[i].chars, len) != len) { close(fd); editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); diff --git a/src/init.c b/src/init.c index 4090cc2..b1d1821 100644 --- a/src/init.c +++ b/src/init.c @@ -75,6 +75,8 @@ void initConfig() { void initTheme() { E.constantes.THEME = (char *)lisp_string( lisp_eval(lisp_read("THEME", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx)); + E.constantes.LSP = lisp_bool(lisp_eval(lisp_read("LSP", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx)); + appDebug("LSP ON : %d", E.constantes.LSP); if (strcmp(E.constantes.THEME, "dark") == 0) { E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(40, 44, 52); E.theme.COLOR_KEYWORD = ANSI_FG_RGB(198, 120, 221); @@ -99,7 +101,7 @@ void initEditor() { if (getWindowSize(&E.screenrows, &E.screencols) == -1) { die("getWindowSize"); } - appDebug("%d %d\n", E.screenrows, E.screencols); + appDebug("%d %d", E.screenrows, E.screencols); E.screenrows -= 2; @@ -137,6 +139,7 @@ void initEditor() { E.prefix_state = 0; E.lsp_client = (LspClient*)bAlloc(sizeof(LspClient)); + E.lsp_client->state = LSP_SHUTDOWN; initConfig(); initTheme(); @@ -154,3 +157,17 @@ void initEditor() { E.quit_times_buffer = E.constantes.QUIT_TIMES; } + +void deInitEditor() +{ + freeScreenLayout(&E.layout); + bFree(E.lsp_client); + bFree(E.status_msg); + bFree(E.init_file_path); + bFree(E.key_binds); + for (int i = 0; i < E.number_of_keybinds; i++) + { + bFree(E.key_binds[i].key_sequence); + } + +} diff --git a/src/input.c b/src/input.c index 3473d65..ac4fd98 100644 --- a/src/input.c +++ b/src/input.c @@ -2,32 +2,27 @@ #include "../include/define.h" #include "../include/editor_op.h" #include "../include/output.h" -#include "include/data.h" #include "include/buffer.h" +#include "include/builtins.h" +#include "../include/completion.h" #include "include/data.h" #include "include/split_screen.h" -#include "include/completion.h" -#include "include/lsp_ui.h" #include -#include #include -#include #include #include #include #include -#include #include "include/terminal.h" #include "include/utf8.h" #include "include/utils.h" -extern struct editorConfig E; - /** * @file input.c * @brief Input handling module for the Beluga text editor - * @details Manages user input processing, key bindings, cursor movement, and file path completion + * @details Manages user input processing, key bindings, cursor movement, and + * file path completion */ /** @@ -36,7 +31,8 @@ extern struct editorConfig E; * the first file or directory entry that matches the filename prefix. * Appends a trailing slash for directory entries. * @param path The file path to complete (can be relative or absolute) - * @return Pointer to the completed file path (dynamically allocated), or NULL if: + * @return Pointer to the completed file path (dynamically allocated), or NULL + * if: * - path ends with '/' (already a directory) * - no matching entries found * - directory cannot be opened @@ -63,7 +59,7 @@ const char *fileCompletion(const char *path) { if (last_slash) { dir_len = last_slash - path + 1; // length of dir_path strncpy(directory, path, dir_len); - predict_len = strlen(path) - dir_len; + predict_len = (int)(strlen(path) - dir_len); strncpy(predict, last_slash + 1, predict_len); directory[dir_len] = '\0'; predict[predict_len] = '\0'; @@ -102,9 +98,9 @@ const char *fileCompletion(const char *path) { /** * @brief Displays an interactive prompt and returns user input - * @details Allows the user to enter text in a prompt with optional path completion - * via Tab key. Supports backspace, delete, and escape key handling. Dynamically - * allocates memory for the input buffer. + * @details Allows the user to enter text in a prompt with optional path + * completion via Tab key. Supports backspace, delete, and escape key handling. + * Dynamically allocates memory for the input buffer. * @param prompt The prompt message format string (printf-style) * @param placeHolder Initial text to display in the input buffer * @param bPathMode If non-zero, enables Tab key file path completion @@ -154,8 +150,7 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) { strcpy(path, buf); } memset(buf, 0, 256); - buf_len = 0; - char * buf_complete = (char *) fileCompletion(path); + char *buf_complete = (char *)fileCompletion(path); strcpy(buf, buf_complete); bFree(buf_complete); buf_len = strlen(buf); @@ -166,14 +161,12 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) { buf_size *= 2; buf = bRealloc(buf, buf_size); } - buf[buf_len++] = c; + buf[buf_len++] = (char)c; buf[buf_len] = '\0'; } } } - - /** * @brief Executes the command bound to a key sequence * @details Searches the keybinding table for a matching key sequence and @@ -210,56 +203,62 @@ int executeKeyBind(char *key_sequence) { * and either executes the bound command or inserts the character. Resets * the quit buffer counter on successful key processing. * @note Updates global editor state E - * @note Calls editorReadKey() to get input and editorInsertChar() for unbound keys + * @note Calls editorReadKey() to get input and editorInsertChar() for unbound + * keys */ void editorProcessKeypress() { int c = editorReadKey(); char key_sequence[8]; - if (E.lsp_client && E.lsp_completion.visible) { - if (c == ARROW_UP || c == CTRL_KEY('p')) { - if (E.lsp_completion.selected > 0) - E.lsp_completion.selected--; - return; // consumed, redraw on next loop - } - if (c == ARROW_DOWN || c == CTRL_KEY('n')) { - if (E.lsp_completion.selected < E.lsp_completion.count - 1) - E.lsp_completion.selected++; + if (E.constantes.LSP) { + if (c == LSP_WAKE_KEY) return; - } - if (c == '\r') { - CompletionItem *item = &E.lsp_completion.items[E.lsp_completion.selected]; - EditorPane *active = splitScreenGetActivePane(); - struct buffer_t *buf = bufferFindById(active->buffer_id); + if (E.lsp_client && E.lsp_completion.visible) { + if (c == ARROW_UP || c == CTRL_KEY('p')) { + if (E.lsp_completion.selected > 0) + E.lsp_completion.selected--; + return; // consumed, redraw on next loop + } + if (c == ARROW_DOWN || c == CTRL_KEY('n')) { + if (E.lsp_completion.selected < E.lsp_completion.count - 1) + E.lsp_completion.selected++; + return; + } + 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) - int file_col = active->cursor_x + active->x_offset; - row_t *row = &buf->row[active->cursor_y + active->y_offset]; + // Find how many chars the user already typed by looking at the + // current word (chars before cursor on the same line) + int file_col = active->cursor_x + active->x_offset; + row_t *row = &buf->row[active->cursor_y + active->y_offset]; - // Walk backwards from cursor to find start of current word - int word_start = file_col; - while (word_start > 0 && - (isalnum((unsigned char)row->chars[word_start - 1]) || - row->chars[word_start - 1] == '_')) - word_start--; + // Walk backwards from cursor to find start of current word + int word_start = file_col; + while (word_start > 0 && + (isalnum((unsigned char)row->chars[word_start - 1]) || + row->chars[word_start - 1] == '_')) + word_start--; - int already_typed = file_col - word_start; // chars user already typed + int already_typed = file_col - word_start; // chars user already typed - // Insert only the suffix — what comes after what's already typed - const char *suffix = item->label + already_typed; - for (int i = 0; suffix[i]; i++) - bufferInsertBytes(&suffix[i], 1); + // Insert only the suffix — what comes after what's already typed + const char *suffix = item->label + already_typed; + for (int i = 0; suffix[i]; i++) + bufferInsertBytes(&suffix[i], 1); + E.lsp_completion.visible = 0; + return; + } + if (c == ESCAPE) { + E.lsp_completion.visible = 0; + return; + } + // Any other key: dismiss popup and fall through to normal handling E.lsp_completion.visible = 0; - return; } - if (c == ESCAPE) { - E.lsp_completion.visible = 0; - return; - } - // Any other key: dismiss popup and fall through to normal handling - E.lsp_completion.visible = 0; } if (executeKeyBind(keyToString(c))) { @@ -268,5 +267,17 @@ 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 (E.lsp_client && E.lsp_client->state == LSP_READY) { + lspDidChange(E.lsp_client, buffer); + + E.lsp_client->completion_just_arrived = 0; // consume the flag + } + buffer->b_has_changed = 0; + editorAutoComplete(lisp_null(), &E.ctx_error, E.ctx); + } E.quit_times_buffer = E.constantes.QUIT_TIMES; } diff --git a/src/lsp_ui.c b/src/lsp_ui.c index 42ea1b2..5a3f417 100644 --- a/src/lsp_ui.c +++ b/src/lsp_ui.c @@ -5,12 +5,12 @@ #include "include/lsp_ui.h" #include "include/data.h" #include -#include #include // We use cJSON — drop cJSON.c + cJSON.h into src/ and include/ #include "include/cJSON.h" #include "include/append_buffer.h" +#include "include/terminal.h" // ─── ANSI helpers ───────────────────────────────────────────────────────────── @@ -73,7 +73,7 @@ static const char *kind_color(int k) { // editorRefreshScreen() before the final write(). void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup) { - fprintf(stderr, "[DRAW] visible=%d count=%d origin=(%d,%d)\n", + appDebug("[DRAW] visible=%d count=%d origin=(%d,%d)\n", popup->visible, popup->count, popup->origin_x, popup->origin_y); if (!popup->visible || popup->count == 0) return; @@ -104,7 +104,7 @@ void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup) { if (is_sel) ab_sgr(ab, "7"); // reverse video (highlight) else ab_sgr(ab, "0;37;40"); - abAppend(ab, "│ ", 2); + abAppend(ab, "| ", 2); // kind tag in color if (!is_sel) ab_sgr(ab, kind_color(it->kind)); @@ -131,7 +131,7 @@ void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup) { AB_RESET(ab); if (is_sel) ab_sgr(ab, "7"); else ab_sgr(ab, "37;40"); - abAppend(ab, " │", 2); + abAppend(ab, " |", 2); AB_RESET(ab); } @@ -162,12 +162,8 @@ int lspUiHandleKey(CompletionPopup *popup, int key) { if (!popup->visible) return 0; switch (key) { - case '\x1b': // ESC — dismiss - popup->visible = 0; - return 1; - - case '\r': // Enter — accept - // Caller should insert popup->items[popup->selected].label + case '\x1b': + case '\r': // ESC — dismiss popup->visible = 0; return 1; @@ -248,14 +244,13 @@ void lspParseCompletion(const char *json, CompletionPopup *popup, int screen_x, int screen_y) { popup->count = 0; popup->selected = 0; - popup->origin_x = screen_x; - popup->origin_y = screen_y + 1; // one row below cursor + popup->origin_x = screen_x + 1; + popup->origin_y = screen_y + 2; // one row below cursor cJSON *root = cJSON_Parse(json); if (!root) return; cJSON *result = cJSON_GetObjectItem(root, "result"); if (!result) { cJSON_Delete(root); return; } - // result can be a list or {isIncomplete, items:[…]} cJSON *items = cJSON_IsArray(result) ? result @@ -276,7 +271,11 @@ void lspParseCompletion(const char *json, CompletionPopup *popup, strncpy(ci->label, raw_label, 127); strncpy(ci->detail, detail ? detail->valuestring : "", 63); - ci->kind = kind ? kind->valueint : 0; + + if (!kind) + ci->kind = 0; + else + ci->kind = kind->valueint; } popup->visible = (popup->count > 0); @@ -317,8 +316,15 @@ void lspParseDiagnostics(const char *json, DiagnosticList *diags, diag->col_end = cJSON_GetObjectItem(end_pos, "character")->valueint; diag->severity = sev ? (DiagSeverity)sev->valueint : DIAG_ERROR; - strncpy(diag->message, msg ? msg->valuestring : "", 255); + const char *raw = msg ? msg->valuestring : ""; + int i = 0; + while (raw[i] && raw[i] != '\n' && i < 255) + { + diag->message[i] = raw[i]; + i++; + } + diag->message[i] = '\0'; } cJSON_Delete(root); -} \ No newline at end of file +} diff --git a/src/output.c b/src/output.c index fffb094..8d72d3b 100644 --- a/src/output.c +++ b/src/output.c @@ -24,8 +24,6 @@ #include "include/completion.h" #include "include/utils.h" -extern struct editorConfig E; - /** * @brief Renders a single pane with its buffer content */ @@ -74,7 +72,10 @@ static void editorDrawPane(struct abuf* ab, EditorPane* pane) } else { - lspUiDrawGutter(ab, &E.lsp_diagnostics, pane->buffer_id, file_row); + if (E.constantes.LSP) { + lspUiDrawGutter(ab, &E.lsp_diagnostics, pane->buffer_id, file_row); + } + if (buf->filename[strlen(buf->filename) - 1] == 'c' || buf->filename[strlen(buf->filename) - 1] == 'h') { @@ -304,11 +305,8 @@ void editorDrawStatusBar(struct abuf* ab) abAppend(ab, render_status, render_len); break; } - else - { - abAppend(ab, " ", 1); - ++len; - } + abAppend(ab, " ", 1); + ++len; } abAppend(ab, "\x1b[m", 3); // normal text mode @@ -336,36 +334,6 @@ void editorDrawMessageBar(struct abuf* ab) } } -void editorDrawContextBuffer(struct abuf* ab) -{ - int pos_len; - char pos_buf[1024]; - int i, j; - - if (!E.context_buffers) - return; - - appDebug("Printing context"); - - for (i = 0; i < E.context_buffers->height; ++i) - { - if (E.context_buffers->editor_y + i + 1 > 0) - { - pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", - E.context_buffers->editor_y + i + 1, E.context_buffers->editor_x - 2); - abAppend(ab, pos_buf, pos_len); - - // Apply background color (6 bytes for RGB format) - abAppend(ab, E.theme.BACKGROUND_COLOR, (int)strlen(E.theme.BACKGROUND_COLOR)); - abAppend(ab, "|", 1); - abAppend(ab, E.context_buffers->rows->chars, E.context_buffers->rows->size); - abAppend(ab, "|", 1); - } - } - bFree(E.context_buffers); - E.context_buffers = NULL; -} - /** * @brief Performs complete screen refresh and buffer synchronization * @details Clears screen, redraws all visible content (rows, status bar, @@ -388,50 +356,62 @@ void editorRefreshScreen() (int)strlen(E.theme.BACKGROUND_COLOR)); editorScroll(); + + + EditorPane* active = splitScreenGetActivePane(); + struct buffer_t* buffer = bufferFindById(active->buffer_id); + editorDrawAllPanes(&ab); + if (E.constantes.LSP) { + + // ── LSP: draw completion popup every frame while visible ────────────────── + appDebug("[REFRESH] lsp_completion.visible=%d\n", + E.lsp_completion.visible); + while (E.lsp_client->completion_requested && !E.lsp_client->completion_just_arrived); + // reset flags + E.lsp_client->completion_just_arrived = 0; + E.lsp_client->completion_requested = 0; + + // ── LSP: diagnostic for current line in status bar ──────────────────────── + const char* diag = lspUiDiagnosticAtCursor( + &E.lsp_diagnostics, + active->buffer_id, + buffer->y); + if (diag) { + char single_line[512]; + int i = 0; + + // Copy until newline, \0, or screen width + while (diag[i] && diag[i] != '\n' && i < E.screencols - 4) + { + single_line[i] = diag[i]; + i++; + } + + // If message was truncated, add ellipsis + if (diag[i] != '\0' && diag[i] != '\n') + { + single_line[i++] = '.'; + single_line[i++] = '.'; + single_line[i++] = '.'; + } + single_line[i] = '\0'; + editorSetStatusMessage("● %s", single_line); + } + + } editorDrawStatusBar(&ab); editorDrawMessageBar(&ab); - - EditorPane *active = splitScreenGetActivePane(); - struct buffer_t *buffer = bufferFindById(active->buffer_id); - - // ── LSP: sync buffer changes to clangd ──────────────────────────────────── - if (buffer->b_has_changed) { - if (E.lsp_client && E.lsp_client->state == LSP_READY) { - lspDidChange(E.lsp_client, buffer); - - E.lsp_client->completion_just_arrived = 0; // consume the flag - } - buffer->b_has_changed = 0; - } - - // ── LSP: draw completion popup every frame while visible ────────────────── - fprintf(stderr, "[REFRESH] lsp_completion.visible=%d\n", - E.lsp_completion.visible); - while (E.lsp_client->completion_requested && !E.lsp_client->completion_just_arrived) - ; - // reset flags - E.lsp_client->completion_just_arrived = 0; - E.lsp_client->completion_requested = 0; - - if (E.lsp_client && E.lsp_client->state == LSP_READY) + if (E.constantes.LSP && (E.lsp_client && E.lsp_client->state == LSP_READY)) { lspUiDrawCompletion(&ab, &E.lsp_completion); - appDebug("ready\n"); } - // ── LSP: diagnostic for current line in status bar ──────────────────────── - const char *diag = lspUiDiagnosticAtCursor( - &E.lsp_diagnostics, - active->buffer_id, - active->cursor_y + active->y_offset); - if (diag) - editorSetStatusMessage("● %s", diag); // ── Position cursor (account for gutter width) ──────────────────────────── snprintf(buf, sizeof(buf), "\x1b[%d;%dH", active->cursor_y + active->origin_y + 1, - active->cursor_x + active->origin_x + 1 + GUTTER_WIDTH); + active->cursor_x + active->origin_x + 1 + (E.constantes.LSP ? GUTTER_WIDTH : 0)); abAppend(&ab, buf, (int)strlen(buf)); abAppend(&ab, SHOW_CURSOR, 6); diff --git a/src/split_screen.c b/src/split_screen.c index 5461b90..6a1d2c2 100644 --- a/src/split_screen.c +++ b/src/split_screen.c @@ -218,3 +218,18 @@ EditorPane *splitScreenGetActivePane(void) { if (E.layout.num_panes == 0) return NULL; return &E.layout.panes[E.layout.active_pane]; } + +void freeScreenLayout(ScreenLayout *layout) +{ + while (--E.layout.num_panes >= 0) + { + bFree(&E.layout.panes); + } + + bFree(&E.layout); +} + +void freePane(EditorPane *pane) +{ + bFree(pane); +} diff --git a/src/syntax_highlighter.c b/src/syntax_highlighter.c index ba178fe..52e062a 100644 --- a/src/syntax_highlighter.c +++ b/src/syntax_highlighter.c @@ -34,6 +34,30 @@ static int utf8_char_len(const char *s) return 1; // continuation byte or invalid — advance 1 to avoid infinite loop } +int is_word_char(const char *s) +{ + uint32_t cp = utf8Decode(&s); + + if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') || + (cp >= '0' && cp <= '9') || cp == '_' || cp == '#') + return 1; + + if (cp == 0xFFFD) return 0; + + if (cp >= 0x00C0 && cp <= 0x017F) return 1; + if (cp >= 0x0370 && cp <= 0x03FF) return 1; + if (cp >= 0x0400 && cp <= 0x04FF) return 1; + if (cp >= 0x0600 && cp <= 0x06FF) return 1; + if (cp >= 0x05D0 && cp <= 0x05EA) return 1; + if (cp >= 0x0900 && cp <= 0x097F) return 1; + if (cp >= 0x4E00 && cp <= 0x9FFF) return 1; + if ((cp >= 0x3040 && cp <= 0x309F) || + (cp >= 0x30A0 && cp <= 0x30FF)) return 1; + if (cp >= 0xAC00 && cp <= 0xD7A3) return 1; + + return 0; +} + // Copy one full UTF-8 character from src+i into dst+pos, advance both indices. static void copy_utf8_char(char *dst, int *dst_pos, const char *src, int *src_pos) { @@ -43,31 +67,6 @@ static void copy_utf8_char(char *dst, int *dst_pos, const char *src, int *src_po } // Check if character is alphanumeric or underscore -int is_word_char(const char *s) -{ - uint32_t cp = utf8Decode(&s); - - if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') || - (cp >= '0' && cp <= '9') || cp == '_' || cp == '#') - return 1; - - if (cp == 0xFFFD) return 0; - - if (cp >= 0x00C0 && cp <= 0x017F) return 1; - if (cp >= 0x0370 && cp <= 0x03FF) return 1; - if (cp >= 0x0400 && cp <= 0x04FF) return 1; - if (cp >= 0x0600 && cp <= 0x06FF) return 1; - if (cp >= 0x05D0 && cp <= 0x05EA) return 1; - if (cp >= 0x0900 && cp <= 0x097F) return 1; - if (cp >= 0x4E00 && cp <= 0x9FFF) return 1; - if ((cp >= 0x3040 && cp <= 0x309F) || - (cp >= 0x30A0 && cp <= 0x30FF)) return 1; - if (cp >= 0xAC00 && cp <= 0xD7A3) return 1; - if ((cp >= 0x0660 && cp <= 0x0669) || - (cp >= 0x06F0 && cp <= 0x06F9)) return 1; - - return 0; -} // Check if string is a keyword int is_keyword(const char *word) { diff --git a/src/terminal.c b/src/terminal.c index 5b42036..ec67b72 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -108,9 +108,41 @@ char *keyToString(int key) { int editorReadKey() { char c; - /* read first byte — may be start of UTF-8 or escape */ - while (read(STDIN_FILENO, &c, 1) != 1) - ; + 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]; + } + + 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 + } + + // 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') { @@ -219,19 +251,18 @@ int getWindowSize(int *rows, int *cols) { return -1; } return getCursorPosition(rows, cols); - } else { - *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, ...) { #ifdef APP_DEBUG va_list ap; - char message[256]; + char message[1024]; va_start(ap, fmt); - vsnprintf(message, 256, fmt, ap); + vsnprintf(message, 1024, fmt, ap); va_end(ap); fprintf(stderr, "%s\n", message); #endif diff --git a/src/utils.c b/src/utils.c index bb45c50..c946fb7 100644 --- a/src/utils.c +++ b/src/utils.c @@ -6,7 +6,6 @@ #include - int beluga_alloc_counter = 0; void * bAlloc(size_t size)