First completion level is working (LSP connected)

This commit is contained in:
2026-05-28 18:28:59 +02:00
parent 8eeef59a98
commit a8b2960eb4
27 changed files with 5167 additions and 335 deletions
+3 -2
View File
@@ -1,7 +1,8 @@
#include "../include/append_buffer.h"
#include "include/utils.h"
void abAppend(struct abuf *ab, const char *s, int len) {
char *new = realloc(ab->b, ab->len + len);
char *new = bRealloc(ab->b, ab->len + len);
if (new == NULL) {
return;
@@ -11,4 +12,4 @@ void abAppend(struct abuf *ab, const char *s, int len) {
ab->len += len;
}
void abFree(struct abuf *ab) { free(ab->b); }
void abFree(struct abuf *ab) { bFree(ab->b); }
+20 -16
View File
@@ -10,10 +10,12 @@
#include "include/split_screen.h"
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <string.h>
#include <sys/stat.h>
#include "include/input.h"
#include "include/utils.h"
/**
@@ -55,18 +57,18 @@ struct buffer_t* bufferFindById(int buffer_id)
* @brief Creates a new buffer for a file
* @details Allocates buffer slot, loads file content, saves buffer metadata.
* If file is already open, switches to existing buffer instead.
* @param filename Path to the file
* @return Buffer ID on success, -1 on failure
*/
int bufferCreate(const char* filename, enum bufferStatus_e state)
int bufferCreate(const char* path, enum bufferStatus_e state)
{
char *filename = basename((char *) path);
// Check if file is already open
const int existing_id = bufferFindByFilename(filename);
const int existing_id = bufferFindByFilename(path);
path = dirname((char *) path);
if (existing_id != -1)
{
return bufferSwitch(existing_id);
}
// Check if we have space for more buffers
if (E.number_of_buffer >= 64)
{
@@ -83,6 +85,7 @@ int bufferCreate(const char* filename, enum bufferStatus_e state)
new_buf->x = 0;
new_buf->y = 0;
new_buf->dirty = 0; // New file starts clean
new_buf->path = strdup(path);
// Load file content using existing editorOpen
editorOpen(new_buf);
@@ -161,7 +164,7 @@ int bufferClose(int buffer_id)
}
// Free buffer resources
free(buf->filename);
bFree(buf->filename);
buf->filename = NULL;
buf->buffer_id = -1;
@@ -297,7 +300,7 @@ void bufferFind(struct buffer_t* buf)
break;
}
}
free(query);
bFree(query);
}
void bufferFindReverse(struct buffer_t* buf)
@@ -320,14 +323,14 @@ void bufferFindReverse(struct buffer_t* buf)
break;
}
}
free(query);
bFree(query);
}
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) {
if (at < 0 || at > buffer->numrows)
return;
row_t *tmp = realloc(buffer->row, sizeof(row_t) * (buffer->numrows + 1));
row_t *tmp = bRealloc(buffer->row, sizeof(row_t) * (buffer->numrows + 1));
if (!tmp)
return;
buffer->row = tmp;
@@ -338,9 +341,9 @@ void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) {
sizeof(row_t) * (buffer->numrows - at)); /* not -at+1 */
}
buffer->row[at].size = len;
buffer->row[at].cap = len + 1;
buffer->row[at].chars = malloc(len + 1);
buffer->row[at].size = (int) len;
buffer->row[at].cap = (int) len + 1;
buffer->row[at].chars = bAlloc(len + 1);
if (!buffer->row[at].chars)
return;
memcpy(buffer->row[at].chars, s, len);
@@ -350,7 +353,7 @@ void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) {
buffer->dirty++;
}
void bufferFreeRow(row_t *row) { free(row->chars); }
void bufferFreeRow(row_t *row) { bFree(row->chars); }
/**
* \fn editorRowInsertChar(erow *row, int at, int c)
@@ -362,12 +365,12 @@ void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at,
return;
if (row->size + n + 1 > row->cap) {
row->cap = (row->size + n + 1) * 2;
row->chars = realloc(row->chars, row->cap);
row->chars = bRealloc(row->chars, row->cap);
}
memmove(row->chars + at + n, row->chars + at, row->size - at);
memcpy(row->chars + at, src, n);
row->size += n;
row->chars = realloc(row->chars, row->size + 2);
row->chars = bRealloc(row->chars, row->size + 2);
++buffer->dirty;
}
@@ -400,6 +403,7 @@ void bufferInsertBytes(const char* src, int n)
}
bufferRowInsertBytes(buf, &buf->row[buf->y], buf->x, src, n);
buf->x += n;
buf->b_has_changed = 1;
}
void bufferDelBytes(void)
@@ -430,7 +434,7 @@ void bufferDelBytes(void)
int prev_char_count = editorRowCharCount(prev, prev->size);
bufferRowInsertBytes(buf, prev, prev->size, r->chars, r->size);
free(r->chars);
bFree(r->chars);
r->chars = NULL;
memmove(&buf->row[buf->y],
@@ -462,7 +466,7 @@ void bufferInsertNewLine(void) {
/* 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);
/* Re-fetch: realloc inside bufferInsertRow may have moved the array */
/* Re-fetch: bRealloc inside bufferInsertRow may have moved the array */
row = &buf->row[buf->y];
/* Truncate the current row at the cursor */
+337 -250
View File
@@ -20,6 +20,9 @@
#include <stdlib.h>
#include <string.h>
#include "include/completion.h"
#include "include/utils.h"
/**
* @brief Finds a prefix configuration by name
* @details Searches the prefix array for a prefix matching the given name.
@@ -28,14 +31,17 @@
* @return Matching prefix_t structure, or E.prefix[0] if not found
* @note Updates global editor state E
*/
struct prefix_t find_prefix(const char prefix_name[64]) {
int i = E.number_of_prefix + 1;
while (i--) {
if (!strcmp(prefix_name, E.prefix[i].prefix_name)) {
return E.prefix[i];
struct prefix_t find_prefix(const char prefix_name[64])
{
int i = E.number_of_prefix + 1;
while (i--)
{
if (!strcmp(prefix_name, E.prefix[i].prefix_name))
{
return E.prefix[i];
}
}
}
return E.prefix[0];
return E.prefix[0];
}
/**
@@ -51,32 +57,37 @@ struct prefix_t find_prefix(const char prefix_name[64]) {
* @return lisp_null()
* @note Updates global editor state E (key_binds array)
*/
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
/*
* 3 arguments keybind command prefix
*/
const char *key_sequence = lisp_string(lisp_car(args));
args = lisp_cdr(args);
// second argument
const Lisp func = lisp_car(args);
Lisp mapKey(Lisp args, LispError* e, LispContext ctx)
{
/*
* 3 arguments keybind command prefix
*/
const char* key_sequence = lisp_string(lisp_car(args));
void* memory_temp;
args = lisp_cdr(args);
// second argument
const Lisp func = lisp_car(args);
E.key_binds = (struct keyBind_t *)realloc(
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
E.key_binds[E.number_of_keybinds - 1].key_sequence =
(char *)malloc(50 * sizeof(char));
memory_temp = (void*)bRealloc(
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
E.key_binds = (struct keyBind_t*)memory_temp;
if (!E.key_binds)
editorQuit(args, e, ctx);
E.key_binds[E.number_of_keybinds - 1].key_sequence =
(char*)bAlloc(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);
E.key_binds[E.number_of_keybinds - 1].command = func;
E.key_binds[E.number_of_keybinds - 1].command = func;
// Third argument
args = lisp_cdr(args);
const char *prefix_name = lisp_string(lisp_car(args));
struct prefix_t prefix = find_prefix(prefix_name);
// Third argument
args = lisp_cdr(args);
const char* prefix_name = lisp_string(lisp_car(args));
struct prefix_t prefix = find_prefix(prefix_name);
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
return lisp_null();
return lisp_null();
}
/**
@@ -91,54 +102,62 @@ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
* @note Updates global editor state E
* @see editorMoveCursor()
*/
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
const char *direction = lisp_string(lisp_car(args));
int is_in = 0;
switch (direction[0]) {
case 'u':
is_in = editorMoveCursor(ARROW_UP);
break;
case 'd':
is_in = editorMoveCursor(ARROW_DOWN);
break;
case 'r':
is_in = editorMoveCursor(ARROW_RIGHT);
break;
case 'l':
is_in = editorMoveCursor(ARROW_LEFT);
break;
}
appDebug("move lisp %d\n", is_in);
return lisp_make_bool(is_in);
Lisp moveCursor(Lisp args, LispError* e, LispContext ctx)
{
const char* direction = lisp_string(lisp_car(args));
int is_in = 0;
switch (direction[0])
{
case 'u':
is_in = editorMoveCursor(ARROW_UP);
break;
case 'd':
is_in = editorMoveCursor(ARROW_DOWN);
break;
case 'r':
is_in = editorMoveCursor(ARROW_RIGHT);
break;
case 'l':
is_in = editorMoveCursor(ARROW_LEFT);
break;
default:
break;
}
appDebug("move lisp %d\n", is_in);
return lisp_make_bool(is_in);
}
/**
* @brief Frees all dynamically allocated editor structures
* @brief bFrees all dynamically allocated editor structures
* @details Releases memory for prefix table, keybinds, filename, and all rows.
* Called during shutdown to prevent memory leaks.
* @note Updates global editor state E
*/
void free_structs(void) {
int i, j;
free(E.prefix);
for (i = 0; i < E.number_of_keybinds; ++i) {
free(E.key_binds[i].key_sequence);
}
free(E.key_binds);
// free layout
free(E.layout.panes);
// Free buffers
for (i = 0; i < E.number_of_buffer; ++i) {
free(E.buffers[i].filename);
for (j = 0; j < E.buffers[i].numrows; ++j) {
free(E.buffers[i].row[j].chars);
void bFree_structs(void)
{
int i, j;
bFree(E.prefix);
for (i = 0; i < E.number_of_keybinds; ++i)
{
bFree(E.key_binds[i].key_sequence);
}
free(E.buffers[i].row);
}
bFree(E.key_binds);
// bFree layout
bFree(E.layout.panes);
free(E.init_file_path);
fclose(E.fd_init_file);
// bFree buffers
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);
}
/**
@@ -152,23 +171,25 @@ void free_structs(void) {
* @return lisp_null() (never returns on successful exit)
* @note Calls exit(0) to terminate program
* @note Updates quit_times_buffer counter
* @see free_structs()
* @see bFree_structs()
*/
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
if (E.dirty && E.quit_times_buffer > 0) {
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
"another time to quit.");
--E.quit_times_buffer;
return lisp_null();
}
free_structs();
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode();
lisp_shutdown(E.ctx);
exit(0);
return lisp_null();
Lisp editorQuit(Lisp args, LispError* e, LispContext ctx)
{
if (E.dirty && E.quit_times_buffer > 0)
{
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
"another time to quit.");
--E.quit_times_buffer;
return lisp_null();
}
bFree_structs();
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode();
lspShutdown(E.lsp_client);
lisp_shutdown(E.ctx);
appDebug("Rest alloc %d\n", beluga_alloc_counter);
exit(0);
}
/**
@@ -181,20 +202,24 @@ Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
* @see editorSave()
*/
Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx) {
if (E.number_of_buffer >= 2) {
splitScreenVertical(E.buffers[0].buffer_id, E.buffers[1].buffer_id);
} else {
editorSetStatusMessage("Need at least 2 buffers open");
}
return lisp_null();
Lisp l_editorSplitScreenVertical(Lisp args, LispError* e, LispContext ctx)
{
if (E.number_of_buffer >= 2)
{
splitScreenVertical(E.buffers[0].buffer_id, E.buffers[1].buffer_id);
}
else
{
editorSetStatusMessage("Need at least 2 buffers open");
}
return lisp_null();
}
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx)
{
editorSave();
editorSave();
return lisp_null();
return lisp_null();
}
/**
@@ -206,11 +231,11 @@ Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @see editorInsertNewLine()
*/
Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx)
{
bufferInsertNewLine();
bufferInsertNewLine();
return lisp_null();
return lisp_null();
}
/**
@@ -222,12 +247,14 @@ Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @note Uses E.constantes.TAB_LENGTH for indentation width
*/
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
for (int i = 0; i < E.constantes.TAB_LENGTH; ++i) {
bufferInsertBytes(" ", 1);
}
Lisp l_editorInserTab(Lisp args, LispError* e, LispContext ctx)
{
for (int i = 0; i < E.constantes.TAB_LENGTH; ++i)
{
bufferInsertBytes(" ", 1);
}
return lisp_null();
return lisp_null();
}
/**
@@ -239,11 +266,12 @@ Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @note Updates global editor state E
*/
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
buf->x = 0;
return lisp_null();
Lisp moveCursorBeginLine(Lisp args, LispError* e, LispContext ctx)
{
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buf = bufferFindById(active->buffer_id);
buf->x = 0;
return lisp_null();
}
/**
@@ -255,11 +283,12 @@ Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @note Updates global editor state E
*/
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
buf->x = buf->row[buf->y].size;
return lisp_null();
Lisp moveCursorEndLine(Lisp args, LispError* e, LispContext ctx)
{
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buf = bufferFindById(active->buffer_id);
buf->x = buf->row[buf->y].size;
return lisp_null();
}
/**
@@ -271,9 +300,10 @@ Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @see editorDelChar()
*/
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
bufferDelBytes();
return lisp_null();
Lisp deletePreviousChar(Lisp args, LispError* e, LispContext ctx)
{
bufferDelBytes();
return lisp_null();
}
/**
@@ -287,14 +317,16 @@ Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
* @note Updates global editor state E
* @see editorMoveCursor()
*/
Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
EditorPane *active = splitScreenGetActivePane();
active->cursor_y = active->y_offset;
int times = E.screenrows;
while (--times) {
editorMoveCursor(ARROW_UP);
}
return lisp_null();
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx)
{
EditorPane* active = splitScreenGetActivePane();
active->cursor_y = active->y_offset;
int times = E.screenrows;
while (--times)
{
editorMoveCursor(ARROW_UP);
}
return lisp_null();
}
/**
@@ -308,19 +340,22 @@ Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
* @note Updates global editor state E
* @see editorMoveCursor()
*/
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
active->cursor_y = active->y_offset + E.screenrows - 1;
if (active->cursor_y > buffer->numrows) {
active->cursor_y = buffer->numrows;
}
int times = E.screenrows;
while (--times) {
editorMoveCursor(ARROW_DOWN);
}
Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx)
{
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
active->cursor_y = active->y_offset + E.screenrows - 1;
if (active->cursor_y > buffer->numrows)
{
active->cursor_y = buffer->numrows;
}
int times = E.screenrows;
while (--times)
{
editorMoveCursor(ARROW_DOWN);
}
return lisp_null();
return lisp_null();
}
/**
@@ -334,16 +369,18 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
* @see editorOpen()
* @see editorPrompt()
*/
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename) {
// editorOpen(filename);
EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(filename, READ_AND_WRITE);
}
free(filename);
Lisp editorOpenFile(Lisp args, LispError* e, LispContext ctx)
{
char* filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename)
{
// editorOpen(filename);
EditorPane* active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(filename, READ_AND_WRITE);
}
bFree(filename);
return lisp_null();
return lisp_null();
}
/**
@@ -356,10 +393,11 @@ Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @note Uses first character of the string argument
*/
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
const char *src = lisp_string(lisp_car(args));
bufferInsertBytes(src, strlen(src));
return lisp_null();
Lisp editorPrintC(Lisp args, LispError* e, LispContext ctx)
{
const char* src = lisp_string(lisp_car(args));
bufferInsertBytes(src, (int) strlen(src));
return lisp_null();
}
/**
@@ -373,46 +411,52 @@ Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
* @note Package files must be valid Lisp code
* @note Package directory defaults to ~/.beluga/packages/
*/
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
const char *package_name = lisp_string(lisp_car(args));
appDebug("%s\n", package_name);
char *package_dir = (char *)calloc(256, sizeof(char));
FILE *fd_package = NULL;
strcat(package_dir, getenv("HOME"));
strcat(package_dir, "/.beluga/packages/");
strcat(package_dir, package_name);
strcat(package_dir, "/init.lisp");
appDebug("%s\n", package_dir);
fd_package = fopen(package_dir, "r");
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx);
fclose(fd_package);
free(package_dir);
Lisp addPackage(Lisp args, LispError* e, LispContext ctx)
{
const char* package_name = lisp_string(lisp_car(args));
appDebug("%s\n", package_name);
char* package_dir = (char*)calloc(256, sizeof(char));
FILE* fd_package = NULL;
strcat(package_dir, getenv("HOME"));
strcat(package_dir, "/.beluga/packages/");
strcat(package_dir, package_name);
strcat(package_dir, "/init.lisp");
appDebug("%s\n", package_dir);
fd_package = fopen(package_dir, "r");
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx);
fclose(fd_package);
bFree(package_dir);
return lisp_null();
return lisp_null();
}
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) {
appDebug("switch buffer\n");
if (E.number_of_buffer > 0) {
EditorPane *active = splitScreenGetActivePane();
int next_idx = (active->buffer_id + 1) % E.number_of_buffer;
active->buffer_id = next_idx;
}
Lisp editorSwitchNextBuffer(Lisp args, LispError* e, LispContext ctx)
{
appDebug("switch buffer\n");
if (E.number_of_buffer > 0)
{
EditorPane* active = splitScreenGetActivePane();
int next_idx = (active->buffer_id + 1) % E.number_of_buffer;
active->buffer_id = next_idx;
}
return lisp_null();
return lisp_null();
}
Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx) {
splitScreenSwitchPane();
return lisp_null();
Lisp editorSwitchNextPane(Lisp args, LispError* e, LispContext ctx)
{
splitScreenSwitchPane();
return lisp_null();
}
Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx) {
if (E.layout.num_panes - 1) {
splitScreenUnify();
}
return lisp_null();
Lisp editorUnifiedPanes(Lisp args, LispError* e, LispContext ctx)
{
if (E.layout.num_panes - 1)
{
splitScreenUnify();
}
return lisp_null();
}
/**
@@ -424,12 +468,13 @@ Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @see editorFind()
*/
Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx) {
appDebug("LispFind\n");
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
bufferFind(buffer);
return lisp_null();
Lisp bufferFind_L(Lisp args, LispError* e, LispContext ctx)
{
appDebug("LispFind\n");
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
bufferFind(buffer);
return lisp_null();
}
/**
@@ -441,12 +486,13 @@ Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @see editorFind()
*/
Lisp bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx) {
appDebug("LispFind\n");
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
bufferFindReverse(buffer);
return lisp_null();
Lisp bufferFindReverse_L(Lisp args, LispError* e, LispContext ctx)
{
appDebug("LispFind\n");
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
bufferFindReverse(buffer);
return lisp_null();
}
/**
@@ -458,16 +504,20 @@ Lisp bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx) {
* @param ctx Lisp context
* @return Lisp character object representing the character at cursor
*/
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
Lisp returned_char;
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
if (buffer->row[buffer->y].chars[buffer->x] == 0) {
returned_char = lisp_make_char('a');
} else {
returned_char = lisp_make_char(buffer->row[buffer->y].chars[buffer->x]);
}
return returned_char;
Lisp editorReadChar_L(Lisp args, LispError* e, LispContext ctx)
{
Lisp returned_char;
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
if (buffer->row[buffer->y].chars[buffer->x] == 0)
{
returned_char = lisp_make_char('a');
}
else
{
returned_char = lisp_make_char(buffer->row[buffer->y].chars[buffer->x]);
}
return returned_char;
}
/**
@@ -481,17 +531,18 @@ Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
* @note Updates global editor state E (prefix_state)
* @see find_prefix()
*/
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
/*
* Set the prefix state of editor to the prefix in argument
*/
const char *prefix_name = lisp_string(lisp_car(args));
struct prefix_t prefix = find_prefix(prefix_name);
E.prefix_state = prefix.prefix_id;
editorSetStatusMessage("prefix %s", prefix.prefix_name);
appDebug("%s set\n", prefix_name);
Lisp editorSetPrefix(Lisp args, LispError* e, LispContext ctx)
{
/*
* Set the prefix state of editor to the prefix in argument
*/
const char* prefix_name = lisp_string(lisp_car(args));
struct prefix_t prefix = find_prefix(prefix_name);
E.prefix_state = prefix.prefix_id;
editorSetStatusMessage("prefix %s", prefix.prefix_name);
appDebug("%s set\n", prefix_name);
return lisp_null();
return lisp_null();
}
/**
@@ -504,57 +555,93 @@ Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @note Updates global editor state E (prefix array)
*/
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx) {
E.prefix = (struct prefix_t *)realloc(E.prefix, (++(E.number_of_prefix) + 1) *
sizeof(struct prefix_t));
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)),
64);
return lisp_null();
Lisp editorPrefix(Lisp args, LispError* e, LispContext ctx)
{
void * memory_temp;
E.prefix = (struct prefix_t*)bRealloc(E.prefix, (++(E.number_of_prefix) + 1) *
sizeof(struct prefix_t));
E.prefix[E.number_of_prefix].prefix_id = E.number_of_prefix;
strncpy(E.prefix[E.number_of_prefix].prefix_name, lisp_string(lisp_car(args)),
64);
return lisp_null();
}
Lisp editorPaste(Lisp args, LispError *e, LispContext ctx)
Lisp editorPaste(Lisp args, LispError* e, LispContext ctx)
{
const char *char_to_paste = editorGetClipboard();
appDebug("editor-paste, %s\n", char_to_paste);
bufferInsertBytes(char_to_paste, (int) strlen(char_to_paste));
return lisp_null();
const char* char_to_paste = editorGetClipboard();
appDebug("editor-paste, %s\n", char_to_paste);
bufferInsertBytes(char_to_paste, (int)strlen(char_to_paste));
return lisp_null();
}
Lisp editorCutEndLine(Lisp args, LispError *e, LispContext ctx)
Lisp editorCutEndLine(Lisp args, LispError* e, LispContext ctx)
{
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
int bytes_to_delete = buffer->row[buffer->y].size - buffer->x;
editorSetClipboard(&buffer->row[buffer->y].chars[buffer->x], bytes_to_delete);
buffer->x = buffer->row[buffer->y].size;
while (bytes_to_delete--)
{
bufferDelBytes();
}
return lisp_null();
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
int bytes_to_delete = editorRowCharCount(&buffer->row[buffer->y],
buffer->row[buffer->y].chars[buffer->row[buffer->y].size]) -
editorRowCharCount(&buffer->row[buffer->y], buffer->x);
editorSetClipboard(&buffer->row[buffer->y].chars[buffer->x], bytes_to_delete);
buffer->x = buffer->row[buffer->y].size;
while (bytes_to_delete--)
{
bufferDelBytes();
}
return lisp_null();
}
Lisp editorMoveBegBuffer(Lisp args, LispError *e, LispContext ctx)
Lisp editorMoveBegBuffer(Lisp args, LispError* e, LispContext ctx)
{
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
buffer->x = 0;
buffer->y = 0;
return lisp_null();
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
buffer->x = 0;
buffer->y = 0;
return lisp_null();
}
Lisp editorMoveEndBuffer(Lisp args, LispError *e, LispContext ctx)
Lisp editorMoveEndBuffer(Lisp args, LispError* e, LispContext ctx)
{
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
buffer->x = buffer->row[buffer->numrows-1].size;
buffer->y = buffer->numrows-1;
return lisp_null();
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
buffer->x = buffer->row[buffer->numrows - 1].size;
buffer->y = buffer->numrows - 1;
return lisp_null();
}
Lisp editorAutoComplete(Lisp args, LispError *e, LispContext ctx)
Lisp editorAutoComplete(Lisp args, LispError* e, LispContext ctx)
{
// createContextBuffer(E.cursor_x - 2, E.cursor_y + 1, "hello");
return lisp_null();
// createContextBuffer(E.cursor_x - 2, E.cursor_y + 1, "hello");
appDebug("editor-auto-complete\n");
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
E.lsp_completion.visible = 1;
lspRequestCompletion(
E.lsp_client,
buffer,
active->cursor_y + active->y_offset, // file line
active->cursor_x + active->x_offset, // file col
active->cursor_x + active->origin_x + GUTTER_WIDTH, // screen x
active->cursor_y + active->origin_y // screen y
);
return lisp_null();
}
Lisp lspDefinition(Lisp args, LispError* e, LispContext ctx)
{
(void)args;
(void)e;
(void)ctx;
if (!E.lsp_client || E.lsp_client->state != LSP_READY)
return lisp_null();
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buf = bufferFindById(active->buffer_id);
if (!buf) return lisp_null();
lspRequestDefinition(
E.lsp_client, buf,
active->cursor_y + active->y_offset,
active->cursor_x + active->x_offset
);
return lisp_null();
}
+3206
View File
File diff suppressed because it is too large Load Diff
+555
View File
@@ -0,0 +1,555 @@
//
// Created by Giorgio on 25/05/2026.
//
#include "../include/completion.h"
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/syslimits.h>
#include "include/append_buffer.h"
#include "include/cJSON.h"
#include "include/data.h"
#include "include/lsp_ui.h"
#include "include/terminal.h"
#include "include/utils.h"
void createContextBuffer(const int x, const int y, const char* text)
{
E.context_buffers = bAlloc(sizeof(ContextBuffer));
ContextBuffer* buffer = E.context_buffers;
buffer->editor_x = x;
buffer->editor_y = y;
buffer->height = 1;
buffer->rows = bAlloc(sizeof(struct row));
if (!buffer->rows) return;
buffer->rows[0].chars = strdup(text);
buffer->rows[0].size = strlen(text);
buffer->width = strlen(text);
}
static void lsp_send(int fd, const char* json)
{
int body_len = strlen(json);
char header[1024];
int header_len = snprintf(header, sizeof(header),
"Content-Length: %d\r\n\r\n", body_len);
// Write header + body atomically in two writes, no dprintf mixing
write(fd, header, header_len);
write(fd, json, body_len);
// Log to stderr for debugging
fprintf(stderr, "[LSP →] Content-Length: %d | %s\n", body_len, json);
fflush(stderr);
}
static int lsp_uri_to_buffer_id(const char* uri)
{
const char *path = uri;
if (strncmp(uri, "file://", 7) == 0)
path = uri + 7;
// path is now "/absolute/path" — realpath output matches this directly
for (int i = 0; i < E.number_of_buffer; i++) {
if (E.buffers[i].filename == NULL) continue;
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];
int content_length = 0;
while (1)
{
int i = 0;
char c;
int n;
// Read until \n
while ((n = read(fd, &c, 1)) == 1 && c != '\n')
header[i++] = c;
if (n <= 0) return NULL;
header[i] = '\0';
// Strip \r explicitly
while (i > 0 && (header[i - 1] == '\r' || header[i - 1] == ' '))
header[--i] = '\0';
if (i == 0) break; // blank line = end of headers
if (strncmp(header, "Content-Length: ", 16) == 0)
content_length = atoi(header + 16);
}
if (content_length == 0) return NULL;
char* body = bAlloc(content_length + 1);
int total = 0;
while (total < content_length)
{
int n = read(fd, body + total, content_length - total);
if (n <= 0)
{
bFree(body);
return NULL;
}
total += n;
}
body[content_length] = '\0';
return body;
}
static void lsp_dispatch(LspClient* lsp, const char* json)
{
if (!json) return;
cJSON* root = cJSON_Parse(json);
if (!root)
{
fprintf(stderr, "[LSP ←] Failed to parse JSON: %.120s\n", json);
return;
}
cJSON* method = cJSON_GetObjectItem(root, "method");
cJSON* id = cJSON_GetObjectItem(root, "id");
cJSON* result = cJSON_GetObjectItem(root, "result");
cJSON* error = cJSON_GetObjectItem(root, "error");
// ── Error response ────────────────────────────────────────────────────────
if (error)
{
cJSON* msg = cJSON_GetObjectItem(error, "message");
fprintf(stderr, "[LSP ←] ERROR: %s\n",
msg ? msg->valuestring : "(no message)");
cJSON_Delete(root);
return;
}
// ── Notification (no id, has method) ─────────────────────────────────────
if (method && !id)
{
const char* m = method->valuestring;
fprintf(stderr, "[LSP ←] NOTIF: %s\n", m);
if (strcmp(m, "textDocument/publishDiagnostics") == 0)
{
// Find which buffer this diagnostic belongs to
cJSON* params = cJSON_GetObjectItem(root, "params");
cJSON* uri = cJSON_GetObjectItem(params, "uri");
int buf_id = lsp_uri_to_buffer_id(
uri ? uri->valuestring : "");
fprintf(stderr, "[LSP ←] Diagnostics for buffer %d\n", buf_id);
pthread_mutex_lock(&lsp->lock);
lspParseDiagnostics(json, &E.lsp_diagnostics, buf_id);
pthread_mutex_unlock(&lsp->lock);
write(lsp->wake_pipe[1], "d", 1);
}
else if (strcmp(m, "window/logMessage") == 0)
{
cJSON* params = cJSON_GetObjectItem(root, "params");
cJSON* message = cJSON_GetObjectItem(params, "message");
fprintf(stderr, "[LSP ←] LOG: %s\n",
message ? message->valuestring : "");
}
E.lsp_client->completion_just_arrived = 1;
cJSON_Delete(root);
return;
}
// ── Response (has id + result) ────────────────────────────────────────────
if (id && result)
{
int response_id = id->valueint;
fprintf(stderr, "[LSP ←] RESPONSE id=%d\n", response_id);
// initialize response → send initialized + mark ready
if (lsp->state == LSP_INITIALIZING)
{
fprintf(stderr, "[LSP ←] Initialize OK, sending initialized\n");
lsp_send(lsp->write_fd,
"{\"jsonrpc\":\"2.0\",\"method\":\"initialized\",\"params\":{}}");
pthread_mutex_lock(&lsp->lock);
lsp->state = LSP_READY;
pthread_cond_signal(&lsp->ready_cond);
pthread_mutex_unlock(&lsp->lock);
E.lsp_client->completion_just_arrived = 1;
cJSON_Delete(root);
return;
}
// completion response → parse items and show popup
cJSON* items = cJSON_GetObjectItem(result, "items");
if (items && cJSON_IsArray(items))
{
int count = cJSON_GetArraySize(items);
fprintf(stderr, "[LSP ←] Completion: %d items\n", count);
// Print each item to stderr for debugging
cJSON* item;
int i = 0;
cJSON_ArrayForEach(item, items)
{
cJSON* label = cJSON_GetObjectItem(item, "label");
cJSON* detail = cJSON_GetObjectItem(item, "detail");
cJSON* kind = cJSON_GetObjectItem(item, "kind");
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;
}
// ─── document sync ───────────────────────────────────────────────────────────
// Build the full buffer text into a bAlloc'd string
static char* buffer_to_text(struct buffer_t* buf)
{
int total = 0;
for (int i = 0; i < buf->numrows; i++)
total += buf->row[i].size + 1; // +1 for \n
char* text = bAlloc(total + 1);
char* p = text;
for (int i = 0; i < buf->numrows; i++)
{
memcpy(p, buf->row[i].chars, buf->row[i].size);
p += buf->row[i].size;
*p++ = '\n';
}
*p = '\0';
return text;
}
void lspDidOpen(LspClient* lsp, struct buffer_t* buf)
{
if (lsp->state != LSP_READY || buf->b_lsp_open) return;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8];
snprintf(uri, sizeof(uri), "file://%s", abs);
const char* lang = "c";
if (strstr(buf->filename, ".cpp") || strstr(buf->filename, ".cc"))
lang = "cpp";
char* raw = buffer_to_text(buf);
// Let cJSON handle ALL escaping — don't touch the text yourself
cJSON* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didOpen");
cJSON_AddStringToObject(td, "uri", uri);
cJSON_AddStringToObject(td, "languageId", lang);
cJSON_AddNumberToObject(td, "version", 1);
cJSON_AddStringToObject(td, "text", raw); // cJSON escapes this
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg);
bFree(msg);
bFree(raw);
cJSON_Delete(root);
buf->b_lsp_open = 1;
}
void lspDidChange(LspClient* lsp, struct buffer_t* buf)
{
if (lsp->state != LSP_READY || !buf->b_lsp_open) return;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8];
snprintf(uri, sizeof(uri), "file://%s", abs);
char* raw = buffer_to_text(buf);
cJSON* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON* changes = cJSON_CreateArray();
cJSON* change = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didChange");
cJSON_AddStringToObject(td, "uri", uri);
cJSON_AddNumberToObject(td, "version", buf->dirty);
cJSON_AddStringToObject(change, "text", raw); // full content sync
cJSON_AddItemToArray(changes, change);
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(params, "contentChanges", changes);
cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg);
bFree(msg);
bFree(raw);
cJSON_Delete(root);
}
void lspDidClose(LspClient* lsp, struct buffer_t* buf)
{
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8];
snprintf(uri, sizeof(uri), "file://%s", abs);
cJSON* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didClose");
cJSON_AddStringToObject(td, "uri", uri); // ← fixed
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg);
bFree(msg);
cJSON_Delete(root);
buf->b_lsp_open = 0;
}
// ─── requests ────────────────────────────────────────────────────────────────
void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf,
int line, int col,
int screen_x, int screen_y)
{
if (lsp->state != LSP_READY) return;
lsp->completion_cursor_x = screen_x; // ← add
lsp->completion_cursor_y = screen_y;
appDebug("LSP REQUEST COMP");
char* msg;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8];
snprintf(uri, sizeof(uri), "file://%s", abs);
cJSON* req = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON* position = cJSON_CreateObject();
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
cJSON_AddStringToObject(req, "method", "textDocument/completion");
cJSON_AddStringToObject(td, "uri", uri);
cJSON_AddNumberToObject(position, "line", line);
cJSON_AddNumberToObject(position, "character", col);
cJSON_AddItemToObject(params, "position", position);
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(req, "params", params);
msg = cJSON_PrintUnformatted(req);
lsp_send(lsp->write_fd, msg);
E.lsp_client->completion_requested = 1;
cJSON_Delete(req);
bFree(msg);
}
void lspRequestDefinition(LspClient* lsp, struct buffer_t* buf, int line, int col)
{
if (lsp->state != LSP_READY) return;
char* msg;
asprintf(&msg,
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"textDocument/definition\","
"\"params\":{\"textDocument\":{\"uri\":\"file://%s\"},"
"\"position\":{\"line\":%d,\"character\":%d}}}",
lsp->next_id++, buf->filename, line, col);
lsp_send(lsp->write_fd, msg);
bFree(msg);
}
void lspShutdown(LspClient* lsp)
{
if (!lsp || lsp->state == LSP_SHUTDOWN) return;
lsp->state = LSP_SHUTDOWN;
// 1. Send shutdown request (clangd expects this before exit)
cJSON* req = cJSON_CreateObject();
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
cJSON_AddStringToObject(req, "method", "shutdown");
cJSON_AddNullToObject(req, "params");
char* msg = cJSON_PrintUnformatted(req);
lsp_send(lsp->write_fd, msg);
bFree(msg);
cJSON_Delete(req);
// 2. Wait briefly for the shutdown response
// (don't block forever — clangd has 2s to reply)
struct timeval tv = {.tv_sec = 2, .tv_usec = 0};
fd_set fds;
FD_ZERO(&fds);
FD_SET(lsp->read_fd, &fds);
if (select(lsp->read_fd + 1, &fds, NULL, NULL, &tv) > 0)
{
char* resp = lsp_recv(lsp->read_fd);
bFree(resp);
}
// 3. Send exit notification
lsp_send(lsp->write_fd,
"{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}");
// 4. Close pipes — this signals the reader thread to stop
close(lsp->write_fd);
close(lsp->read_fd);
// 5. Wait for reader thread to finish
pthread_join(lsp->reader_thread, NULL);
pthread_mutex_destroy(&lsp->lock);
// 6. Reap the clangd process
waitpid(lsp->pid, NULL, 0);
bFree(lsp);
}
+3 -2
View File
@@ -8,6 +8,7 @@
#include "../include/split_screen.h"
#include "../include/terminal.h"
#include "../include/utf8.h"
#include "include/utils.h"
extern struct editorConfig E;
@@ -83,13 +84,13 @@ char *editorGetClipboard(void) {
size_t cap = 4096;
size_t len = 0;
char *buf = malloc(cap);
char *buf = bAlloc(cap);
int c;
while ((c = fgetc(pipe)) != EOF) {
if (len + 1 >= cap) {
cap *= 2;
buf = realloc(buf, cap);
buf = bRealloc(buf, cap);
}
buf[len++] = (char)c;
}
+14 -6
View File
@@ -21,6 +21,8 @@
#include <unistd.h>
#include <errno.h>
#include "../include/utils.h"
extern struct editorConfig E;
/**
@@ -37,13 +39,13 @@ void editorCloseFile(void) {
active->x_offset = 0;
active->y_offset = 0;
for (int i = 0; i < buf->numrows; ++i) {
free(buf->row[i].chars);
bFree(buf->row[i].chars);
}
buf->numrows = 0;
free(buf->row);
bFree(buf->row);
buf->row = NULL;
buf->dirty = 0;
free(buf->filename);
bFree(buf->filename);
buf->filename = NULL;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
@@ -62,8 +64,14 @@ void editorCloseFile(void) {
*/
void editorOpen(struct buffer_t* buffer) {
FILE *fp;
char full_name[1024];
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(buffer->filename, "a+");
fp = fopen(full_name, "a+");
if (!fp)
die("fopen");
@@ -80,10 +88,10 @@ void editorOpen(struct buffer_t* buffer) {
}
appDebug("line %s\n", line);
bufferInsertRow(buffer, buffer->numrows, line, line_len);
free(line);
bFree(line);
line = NULL;
}
free(line);
bFree(line);
fclose(fp);
E.dirty = 0;
}
+6 -2
View File
@@ -11,6 +11,7 @@
#define LISP_IMPLEMENTATION
#include "../include/lisp.h"
#include "../include/lisp_lib.h"
#include "include/utils.h"
struct editorConfig;
@@ -50,6 +51,8 @@ void initBuiltins() {
registerBuiltin("editor-move-cursor-beg-buffer", editorMoveBegBuffer);
registerBuiltin("editor-move-cursor-end-buffer", editorMoveEndBuffer);
registerBuiltin("editor-auto-complete", editorAutoComplete);
// registerBuiltin("lsp-complete", lspComplete);
registerBuiltin("lsp-definition", lspDefinition);
}
void initConfig() {
@@ -128,13 +131,14 @@ void initEditor() {
E.number_of_keybinds = 0;
E.number_of_prefix = 0;
// General prefix is 0 (no prefix)
E.prefix = (struct prefix_t *)malloc(sizeof(struct prefix_t));
E.prefix = (struct prefix_t *)bAlloc(sizeof(struct prefix_t));
E.prefix[0].prefix_id = 0;
strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
E.prefix_state = 0;
initConfig();
E.lsp_client = (LspClient*)bAlloc(sizeof(LspClient));
initConfig();
initTheme();
// To modify
+56 -7
View File
@@ -6,6 +6,8 @@
#include "include/buffer.h"
#include "include/data.h"
#include "include/split_screen.h"
#include "include/completion.h"
#include "include/lsp_ui.h"
#include <ctype.h>
#include <sys/stat.h>
#include <dirent.h>
@@ -18,6 +20,7 @@
#include "include/terminal.h"
#include "include/utf8.h"
#include "include/utils.h"
extern struct editorConfig E;
@@ -37,7 +40,7 @@ extern struct editorConfig E;
* - path ends with '/' (already a directory)
* - no matching entries found
* - directory cannot be opened
* @note Caller is responsible for freeing the returned string
* @note Caller is responsible for bFreeing the returned string
* @note Uses static buffer internally; may return stale pointers across calls
*/
const char *fileCompletion(const char *path) {
@@ -92,7 +95,7 @@ const char *fileCompletion(const char *path) {
// Cleanup when no more entries
closedir(dir);
dir = NULL;
free(entry);
bFree(entry);
appDebug("[FILE COMP] no entries\n");
return strdup(path);
}
@@ -108,13 +111,13 @@ const char *fileCompletion(const char *path) {
* @return Pointer to the user-entered text (dynamically allocated), or NULL if:
* - User pressed ESC to cancel
* - Input buffer is empty when Enter is pressed
* @note Caller is responsible for freeing the returned string
* @note Caller is responsible for bFreeing the returned string
* @note Uses editorReadKey() for input and editorRefreshScreen() for display
*/
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
size_t buf_size = 256;
appDebug("[FILE COMP] %s %d\n", placeHolder, strlen(placeHolder));
char *buf = malloc(buf_size);
char *buf = bAlloc(buf_size);
size_t buf_len = 0;
int c = 0;
buf[0] = '\0';
@@ -131,7 +134,7 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
}
} else if (c == ESCAPE) {
editorSetStatusMessage("");
free(buf);
bFree(buf);
return NULL;
} else if (c == '\r') {
if (buf_len != 0) {
@@ -154,14 +157,14 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
buf_len = 0;
char * buf_complete = (char *) fileCompletion(path);
strcpy(buf, buf_complete);
free(buf_complete);
bFree(buf_complete);
buf_len = strlen(buf);
buf[buf_len] = '\0';
} else if (!iscntrl(c) && c < 256) {
if (buf_len == buf_size - 1) {
buf_size *= 2;
buf = realloc(buf, buf_size);
buf = bRealloc(buf, buf_size);
}
buf[buf_len++] = c;
buf[buf_len] = '\0';
@@ -213,6 +216,52 @@ void editorProcessKeypress() {
int c = editorReadKey();
char key_sequence[8];
if (E.lsp_client && E.lsp_completion.visible) {
if (c == ARROW_UP || c == CTRL_KEY('p')) {
if (E.lsp_completion.selected > 0)
E.lsp_completion.selected--;
return; // consumed, redraw on next loop
}
if (c == ARROW_DOWN || c == CTRL_KEY('n')) {
if (E.lsp_completion.selected < E.lsp_completion.count - 1)
E.lsp_completion.selected++;
return;
}
if (c == '\r') {
CompletionItem *item = &E.lsp_completion.items[E.lsp_completion.selected];
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
// Find how many chars the user already typed by looking at the
// current word (chars before cursor on the same line)
int file_col = active->cursor_x + active->x_offset;
row_t *row = &buf->row[active->cursor_y + active->y_offset];
// Walk backwards from cursor to find start of current word
int word_start = file_col;
while (word_start > 0 &&
(isalnum((unsigned char)row->chars[word_start - 1]) ||
row->chars[word_start - 1] == '_'))
word_start--;
int already_typed = file_col - word_start; // chars user already typed
// Insert only the suffix — what comes after what's already typed
const char *suffix = item->label + already_typed;
for (int i = 0; suffix[i]; i++)
bufferInsertBytes(&suffix[i], 1);
E.lsp_completion.visible = 0;
return;
}
if (c == ESCAPE) {
E.lsp_completion.visible = 0;
return;
}
// Any other key: dismiss popup and fall through to normal handling
E.lsp_completion.visible = 0;
}
if (executeKeyBind(keyToString(c))) {
return;
}
+324
View File
@@ -0,0 +1,324 @@
//
// Created by Giorgio on 27/05/2026.
//
#include "include/lsp_ui.h"
#include "include/data.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// We use cJSON — drop cJSON.c + cJSON.h into src/ and include/
#include "include/cJSON.h"
#include "include/append_buffer.h"
// ─── ANSI helpers ─────────────────────────────────────────────────────────────
// Move terminal cursor to absolute (col, row) — 1-based
static void ab_move(struct abuf *ab, int row, int col) {
char esc[32];
int n = snprintf(esc, sizeof(esc), "\x1b[%d;%dH", row, col);
abAppend(ab, esc, n); // your existing abAppend
}
static void ab_sgr(struct abuf *ab, const char *codes) {
char esc[32];
int n = snprintf(esc, sizeof(esc), "\x1b[%sm", codes);
abAppend(ab, esc, n);
}
#define AB_RESET(ab) ab_sgr(ab, "0")
#define AB_BOLD(ab) ab_sgr(ab, "1")
#define AB_DIM(ab) ab_sgr(ab, "2")
#define AB_FG(ab, c) ab_sgr(ab, c) // e.g. "32" = green
#define AB_BG(ab, c) ab_sgr(ab, c) // e.g. "44" = blue bg
// ─── Completion item kind → short tag ────────────────────────────────────────
static const char *kind_tag(int k) {
switch (k) {
case 2: return "mth"; // Method
case 3: return "fn "; // Function
case 4: return "ctr"; // Constructor
case 5: return "fld"; // Field
case 6: return "var"; // Variable
case 7: return "cls"; // Class
case 9: return "ifc"; // Interface
case 14: return "kwd"; // Keyword
default: return " ";
}
}
static const char *kind_color(int k) {
switch (k) {
case 3: return "33"; // yellow — functions
case 2: return "36"; // cyan — methods
case 7: return "35"; // magenta — classes
case 14: return "34"; // blue — keywords
case 5:
case 6: return "32"; // green — fields/vars
default: return "37"; // white
}
}
// ─── Draw completion popup ───────────────────────────────────────────────────
//
// ┌─────────────────────────────────────┐
// │ fn printf int (const …) │ ← selected (bold, inverted)
// │ fn fprintf │
// │ fn sprintf │
// └─────────────────────────────────────┘
//
// Drawn using absolute cursor positioning — safe to call inside
// editorRefreshScreen() before the final write().
void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup) {
fprintf(stderr, "[DRAW] visible=%d count=%d origin=(%d,%d)\n",
popup->visible, popup->count,
popup->origin_x, popup->origin_y);
if (!popup->visible || popup->count == 0) return;
int visible_count = popup->count < COMPLETION_POPUP_H
? popup->count : COMPLETION_POPUP_H;
int box_w = COMPLETION_MAX_WIDTH;
// Scroll window so selected item is always visible
int scroll = 0;
if (popup->selected >= COMPLETION_POPUP_H)
scroll = popup->selected - COMPLETION_POPUP_H + 1;
// ── top border ────────────────────────────────────────────────────────────
ab_move(ab, popup->origin_y, popup->origin_x);
ab_sgr(ab, "0;37;40"); // white on black
abAppend(ab, "", 3); // UTF-8 box chars (3 bytes each)
for (int i = 0; i < box_w - 2; i++) abAppend(ab, "", 3);
abAppend(ab, "", 3);
// ── rows ──────────────────────────────────────────────────────────────────
for (int i = 0; i < visible_count; i++) {
int idx = i + scroll;
CompletionItem *it = &popup->items[idx];
int is_sel = (idx == popup->selected);
ab_move(ab, popup->origin_y + 1 + i, popup->origin_x);
if (is_sel) ab_sgr(ab, "7"); // reverse video (highlight)
else ab_sgr(ab, "0;37;40");
abAppend(ab, "", 2);
// kind tag in color
if (!is_sel) ab_sgr(ab, kind_color(it->kind));
abAppend(ab, kind_tag(it->kind), 3);
abAppend(ab, " ", 1);
if (!is_sel) AB_RESET(ab);
if (!is_sel) ab_sgr(ab, "37;40");
// label — left column, fixed width 20
char label_col[21];
snprintf(label_col, sizeof(label_col), "%-20s", it->label);
if (is_sel) AB_BOLD(ab);
abAppend(ab, label_col, 20);
if (is_sel) ab_sgr(ab, "22"); // bold off
abAppend(ab, " ", 1);
// detail — right column, dimmed
if (!is_sel) AB_DIM(ab);
char detail_col[14];
snprintf(detail_col, sizeof(detail_col), "%-13s", it->detail);
abAppend(ab, detail_col, 13);
AB_RESET(ab);
if (is_sel) ab_sgr(ab, "7");
else ab_sgr(ab, "37;40");
abAppend(ab, "", 2);
AB_RESET(ab);
}
// ── scroll indicator on right border if needed ────────────────────────────
if (popup->count > COMPLETION_POPUP_H) {
int bar_row = popup->origin_y + 1 +
(popup->selected * (COMPLETION_POPUP_H - 1))
/ (popup->count - 1);
ab_move(ab, bar_row, popup->origin_x + box_w - 1);
ab_sgr(ab, "0;37;40");
abAppend(ab, "", 3);
AB_RESET(ab);
}
// ── bottom border ─────────────────────────────────────────────────────────
ab_move(ab, popup->origin_y + 1 + visible_count, popup->origin_x);
ab_sgr(ab, "0;37;40");
abAppend(ab, "", 3);
for (int i = 0; i < box_w - 2; i++) abAppend(ab, "", 3);
abAppend(ab, "", 3);
AB_RESET(ab);
}
// ─── Handle popup keys ────────────────────────────────────────────────────────
// Returns 1 if key was consumed, 0 if editor should handle it normally.
int lspUiHandleKey(CompletionPopup *popup, int key) {
if (!popup->visible) return 0;
switch (key) {
case '\x1b': // ESC — dismiss
popup->visible = 0;
return 1;
case '\r': // Enter — accept
// Caller should insert popup->items[popup->selected].label
popup->visible = 0;
return 1;
// Arrow up / Ctrl-p
case 'A' + 0x80:
case 0x10:
if (popup->selected > 0) popup->selected--;
return 1;
// Arrow down / Ctrl-n
case 'B' + 0x80:
case 0x0e:
if (popup->selected < popup->count - 1) popup->selected++;
return 1;
default:
// Any printable key: dismiss popup, let editor handle it
popup->visible = 0;
return 0;
}
}
static DiagSeverity diag_worst_on_line(DiagnosticList *diags,
int buffer_id, int line) {
DiagSeverity worst = 0;
for (int i = 0; i < diags->count; i++) {
Diagnostic *d = &diags->entries[i];
if (d->buffer_id == buffer_id && d->line == line) {
if (d->severity > worst) worst = d->severity; // ERROR > WARN > HINT
}
}
return worst;
}
// Call this once per screen row, right before you draw the row text.
// ab : your append buffer
// diags : &E.lsp_diagnostics
// buf_id : active buffer id
// line : file line number (0-based) for this screen row
void lspUiDrawGutter(struct abuf *ab, DiagnosticList *diags,
int buf_id, int line) {
DiagSeverity s = diag_worst_on_line(diags, buf_id, line);
switch (s) {
case DIAG_ERROR:
ab_sgr(ab, "1;31"); // bold red
abAppend(ab, "", 3);
break;
case DIAG_WARNING:
ab_sgr(ab, "1;33"); // bold yellow
abAppend(ab, "", 3);
break;
case DIAG_HINT:
ab_sgr(ab, "2;37"); // dim white
abAppend(ab, "·", 1);
break;
default:
abAppend(ab, " ", 1); // no diagnostic
break;
}
AB_RESET(ab);
abAppend(ab, " ", 1); // gutter padding
}
// Returns the diagnostic message for the line the cursor is on, or NULL.
const char *lspUiDiagnosticAtCursor(DiagnosticList *diags,
int buffer_id, int cursor_line) {
for (int i = 0; i < diags->count; i++) {
Diagnostic *d = &diags->entries[i];
if (d->buffer_id == buffer_id && d->line == cursor_line)
return d->message;
}
return NULL;
}
// ─── Parse clangd JSON → our structs ─────────────────────────────────────────
void lspParseCompletion(const char *json, CompletionPopup *popup,
int screen_x, int screen_y) {
popup->count = 0;
popup->selected = 0;
popup->origin_x = screen_x;
popup->origin_y = screen_y + 1; // one row below cursor
cJSON *root = cJSON_Parse(json);
if (!root) return;
cJSON *result = cJSON_GetObjectItem(root, "result");
if (!result) { cJSON_Delete(root); return; }
// result can be a list or {isIncomplete, items:[…]}
cJSON *items = cJSON_IsArray(result)
? result
: cJSON_GetObjectItem(result, "items");
if (!items) { cJSON_Delete(root); return; }
cJSON *item;
cJSON_ArrayForEach(item, items) {
if (popup->count >= COMPLETION_MAX_ITEMS) break;
CompletionItem *ci = &popup->items[popup->count++];
cJSON *label = cJSON_GetObjectItem(item, "label");
cJSON *detail = cJSON_GetObjectItem(item, "detail");
cJSON *kind = cJSON_GetObjectItem(item, "kind");
const char *raw_label = label ? label->valuestring : "";
if (raw_label[0] == ' ') raw_label++;
strncpy(ci->label, raw_label, 127);
strncpy(ci->detail, detail ? detail->valuestring : "", 63);
ci->kind = kind ? kind->valueint : 0;
}
popup->visible = (popup->count > 0);
cJSON_Delete(root);
}
void lspParseDiagnostics(const char *json, DiagnosticList *diags,
int buffer_id) {
cJSON *root = cJSON_Parse(json);
if (!root) return;
cJSON *params = cJSON_GetObjectItem(root, "params");
if (!params) { cJSON_Delete(root); return; }
cJSON *diag_arr = cJSON_GetObjectItem(params, "diagnostics");
if (!diag_arr) { cJSON_Delete(root); return; }
// Remove old entries for this buffer
int w = 0;
for (int i = 0; i < diags->count; i++)
if (diags->entries[i].buffer_id != buffer_id)
diags->entries[w++] = diags->entries[i];
diags->count = w;
cJSON *d;
cJSON_ArrayForEach(d, diag_arr) {
if (diags->count >= DIAG_MAX) break;
Diagnostic *diag = &diags->entries[diags->count++];
diag->buffer_id = buffer_id;
cJSON *range = cJSON_GetObjectItem(d, "range");
cJSON *start = cJSON_GetObjectItem(range, "start");
cJSON *end_pos = cJSON_GetObjectItem(range, "end");
cJSON *sev = cJSON_GetObjectItem(d, "severity");
cJSON *msg = cJSON_GetObjectItem(d, "message");
diag->line = cJSON_GetObjectItem(start, "line")->valueint;
diag->col_start = cJSON_GetObjectItem(start, "character")->valueint;
diag->col_end = cJSON_GetObjectItem(end_pos, "character")->valueint;
diag->severity = sev ? (DiagSeverity)sev->valueint : DIAG_ERROR;
strncpy(diag->message, msg ? msg->valuestring : "", 255);
}
cJSON_Delete(root);
}
+69 -33
View File
@@ -15,11 +15,15 @@
#include "../include/split_screen.h"
#include "../include/syntax_highlighter.h"
#include "../include/terminal.h"
#include "../include/lsp_ui.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "include/completion.h"
#include "include/utils.h"
extern struct editorConfig E;
/**
@@ -52,7 +56,7 @@ static void editorDrawPane(struct abuf* ab, EditorPane* pane)
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, E.theme.BACKGROUND_COLOR, (int)strlen(E.theme.BACKGROUND_COLOR));
// pane line is out of buffer
@@ -70,7 +74,7 @@ static void editorDrawPane(struct abuf* ab, EditorPane* pane)
}
else
{
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')
{
@@ -80,7 +84,7 @@ static void editorDrawPane(struct abuf* ab, EditorPane* pane)
// Print only up to pane width
abAppend(ab, highlighted, byte_len_to_print);
free(highlighted);
bFree(highlighted);
}
else
{
@@ -130,7 +134,7 @@ static void editorDrawAllPanes(struct abuf* ab)
{
char pos_buf[32];
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", y + 1, divider_col);
abAppend(ab, pos_buf, (int) strlen(pos_buf));
abAppend(ab, pos_buf, (int)strlen(pos_buf));
abAppend(ab, "\x1b[1m|\x1b[0m", 9); // Bold pipe divider
}
}
@@ -140,7 +144,7 @@ static void editorDrawAllPanes(struct abuf* ab)
int divider_row = layout->panes[0].height;
char pos_buf[32];
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", divider_row + 1, 1);
abAppend(ab, pos_buf, (int) strlen(pos_buf));
abAppend(ab, pos_buf, (int)strlen(pos_buf));
for (int x = 0; x < E.screencols; x++)
{
abAppend(ab, "\x1b[1m-\x1b[0m", 9); // Bold dash divider
@@ -180,11 +184,13 @@ void editorScroll()
if (active->cursor_x == 0 && active->x_offset)
{
active->x_offset--;
} else
}
else
{
active->cursor_x--;
}
} else
}
else
{
// RIGHT
if (rel_x > active->cursor_x + active->x_offset)
@@ -192,7 +198,8 @@ void editorScroll()
if (active->cursor_x == active->width - 1)
{
active->x_offset++;
} else
}
else
{
active->cursor_x++;
}
@@ -204,7 +211,8 @@ void editorScroll()
if (active->cursor_y == 0 && active->y_offset)
{
active->y_offset--;
} else
}
else
{
active->cursor_y--;
}
@@ -215,25 +223,25 @@ void editorScroll()
if (active->cursor_y == active->height - 1)
{
active->y_offset++;
} else
}
else
{
active->cursor_y++;
}
}
}
}
char * basename(char *path)
char* basename(char* path)
{
int len = (int) strlen(path);
int len = (int)strlen(path);
for(int i=len-1; i>0; i--)
for (int i = len - 1; i > 0; i--)
{
if(path[i]=='/' )
if (path[i] == '/')
{
path = path+i+1;
path = path + i + 1;
break;
}
}
@@ -316,7 +324,7 @@ void editorDrawStatusBar(struct abuf* ab)
*/
void editorDrawMessageBar(struct abuf* ab)
{
int msg_len = (int) strlen(E.status_msg);
int msg_len = (int)strlen(E.status_msg);
abAppend(ab, ERASE_END_LINE, 3);
if (msg_len > E.screencols)
{
@@ -344,17 +352,17 @@ void editorDrawContextBuffer(struct abuf* ab)
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);
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, 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);
}
}
free(E.context_buffers);
bFree(E.context_buffers);
E.context_buffers = NULL;
}
@@ -377,27 +385,55 @@ void editorRefreshScreen()
abAppend(&ab, HIDE_CURSOR, 6);
abAppend(&ab, CURSOR_TOP_LEFT, 3);
abAppend(&ab, E.theme.BACKGROUND_COLOR,
(int) strlen(E.theme.BACKGROUND_COLOR)); // RGB background is 12 bytes
(int)strlen(E.theme.BACKGROUND_COLOR));
// Draw all panes
editorScroll();
editorDrawAllPanes(&ab);
// Draw status bar and message bar
editorDrawStatusBar(&ab);
editorDrawMessageBar(&ab);
// editorDrawContextBuffer(&ab);
// Position cursor in active pane
EditorPane* active = splitScreenGetActivePane();
if (active != NULL)
{
snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
active->cursor_y + active->origin_y + 1,
active->cursor_x + active->origin_x + 1);
abAppend(&ab, buf, (int) strlen(buf));
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
// ── LSP: sync buffer changes to clangd ────────────────────────────────────
if (buffer->b_has_changed) {
if (E.lsp_client && E.lsp_client->state == LSP_READY) {
lspDidChange(E.lsp_client, buffer);
E.lsp_client->completion_just_arrived = 0; // consume the flag
}
buffer->b_has_changed = 0;
}
// ── LSP: draw completion popup every frame while visible ──────────────────
fprintf(stderr, "[REFRESH] lsp_completion.visible=%d\n",
E.lsp_completion.visible);
while (E.lsp_client->completion_requested && !E.lsp_client->completion_just_arrived)
;
// reset flags
E.lsp_client->completion_just_arrived = 0;
E.lsp_client->completion_requested = 0;
if (E.lsp_client && E.lsp_client->state == LSP_READY)
{
lspUiDrawCompletion(&ab, &E.lsp_completion);
appDebug("ready\n");
}
// ── LSP: diagnostic for current line in status bar ────────────────────────
const char *diag = lspUiDiagnosticAtCursor(
&E.lsp_diagnostics,
active->buffer_id,
active->cursor_y + active->y_offset);
if (diag)
editorSetStatusMessage("● %s", diag);
// ── Position cursor (account for gutter width) ────────────────────────────
snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
active->cursor_y + active->origin_y + 1,
active->cursor_x + active->origin_x + 1 + GUTTER_WIDTH);
abAppend(&ab, buf, (int)strlen(buf));
abAppend(&ab, SHOW_CURSOR, 6);
write(STDOUT_FILENO, ab.b, ab.len);
abFree(&ab);
+7 -5
View File
@@ -9,6 +9,8 @@
#include <stdlib.h>
#include <string.h>
#include "include/utils.h"
extern struct editorConfig E;
/**
@@ -19,7 +21,7 @@ void splitScreenInit(void) {
E.layout.num_panes = 1;
E.layout.active_pane = 0;
E.layout.panes = malloc(sizeof(EditorPane) * 2);
E.layout.panes = bAlloc(sizeof(EditorPane) * 2);
// Initialize single fullscreen pane
E.layout.panes[0].buffer_id = -1; // No buffer for now
@@ -48,8 +50,8 @@ int splitScreenVertical(int buffer_id_left, int buffer_id_right) {
return -1;
}
// Reallocate panes array
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
// bReallocate panes array
E.layout.panes = bRealloc(E.layout.panes, sizeof(EditorPane) * 2);
E.layout.mode = SPLIT_VERTICAL;
E.layout.num_panes = 2;
E.layout.active_pane = 0;
@@ -99,8 +101,8 @@ int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom) {
return -1;
}
// Reallocate panes array
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
// bReallocate panes array
E.layout.panes = bRealloc(E.layout.panes, sizeof(EditorPane) * 2);
E.layout.mode = SPLIT_HORIZONTAL;
E.layout.num_panes = 2;
E.layout.active_pane = 0;
+3 -1
View File
@@ -5,6 +5,8 @@
#include <stdlib.h>
#include <string.h>
#include "include/utils.h"
extern struct editorConfig E;
const char *c_keywords[] = {
@@ -112,7 +114,7 @@ char *highlight_line(const char *line, int *length) {
// Allocate generously based on line length to avoid overflow.
int line_len = strlen(line);
int buf_size = line_len * 32 + 256;
char *result = malloc(buf_size);
char *result = bAlloc(buf_size);
int result_pos = 0;
int i = 0;
+38
View File
@@ -0,0 +1,38 @@
//
// 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;
}