First completion level is working (LSP connected)
This commit is contained in:
+3
-2
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user