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
+2 -1
View File
@@ -90,4 +90,5 @@
(map-key "CTRL-k" editor-cut-end-line "no-prefix")
(map-key "a" editor-move-cursor-beg-buffer "user")
(map-key "z" editor-move-cursor-end-buffer "user")
;; (map-key "x" editor-auto-complete "user")
(map-key "CTRL-h" editor-auto-complete "no-prefix")
(map-key "C-x" lsp-complete "user")
+1 -1
View File
@@ -13,7 +13,7 @@
* @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);
/**
* @brief Switches to a specific buffer by ID
+2 -1
View File
@@ -54,7 +54,8 @@ Lisp editorMoveEndBuffer(Lisp args, LispError *e, LispContext ctx);
// Other
Lisp editorAutoComplete(Lisp args, LispError *e, LispContext ctx);
// Lisp lspComplete(Lisp args, LispError *e, LispContext ctx);
Lisp lspDefinition(Lisp args, LispError *e, LispContext ctx);
void free_structs(void);
+306
View File
@@ -0,0 +1,306 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 19
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* Limits the length of circular references can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_CIRCULAR_LIMIT
#define CJSON_CIRCULAR_LIMIT 10000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable address area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
#define cJSON_SetBoolValue(object, boolValue) ( \
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
cJSON_Invalid\
)
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif
+29
View File
@@ -0,0 +1,29 @@
//
// Created by Giorgio on 25/05/2026.
//
#ifndef BELUGA_COMPLETION_H
#define BELUGA_COMPLETION_H
#include "data.h"
void createContextBuffer(const int x, const int y, const char * text);
int lspStart(LspClient *lsp, const char *project_root);
void lspShutdown(LspClient *lsp);
// Document sync — call these from your buffer open/save/edit hooks
void lspDidOpen(LspClient *lsp, struct buffer_t *buf);
void lspDidChange(LspClient *lsp, struct buffer_t *buf);
void lspDidClose(LspClient *lsp, struct buffer_t *buf);
// Requests
void lspRequestCompletion(LspClient *lsp, struct buffer_t *buf,
int line, int col,
int screen_x, int screen_y);
void lspRequestHover(LspClient *lsp, struct buffer_t *buf, int line, int col);
void lspRequestDefinition(LspClient *lsp, struct buffer_t *buf, int line, int col);
#endif //BELUGA_COMPLETION_H
+91
View File
@@ -5,8 +5,12 @@
#include <termios.h>
#include <time.h>
#include "define.h"
#include "lisp.h"
typedef struct lsp_client_t LspClient;
/**
* \struct row_t
* \brief Store one editor row
@@ -94,15 +98,98 @@ struct keyBind_t {
Lisp command;
};
// In data.h — add these
#include <pthread.h>
#define LSP_MAX_PENDING 64
typedef enum {
LSP_NOT_STARTED = 0,
LSP_INITIALIZING,
LSP_READY,
LSP_SHUTDOWN,
} LspState;
typedef struct {
int id;
void (*callback)(struct lsp_client_t *lsp, const char *json);
} LspPending;
typedef struct lsp_client_t {
// ── Process ───────────────────────────────────────────────────────────────
pid_t pid;
int write_fd;
int read_fd;
int completion_just_arrived;
int completion_requested;
int wake_pipe[2]; // [0] = read end (main loop), [1] = write end (reader thread)
// ── State ─────────────────────────────────────────────────────────────────
LspState state;
int next_id;
// ── Pending requests ──────────────────────────────────────────────────────
LspPending pending[LSP_MAX_PENDING];
int pending_count;
// ── Threading ─────────────────────────────────────────────────────────────
pthread_t reader_thread;
pthread_mutex_t lock;
pthread_cond_t ready_cond; // signaled when state → LSP_READY
// ── Completion context ────────────────────────────────────────────────────
int completion_cursor_x; // screen position when
int completion_cursor_y; // completion was requested
} LspClient;
typedef struct {
char label[128]; // display text e.g. "printf"
char detail[64]; // type/sig hint e.g. "int (const char *, ...)"
int kind; // LSP CompletionItemKind (1=Text,2=Method,3=Function…)
} CompletionItem;
typedef struct {
CompletionItem items[COMPLETION_MAX_ITEMS];
int count;
int selected; // currently highlighted row
int visible; // is the popup shown?
int origin_x; // screen col where popup appears
int origin_y; // screen row where popup appears
} CompletionPopup;
typedef enum { DIAG_ERROR = 1, DIAG_WARNING, DIAG_HINT } DiagSeverity;
typedef struct {
int buffer_id;
int line; // 0-based
int col_start; // 0-based
int col_end;
DiagSeverity severity;
char message[256];
} Diagnostic;
typedef struct {
Diagnostic entries[DIAG_MAX];
int count;
} DiagnosticList;
enum buffer_type { FILE_BUFF, TERMINAL_BUFF };
struct buffer_t {
enum buffer_type type;
int buffer_id;
int b_lsp_open;
int x, y; /**< Position in the file */
row_t *row;
int numrows;
int b_has_changed;
char *filename;
char *path;
enum bufferStatus_e state;
int dirty; /**< Has this buffer been modified since last save */
};
@@ -122,6 +209,10 @@ struct editorConfig {
ContextBuffer* context_buffers;
LspClient *lsp_client;
CompletionPopup lsp_completion;
DiagnosticList lsp_diagnostics;
int dirty;
char *status_msg;
+7
View File
@@ -14,6 +14,13 @@
#define SPACE "\x20"
#define APP_DEBUG
#define COMPLETION_MAX_ITEMS 16
#define COMPLETION_MAX_WIDTH 40
#define COMPLETION_POPUP_H 8 // max visible rows
#define DIAG_MAX 128
#define GUTTER_WIDTH 2
enum editorKey_e {
BACKSPACE = 127,
ARROW_LEFT = 1000,
-4
View File
@@ -1,10 +1,6 @@
#ifndef INIT_H_
#define INIT_H_
#include "builtins.h"
#include "data.h"
#include "terminal.h"
#include <stdio.h>
/**
* \fn void initEditor()
+35
View File
@@ -0,0 +1,35 @@
//
// Created by Giorgio on 27/05/2026.
//
#ifndef BELUGA_LSP_UI_H
#define BELUGA_LSP_UI_H
#include "data.h"
#include "buffer.h" // for abuf
// ── Public API ────────────────────────────────────────────────────────────────
// Call from editorRefreshScreen() — appends popup/underlines to abuf
void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup);
void lspUiDrawDiagnostics(struct abuf *ab, DiagnosticList *diags,
EditorPane *pane, struct buffer_t *buf);
// Input handling — returns 1 if the key was consumed by the popup
int lspUiHandleKey(CompletionPopup *popup, int key);
// Parse clangd JSON into our structs
void lspParseCompletion(const char *json, CompletionPopup *popup,
int screen_x, int screen_y);
void lspParseDiagnostics(const char *json, DiagnosticList *diags, int buffer_id);
void lspUiDrawGutter(struct abuf *ab, DiagnosticList *diags,
int buf_id, int line);
const char *lspUiDiagnosticAtCursor(DiagnosticList *diags,
int buffer_id, int cursor_line);
#endif
+16
View File
@@ -0,0 +1,16 @@
//
// Created by Giorgio on 28/05/2026.
//
#ifndef BELUGA_UTILS_H
#define BELUGA_UTILS_H
#include <sys/_types/_size_t.h>
extern int beluga_alloc_counter;
void * bAlloc(size_t size);
void * bRealloc(void * ptr, size_t size);
void * bFree(void * ptr);
#endif //BELUGA_UTILS_H
+26 -1
View File
@@ -9,6 +9,8 @@
#define _BSD_SOURCE
#define _GNU_SOURCE
#include "include/utils.h"
#include "include/buffer.h"
#include "include/split_screen.h"
#include <string.h>
@@ -19,18 +21,36 @@
#include "include/input.h"
#include "include/editor_op.h"
#include "include/terminal.h"
#include "include/completion.h"
#include <signal.h>
#include "include/utils.h"
struct editorConfig E;
int main(int argc, char *argv[]) {
char * splash_screen = (char *) calloc(256, sizeof(char));
char * splash_screen = bAlloc(sizeof(char) * 512);
signal(SIGPIPE, SIG_IGN); // don't die on broken pipe, just get EPIPE from write()
enableRawMode();
initEditor();
if (argc >= 2) {
EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE);
char project_root[512];
realpath(argv[1], project_root);
char *slash = strrchr(project_root, '/');
if (slash) *slash = '\0';
appDebug("peoject root : %s\n", project_root);
lspStart(E.lsp_client, project_root);
struct buffer_t *buf = &E.buffers[active->buffer_id];
lspDidOpen(E.lsp_client, buf);
} else {
strcat(splash_screen, getenv("HOME"));
strcat(splash_screen, "/.beluga/assets/beluga.txt");
@@ -38,11 +58,16 @@ int main(int argc, char *argv[]) {
appDebug("splash : %s\n", splash_screen);
EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(splash_screen, READ_ONLY);
struct buffer_t *buf = &E.buffers[active->buffer_id];
lspStart(E.lsp_client, splash_screen);
lspDidOpen(E.lsp_client, buf);
}
free(splash_screen);
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
appDebug("allocation : %d\n", beluga_alloc_counter);
while (1) {
editorRefreshScreen();
+7 -2
View File
@@ -7,6 +7,7 @@ project('beluga', 'c',
cc = meson.get_compiler('c')
m = cc.find_library('m', required: false)
thread_dep = dependency('threads')
# Source files
src_files = files(
@@ -23,11 +24,15 @@ src_files = files(
'src/builtins.c',
'src/buffer.c',
'src/split_screen.c',
'src/utf8.c'
'src/utf8.c',
'src/completion.c',
'src/lsp_ui.c',
'src/cJSON.c',
'src/utils.c'
)
# Executable
executable('beluga',
src_files,
dependencies: [m]
dependencies: [m, thread_dep]
)
+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 */
+193 -106
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,10 +31,13 @@
* @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]) {
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)) {
while (i--)
{
if (!strcmp(prefix_name, E.prefix[i].prefix_name))
{
return E.prefix[i];
}
}
@@ -51,19 +57,24 @@ 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) {
Lisp mapKey(Lisp args, LispError* e, LispContext ctx)
{
/*
* 3 arguments keybind command prefix
*/
const char *key_sequence = lisp_string(lisp_car(args));
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(
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 *)malloc(50 * sizeof(char));
(char*)bAlloc(50 * sizeof(char));
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
@@ -71,7 +82,7 @@ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
// Third argument
args = lisp_cdr(args);
const char *prefix_name = lisp_string(lisp_car(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;
@@ -91,10 +102,12 @@ 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));
Lisp moveCursor(Lisp args, LispError* e, LispContext ctx)
{
const char* direction = lisp_string(lisp_car(args));
int is_in = 0;
switch (direction[0]) {
switch (direction[0])
{
case 'u':
is_in = editorMoveCursor(ARROW_UP);
break;
@@ -107,37 +120,43 @@ Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
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) {
void bFree_structs(void)
{
int i, j;
free(E.prefix);
for (i = 0; i < E.number_of_keybinds; ++i) {
free(E.key_binds[i].key_sequence);
bFree(E.prefix);
for (i = 0; i < E.number_of_keybinds; ++i)
{
bFree(E.key_binds[i].key_sequence);
}
free(E.key_binds);
// free layout
free(E.layout.panes);
bFree(E.key_binds);
// bFree layout
bFree(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);
// 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);
}
free(E.buffers[i].row);
bFree(E.buffers[i].row);
}
free(E.init_file_path);
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) {
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();
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);
return lisp_null();
}
/**
@@ -181,17 +202,21 @@ 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) {
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 {
}
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();
return lisp_null();
@@ -206,8 +231,8 @@ 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();
return lisp_null();
@@ -222,8 +247,10 @@ 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) {
Lisp l_editorInserTab(Lisp args, LispError* e, LispContext ctx)
{
for (int i = 0; i < E.constantes.TAB_LENGTH; ++i)
{
bufferInsertBytes(" ", 1);
}
@@ -239,9 +266,10 @@ 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);
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,9 +283,10 @@ 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);
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,7 +300,8 @@ Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @see editorDelChar()
*/
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
Lisp deletePreviousChar(Lisp args, LispError* e, LispContext ctx)
{
bufferDelBytes();
return lisp_null();
}
@@ -287,11 +317,13 @@ 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();
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx)
{
EditorPane* active = splitScreenGetActivePane();
active->cursor_y = active->y_offset;
int times = E.screenrows;
while (--times) {
while (--times)
{
editorMoveCursor(ARROW_UP);
}
return lisp_null();
@@ -308,15 +340,18 @@ 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);
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) {
if (active->cursor_y > buffer->numrows)
{
active->cursor_y = buffer->numrows;
}
int times = E.screenrows;
while (--times) {
while (--times)
{
editorMoveCursor(ARROW_DOWN);
}
@@ -334,14 +369,16 @@ 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) {
Lisp editorOpenFile(Lisp args, LispError* e, LispContext ctx)
{
char* filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename)
{
// editorOpen(filename);
EditorPane *active = splitScreenGetActivePane();
EditorPane* active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(filename, READ_AND_WRITE);
}
free(filename);
bFree(filename);
return lisp_null();
}
@@ -356,9 +393,10 @@ 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));
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,11 +411,12 @@ 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));
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;
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);
@@ -387,15 +426,17 @@ Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx);
fclose(fd_package);
free(package_dir);
bFree(package_dir);
return lisp_null();
}
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) {
Lisp editorSwitchNextBuffer(Lisp args, LispError* e, LispContext ctx)
{
appDebug("switch buffer\n");
if (E.number_of_buffer > 0) {
EditorPane *active = splitScreenGetActivePane();
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;
}
@@ -403,13 +444,16 @@ Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) {
return lisp_null();
}
Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx) {
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) {
Lisp editorUnifiedPanes(Lisp args, LispError* e, LispContext ctx)
{
if (E.layout.num_panes - 1)
{
splitScreenUnify();
}
return lisp_null();
@@ -424,10 +468,11 @@ Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @see editorFind()
*/
Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx) {
Lisp bufferFind_L(Lisp args, LispError* e, LispContext ctx)
{
appDebug("LispFind\n");
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
bufferFind(buffer);
return lisp_null();
}
@@ -441,10 +486,11 @@ Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx) {
* @return lisp_null()
* @see editorFind()
*/
Lisp bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx) {
Lisp bufferFindReverse_L(Lisp args, LispError* e, LispContext ctx)
{
appDebug("LispFind\n");
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buffer = bufferFindById(active->buffer_id);
bufferFindReverse(buffer);
return lisp_null();
}
@@ -458,13 +504,17 @@ 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 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) {
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 {
}
else
{
returned_char = lisp_make_char(buffer->row[buffer->y].chars[buffer->x]);
}
return returned_char;
@@ -481,11 +531,12 @@ 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) {
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));
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);
@@ -504,8 +555,10 @@ 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) *
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)),
@@ -513,19 +566,21 @@ Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx) {
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();
const char* char_to_paste = editorGetClipboard();
appDebug("editor-paste, %s\n", char_to_paste);
bufferInsertBytes(char_to_paste, (int) strlen(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;
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--)
@@ -535,26 +590,58 @@ Lisp editorCutEndLine(Lisp args, LispError *e, LispContext ctx)
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);
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;
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");
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);
}
+66 -30
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)
{
@@ -348,13 +356,13 @@ void editorDrawContextBuffer(struct abuf* ab)
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,26 +385,54 @@ 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)
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);
abAppend(&ab, buf, (int) strlen(buf));
}
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);
+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;
}
+3
View File
@@ -0,0 +1,3 @@
int main() {
printf("hello");
}