6 Commits

Author SHA1 Message Date
arthur 63bf73f1f2 No leaks for splash screen and text files 2026-06-05 15:33:19 +02:00
arthur 2ad55b9132 leaks fix 2026-06-05 11:04:01 +02:00
arthur 23f7701eb9 overflow errors 2026-06-03 14:09:01 +02:00
arthur 564292b03e linux lib compatibility 2026-06-03 09:20:00 +02:00
arthur cec92cacd1 working for starting screen 2026-06-02 19:06:51 +02:00
arthur 2738bc8042 popup and diagnose ui 2026-06-02 13:00:13 +02:00
27 changed files with 1387 additions and 1222 deletions
+1
View File
@@ -3,6 +3,7 @@
(define TAB-LENGTH 4) (define TAB-LENGTH 4)
(define QUIT-TIMES 1) (define QUIT-TIMES 1)
(define THEME "dark") (define THEME "dark")
(define LSP #t)
;; PACKAGES ;; PACKAGES
+2
View File
@@ -64,6 +64,8 @@ void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at, const cha
void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n); void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n);
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len); void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len);
void bufferFreeRow(row_t *row); void bufferFreeRow(row_t *row);
char* bufferToText(struct buffer_t* buf);
#endif #endif
+8 -11
View File
@@ -7,23 +7,20 @@
#include "data.h" #include "data.h"
int lspStart(LspClient* lsp, const char* project_root);
void createContextBuffer(const int x, const int y, const char * text); 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 // Document sync — call these from your buffer open/save/edit hooks
void lspDidOpen(LspClient *lsp, struct buffer_t *buf); void lspDidOpen(LspClient* lsp, struct buffer_t* buf);
void lspDidChange(LspClient *lsp, struct buffer_t *buf); void lspDidChange(LspClient* lsp, struct buffer_t* buf);
void lspDidClose(LspClient *lsp, struct buffer_t *buf); void lspDidClose(LspClient* lsp, struct buffer_t* buf);
// Requests // Requests
void lspRequestCompletion(LspClient *lsp, struct buffer_t *buf, void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf,
int line, int col, int line, int col,
int screen_x, int screen_y); int screen_x, int screen_y);
void lspRequestHover(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); void lspRequestDefinition(LspClient* lsp, struct buffer_t* buf, int line, int col);
#endif //BELUGA_COMPLETION_H #endif //BELUGA_COMPLETION_H
+64 -52
View File
@@ -6,7 +6,6 @@
#include <time.h> #include <time.h>
#include "define.h" #include "define.h"
#include "lisp.h" #include "lisp.h"
typedef struct lsp_client_t LspClient; typedef struct lsp_client_t LspClient;
@@ -17,23 +16,25 @@ typedef struct lsp_client_t LspClient;
* \param * \param
* */ * */
typedef struct row { typedef struct row
{
int size; /**< Size of the line */ int size; /**< Size of the line */
int cap; /**< Size of the render line */ int cap; /**< Size of the render line */
char *chars; /**< Characters of the line */ char* chars; /**< Characters of the line */
} row_t; } row_t;
typedef struct context_buffer_t typedef struct context_buffer_t
{ {
int editor_x, editor_y; int editor_x, editor_y;
int width, height; int width, height;
row_t *rows; row_t* rows;
} ContextBuffer; } ContextBuffer;
/** /**
* @brief Split modes for screen layout * @brief Split modes for screen layout
*/ */
typedef enum { typedef enum
{
SPLIT_NONE = 0, // Single buffer fullscreen SPLIT_NONE = 0, // Single buffer fullscreen
SPLIT_VERTICAL, // Left-right split SPLIT_VERTICAL, // Left-right split
SPLIT_HORIZONTAL // Top-bottom split SPLIT_HORIZONTAL // Top-bottom split
@@ -42,7 +43,8 @@ typedef enum {
/** /**
* @brief Represents an editor viewport/pane * @brief Represents an editor viewport/pane
*/ */
typedef struct { typedef struct
{
int buffer_id; // Which buffer this pane displays int buffer_id; // Which buffer this pane displays
int height; // Height of this pane int height; // Height of this pane
int width; // Width of this pane int width; // Width of this pane
@@ -56,44 +58,50 @@ typedef struct {
/** /**
* @brief Screen layout manager * @brief Screen layout manager
*/ */
typedef struct { typedef struct
{
SplitMode mode; SplitMode mode;
EditorPane *panes; EditorPane* panes;
int num_panes; int num_panes;
int active_pane; // Index of active pane int active_pane; // Index of active pane
} ScreenLayout; } ScreenLayout;
typedef struct theme { typedef struct theme
char *BACKGROUND_COLOR; {
char *COLOR_KEYWORD; char* BACKGROUND_COLOR;
char *COLOR_TYPE; char* COLOR_KEYWORD;
char *COLOR_STRING; char* COLOR_TYPE;
char *COLOR_COMMENT; char* COLOR_STRING;
char *COLOR_NUMBER; char* COLOR_COMMENT;
char *COLOR_DEFAULT; char* COLOR_NUMBER;
char* COLOR_DEFAULT;
} theme_t; } theme_t;
enum bufferStatus_e { enum bufferStatus_e
{
IDLE, IDLE,
READ_ONLY, READ_ONLY,
READ_AND_WRITE, READ_AND_WRITE,
}; };
struct const_t { struct const_t
{
int TAB_LENGTH; int TAB_LENGTH;
int QUIT_TIMES; int QUIT_TIMES;
char *THEME; char* THEME;
int LSP;
}; };
struct prefix_t { struct prefix_t
{
char prefix_name[64]; char prefix_name[64];
int prefix_id; int prefix_id;
}; };
struct keyBind_t { struct keyBind_t
char *key_sequence; {
char* key_sequence;
int prefix_id; int prefix_id;
Lisp command; Lisp command;
}; };
@@ -104,19 +112,23 @@ struct keyBind_t {
#define LSP_MAX_PENDING 64 #define LSP_MAX_PENDING 64
typedef enum { typedef enum
{
LSP_NOT_STARTED = 0, LSP_NOT_STARTED = 0,
LSP_INITIALIZING, LSP_INITIALIZING,
LSP_READY, LSP_READY,
LSP_SHUTTING_DOWN,
LSP_SHUTDOWN, LSP_SHUTDOWN,
} LspState; } LspState;
typedef struct { typedef struct
{
int id; int id;
void (*callback)(struct lsp_client_t *lsp, const char *json); void (*callback)(struct lsp_client_t* lsp, const char* json);
} LspPending; } LspPending;
typedef struct lsp_client_t { typedef struct lsp_client_t
{
// ── Process ─────────────────────────────────────────────────────────────── // ── Process ───────────────────────────────────────────────────────────────
pid_t pid; pid_t pid;
int write_fd; int write_fd;
@@ -124,11 +136,7 @@ typedef struct lsp_client_t {
int completion_just_arrived; int completion_just_arrived;
int completion_requested; int completion_requested;
int wake_pipe[2]; // [0] = read end (main loop), [1] = write end (reader thread) int wake_pipe[2]; // [0] = read end (main loop), [1] = write end (reader thread)
// ── State ───────────────────────────────────────────────────────────────── // ── State ─────────────────────────────────────────────────────────────────
LspState state; LspState state;
int next_id; int next_id;
@@ -147,13 +155,15 @@ typedef struct lsp_client_t {
int completion_cursor_y; // completion was requested int completion_cursor_y; // completion was requested
} LspClient; } LspClient;
typedef struct { typedef struct
{
char label[128]; // display text e.g. "printf" char label[128]; // display text e.g. "printf"
char detail[64]; // type/sig hint e.g. "int (const char *, ...)" char detail[64]; // type/sig hint e.g. "int (const char *, ...)"
int kind; // LSP CompletionItemKind (1=Text,2=Method,3=Function…) int kind; // LSP CompletionItemKind (1=Text,2=Method,3=Function…)
} CompletionItem; } CompletionItem;
typedef struct { typedef struct
{
CompletionItem items[COMPLETION_MAX_ITEMS]; CompletionItem items[COMPLETION_MAX_ITEMS];
int count; int count;
int selected; // currently highlighted row int selected; // currently highlighted row
@@ -164,7 +174,8 @@ typedef struct {
typedef enum { DIAG_ERROR = 1, DIAG_WARNING, DIAG_HINT } DiagSeverity; typedef enum { DIAG_ERROR = 1, DIAG_WARNING, DIAG_HINT } DiagSeverity;
typedef struct { typedef struct
{
int buffer_id; int buffer_id;
int line; // 0-based int line; // 0-based
int col_start; // 0-based int col_start; // 0-based
@@ -173,23 +184,26 @@ typedef struct {
char message[256]; char message[256];
} Diagnostic; } Diagnostic;
typedef struct { typedef struct
{
Diagnostic entries[DIAG_MAX]; Diagnostic entries[DIAG_MAX];
int count; int count;
} DiagnosticList; } DiagnosticList;
enum buffer_type { FILE_BUFF, TERMINAL_BUFF }; enum buffer_type { FILE_BUFF, TERMINAL_BUFF };
struct buffer_t { struct buffer_t
{
enum buffer_type type; enum buffer_type type;
int buffer_id; int buffer_id;
int b_lsp_open; int b_lsp_open;
int x, y; /**< Position in the file */ int x, y; /**< Position in the file */
row_t *row; row_t* row;
int numrows; int numrows;
int b_has_changed; int b_has_changed;
char *filename; char* filename;
char *path; char* path;
char * fullname;
enum bufferStatus_e state; enum bufferStatus_e state;
int dirty; /**< Has this buffer been modified since last save */ int dirty; /**< Has this buffer been modified since last save */
}; };
@@ -198,41 +212,38 @@ struct buffer_t {
* \struct editorConfig * \struct editorConfig
* \brief Containing our editor state. * \brief Containing our editor state.
*/ */
struct editorConfig { struct editorConfig
{
int cursor_x, cursor_y; /**< Cursor position */ int cursor_x, cursor_y; /**< Cursor position */
int screenrows; /**< Terminal height*/ int screenrows; /**< Terminal height*/
int screencols; /**< Terminal width*/ int screencols; /**< Terminal width*/
ScreenLayout layout; ScreenLayout layout;
row_t *rows; /**< Store all the rows printed */ LspClient* lsp_client;
ContextBuffer* context_buffers;
LspClient *lsp_client;
CompletionPopup lsp_completion; CompletionPopup lsp_completion;
DiagnosticList lsp_diagnostics; DiagnosticList lsp_diagnostics;
int dirty; int dirty;
char *status_msg; char* status_msg;
time_t status_msg_time; time_t status_msg_time;
struct termios orig_termios; /**< Terminal communication interface */ struct termios orig_termios; /**< Terminal communication interface */
struct const_t constantes; struct const_t constantes;
int quit_times_buffer; int quit_times_buffer;
char *init_file_path; char* init_file_path;
FILE *fd_init_file; FILE* fd_init_file;
Lisp env; Lisp env;
LispContext ctx; /** Lisp context */ LispContext ctx; /** Lisp context */
Lisp ctx_data; /** Lisp data context */ Lisp ctx_data; /** Lisp data context */
LispError ctx_error; /** Lisp ctx error */ LispError ctx_error; /** Lisp ctx error */
struct keyBind_t *key_binds; struct keyBind_t* key_binds;
int number_of_keybinds; int number_of_keybinds;
struct prefix_t *prefix; struct prefix_t* prefix;
int number_of_prefix; int number_of_prefix;
int prefix_state; int prefix_state;
@@ -247,8 +258,9 @@ struct editorConfig {
* \brief Contains text to add before writing to screen. * \brief Contains text to add before writing to screen.
* */ * */
struct abuf { struct abuf
char *b; /**< Text that will be printed */ {
char* b; /**< Text that will be printed */
int len; /**< Length of the text */ int len; /**< Length of the text */
}; };
+8
View File
@@ -12,6 +12,12 @@
#define ERASE_END_LINE "\x1b[K" #define ERASE_END_LINE "\x1b[K"
#define TAB "\t" #define TAB "\t"
#define SPACE "\x20" #define SPACE "\x20"
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
/* Uncomment to see debug logs on stderr */
#define APP_DEBUG #define APP_DEBUG
#define COMPLETION_MAX_ITEMS 16 #define COMPLETION_MAX_ITEMS 16
@@ -21,6 +27,7 @@
#define GUTTER_WIDTH 2 #define GUTTER_WIDTH 2
enum editorKey_e { enum editorKey_e {
BACKSPACE = 127, BACKSPACE = 127,
ARROW_LEFT = 1000, ARROW_LEFT = 1000,
@@ -32,6 +39,7 @@ enum editorKey_e {
END_LINE, END_LINE,
PAGE_UP, PAGE_UP,
PAGE_DOWN, PAGE_DOWN,
LSP_WAKE_KEY = 2000
}; };
#define ABUF_INIT {NULL, 0} #define ABUF_INIT {NULL, 0}
+3
View File
@@ -11,4 +11,7 @@ void initBuiltins();
void initEditor(); void initEditor();
void deInitEditor();
#endif // INIT_H_ #endif // INIT_H_
+2 -2
View File
@@ -1163,7 +1163,7 @@ static Lisp sch_string_ref(Lisp args, LispError* e, LispContext ctx)
return lisp_null(); 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) 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) 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) static Lisp sch_is_table(Lisp args, LispError* e, LispContext ctx)
+4
View File
@@ -59,4 +59,8 @@ ScreenLayout *splitScreenGetLayout(void);
*/ */
EditorPane *splitScreenGetActivePane(void); EditorPane *splitScreenGetActivePane(void);
void freePane(EditorPane *pane);
void freeScreenLayout(ScreenLayout *layout);
#endif #endif
+1
View File
@@ -11,6 +11,7 @@ int utf8Encode(uint32_t cp, char *buf);
int utf8Seqlen(unsigned char c); int utf8Seqlen(unsigned char c);
int codepointWidth(uint32_t codepoint); int codepointWidth(uint32_t codepoint);
uint32_t utf8Decode(const char** s); uint32_t utf8Decode(const char** s);
int is_word_char(const char *s);
#endif //BELUGA_UTF8_H #endif //BELUGA_UTF8_H
-16
View File
@@ -1,16 +0,0 @@
//
// Created by Giorgio on 28/05/2026.
//
#ifndef BELUGA_UTILS_H
#define BELUGA_UTILS_H
#include <sys/_types/_size_t.h>
extern int beluga_alloc_counter;
void * bAlloc(size_t size);
void * bRealloc(void * ptr, size_t size);
void * bFree(void * ptr);
#endif //BELUGA_UTILS_H
+21 -30
View File
@@ -9,7 +9,7 @@
#define _BSD_SOURCE #define _BSD_SOURCE
#define _GNU_SOURCE #define _GNU_SOURCE
#include "include/utils.h" #include <libgen.h>
#include "include/buffer.h" #include "include/buffer.h"
#include "include/split_screen.h" #include "include/split_screen.h"
@@ -24,51 +24,42 @@
#include "include/completion.h" #include "include/completion.h"
#include <signal.h> #include <signal.h>
#include "include/utils.h"
struct editorConfig E; struct editorConfig E;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Get HOME with NULL check
const char *home = getenv("HOME");
if (!home) {
fprintf(stderr, "Error: HOME environment variable not set\n");
return 1;
}
char * splash_screen = bAlloc(sizeof(char) * 512); // Allocate and build splash screen path safely
char *splash_screen;
signal(SIGPIPE, SIG_IGN); // don't die on broken pipe, just get EPIPE from write() if (asprintf(&splash_screen, "%s/.beluga/assets/beluga.txt", home) == -1) {
fprintf(stderr, "Error: Failed to allocate splash screen path\n");
return 1;
}
signal(SIGPIPE, SIG_IGN);
enableRawMode(); enableRawMode();
initEditor(); initEditor();
if (argc >= 2) {
EditorPane *active = splitScreenGetActivePane(); EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE); struct buffer_t *buf;
char project_root[512];
realpath(argv[1], project_root);
char *slash = strrchr(project_root, '/');
if (slash) *slash = '\0';
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); appDebug("splash : %s\n", splash_screen);
EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(splash_screen, READ_ONLY); 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);
if (argc >= 2) {
active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE);
buf = &E.buffers[active->buffer_id];
} }
free(splash_screen);
free(splash_screen); // Now guaranteed safe
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit"); editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
appDebug("allocation : %d\n", beluga_alloc_counter);
while (1) { while (1) {
editorRefreshScreen(); editorRefreshScreen();
editorProcessKeypress(); editorProcessKeypress();
+2 -3
View File
@@ -1,7 +1,7 @@
project('beluga', 'c', project('beluga', 'c',
version : '2.3', version : '2.3',
default_options : [ default_options : [
'c_std=none', 'c_std=c99',
] ]
) )
@@ -27,8 +27,7 @@ src_files = files(
'src/utf8.c', 'src/utf8.c',
'src/completion.c', 'src/completion.c',
'src/lsp_ui.c', 'src/lsp_ui.c',
'src/cJSON.c', 'src/cJSON.c'
'src/utils.c'
) )
# Executable # Executable
+6 -3
View File
@@ -1,8 +1,7 @@
#include "../include/append_buffer.h" #include "../include/append_buffer.h"
#include "include/utils.h"
void abAppend(struct abuf *ab, const char *s, int len) { void abAppend(struct abuf *ab, const char *s, int len) {
char *new = bRealloc(ab->b, ab->len + len); char *new = realloc(ab->b, ab->len + len);
if (new == NULL) { if (new == NULL) {
return; return;
@@ -12,4 +11,8 @@ void abAppend(struct abuf *ab, const char *s, int len) {
ab->len += len; ab->len += len;
} }
void abFree(struct abuf *ab) { bFree(ab->b); } void abFree(struct abuf *ab) {
free(ab->b);
ab->b = NULL;
ab->len = 0;
}
+87 -31
View File
@@ -14,8 +14,8 @@
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include "include/completion.h"
#include "include/input.h" #include "include/input.h"
#include "include/utils.h"
/** /**
@@ -61,10 +61,12 @@ struct buffer_t* bufferFindById(int buffer_id)
*/ */
int bufferCreate(const char* path, enum bufferStatus_e state) int bufferCreate(const char* path, enum bufferStatus_e state)
{ {
char *filename = basename((char *) path); appDebug("Creating new buffer");
char *path_cpy = strdup(path);
char* filename = basename((char*)path_cpy);
char fullname[PATH_MAX + 1];
// Check if file is already open // Check if file is already open
const int existing_id = bufferFindByFilename(path); const int existing_id = bufferFindByFilename(path);
path = dirname((char *) path);
if (existing_id != -1) if (existing_id != -1)
{ {
return bufferSwitch(existing_id); return bufferSwitch(existing_id);
@@ -80,17 +82,31 @@ int bufferCreate(const char* path, enum bufferStatus_e state)
struct buffer_t* new_buf = &E.buffers[E.number_of_buffer]; struct buffer_t* new_buf = &E.buffers[E.number_of_buffer];
new_buf->buffer_id = E.number_of_buffer; new_buf->buffer_id = E.number_of_buffer;
new_buf->filename = strdup(filename); new_buf->filename = strdup(filename);
if (!new_buf->filename)
{
return -1;
}
realpath(path, fullname);
new_buf->fullname = strdup(fullname);
new_buf->path = dirname(fullname);
new_buf->type = FILE_BUFF; new_buf->type = FILE_BUFF;
new_buf->state = state; new_buf->state = state;
new_buf->x = 0; new_buf->x = 0;
new_buf->y = 0; new_buf->y = 0;
new_buf->dirty = 0; // New file starts clean new_buf->dirty = 0; // New file starts clean
new_buf->path = strdup(path); new_buf->b_lsp_open = 0;
// Load file content using existing editorOpen // Load file content using existing editorOpen
editorOpen(new_buf); editorOpen(new_buf);
E.number_of_buffer++; E.number_of_buffer++;
if (new_buf->filename[strlen(new_buf->filename) - 1] == 'c')
{
if (E.lsp_client->state == LSP_SHUTDOWN)
lspStart(E.lsp_client, 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); editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id);
return new_buf->buffer_id; return new_buf->buffer_id;
@@ -164,7 +180,7 @@ int bufferClose(int buffer_id)
} }
// Free buffer resources // Free buffer resources
bFree(buf->filename); free(buf->filename);
buf->filename = NULL; buf->filename = NULL;
buf->buffer_id = -1; buf->buffer_id = -1;
@@ -290,7 +306,7 @@ void bufferFind(struct buffer_t* buf)
if (query == NULL) if (query == NULL)
return; return;
int i; int i;
for (i = buf->y+1; i < buf->numrows; i++) for (i = buf->y + 1; i < buf->numrows; i++)
{ {
row_t* row = &buf->row[i]; row_t* row = &buf->row[i];
char* match = strstr(row->chars, query); char* match = strstr(row->chars, query);
@@ -300,7 +316,7 @@ void bufferFind(struct buffer_t* buf)
break; break;
} }
} }
bFree(query); free(query);
} }
void bufferFindReverse(struct buffer_t* buf) void bufferFindReverse(struct buffer_t* buf)
@@ -323,27 +339,29 @@ void bufferFindReverse(struct buffer_t* buf)
break; break;
} }
} }
bFree(query); free(query);
} }
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) { void bufferInsertRow(struct buffer_t* buffer, int at, char* s, size_t len)
{
if (at < 0 || at > buffer->numrows) if (at < 0 || at > buffer->numrows)
return; return;
row_t *tmp = bRealloc(buffer->row, sizeof(row_t) * (buffer->numrows + 1)); row_t* tmp = realloc(buffer->row, sizeof(row_t) * (buffer->numrows + 1));
if (!tmp) if (!tmp)
return; return;
buffer->row = tmp; buffer->row = tmp;
/* Shift existing rows to make room at 'at' — no at++ */ /* Shift existing rows to make room at 'at' — no at++ */
if (at < buffer->numrows) { if (at < buffer->numrows)
{
memmove(&buffer->row[at + 1], &buffer->row[at], memmove(&buffer->row[at + 1], &buffer->row[at],
sizeof(row_t) * (buffer->numrows - at)); /* not -at+1 */ sizeof(row_t) * (buffer->numrows - at)); /* not -at+1 */
} }
buffer->row[at].size = (int) len; buffer->row[at].size = (int)len;
buffer->row[at].cap = (int) len + 1; buffer->row[at].cap = (int)len + 1;
buffer->row[at].chars = bAlloc(len + 1); buffer->row[at].chars = malloc(len + 1);
if (!buffer->row[at].chars) if (!buffer->row[at].chars)
return; return;
memcpy(buffer->row[at].chars, s, len); memcpy(buffer->row[at].chars, s, len);
@@ -353,24 +371,41 @@ void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) {
buffer->dirty++; buffer->dirty++;
} }
void bufferFreeRow(row_t *row) { bFree(row->chars); } void bufferFreeRow(row_t* row) { free(row->chars); }
/** /**
* \fn editorRowInsertChar(erow *row, int at, int c) * \fn editorRowInsertChar(erow *row, int at, int c)
* \param at Index of where we want to insert the char */ * \param at Index of where we want to insert the char */
void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at, void bufferRowInsertBytes(struct buffer_t* buffer, row_t* row, int at,
const char *src, int n) { const char* src, int n)
{
if (buffer->state == READ_ONLY) if (buffer->state == READ_ONLY)
return; return;
if (row->size + n + 1 > row->cap) { if (at < 0 || at > row->size)
row->cap = (row->size + n + 1) * 2; {
row->chars = bRealloc(row->chars, row->cap); return;
}
if (row->size + n + 1 > row->cap)
{
size_t new_cap = row->cap == 0 ? 128 : row->cap * 2;
while (new_cap < row->size + n + 1) {
new_cap *= 2;
}
char *new_chars = realloc(row->chars, new_cap);
if (!new_chars) {
return; // Allocation failed
}
row->chars = new_chars;
row->cap = (int) new_cap;
} }
memmove(row->chars + at + n, row->chars + at, row->size - at); memmove(row->chars + at + n, row->chars + at, row->size - at);
// Copy new bytes
memcpy(row->chars + at, src, n); memcpy(row->chars + at, src, n);
row->size += n; row->size += n;
row->chars = bRealloc(row->chars, row->size + 2);
row->chars[row->size] = '\0';
++buffer->dirty; ++buffer->dirty;
} }
@@ -379,7 +414,8 @@ void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at,
* \brief Delete the a char at the chosen position on the given row * \brief Delete the a char at the chosen position on the given row
* \param at Index of the char to delete * \param at Index of the char to delete
* \param row Row on operation is made */ * \param row Row on operation is made */
void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n) { void bufferRowDelByte(struct buffer_t* buffer, row_t* row, int at, int n)
{
if (buffer->state == READ_ONLY) if (buffer->state == READ_ONLY)
return; return;
if (at < 0 || at >= row->size) if (at < 0 || at >= row->size)
@@ -434,7 +470,7 @@ void bufferDelBytes(void)
int prev_char_count = editorRowCharCount(prev, prev->size); int prev_char_count = editorRowCharCount(prev, prev->size);
bufferRowInsertBytes(buf, prev, prev->size, r->chars, r->size); bufferRowInsertBytes(buf, prev, prev->size, r->chars, r->size);
bFree(r->chars); free(r->chars);
r->chars = NULL; r->chars = NULL;
memmove(&buf->row[buf->y], memmove(&buf->row[buf->y],
@@ -450,18 +486,21 @@ void bufferDelBytes(void)
} }
} }
void bufferInsertNewLine(void) { void bufferInsertNewLine(void)
{
appDebug("Inserting new line\n"); appDebug("Inserting new line\n");
EditorPane *active = splitScreenGetActivePane(); EditorPane* active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id); struct buffer_t* buf = bufferFindById(active->buffer_id);
appDebug("buf x %d\n", buf->x);
if (buf->y >= buf->numrows) { if (buf->y >= buf->numrows)
{
/* Cursor is past the last row: just append a blank line */ /* Cursor is past the last row: just append a blank line */
bufferInsertRow(buf, buf->numrows, "", 0); bufferInsertRow(buf, buf->numrows, "", 0);
} else { }
row_t *row = &buf->row[buf->y]; else
{
row_t* row = &buf->row[buf->y];
/* Insert the tail (from cursor to end) as a new row below */ /* Insert the tail (from cursor to end) as a new row below */
bufferInsertRow(buf, buf->y + 1, &row->chars[buf->x], row->size - buf->x); bufferInsertRow(buf, buf->y + 1, &row->chars[buf->x], row->size - buf->x);
@@ -479,3 +518,20 @@ void bufferInsertNewLine(void) {
buf->x = 0; buf->x = 0;
appDebug("Insert new line done\n"); appDebug("Insert new line done\n");
} }
char* bufferToText(struct buffer_t* buf)
{
int total = 0;
for (int i = 0; i < buf->numrows; i++)
total += buf->row[i].size + 1; // +1 for \n
char* text = malloc(total + 1);
char* p = text;
for (int i = 0; i < buf->numrows; i++)
{
memcpy(p, buf->row[i].chars, buf->row[i].size);
p += buf->row[i].size;
*p++ = '\n';
}
*p = '\0';
return text;
}
+58 -41
View File
@@ -21,7 +21,8 @@
#include <string.h> #include <string.h>
#include "include/completion.h" #include "include/completion.h"
#include "include/utils.h" #include "include/init.h"
/** /**
* @brief Finds a prefix configuration by name * @brief Finds a prefix configuration by name
@@ -68,13 +69,13 @@ Lisp mapKey(Lisp args, LispError* e, LispContext ctx)
// second argument // second argument
const Lisp func = lisp_car(args); const Lisp func = lisp_car(args);
memory_temp = (void*)bRealloc( memory_temp = realloc(
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t)); E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
E.key_binds = (struct keyBind_t*)memory_temp; E.key_binds = (struct keyBind_t*)memory_temp;
if (!E.key_binds) if (!E.key_binds)
editorQuit(args, e, ctx); editorQuit(args, e, ctx);
E.key_binds[E.number_of_keybinds - 1].key_sequence = E.key_binds[E.number_of_keybinds - 1].key_sequence =
(char*)bAlloc(50 * sizeof(char)); (char*)malloc(50 * sizeof(char));
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50); strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
@@ -136,27 +137,30 @@ Lisp moveCursor(Lisp args, LispError* e, LispContext ctx)
void bFree_structs(void) void bFree_structs(void)
{ {
int i, j; int i, j;
bFree(E.prefix); free(E.prefix);
for (i = 0; i < E.number_of_keybinds; ++i) for (i = 0; i < E.number_of_keybinds; ++i) {
{ free(E.key_binds[i].key_sequence);
bFree(E.key_binds[i].key_sequence); E.key_binds[i].key_sequence = NULL;
}
free(E.key_binds);
E.key_binds = NULL;
E.number_of_keybinds = 0;
// Similar fix for buffers
for (i = 0; i < E.number_of_buffer; ++i) {
struct buffer_t *b = &E.buffers[i];
for (j = 0; j < b->numrows; j++)
free(b->row[j].chars); // ← free each row's content
free(b->row); // ← then free the array
if (b->filename) free(b->filename);
// if (b->path) free(b->path);
if (b->fullname) free(b->fullname);
} }
bFree(E.key_binds);
// bFree layout // bFree layout
bFree(E.layout.panes); free(E.layout.panes);
free(E.status_msg);
// bFree buffers free(E.init_file_path);
for (i = 0; i < E.number_of_buffer; ++i)
{
bFree(E.buffers[i].filename);
for (j = 0; j < E.buffers[i].numrows; ++j)
{
bFree(E.buffers[i].row[j].chars);
}
bFree(E.buffers[i].row);
}
bFree(E.init_file_path);
fclose(E.fd_init_file); fclose(E.fd_init_file);
} }
@@ -182,13 +186,14 @@ Lisp editorQuit(Lisp args, LispError* e, LispContext ctx)
--E.quit_times_buffer; --E.quit_times_buffer;
return lisp_null(); return lisp_null();
} }
disableRawMode();
bFree_structs(); bFree_structs();
deInitEditor();
write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode();
lspShutdown(E.lsp_client); lspShutdown(E.lsp_client);
free(E.lsp_client);
lisp_shutdown(E.ctx); lisp_shutdown(E.ctx);
appDebug("Rest alloc %d\n", beluga_alloc_counter);
exit(0); exit(0);
} }
@@ -217,8 +222,8 @@ Lisp l_editorSplitScreenVertical(Lisp args, LispError* e, LispContext ctx)
Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx) Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx)
{ {
appDebug("[EDITOR SAVE]");
editorSave(); editorSave();
return lisp_null(); return lisp_null();
} }
@@ -371,14 +376,14 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx)
*/ */
Lisp editorOpenFile(Lisp args, LispError* e, LispContext ctx) Lisp editorOpenFile(Lisp args, LispError* e, LispContext ctx)
{ {
appDebug("[EDITOR OPEN FILE]\n");
char* filename = editorPrompt("Open : %s", getenv("PWD"), 1); char* filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename) if (filename)
{ {
// editorOpen(filename);
EditorPane* active = splitScreenGetActivePane(); EditorPane* active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(filename, READ_AND_WRITE); active->buffer_id = bufferCreate(filename, READ_AND_WRITE);
} }
bFree(filename); free(filename);
return lisp_null(); return lisp_null();
} }
@@ -415,18 +420,25 @@ Lisp addPackage(Lisp args, LispError* e, LispContext ctx)
{ {
const char* package_name = lisp_string(lisp_car(args)); const char* package_name = lisp_string(lisp_car(args));
appDebug("%s\n", package_name); appDebug("%s\n", package_name);
char* package_dir = (char*)calloc(256, sizeof(char)); const char *home = getenv("HOME");
FILE* fd_package = NULL; if (!home) {
strcat(package_dir, getenv("HOME")); return lisp_null();
strcat(package_dir, "/.beluga/packages/"); }
strcat(package_dir, package_name);
strcat(package_dir, "/init.lisp"); char *package_dir;
appDebug("%s\n", package_dir); if (asprintf(&package_dir, "%s/.beluga/packages/%s/init.lisp", home, package_name) == -1) {
fd_package = fopen(package_dir, "r"); return lisp_null();
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error, }
E.ctx);
FILE* fd_package = fopen(package_dir, "r");
if (!fd_package) {
free(package_dir);
return lisp_null();
}
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error, E.ctx);
fclose(fd_package); fclose(fd_package);
bFree(package_dir); free(package_dir);
return lisp_null(); return lisp_null();
} }
@@ -557,8 +569,7 @@ Lisp editorSetPrefix(Lisp args, LispError* e, LispContext ctx)
*/ */
Lisp editorPrefix(Lisp args, LispError* e, LispContext ctx) Lisp editorPrefix(Lisp args, LispError* e, LispContext ctx)
{ {
void * memory_temp; E.prefix = (struct prefix_t*)realloc(E.prefix, (++(E.number_of_prefix) + 1) *
E.prefix = (struct prefix_t*)bRealloc(E.prefix, (++(E.number_of_prefix) + 1) *
sizeof(struct prefix_t)); sizeof(struct prefix_t));
E.prefix[E.number_of_prefix].prefix_id = E.number_of_prefix; E.prefix[E.number_of_prefix].prefix_id = E.number_of_prefix;
strncpy(E.prefix[E.number_of_prefix].prefix_name, lisp_string(lisp_car(args)), strncpy(E.prefix[E.number_of_prefix].prefix_name, lisp_string(lisp_car(args)),
@@ -610,6 +621,9 @@ Lisp editorMoveEndBuffer(Lisp args, LispError* e, LispContext ctx)
Lisp editorAutoComplete(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"); // createContextBuffer(E.cursor_x - 2, E.cursor_y + 1, "hello");
appDebug("editor-auto-complete\n"); appDebug("editor-auto-complete\n");
EditorPane* active = splitScreenGetActivePane(); EditorPane* active = splitScreenGetActivePane();
@@ -618,8 +632,8 @@ Lisp editorAutoComplete(Lisp args, LispError* e, LispContext ctx)
lspRequestCompletion( lspRequestCompletion(
E.lsp_client, E.lsp_client,
buffer, buffer,
active->cursor_y + active->y_offset, // file line buffer->y, // file line
active->cursor_x + active->x_offset, // file col buffer->x, // file col
active->cursor_x + active->origin_x + GUTTER_WIDTH, // screen x active->cursor_x + active->origin_x + GUTTER_WIDTH, // screen x
active->cursor_y + active->origin_y // screen y active->cursor_y + active->origin_y // screen y
); );
@@ -628,6 +642,9 @@ Lisp editorAutoComplete(Lisp args, LispError* e, LispContext ctx)
Lisp lspDefinition(Lisp args, LispError* e, LispContext ctx) Lisp lspDefinition(Lisp args, LispError* e, LispContext ctx)
{ {
if (!E.constantes.LSP) {
return lisp_null();
}
(void)args; (void)args;
(void)e; (void)e;
(void)ctx; (void)ctx;
+1
View File
@@ -1906,6 +1906,7 @@ CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array)
return 0; return 0;
} }
child = array->child; child = array->child;
while(child != NULL) while(child != NULL)
+404 -396
View File
@@ -1,473 +1,321 @@
// /**
// Created by Giorgio on 25/05/2026. * @file completion.c
// * @brief LSP (Language Server Protocol) client implementation for Beluga
*/
#include "../include/completion.h" #include "../include/completion.h"
#include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include <sys/syslimits.h> #include <sys/wait.h>
#include <errno.h>
#include "include/append_buffer.h" #include "../include/append_buffer.h"
#include "include/cJSON.h" #include "../include/cJSON.h"
#include "include/data.h" #include "../include/data.h"
#include "include/lsp_ui.h" #include "../include/lsp_ui.h"
#include "include/terminal.h" #include "../include/split_screen.h"
#include "include/utils.h" #include "../include/terminal.h"
#include "../include/buffer.h"
void createContextBuffer(const int x, const int y, const char* text) // ─── Static Functions ──────────────────────────────────────────────────────
{
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) static void lsp_send(LspClient *lsp, const char *json)
{ {
int body_len = strlen(json); int body_len = strlen(json);
char header[1024]; char header[1024];
int header_len = snprintf(header, sizeof(header), int header_len = snprintf(header, sizeof(header),
"Content-Length: %d\r\n\r\n", body_len); "Content-Length: %d\r\n\r\n", body_len);
// Write header + body atomically in two writes, no dprintf mixing // Write header + body atomically
write(fd, header, header_len); write(lsp->read_fd, header, header_len);
write(fd, json, body_len); write(lsp->read_fd, json, body_len);
// Log to stderr for debugging // Log to stderr for debugging
fprintf(stderr, "[LSP →] Content-Length: %d | %s\n", body_len, json); appDebug("[LSP →] Content-Length: %d | %s\n", body_len, json);
fflush(stderr); fflush(stderr);
} }
static int lsp_uri_to_buffer_id(const char* uri) static char *lsp_recv(LspClient *lsp)
{
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;
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)
return E.buffers[i].buffer_id;
}
return -1;
}
static char* lsp_recv(int fd)
{ {
char header[1024]; char header[1024];
int content_length = 0; int content_length = 0;
while (1) while (1) {
{
int i = 0; int i = 0;
char c; char c;
int n; int n;
// Read until \n
while ((n = read(fd, &c, 1)) == 1 && c != '\n') // Read header line by line
while ((n = read(lsp->write_fd, &c, 1)) == 1 && c != '\n') {
if (i < (int)(sizeof(header) - 1))
header[i++] = c; header[i++] = c;
if (n <= 0) return NULL; }
header[i] = '\0'; header[i] = '\0';
// Strip \r explicitly
while (i > 0 && (header[i - 1] == '\r' || header[i - 1] == ' ')) // Skip empty lines
header[--i] = '\0'; if (i == 0)
if (i == 0) break; // blank line = end of headers break;
if (strncmp(header, "Content-Length: ", 16) == 0)
// Parse Content-Length header
if (strncmp(header, "Content-Length: ", 16) == 0) {
content_length = atoi(header + 16); content_length = atoi(header + 16);
} }
}
if (content_length == 0) return NULL; if (content_length <= 0)
return NULL;
char* body = bAlloc(content_length + 1); // Read body
int total = 0; char *body = malloc(content_length + 1);
while (total < content_length) if (!body)
{ return NULL;
int n = read(fd, body + total, content_length - total);
if (n <= 0) int total_read = 0;
{ while (total_read < content_length) {
bFree(body); int n = read(lsp->write_fd, body + total_read, content_length - total_read);
if (n <= 0) {
free(body);
return NULL; return NULL;
} }
total += n; total_read += n;
} }
body[content_length] = '\0'; body[content_length] = '\0';
appDebug("[LSP ←] %s\n", body);
return body; return body;
} }
static void lsp_dispatch(LspClient* lsp, const char* json) // ─── Public LSP Functions ──────────────────────────────────────────────────
/**
* @brief Starts the LSP server (clangd)
* @param client LSP client structure
* @param root_dir Project root directory
* @return 0 on success, -1 on failure
*/
int lspStart(LspClient *client, const char *root_dir)
{ {
if (!json) return; if (!client || !root_dir)
return -1;
cJSON* root = cJSON_Parse(json); // Create pipes for communication with LSP server
if (!root) int stdin_pipe[2];
{ int stdout_pipe[2];
fprintf(stderr, "[LSP ←] Failed to parse JSON: %.120s\n", json);
return; if (pipe(stdin_pipe) == -1 || pipe(stdout_pipe) == -1) {
return -1;
} }
cJSON* method = cJSON_GetObjectItem(root, "method"); // Create wake pipe for interrupting select()
cJSON* id = cJSON_GetObjectItem(root, "id"); if (pipe(client->wake_pipe) == -1) {
cJSON* result = cJSON_GetObjectItem(root, "result"); close(stdin_pipe[0]);
cJSON* error = cJSON_GetObjectItem(root, "error"); close(stdin_pipe[1]);
close(stdout_pipe[0]);
// ── Error response ──────────────────────────────────────────────────────── close(stdout_pipe[1]);
if (error) return -1;
{
cJSON* msg = cJSON_GetObjectItem(error, "message");
fprintf(stderr, "[LSP ←] ERROR: %s\n",
msg ? msg->valuestring : "(no message)");
cJSON_Delete(root);
return;
} }
// ── Notification (no id, has method) ───────────────────────────────────── // Fork LSP server process
if (method && !id) pid_t pid = fork();
{ if (pid == 0) {
const char* m = method->valuestring; // Child process - exec clangd
fprintf(stderr, "[LSP ←] NOTIF: %s\n", m); close(stdin_pipe[1]);
close(stdout_pipe[0]);
close(client->wake_pipe[0]);
close(client->wake_pipe[1]);
if (strcmp(m, "textDocument/publishDiagnostics") == 0) dup2(stdin_pipe[0], STDIN_FILENO);
{ dup2(stdout_pipe[1], STDOUT_FILENO);
// Find which buffer this diagnostic belongs to
cJSON* params = cJSON_GetObjectItem(root, "params");
cJSON* uri = cJSON_GetObjectItem(params, "uri");
int buf_id = lsp_uri_to_buffer_id(
uri ? uri->valuestring : "");
fprintf(stderr, "[LSP ←] Diagnostics for buffer %d\n", buf_id); close(stdin_pipe[0]);
close(stdout_pipe[1]);
pthread_mutex_lock(&lsp->lock); // Find clangd in PATH
lspParseDiagnostics(json, &E.lsp_diagnostics, buf_id); execlp("clangd", "clangd", NULL);
pthread_mutex_unlock(&lsp->lock);
write(lsp->wake_pipe[1], "d", 1);
}
else if (strcmp(m, "window/logMessage") == 0) // If execlp fails
{ exit(1);
cJSON* params = cJSON_GetObjectItem(root, "params"); } else if (pid > 0) {
cJSON* message = cJSON_GetObjectItem(params, "message"); // Parent process
fprintf(stderr, "[LSP ←] LOG: %s\n", close(stdin_pipe[0]);
message ? message->valuestring : ""); close(stdout_pipe[1]);
}
E.lsp_client->completion_just_arrived = 1;
client->read_fd = stdin_pipe[1];
client->write_fd = stdout_pipe[0];
client->pid = pid;
client->next_id = 1;
client->state = LSP_INITIALIZING;
cJSON_Delete(root); // Send initialize request
return; char root_uri[PATH_MAX + 8];
} snprintf(root_uri, sizeof(root_uri), "file://%s", root_dir);
// ── Response (has id + result) ──────────────────────────────────────────── cJSON *req = cJSON_CreateObject();
if (id && result) cJSON_AddStringToObject(req, "jsonrpc", "2.0");
{ cJSON_AddNumberToObject(req, "id", client->next_id++);
int response_id = id->valueint; cJSON_AddStringToObject(req, "method", "initialize");
fprintf(stderr, "[LSP ←] RESPONSE id=%d\n", response_id);
// initialize response → send initialized + mark ready cJSON *params = cJSON_CreateObject();
if (lsp->state == LSP_INITIALIZING) cJSON_AddNumberToObject(params, "processId", getpid());
{ cJSON_AddStringToObject(params, "rootUri", root_uri);
fprintf(stderr, "[LSP ←] Initialize OK, sending initialized\n");
lsp_send(lsp->write_fd,
"{\"jsonrpc\":\"2.0\",\"method\":\"initialized\",\"params\":{}}");
pthread_mutex_lock(&lsp->lock); // Capabilities
lsp->state = LSP_READY; cJSON *caps = cJSON_CreateObject();
pthread_cond_signal(&lsp->ready_cond); cJSON *td_caps = cJSON_CreateObject();
pthread_mutex_unlock(&lsp->lock); cJSON *comp_caps = cJSON_CreateObject();
E.lsp_client->completion_just_arrived = 1; cJSON *comp_item = cJSON_CreateObject();
cJSON_Delete(root); cJSON_AddBoolToObject(comp_item, "snippetSupport", 0);
return; 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);
// completion response → parse items and show popup char *msg = cJSON_PrintUnformatted(req);
cJSON* items = cJSON_GetObjectItem(result, "items"); lsp_send(client, msg);
if (items && cJSON_IsArray(items))
{
int count = cJSON_GetArraySize(items);
fprintf(stderr, "[LSP ←] Completion: %d items\n", count);
// Print each item to stderr for debugging free(msg);
cJSON* item; cJSON_Delete(req);
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");
fprintf(stderr, " [%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);
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;
fprintf(stderr, "[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;
fprintf(stderr, "[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;
}
fprintf(stderr, "[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)
{
int to_clangd[2], from_clangd[2];
pipe(to_clangd);
pipe(from_clangd);
pipe(lsp->wake_pipe);
lsp->pid = fork();
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(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);
// 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);
pthread_mutex_init(&lsp->lock, NULL);
pthread_cond_init(&lsp->ready_cond, NULL);
pthread_create(&lsp->reader_thread, NULL, lsp_reader, lsp);
lsp_send(lsp->write_fd, buf);
pthread_mutex_lock(&lsp->lock);
while (lsp->state != LSP_READY)
pthread_cond_wait(&lsp->ready_cond, &lsp->lock);
pthread_mutex_unlock(&lsp->lock);
return 0; return 0;
}
// ─── 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; return -1;
} }
/**
* @brief Sends textDocument/didOpen notification
* @param client LSP client
* @param buf Buffer to open
*/
void lspDidOpen(LspClient* lsp, struct buffer_t* buf) void lspDidOpen(LspClient* lsp, struct buffer_t* buf)
{ {
if (lsp->state != LSP_READY || buf->b_lsp_open) return; if (!lsp || !buf)
return;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8]; 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"))
lang = "cpp";
char* raw = buffer_to_text(buf);
// Let cJSON handle ALL escaping — don't touch the text yourself
cJSON* root = cJSON_CreateObject(); cJSON* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0"); cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didOpen"); cJSON_AddStringToObject(root, "method", "textDocument/didOpen");
cJSON_AddStringToObject(td, "uri", uri); cJSON* params = cJSON_CreateObject();
cJSON_AddStringToObject(td, "languageId", lang); cJSON* textDoc = cJSON_CreateObject();
cJSON_AddNumberToObject(td, "version", 1); cJSON_AddStringToObject(textDoc, "uri", uri);
cJSON_AddStringToObject(td, "text", raw); // cJSON escapes this cJSON_AddStringToObject(textDoc, "languageId", "c");
cJSON_AddNumberToObject(textDoc, "version", 1);
cJSON_AddItemToObject(params, "textDocument", td); cJSON_AddStringToObject(textDoc, "text", "");
cJSON_AddItemToObject(params, "textDocument", textDoc);
cJSON_AddItemToObject(root, "params", params); cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root); char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg); lsp_send(lsp, msg);
bFree(msg); free(msg);
bFree(raw);
cJSON_Delete(root); cJSON_Delete(root);
buf->b_lsp_open = 1;
} }
/**
* @brief Sends textDocument/didChange notification
* @param client LSP client
* @param buf Buffer that changed
*/
void lspDidChange(LspClient* lsp, struct buffer_t* buf) void lspDidChange(LspClient* lsp, struct buffer_t* buf)
{ {
if (lsp->state != LSP_READY || !buf->b_lsp_open) return; if (!lsp || !buf)
return;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8]; 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); char* raw = bufferToText(buf);
cJSON* root = cJSON_CreateObject(); cJSON* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON* changes = cJSON_CreateArray();
cJSON* change = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0"); cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didChange"); cJSON_AddStringToObject(root, "method", "textDocument/didChange");
cJSON_AddStringToObject(td, "uri", uri); cJSON* params = cJSON_CreateObject();
cJSON_AddNumberToObject(td, "version", buf->dirty);
cJSON_AddStringToObject(change, "text", raw); // full content sync cJSON* textDoc = cJSON_CreateObject();
cJSON_AddStringToObject(textDoc, "uri", uri);
cJSON_AddNumberToObject(textDoc, "version", buf->dirty);
cJSON_AddItemToObject(params, "textDocument", textDoc);
cJSON* changes = cJSON_CreateArray();
cJSON* change = cJSON_CreateObject();
cJSON_AddStringToObject(change, "text", raw);
cJSON_AddItemToArray(changes, change); cJSON_AddItemToArray(changes, change);
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(params, "contentChanges", changes); cJSON_AddItemToObject(params, "contentChanges", changes);
cJSON_AddItemToObject(root, "params", params); cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root); char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg); lsp_send(lsp, msg);
bFree(msg); free(msg);
bFree(raw);
cJSON_Delete(root); cJSON_Delete(root);
} }
/**
* @brief Sends textDocument/didClose notification
* @param client LSP client
* @param buf Buffer to close
*/
void lspDidClose(LspClient* lsp, struct buffer_t* buf) void lspDidClose(LspClient* lsp, struct buffer_t* buf)
{ {
char abs[PATH_MAX]; if (!lsp || !buf)
realpath(buf->filename, abs); return;
char uri[PATH_MAX + 8]; 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* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0"); cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didClose"); cJSON_AddStringToObject(root, "method", "textDocument/didClose");
cJSON_AddStringToObject(td, "uri", uri); // ← fixed
cJSON_AddItemToObject(params, "textDocument", td); cJSON* params = cJSON_CreateObject();
cJSON* textDoc = cJSON_CreateObject();
cJSON_AddStringToObject(textDoc, "uri", uri);
cJSON_AddItemToObject(params, "textDocument", textDoc);
cJSON_AddItemToObject(root, "params", params); cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root); char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg); lsp_send(lsp, msg);
bFree(msg);
free(msg);
cJSON_Delete(root); cJSON_Delete(root);
buf->b_lsp_open = 0;
} }
// ─── requests ──────────────────────────────────────────────────────────────── /**
* @brief Requests completion at cursor position
void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf, * @param client LSP client
* @param buf Buffer
* @param line Line number (0-based)
* @param col Column number (0-based)
*/
void lspRequestCompletion(LspClient *lsp, struct buffer_t *buf,
int line, int col, int line, int col,
int screen_x, int screen_y) int screen_x, int screen_y)
{ {
if (lsp->state != LSP_READY) return; 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; lsp->completion_cursor_y = screen_y;
appDebug("LSP REQUEST COMP"); appDebug("LSP REQUEST COMP");
char* msg; char* msg;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8]; 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* req = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject(); cJSON* params = cJSON_CreateObject();
@@ -488,68 +336,228 @@ void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf,
cJSON_AddItemToObject(req, "params", params); cJSON_AddItemToObject(req, "params", params);
msg = cJSON_PrintUnformatted(req); msg = cJSON_PrintUnformatted(req);
lsp_send(lsp->write_fd, msg); lsp_send(lsp, msg);
E.lsp_client->completion_requested = 1; E.lsp_client->completion_requested = 1;
cJSON_Delete(req); cJSON_Delete(req);
bFree(msg); free(msg);
} }
void lspRequestDefinition(LspClient* lsp, struct buffer_t* buf, int line, int col) /**
* @brief Requests goto definition
* @param client LSP client
* @param buf Buffer
* @param line Line number (0-based)
* @param col Column number (0-based)
*/
void lspRequestDefinition(LspClient *lsp, struct buffer_t *buf,
int line, int col)
{ {
if (lsp->state != LSP_READY) return; if (!lsp || lsp->state != LSP_READY || !buf)
char* msg; return;
asprintf(&msg,
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"textDocument/definition\","
"\"params\":{\"textDocument\":{\"uri\":\"file://%s\"},"
"\"position\":{\"line\":%d,\"character\":%d}}}",
lsp->next_id++, buf->filename, line, col);
lsp_send(lsp->write_fd, msg);
bFree(msg);
}
void lspShutdown(LspClient* lsp) char uri[PATH_MAX + 8];
{ snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
if (!lsp || lsp->state == LSP_SHUTDOWN) return;
lsp->state = LSP_SHUTDOWN;
// 1. Send shutdown request (clangd expects this before exit)
cJSON* req = cJSON_CreateObject(); cJSON* req = cJSON_CreateObject();
cJSON_AddStringToObject(req, "jsonrpc", "2.0"); cJSON_AddStringToObject(req, "jsonrpc", "2.0");
cJSON_AddNumberToObject(req, "id", lsp->next_id++); cJSON_AddNumberToObject(req, "id", lsp->next_id++);
cJSON_AddStringToObject(req, "method", "shutdown"); cJSON_AddStringToObject(req, "method", "textDocument/definition");
cJSON_AddNullToObject(req, "params");
char* msg = cJSON_PrintUnformatted(req);
lsp_send(lsp->write_fd, msg);
bFree(msg);
cJSON_Delete(req);
// 2. Wait briefly for the shutdown response cJSON* params = cJSON_CreateObject();
// (don't block forever — clangd has 2s to reply)
struct timeval tv = {.tv_sec = 2, .tv_usec = 0}; cJSON* textDoc = cJSON_CreateObject();
fd_set fds; cJSON_AddStringToObject(textDoc, "uri", uri);
FD_ZERO(&fds); cJSON_AddItemToObject(params, "textDocument", textDoc);
FD_SET(lsp->read_fd, &fds);
if (select(lsp->read_fd + 1, &fds, NULL, NULL, &tv) > 0) cJSON* position = cJSON_CreateObject();
{ cJSON_AddNumberToObject(position, "line", line);
char* resp = lsp_recv(lsp->read_fd); cJSON_AddNumberToObject(position, "character", col);
bFree(resp); cJSON_AddItemToObject(params, "position", position);
cJSON_AddItemToObject(req, "params", params);
char* msg = cJSON_PrintUnformatted(req);
lsp_send(lsp, msg);
free(msg);
cJSON_Delete(req);
}
/**
* @brief Reads and processes LSP messages (non-blocking)
* @param client LSP client
* @return 1 if message processed, 0 if no message, -1 on error
*/
int lspReadMessages(LspClient *client)
{
if (!client || client->state != LSP_READY)
return 0;
char *msg = lsp_recv(client);
if (!msg)
return 0;
// Parse response
cJSON *root = cJSON_Parse(msg);
if (!root) {
free(msg);
return 0;
} }
// 3. Send exit notification // Handle different message types
lsp_send(lsp->write_fd, cJSON *id = cJSON_GetObjectItem(root, "id");
"{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}"); cJSON *method = cJSON_GetObjectItem(root, "method");
cJSON *result = cJSON_GetObjectItem(root, "result");
// 4. Close pipes — this signals the reader thread to stop if (id) {
close(lsp->write_fd); // This is a response - check if it's shutdown response
close(lsp->read_fd); int msg_id = id->valueint;
if (msg_id == 0) { // Our shutdown request ID
client->state = LSP_SHUTDOWN;
}
}
// 5. Wait for reader thread to finish if (method) {
pthread_join(lsp->reader_thread, NULL); const char *method_str = method->valuestring;
pthread_mutex_destroy(&lsp->lock);
// 6. Reap the clangd process // Handle completion results
waitpid(lsp->pid, NULL, 0); if (strcmp(method_str, "textDocument/completion") == 0) {
// Process completion items
// ... (completion handling code)
}
}
bFree(lsp); cJSON_Delete(root);
free(msg);
return 1;
}
/**
* @brief Shuts down the LSP server gracefully with timeout
* @param client LSP client
*/
void lspShutdown(LspClient *client)
{
if (!client)
return;
// Send didClose for all open buffers first
for (int i = 0; i < E.number_of_buffer; i++) {
if (E.buffers[i].b_lsp_open) {
lspDidClose(client, &E.buffers[i]);
E.buffers[i].b_lsp_open = 0;
}
}
// Only attempt shutdown if server is ready
if (client->state == LSP_READY) {
// Send shutdown request
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddNumberToObject(root, "id", 1);
cJSON_AddStringToObject(root, "method", "shutdown");
char *msg = cJSON_PrintUnformatted(root);
lsp_send(client, msg);
free(msg);
cJSON_Delete(root);
client->state = LSP_SHUTTING_DOWN;
// Don't wait forever - timeout after 1 second
int timeout = 1000;
while (client->state == LSP_SHUTTING_DOWN && timeout > 0) {
usleep(10000);
timeout -= 10;
// Try to read any response
lspReadMessages(client);
}
// Send exit notification
root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "exit");
msg = cJSON_PrintUnformatted(root);
lsp_send(client, msg);
free(msg);
cJSON_Delete(root);
}
// Force state to shutdown
client->state = LSP_SHUTDOWN;
// ── Clean up pipes ──────────────────────────────────────────────────────
// Close wake pipe
if (client->wake_pipe[0] > 0) {
close(client->wake_pipe[0]);
client->wake_pipe[0] = -1;
}
if (client->wake_pipe[1] > 0) {
close(client->wake_pipe[1]);
client->wake_pipe[1] = -1;
}
// Close LSP communication pipes
if (client->write_fd > 0) {
close(client->write_fd);
client->pid = -1;
}
if (client->read_fd > 0) {
close(client->read_fd);
client->read_fd = -1;
}
// ── Kill LSP server process ──────────────────────────────────────────────
if (client->pid > 0) {
// First try graceful termination
kill(client->pid, SIGTERM);
// Wait briefly for process to exit
usleep(100000); // 100ms
int status;
pid_t result = waitpid(client->pid, &status, WNOHANG);
if (result == 0) {
// Process still running - force kill
kill(client->pid, SIGKILL);
waitpid(client->pid, NULL, 0);
}
client->pid = -1;
}
// ── Clean up mutex/cond ──────────────────────────────────────────────────
pthread_mutex_destroy(&client->lock);
pthread_cond_destroy(&client->ready_cond);
}
/**
* @brief Wakes up the select() loop from another thread
* @param client LSP client
*/
void lspWake(LspClient *client)
{
if (!client || client->wake_pipe[1] <= 0)
return;
char c = 1;
write(client->wake_pipe[1], &c, 1);
}
/**
* @brief Cleans up LSP client resources
* @param client LSP client
*/
void lspCleanup(LspClient *client)
{
if (!client)
return;
lspShutdown(client);
free(client);
} }
+17 -6
View File
@@ -8,7 +8,6 @@
#include "../include/split_screen.h" #include "../include/split_screen.h"
#include "../include/terminal.h" #include "../include/terminal.h"
#include "../include/utf8.h" #include "../include/utf8.h"
#include "include/utils.h"
extern struct editorConfig E; extern struct editorConfig E;
@@ -50,13 +49,13 @@ int editorMoveCursor(int key) {
if (row && buf->x < row->size) { if (row && buf->x < row->size) {
int len = utf8Seqlen(row->chars[buf->x]); int len = utf8Seqlen(row->chars[buf->x]);
buf->x += len; buf->x += len;
} else if (row && buf->y < buf->numrows) { } else if (row && buf->y < buf->numrows - 1) {
buf->y++; buf->y++;
buf->x = 0; buf->x = 0;
} }
break; break;
case ARROW_DOWN: case ARROW_DOWN:
if (buf->y < buf->numrows) { if (buf->y < buf->numrows - 1) {
buf->y++; buf->y++;
} }
@@ -74,6 +73,8 @@ int editorMoveCursor(int key) {
buf->x = buf->row[buf->y].size; buf->x = buf->row[buf->y].size;
} }
break; break;
default:
break;
} }
return 1; return 1;
} }
@@ -84,19 +85,29 @@ char *editorGetClipboard(void) {
size_t cap = 4096; size_t cap = 4096;
size_t len = 0; size_t len = 0;
char *buf = bAlloc(cap); char *buf = malloc(cap);
if (!buf) {
pclose(pipe);
return NULL;
}
int c; int c;
while ((c = fgetc(pipe)) != EOF) { while ((c = fgetc(pipe)) != EOF) {
if (len + 1 >= cap) { if (len + 1 >= cap) {
cap *= 2; cap *= 2;
buf = bRealloc(buf, cap); char *new_buf = realloc(buf, cap);
if (!new_buf) {
free(buf);
pclose(pipe);
return NULL;
}
buf = new_buf;
} }
buf[len++] = (char)c; buf[len++] = (char)c;
} }
buf[len] = '\0'; buf[len] = '\0';
pclose(pipe); pclose(pipe);
return buf; // caller must free return buf;
} }
void editorSetClipboard(const char *text, int len) { void editorSetClipboard(const char *text, int len) {
+18 -28
View File
@@ -17,13 +17,9 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include "../include/utils.h"
extern struct editorConfig E;
/** /**
* @brief Closes the current file and resets editor state * @brief Closes the current file and resets editor state
@@ -39,13 +35,13 @@ void editorCloseFile(void) {
active->x_offset = 0; active->x_offset = 0;
active->y_offset = 0; active->y_offset = 0;
for (int i = 0; i < buf->numrows; ++i) { for (int i = 0; i < buf->numrows; ++i) {
bFree(buf->row[i].chars); free(buf->row[i].chars);
} }
buf->numrows = 0; buf->numrows = 0;
bFree(buf->row); free(buf->row);
buf->row = NULL; buf->row = NULL;
buf->dirty = 0; buf->dirty = 0;
bFree(buf->filename); free(buf->filename);
buf->filename = NULL; buf->filename = NULL;
E.status_msg[0] = '\0'; E.status_msg[0] = '\0';
E.status_msg_time = 0; E.status_msg_time = 0;
@@ -56,7 +52,7 @@ void editorCloseFile(void) {
* @details Loads file content into editor rows, one line per row. If another * @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 * file is already open, it is closed first (without saving). File is opened in
* a+ (read/append) mode to allow both reading and modification. * 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 Updates global editor state E
* @note Calls die() on file open failure * @note Calls die() on file open failure
* @note Newline characters are stripped from loaded lines * @note Newline characters are stripped from loaded lines
@@ -64,19 +60,12 @@ void editorCloseFile(void) {
*/ */
void editorOpen(struct buffer_t* buffer) { void editorOpen(struct buffer_t* buffer) {
FILE *fp; FILE *fp;
char full_name[1024]; fp = fopen(buffer->fullname, "r");
strcpy(full_name, buffer->path);
strcat(full_name, "/");
strcat(full_name, buffer->filename);
strcat(full_name, "\0");
appDebug("full name : %s", full_name);
fp = fopen(full_name, "a+");
if (!fp) if (!fp)
die("fopen"); die("fopen");
char *line = NULL; char *line = NULL;
size_t line_cap; size_t line_cap = 0;
ssize_t line_len; ssize_t line_len;
rewind(fp); rewind(fp);
@@ -86,12 +75,13 @@ void editorOpen(struct buffer_t* buffer) {
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) { (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
--line_len; --line_len;
} }
appDebug("line %s\n", line);
bufferInsertRow(buffer, buffer->numrows, line, line_len); bufferInsertRow(buffer, buffer->numrows, line, line_len);
bFree(line); free(line);
line = NULL; line = NULL;
line_cap = 0; // Reset for next iteration
} }
bFree(line); free(line);
line = NULL;
fclose(fp); fclose(fp);
E.dirty = 0; E.dirty = 0;
} }
@@ -110,7 +100,7 @@ void editorSave() {
EditorPane *active = splitScreenGetActivePane(); EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id); struct buffer_t *buffer = bufferFindById(active->buffer_id);
int len; int len;
int fd; FILE *fd;
if (buffer->filename == NULL) { if (buffer->filename == NULL) {
buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1); buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (buffer->filename == NULL) { if (buffer->filename == NULL) {
@@ -118,21 +108,21 @@ void editorSave() {
return; return;
} }
} }
fd = open(buffer->filename, O_RDWR | O_CREAT, 0644); fd = fopen(buffer->fullname, "w");
if (fd != -1) { if (fd != NULL) {
for (int i = 0; i < buffer->numrows; ++i) 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) { if (fwrite(buffer->row[i].chars, 1, len, fd) != len) {
close(fd); fclose(fd);
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
return; return;
} }
write(fd, "\n", 1); fwrite("\n", 1, 1, fd);
} }
buffer->dirty = 0; buffer->dirty = 0;
close(fd); fclose(fd);
} }
editorSetStatusMessage("File saved"); editorSetStatusMessage("File saved");
} }
+31 -12
View File
@@ -11,7 +11,6 @@
#define LISP_IMPLEMENTATION #define LISP_IMPLEMENTATION
#include "../include/lisp.h" #include "../include/lisp.h"
#include "../include/lisp_lib.h" #include "../include/lisp_lib.h"
#include "include/utils.h"
struct editorConfig; struct editorConfig;
@@ -58,7 +57,7 @@ void initBuiltins() {
void initConfig() { void initConfig() {
E.ctx = lisp_init(); E.ctx = lisp_init();
E.ctx.p->err_port = fopen("log.err", "w"); E.ctx.p->err_port = fopen("lisp_log.err", "w");
E.env = lisp_env(E.ctx); E.env = lisp_env(E.ctx);
lisp_lib_load(E.ctx); lisp_lib_load(E.ctx);
// Init builtins lisp functions // Init builtins lisp functions
@@ -75,6 +74,8 @@ void initConfig() {
void initTheme() { void initTheme() {
E.constantes.THEME = (char *)lisp_string( E.constantes.THEME = (char *)lisp_string(
lisp_eval(lisp_read("THEME", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx)); 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) { if (strcmp(E.constantes.THEME, "dark") == 0) {
E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(40, 44, 52); E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(40, 44, 52);
E.theme.COLOR_KEYWORD = ANSI_FG_RGB(198, 120, 221); E.theme.COLOR_KEYWORD = ANSI_FG_RGB(198, 120, 221);
@@ -99,7 +100,7 @@ void initEditor() {
if (getWindowSize(&E.screenrows, &E.screencols) == -1) { if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
die("getWindowSize"); die("getWindowSize");
} }
appDebug("%d %d\n", E.screenrows, E.screencols); appDebug("%d %d", E.screenrows, E.screencols);
E.screenrows -= 2; E.screenrows -= 2;
@@ -116,14 +117,26 @@ void initEditor() {
active->width = E.screencols; active->width = E.screencols;
active->height = E.screenrows; active->height = E.screenrows;
E.init_file_path = (char *)calloc(256, sizeof(char)); const char *home = getenv("HOME");
strcat(E.init_file_path, getenv("HOME")); if (!home) {
strcat(E.init_file_path, "/.beluga/config/init.lisp"); die("HOME environment variable not set");
appDebug("%s\n", E.init_file_path); }
E.fd_init_file = fopen(E.init_file_path, "r");
// Status bar if (asprintf(&E.init_file_path, "%s/.beluga/config/init.lisp", home) == -1) {
E.status_msg = (char *)calloc(E.screencols, sizeof(char)); die("asprintf failed");
}
E.fd_init_file = fopen(E.init_file_path, "r");
if (!E.fd_init_file) {
// File might not exist, that's okay for some cases
// Handle accordingly
}
E.status_msg = calloc(E.screencols, sizeof(char));
if (!E.status_msg) {
die("malloc failed");
}
E.status_msg[0] = '\0';
E.status_msg[0] = '\0'; E.status_msg[0] = '\0';
E.status_msg_time = 0; E.status_msg_time = 0;
@@ -131,12 +144,13 @@ void initEditor() {
E.number_of_keybinds = 0; E.number_of_keybinds = 0;
E.number_of_prefix = 0; E.number_of_prefix = 0;
// General prefix is 0 (no prefix) // General prefix is 0 (no prefix)
E.prefix = (struct prefix_t *)bAlloc(sizeof(struct prefix_t)); E.prefix = (struct prefix_t *)malloc(sizeof(struct prefix_t));
E.prefix[0].prefix_id = 0; E.prefix[0].prefix_id = 0;
strncpy(E.prefix[0].prefix_name, "no-prefix", 64); strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
E.prefix_state = 0; E.prefix_state = 0;
E.lsp_client = (LspClient*)bAlloc(sizeof(LspClient)); E.lsp_client = (LspClient*)calloc(1,sizeof(LspClient));
E.lsp_client->state = LSP_SHUTDOWN;
initConfig(); initConfig();
initTheme(); initTheme();
@@ -154,3 +168,8 @@ void initEditor() {
E.quit_times_buffer = E.constantes.QUIT_TIMES; E.quit_times_buffer = E.constantes.QUIT_TIMES;
} }
void deInitEditor()
{
fclose(E.ctx.p->err_port);
}
+43 -31
View File
@@ -2,32 +2,26 @@
#include "../include/define.h" #include "../include/define.h"
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/output.h" #include "../include/output.h"
#include "include/data.h"
#include "include/buffer.h" #include "include/buffer.h"
#include "include/builtins.h"
#include "../include/completion.h"
#include "include/data.h" #include "include/data.h"
#include "include/split_screen.h" #include "include/split_screen.h"
#include "include/completion.h"
#include "include/lsp_ui.h"
#include <ctype.h> #include <ctype.h>
#include <sys/stat.h>
#include <dirent.h> #include <dirent.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h>
#include "include/terminal.h" #include "include/terminal.h"
#include "include/utf8.h" #include "include/utf8.h"
#include "include/utils.h"
extern struct editorConfig E;
/** /**
* @file input.c * @file input.c
* @brief Input handling module for the Beluga text editor * @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 +30,8 @@ extern struct editorConfig E;
* the first file or directory entry that matches the filename prefix. * the first file or directory entry that matches the filename prefix.
* Appends a trailing slash for directory entries. * Appends a trailing slash for directory entries.
* @param path The file path to complete (can be relative or absolute) * @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) * - path ends with '/' (already a directory)
* - no matching entries found * - no matching entries found
* - directory cannot be opened * - directory cannot be opened
@@ -63,7 +58,7 @@ const char *fileCompletion(const char *path) {
if (last_slash) { if (last_slash) {
dir_len = last_slash - path + 1; // length of dir_path dir_len = last_slash - path + 1; // length of dir_path
strncpy(directory, path, dir_len); 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); strncpy(predict, last_slash + 1, predict_len);
directory[dir_len] = '\0'; directory[dir_len] = '\0';
predict[predict_len] = '\0'; predict[predict_len] = '\0';
@@ -95,16 +90,16 @@ const char *fileCompletion(const char *path) {
// Cleanup when no more entries // Cleanup when no more entries
closedir(dir); closedir(dir);
dir = NULL; dir = NULL;
bFree(entry); free(entry);
appDebug("[FILE COMP] no entries\n"); appDebug("[FILE COMP] no entries\n");
return strdup(path); return strdup(path);
} }
/** /**
* @brief Displays an interactive prompt and returns user input * @brief Displays an interactive prompt and returns user input
* @details Allows the user to enter text in a prompt with optional path completion * @details Allows the user to enter text in a prompt with optional path
* via Tab key. Supports backspace, delete, and escape key handling. Dynamically * completion via Tab key. Supports backspace, delete, and escape key handling.
* allocates memory for the input buffer. * Dynamically allocates memory for the input buffer.
* @param prompt The prompt message format string (printf-style) * @param prompt The prompt message format string (printf-style)
* @param placeHolder Initial text to display in the input buffer * @param placeHolder Initial text to display in the input buffer
* @param bPathMode If non-zero, enables Tab key file path completion * @param bPathMode If non-zero, enables Tab key file path completion
@@ -117,7 +112,7 @@ const char *fileCompletion(const char *path) {
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) { char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
size_t buf_size = 256; size_t buf_size = 256;
appDebug("[FILE COMP] %s %d\n", placeHolder, strlen(placeHolder)); appDebug("[FILE COMP] %s %d\n", placeHolder, strlen(placeHolder));
char *buf = bAlloc(buf_size); char *buf = malloc(buf_size);
size_t buf_len = 0; size_t buf_len = 0;
int c = 0; int c = 0;
buf[0] = '\0'; buf[0] = '\0';
@@ -134,7 +129,7 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
} }
} else if (c == ESCAPE) { } else if (c == ESCAPE) {
editorSetStatusMessage(""); editorSetStatusMessage("");
bFree(buf); free(buf);
return NULL; return NULL;
} else if (c == '\r') { } else if (c == '\r') {
if (buf_len != 0) { if (buf_len != 0) {
@@ -154,26 +149,26 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
strcpy(path, buf); strcpy(path, buf);
} }
memset(buf, 0, 256); memset(buf, 0, 256);
buf_len = 0; char *buf_complete = (char *)fileCompletion(path);
char * buf_complete = (char *) fileCompletion(path);
strcpy(buf, buf_complete); strcpy(buf, buf_complete);
bFree(buf_complete); free(buf_complete);
buf_len = strlen(buf); buf_len = strlen(buf);
buf[buf_len] = '\0'; buf[buf_len] = '\0';
} else if (!iscntrl(c) && c < 256) { } else if (!iscntrl(c) && c < 256) {
if (buf_len == buf_size - 1) { if (buf_len == buf_size - 1) {
buf_size *= 2; buf_size *= 2;
buf = bRealloc(buf, buf_size); char *new_buf = realloc(buf, buf_size);
if (!new_buf) {
free(buf);
return NULL;
}
buf = new_buf;
} }
buf[buf_len++] = c;
buf[buf_len] = '\0';
} }
} }
} }
/** /**
* @brief Executes the command bound to a key sequence * @brief Executes the command bound to a key sequence
* @details Searches the keybinding table for a matching key sequence and * @details Searches the keybinding table for a matching key sequence and
@@ -210,12 +205,18 @@ int executeKeyBind(char *key_sequence) {
* and either executes the bound command or inserts the character. Resets * and either executes the bound command or inserts the character. Resets
* the quit buffer counter on successful key processing. * the quit buffer counter on successful key processing.
* @note Updates global editor state E * @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() { void editorProcessKeypress() {
int c = editorReadKey(); int c = editorReadKey();
char key_sequence[8]; char key_sequence[8] = {0};
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
if (E.constantes.LSP && buf->b_lsp_open) {
if (c == LSP_WAKE_KEY)
return;
if (E.lsp_client && E.lsp_completion.visible) { if (E.lsp_client && E.lsp_completion.visible) {
if (c == ARROW_UP || c == CTRL_KEY('p')) { if (c == ARROW_UP || c == CTRL_KEY('p')) {
if (E.lsp_completion.selected > 0) if (E.lsp_completion.selected > 0)
@@ -228,9 +229,9 @@ void editorProcessKeypress() {
return; return;
} }
if (c == '\r') { if (c == '\r') {
CompletionItem *item = &E.lsp_completion.items[E.lsp_completion.selected]; CompletionItem *item =
EditorPane *active = splitScreenGetActivePane(); &E.lsp_completion.items[E.lsp_completion.selected];
struct buffer_t *buf = bufferFindById(active->buffer_id);
// Find how many chars the user already typed by looking at the // Find how many chars the user already typed by looking at the
// current word (chars before cursor on the same line) // current word (chars before cursor on the same line)
@@ -261,6 +262,7 @@ void editorProcessKeypress() {
// Any other key: dismiss popup and fall through to normal handling // Any other key: dismiss popup and fall through to normal handling
E.lsp_completion.visible = 0; E.lsp_completion.visible = 0;
} }
}
if (executeKeyBind(keyToString(c))) { if (executeKeyBind(keyToString(c))) {
return; return;
@@ -268,5 +270,15 @@ void editorProcessKeypress() {
int seq_len = utf8Encode(c, key_sequence); int seq_len = utf8Encode(c, key_sequence);
appDebug("key seq : %s\n", key_sequence); appDebug("key seq : %s\n", key_sequence);
bufferInsertBytes(key_sequence, seq_len); bufferInsertBytes(key_sequence, seq_len);
if (buf->b_lsp_open && is_word_char(key_sequence)) {
if (E.lsp_client && E.lsp_client->state == LSP_READY) {
lspDidChange(E.lsp_client, buf);
E.lsp_client->completion_just_arrived = 0; // consume the flag
}
buf->b_has_changed = 0;
editorAutoComplete(lisp_null(), &E.ctx_error, E.ctx);
}
E.quit_times_buffer = E.constantes.QUIT_TIMES; E.quit_times_buffer = E.constantes.QUIT_TIMES;
} }
+21 -15
View File
@@ -5,12 +5,12 @@
#include "include/lsp_ui.h" #include "include/lsp_ui.h"
#include "include/data.h" #include "include/data.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
// We use cJSON — drop cJSON.c + cJSON.h into src/ and include/ // We use cJSON — drop cJSON.c + cJSON.h into src/ and include/
#include "include/cJSON.h" #include "include/cJSON.h"
#include "include/append_buffer.h" #include "include/append_buffer.h"
#include "include/terminal.h"
// ─── ANSI helpers ───────────────────────────────────────────────────────────── // ─── ANSI helpers ─────────────────────────────────────────────────────────────
@@ -73,7 +73,7 @@ static const char *kind_color(int k) {
// editorRefreshScreen() before the final write(). // editorRefreshScreen() before the final write().
void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup) { 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->visible, popup->count,
popup->origin_x, popup->origin_y); popup->origin_x, popup->origin_y);
if (!popup->visible || popup->count == 0) return; 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) if (is_sel) ab_sgr(ab, "7"); // reverse video (highlight)
else ab_sgr(ab, "0;37;40"); else ab_sgr(ab, "0;37;40");
abAppend(ab, " ", 2); abAppend(ab, "| ", 2);
// kind tag in color // kind tag in color
if (!is_sel) ab_sgr(ab, kind_color(it->kind)); if (!is_sel) ab_sgr(ab, kind_color(it->kind));
@@ -131,7 +131,7 @@ void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup) {
AB_RESET(ab); AB_RESET(ab);
if (is_sel) ab_sgr(ab, "7"); if (is_sel) ab_sgr(ab, "7");
else ab_sgr(ab, "37;40"); else ab_sgr(ab, "37;40");
abAppend(ab, " ", 2); abAppend(ab, " |", 2);
AB_RESET(ab); AB_RESET(ab);
} }
@@ -162,12 +162,8 @@ int lspUiHandleKey(CompletionPopup *popup, int key) {
if (!popup->visible) return 0; if (!popup->visible) return 0;
switch (key) { switch (key) {
case '\x1b': // ESC — dismiss case '\x1b':
popup->visible = 0; case '\r': // ESC — dismiss
return 1;
case '\r': // Enter — accept
// Caller should insert popup->items[popup->selected].label
popup->visible = 0; popup->visible = 0;
return 1; return 1;
@@ -248,14 +244,13 @@ void lspParseCompletion(const char *json, CompletionPopup *popup,
int screen_x, int screen_y) { int screen_x, int screen_y) {
popup->count = 0; popup->count = 0;
popup->selected = 0; popup->selected = 0;
popup->origin_x = screen_x; popup->origin_x = screen_x + 1;
popup->origin_y = screen_y + 1; // one row below cursor popup->origin_y = screen_y + 2; // one row below cursor
cJSON *root = cJSON_Parse(json); cJSON *root = cJSON_Parse(json);
if (!root) return; if (!root) return;
cJSON *result = cJSON_GetObjectItem(root, "result"); cJSON *result = cJSON_GetObjectItem(root, "result");
if (!result) { cJSON_Delete(root); return; } if (!result) { cJSON_Delete(root); return; }
// result can be a list or {isIncomplete, items:[…]} // result can be a list or {isIncomplete, items:[…]}
cJSON *items = cJSON_IsArray(result) cJSON *items = cJSON_IsArray(result)
? result ? result
@@ -276,7 +271,11 @@ void lspParseCompletion(const char *json, CompletionPopup *popup,
strncpy(ci->label, raw_label, 127); strncpy(ci->label, raw_label, 127);
strncpy(ci->detail, detail ? detail->valuestring : "", 63); 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); popup->visible = (popup->count > 0);
@@ -317,7 +316,14 @@ void lspParseDiagnostics(const char *json, DiagnosticList *diags,
diag->col_end = cJSON_GetObjectItem(end_pos, "character")->valueint; diag->col_end = cJSON_GetObjectItem(end_pos, "character")->valueint;
diag->severity = sev ? (DiagSeverity)sev->valueint : DIAG_ERROR; 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); cJSON_Delete(root);
+45 -68
View File
@@ -21,11 +21,6 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "include/completion.h"
#include "include/utils.h"
extern struct editorConfig E;
/** /**
* @brief Renders a single pane with its buffer content * @brief Renders a single pane with its buffer content
*/ */
@@ -74,7 +69,10 @@ static void editorDrawPane(struct abuf* ab, EditorPane* pane)
} }
else else
{ {
if (E.constantes.LSP) {
lspUiDrawGutter(ab, &E.lsp_diagnostics, pane->buffer_id, file_row); 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') if (buf->filename[strlen(buf->filename) - 1] == 'c' || buf->filename[strlen(buf->filename) - 1] == 'h')
{ {
@@ -84,7 +82,7 @@ static void editorDrawPane(struct abuf* ab, EditorPane* pane)
// Print only up to pane width // Print only up to pane width
abAppend(ab, highlighted, byte_len_to_print); abAppend(ab, highlighted, byte_len_to_print);
bFree(highlighted); free(highlighted);
} }
else else
{ {
@@ -304,12 +302,9 @@ void editorDrawStatusBar(struct abuf* ab)
abAppend(ab, render_status, render_len); abAppend(ab, render_status, render_len);
break; break;
} }
else
{
abAppend(ab, " ", 1); abAppend(ab, " ", 1);
++len; ++len;
} }
}
abAppend(ab, "\x1b[m", 3); // normal text mode abAppend(ab, "\x1b[m", 3); // normal text mode
abAppend(ab, "\r\n", 2); abAppend(ab, "\r\n", 2);
@@ -336,36 +331,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 * @brief Performs complete screen refresh and buffer synchronization
* @details Clears screen, redraws all visible content (rows, status bar, * @details Clears screen, redraws all visible content (rows, status bar,
@@ -388,50 +353,62 @@ void editorRefreshScreen()
(int)strlen(E.theme.BACKGROUND_COLOR)); (int)strlen(E.theme.BACKGROUND_COLOR));
editorScroll(); editorScroll();
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
editorDrawAllPanes(&ab); editorDrawAllPanes(&ab);
editorDrawStatusBar(&ab); if (E.constantes.LSP) {
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 ────────────────── // ── LSP: draw completion popup every frame while visible ──────────────────
fprintf(stderr, "[REFRESH] lsp_completion.visible=%d\n", appDebug("[REFRESH] lsp_completion.visible=%d\n",
E.lsp_completion.visible); E.lsp_completion.visible);
while (E.lsp_client->completion_requested && !E.lsp_client->completion_just_arrived) while (E.lsp_client->completion_requested && !E.lsp_client->completion_just_arrived);
;
// reset flags // reset flags
E.lsp_client->completion_just_arrived = 0; E.lsp_client->completion_just_arrived = 0;
E.lsp_client->completion_requested = 0; E.lsp_client->completion_requested = 0;
if (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 ──────────────────────── // ── LSP: diagnostic for current line in status bar ────────────────────────
const char *diag = lspUiDiagnosticAtCursor( const char* diag = lspUiDiagnosticAtCursor(
&E.lsp_diagnostics, &E.lsp_diagnostics,
active->buffer_id, active->buffer_id,
active->cursor_y + active->y_offset); buffer->y);
if (diag) if (diag) {
editorSetStatusMessage("● %s", 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);
if (E.constantes.LSP && (E.lsp_client && E.lsp_client->state == LSP_READY))
{
lspUiDrawCompletion(&ab, &E.lsp_completion);
}
// ── Position cursor (account for gutter width) ──────────────────────────── // ── Position cursor (account for gutter width) ────────────────────────────
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
active->cursor_y + active->origin_y + 1, 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, buf, (int)strlen(buf));
abAppend(&ab, SHOW_CURSOR, 6); abAppend(&ab, SHOW_CURSOR, 6);
+20 -4
View File
@@ -9,7 +9,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "include/utils.h"
extern struct editorConfig E; extern struct editorConfig E;
@@ -21,7 +20,12 @@ void splitScreenInit(void) {
E.layout.num_panes = 1; E.layout.num_panes = 1;
E.layout.active_pane = 0; E.layout.active_pane = 0;
E.layout.panes = bAlloc(sizeof(EditorPane) * 2); EditorPane *new_panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
if (!new_panes) {
editorSetStatusMessage("Error: realloc failed");
return;
}
E.layout.panes = new_panes;
// Initialize single fullscreen pane // Initialize single fullscreen pane
E.layout.panes[0].buffer_id = -1; // No buffer for now E.layout.panes[0].buffer_id = -1; // No buffer for now
@@ -51,7 +55,7 @@ int splitScreenVertical(int buffer_id_left, int buffer_id_right) {
} }
// bReallocate panes array // bReallocate panes array
E.layout.panes = bRealloc(E.layout.panes, sizeof(EditorPane) * 2); E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
E.layout.mode = SPLIT_VERTICAL; E.layout.mode = SPLIT_VERTICAL;
E.layout.num_panes = 2; E.layout.num_panes = 2;
E.layout.active_pane = 0; E.layout.active_pane = 0;
@@ -102,7 +106,7 @@ int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom) {
} }
// bReallocate panes array // bReallocate panes array
E.layout.panes = bRealloc(E.layout.panes, sizeof(EditorPane) * 2); E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
E.layout.mode = SPLIT_HORIZONTAL; E.layout.mode = SPLIT_HORIZONTAL;
E.layout.num_panes = 2; E.layout.num_panes = 2;
E.layout.active_pane = 0; E.layout.active_pane = 0;
@@ -218,3 +222,15 @@ EditorPane *splitScreenGetActivePane(void) {
if (E.layout.num_panes == 0) return NULL; if (E.layout.num_panes == 0) return NULL;
return &E.layout.panes[E.layout.active_pane]; return &E.layout.panes[E.layout.active_pane];
} }
void freeScreenLayout(ScreenLayout *layout) {
if (layout) {
free(layout->panes);
layout->panes = NULL;
}
}
void freePane(EditorPane *pane)
{
free(pane);
}
+31 -29
View File
@@ -5,7 +5,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "include/utils.h"
extern struct editorConfig E; extern struct editorConfig E;
@@ -34,15 +33,6 @@ static int utf8_char_len(const char *s)
return 1; // continuation byte or invalid — advance 1 to avoid infinite loop return 1; // continuation byte or invalid — advance 1 to avoid infinite loop
} }
// 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)
{
int len = utf8_char_len(&src[*src_pos]);
for (int b = 0; b < len; b++)
dst[(*dst_pos)++] = src[(*src_pos)++];
}
// Check if character is alphanumeric or underscore
int is_word_char(const char *s) int is_word_char(const char *s)
{ {
uint32_t cp = utf8Decode(&s); uint32_t cp = utf8Decode(&s);
@@ -63,12 +53,20 @@ int is_word_char(const char *s)
if ((cp >= 0x3040 && cp <= 0x309F) || if ((cp >= 0x3040 && cp <= 0x309F) ||
(cp >= 0x30A0 && cp <= 0x30FF)) return 1; (cp >= 0x30A0 && cp <= 0x30FF)) return 1;
if (cp >= 0xAC00 && cp <= 0xD7A3) return 1; if (cp >= 0xAC00 && cp <= 0xD7A3) return 1;
if ((cp >= 0x0660 && cp <= 0x0669) ||
(cp >= 0x06F0 && cp <= 0x06F9)) return 1;
return 0; 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)
{
int len = utf8_char_len(&src[*src_pos]);
for (int b = 0; b < len; b++)
dst[(*dst_pos)++] = src[(*src_pos)++];
}
// Check if character is alphanumeric or underscore
// Check if string is a keyword // Check if string is a keyword
int is_keyword(const char *word) { int is_keyword(const char *word) {
for (int i = 0; c_keywords[i] != NULL; i++) { for (int i = 0; c_keywords[i] != NULL; i++) {
@@ -112,9 +110,13 @@ int comment_section = 0;
char *highlight_line(const char *line, int *length) { char *highlight_line(const char *line, int *length) {
// Each byte can expand to at most (color_prefix + 4 bytes + color_reset). // Each byte can expand to at most (color_prefix + 4 bytes + color_reset).
// Allocate generously based on line length to avoid overflow. // Allocate generously based on line length to avoid overflow.
int line_len = strlen(line); int line_len = (int) strlen(line);
int buf_size = line_len * 32 + 256; int buf_size = line_len * 32 + 256;
char *result = bAlloc(buf_size); char *result = malloc(buf_size);
if (!result) {
*length = 0;
return NULL;
}
int result_pos = 0; int result_pos = 0;
int i = 0; int i = 0;
@@ -127,31 +129,31 @@ char *highlight_line(const char *line, int *length) {
} }
if (comment_section) { if (comment_section) {
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT); result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_COMMENT);
while (line[i] != '\0' && line[i] != '\n') { while (line[i] != '\0' && line[i] != '\n') {
if (line[i] == '*' && line[i + 1] == '/') { if (line[i] == '*' && line[i + 1] == '/') {
comment_section = 0; comment_section = 0;
} }
copy_utf8_char(result, &result_pos, line, &i); copy_utf8_char(result, &result_pos, line, &i);
} }
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue; continue;
} }
// Handle line comments // Handle line comments
if (line[i] == '/' && line[i + 1] == '/') { if (line[i] == '/' && line[i + 1] == '/') {
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT); result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_COMMENT);
while (line[i] != '\0' && line[i] != '\n') { while (line[i] != '\0' && line[i] != '\n') {
copy_utf8_char(result, &result_pos, line, &i); copy_utf8_char(result, &result_pos, line, &i);
} }
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue; continue;
} }
// Handle block comments // Handle block comments
if (line[i] == '/' && line[i + 1] == '*') { if (line[i] == '/' && line[i + 1] == '*') {
comment_section = 1; comment_section = 1;
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT); result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_COMMENT);
result[result_pos++] = line[i++]; result[result_pos++] = line[i++];
result[result_pos++] = line[i++]; result[result_pos++] = line[i++];
while (line[i] != '\0') { while (line[i] != '\0') {
@@ -163,13 +165,13 @@ char *highlight_line(const char *line, int *length) {
} }
copy_utf8_char(result, &result_pos, line, &i); copy_utf8_char(result, &result_pos, line, &i);
} }
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue; continue;
} }
// Handle strings // Handle strings
if (line[i] == '"') { if (line[i] == '"') {
result_pos += sprintf(&result[result_pos], "%s\"", E.theme.COLOR_STRING); result_pos += snprintf(&result[result_pos], 10, "%s\"", E.theme.COLOR_STRING);
i++; i++;
while (line[i] != '\0' && line[i] != '"') { while (line[i] != '\0' && line[i] != '"') {
if (line[i] == '\\') { if (line[i] == '\\') {
@@ -181,13 +183,13 @@ char *highlight_line(const char *line, int *length) {
} }
if (line[i] == '"') if (line[i] == '"')
result[result_pos++] = line[i++]; result[result_pos++] = line[i++];
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue; continue;
} }
// Handle character literals // Handle character literals
if (line[i] == '\'') { if (line[i] == '\'') {
result_pos += sprintf(&result[result_pos], "%s'", E.theme.COLOR_STRING); result_pos += snprintf(&result[result_pos], 10, "%s'", E.theme.COLOR_STRING);
i++; i++;
while (line[i] != '\0' && line[i] != '\'') { while (line[i] != '\0' && line[i] != '\'') {
if (line[i] == '\\') { if (line[i] == '\\') {
@@ -199,17 +201,17 @@ char *highlight_line(const char *line, int *length) {
} }
if (line[i] == '\'') if (line[i] == '\'')
result[result_pos++] = line[i++]; result[result_pos++] = line[i++];
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue; continue;
} }
// Handle numbers // Handle numbers
if (line[i] >= '0' && line[i] <= '9') { if (line[i] >= '0' && line[i] <= '9') {
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_NUMBER); result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_NUMBER);
while (is_word_char(&line[i]) || line[i] == '.') { while (is_word_char(&line[i]) || line[i] == '.') {
copy_utf8_char(result, &result_pos, line, &i); copy_utf8_char(result, &result_pos, line, &i);
} }
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue; continue;
} }
@@ -233,15 +235,15 @@ char *highlight_line(const char *line, int *length) {
else if (is_type(word)) else if (is_type(word))
type = TOKEN_TYPE; type = TOKEN_TYPE;
result_pos += sprintf(&result[result_pos], "%s%s%s", get_color(type), result_pos += snprintf(&result[result_pos], 100, "%s%s%s", get_color(type),
word, COLOR_RESET); word, COLOR_RESET);
continue; continue;
} }
// Handle operators and other characters (including non-ASCII multi-byte) // Handle operators and other characters (including non-ASCII multi-byte)
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_DEFAULT); result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_DEFAULT);
copy_utf8_char(result, &result_pos, line, &i); copy_utf8_char(result, &result_pos, line, &i);
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
} }
result[result_pos] = '\0'; result[result_pos] = '\0';
+128 -45
View File
@@ -10,9 +10,12 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include "include/utf8.h" #include "../include/buffer.h"
#include "../include/split_screen.h"
#include "../include/utf8.h"
void die(const char *s) { void die(const char* s)
{
write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
lisp_shutdown(E.ctx); lisp_shutdown(E.ctx);
@@ -20,14 +23,18 @@ void die(const char *s) {
exit(1); exit(1);
} }
void disableRawMode() { void disableRawMode()
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) { {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1)
{
die("tcsetattr"); die("tcsetattr");
} }
} }
void enableRawMode() { void enableRawMode()
if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) { {
if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1)
{
die("tcgetattr"); die("tcgetattr");
} }
@@ -39,24 +46,34 @@ void enableRawMode() {
raw.c_cc[VMIN] = 0; raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1; raw.c_cc[VTIME] = 1;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
{
die("tcgetattr"); die("tcgetattr");
} }
} }
#include <ctype.h> /* isprint */ #include <ctype.h> /* isprint */
char *keyToString(int key) { char* keyToString(int key)
{
static char key_str[32]; static char key_str[32];
if (key == '\r') { if (key == '\r')
{
strcpy(key_str, "ENTER"); strcpy(key_str, "ENTER");
} else if (key == 0x09) { }
else if (key == 0x09)
{
strcpy(key_str, "TAB"); strcpy(key_str, "TAB");
} else if (key >= 1 && key <= 26) { }
else if (key >= 1 && key <= 26)
{
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1); snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
} else { }
switch (key) { else
{
switch (key)
{
case ARROW_UP: case ARROW_UP:
strcpy(key_str, "ARROW-UP"); strcpy(key_str, "ARROW-UP");
break; break;
@@ -91,14 +108,19 @@ char *keyToString(int key) {
strcpy(key_str, "ESCAPE"); strcpy(key_str, "ESCAPE");
break; break;
default: default:
if (key > 127) { if (key > 127)
{
/* UTF-8 code point — re-encode into the buffer */ /* UTF-8 code point — re-encode into the buffer */
char buf[5] = {0}; char buf[5] = {0};
int n = utf8Encode((uint32_t)key, buf); int n = utf8Encode((uint32_t)key, buf);
snprintf(key_str, sizeof(key_str), "%.*s", n, buf); snprintf(key_str, sizeof(key_str), "%.*s", n, buf);
} else if (isprint(key)) { }
else if (isprint(key))
{
snprintf(key_str, sizeof(key_str), "%c", key); snprintf(key_str, sizeof(key_str), "%c", key);
} else { }
else
{
snprintf(key_str, sizeof(key_str), "KEY-%d", key); snprintf(key_str, sizeof(key_str), "KEY-%d", key);
} }
} }
@@ -106,15 +128,58 @@ char *keyToString(int key) {
return key_str; return key_str;
} }
int editorReadKey() { int editorReadKey()
{
char c; char c;
/* read first byte — may be start of UTF-8 or escape */ int nread;
while (read(STDIN_FILENO, &c, 1) != 1)
; 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;
}
}
appDebug("f : %hhu %ld\r\n", c, 0x200); appDebug("f : %hhu %ld\r\n", c, 0x200);
if (c == '\x1b') { if (c == '\x1b')
char seq[6]; {
char seq[6] = {0};
/* try to read escape sequence */ /* try to read escape sequence */
if (read(STDIN_FILENO, &seq[0], 1) != 1) if (read(STDIN_FILENO, &seq[0], 1) != 1)
return '\x1b'; return '\x1b';
@@ -122,12 +187,16 @@ int editorReadKey() {
return '\x1b'; return '\x1b';
appDebug("f2 : %s\r\n", seq); appDebug("f2 : %s\r\n", seq);
if (seq[0] == '[') { if (seq[0] == '[')
if (seq[1] >= '0' && seq[1] <= '9') { {
if (seq[1] >= '0' && seq[1] <= '9')
{
if (read(STDIN_FILENO, &seq[2], 1) != 1) if (read(STDIN_FILENO, &seq[2], 1) != 1)
return '\x1b'; return '\x1b';
if (seq[2] == '~') { if (seq[2] == '~')
switch (seq[1]) { {
switch (seq[1])
{
case '1': case '1':
return BEG_LINE; return BEG_LINE;
case '3': case '3':
@@ -144,8 +213,11 @@ int editorReadKey() {
return END_LINE; return END_LINE;
} }
} }
} else { }
switch (seq[1]) { else
{
switch (seq[1])
{
case 'A': case 'A':
return ARROW_UP; return ARROW_UP;
case 'B': case 'B':
@@ -166,7 +238,8 @@ int editorReadKey() {
/* multi-byte UTF-8: read remaining bytes */ /* multi-byte UTF-8: read remaining bytes */
int seqlen = utf8Seqlen((unsigned char)c); int seqlen = utf8Seqlen((unsigned char)c);
if (seqlen > 1) { if (seqlen > 1)
{
/* pack into a pseudo-codepoint just to pass bytes through; /* pack into a pseudo-codepoint just to pass bytes through;
we handle encoding/decoding at the row level */ we handle encoding/decoding at the row level */
char buf[4] = {c, 0, 0, 0}; char buf[4] = {c, 0, 0, 0};
@@ -174,7 +247,7 @@ int editorReadKey() {
if (read(STDIN_FILENO, &buf[i], 1) != 1) if (read(STDIN_FILENO, &buf[i], 1) != 1)
break; break;
/* decode and return as uint32, but we need int — use high range */ /* decode and return as uint32, but we need int — use high range */
const char *p = buf; const char* p = buf;
uint32_t cp = utf8Decode(&p); uint32_t cp = utf8Decode(&p);
return (int)cp; /* caller re-encodes when inserting */ return (int)cp; /* caller re-encodes when inserting */
} }
@@ -182,56 +255,66 @@ int editorReadKey() {
return (unsigned char)c; return (unsigned char)c;
} }
int getCursorPosition(int *rows, int *cols) { int getCursorPosition(int* rows, int* cols)
{
char buf[32]; char buf[32];
unsigned int i = 0; unsigned int i = 0;
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) { if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4)
{
return -1; return -1;
} }
while (i < sizeof(buf) - 1) { while (i < sizeof(buf) - 1)
if (read(STDIN_FILENO, &buf[i], 1) != 1) { {
if (read(STDIN_FILENO, &buf[i], 1) != 1)
{
break; break;
} }
if (buf[i] == 'R') { if (buf[i] == 'R')
{
break; break;
} }
++i; ++i;
} }
buf[i] = '\0'; buf[i] = '\0';
if (buf[0] != '\x1b' || buf[1] != '[') { if (buf[0] != '\x1b' || buf[1] != '[')
{
return -1; return -1;
} }
if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) { if (sscanf(&buf[2], "%d;%d", rows, cols) != 2)
{
return -1; return -1;
} }
return 0; return 0;
} }
int getWindowSize(int *rows, int *cols) { int getWindowSize(int* rows, int* cols)
{
struct winsize ws; struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0)
if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) { {
if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12)
{
return -1; return -1;
} }
return getCursorPosition(rows, cols); return getCursorPosition(rows, cols);
} else { }
*cols = ws.ws_col; *cols = ws.ws_col;
*rows = ws.ws_row; *rows = ws.ws_row;
return 0; return 0;
}
} }
void appDebug(const char *fmt, ...) { void appDebug(const char* fmt, ...)
{
#ifdef APP_DEBUG #ifdef APP_DEBUG
va_list ap; va_list ap;
char message[256]; char message[1024];
va_start(ap, fmt); va_start(ap, fmt);
vsnprintf(message, 256, fmt, ap); vsnprintf(message, 1024, fmt, ap);
va_end(ap); va_end(ap);
fprintf(stderr, "%s\n", message); fprintf(stderr, "%s\n", message);
#endif #endif
-38
View File
@@ -1,38 +0,0 @@
//
// Created by Giorgio on 28/05/2026.
//
#include "../include/utils.h"
#include <stdlib.h>
int beluga_alloc_counter = 0;
void * bAlloc(size_t size)
{
void * result = malloc(size);
if (!result)
return NULL;
beluga_alloc_counter++;
return result;
}
void * bRealloc(void * ptr, size_t size)
{
void * result = realloc(ptr, size);
if (!result)
return NULL;
beluga_alloc_counter++;
return result;
}
void * bFree(void * ptr)
{
if (ptr)
{
free(ptr);
beluga_alloc_counter--;
}
return NULL;
}