Compare commits
5 Commits
main
...
completion
| Author | SHA1 | Date | |
|---|---|---|---|
| 564292b03e | |||
| cec92cacd1 | |||
| 2738bc8042 | |||
| a8b2960eb4 | |||
| 8eeef59a98 |
@@ -3,6 +3,7 @@
|
||||
(define TAB-LENGTH 4)
|
||||
(define QUIT-TIMES 1)
|
||||
(define THEME "dark")
|
||||
(define LSP #t)
|
||||
|
||||
;; PACKAGES
|
||||
|
||||
@@ -90,3 +91,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 "CTRL-h" editor-auto-complete "no-prefix")
|
||||
(map-key "C-x" lsp-complete "user")
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
|
||||
void abAppend(struct abuf *ab, const char *s, int len);
|
||||
|
||||
void abFree(struct abuf *ab);
|
||||
void abFree(const struct abuf *ab);
|
||||
|
||||
#endif // APPEND_BUFFER_H_
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+3
-1
@@ -53,7 +53,9 @@ 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
@@ -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
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by Giorgio on 25/05/2026.
|
||||
//
|
||||
|
||||
#ifndef BELUGA_COMPLETION_H
|
||||
#define BELUGA_COMPLETION_H
|
||||
#include "data.h"
|
||||
|
||||
|
||||
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
|
||||
+188
-85
@@ -5,150 +5,252 @@
|
||||
#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
|
||||
* \param
|
||||
* */
|
||||
|
||||
typedef struct row {
|
||||
int size; /**< Size of the line */
|
||||
int cap; /**< Size of the render line */
|
||||
char *chars; /**< Characters of the line */
|
||||
typedef struct row
|
||||
{
|
||||
int size; /**< Size of the line */
|
||||
int cap; /**< Size of the render line */
|
||||
char* chars; /**< Characters of the line */
|
||||
} row_t;
|
||||
|
||||
typedef struct context_buffer_t
|
||||
{
|
||||
int editor_x, editor_y;
|
||||
int width, height;
|
||||
row_t *rows;
|
||||
int editor_x, editor_y;
|
||||
int width, height;
|
||||
row_t* rows;
|
||||
} ContextBuffer;
|
||||
|
||||
/**
|
||||
* @brief Split modes for screen layout
|
||||
*/
|
||||
typedef enum {
|
||||
SPLIT_NONE = 0, // Single buffer fullscreen
|
||||
SPLIT_VERTICAL, // Left-right split
|
||||
SPLIT_HORIZONTAL // Top-bottom split
|
||||
typedef enum
|
||||
{
|
||||
SPLIT_NONE = 0, // Single buffer fullscreen
|
||||
SPLIT_VERTICAL, // Left-right split
|
||||
SPLIT_HORIZONTAL // Top-bottom split
|
||||
} SplitMode;
|
||||
|
||||
/**
|
||||
* @brief Represents an editor viewport/pane
|
||||
*/
|
||||
typedef struct {
|
||||
int buffer_id; // Which buffer this pane displays
|
||||
int height; // Height of this pane
|
||||
int width; // Width of this pane
|
||||
typedef struct
|
||||
{
|
||||
int buffer_id; // Which buffer this pane displays
|
||||
int height; // Height of this pane
|
||||
int width; // Width of this pane
|
||||
int origin_x, origin_y;
|
||||
int cursor_x; // Local cursor x in this pane
|
||||
int cursor_y; // Local cursor y in this pane
|
||||
int cursor_x; // Local cursor x in this pane
|
||||
int cursor_y; // Local cursor y in this pane
|
||||
int x_offset, y_offset;
|
||||
int is_active; // Is this pane currently active
|
||||
int is_active; // Is this pane currently active
|
||||
} EditorPane;
|
||||
|
||||
/**
|
||||
* @brief Screen layout manager
|
||||
*/
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
SplitMode mode;
|
||||
EditorPane *panes;
|
||||
EditorPane* panes;
|
||||
int num_panes;
|
||||
int active_pane; // Index of active pane
|
||||
int active_pane; // Index of active pane
|
||||
} ScreenLayout;
|
||||
|
||||
|
||||
typedef struct theme {
|
||||
char *BACKGROUND_COLOR;
|
||||
char *COLOR_KEYWORD;
|
||||
char *COLOR_TYPE;
|
||||
char *COLOR_STRING;
|
||||
char *COLOR_COMMENT;
|
||||
char *COLOR_NUMBER;
|
||||
char *COLOR_DEFAULT;
|
||||
|
||||
typedef struct theme
|
||||
{
|
||||
char* BACKGROUND_COLOR;
|
||||
char* COLOR_KEYWORD;
|
||||
char* COLOR_TYPE;
|
||||
char* COLOR_STRING;
|
||||
char* COLOR_COMMENT;
|
||||
char* COLOR_NUMBER;
|
||||
char* COLOR_DEFAULT;
|
||||
} theme_t;
|
||||
|
||||
enum bufferStatus_e {
|
||||
IDLE,
|
||||
READ_ONLY,
|
||||
READ_AND_WRITE,
|
||||
enum bufferStatus_e
|
||||
{
|
||||
IDLE,
|
||||
READ_ONLY,
|
||||
READ_AND_WRITE,
|
||||
};
|
||||
|
||||
struct const_t {
|
||||
int TAB_LENGTH;
|
||||
int QUIT_TIMES;
|
||||
char *THEME;
|
||||
struct const_t
|
||||
{
|
||||
int TAB_LENGTH;
|
||||
int QUIT_TIMES;
|
||||
char* THEME;
|
||||
int LSP;
|
||||
};
|
||||
|
||||
struct prefix_t {
|
||||
char prefix_name[64];
|
||||
int prefix_id;
|
||||
struct prefix_t
|
||||
{
|
||||
char prefix_name[64];
|
||||
int prefix_id;
|
||||
};
|
||||
|
||||
struct keyBind_t {
|
||||
char *key_sequence;
|
||||
int prefix_id;
|
||||
Lisp command;
|
||||
struct keyBind_t
|
||||
{
|
||||
char* key_sequence;
|
||||
int prefix_id;
|
||||
Lisp command;
|
||||
};
|
||||
|
||||
// In data.h — add these
|
||||
|
||||
#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 x, y; /**< Position in the file */
|
||||
row_t *row;
|
||||
int numrows;
|
||||
char *filename;
|
||||
enum bufferStatus_e state;
|
||||
int dirty; /**< Has this buffer been modified since last save */
|
||||
struct buffer_t
|
||||
{
|
||||
enum buffer_type type;
|
||||
int buffer_id;
|
||||
int b_lsp_open;
|
||||
int x, y; /**< Position in the file */
|
||||
row_t* row;
|
||||
int numrows;
|
||||
int b_has_changed;
|
||||
char* filename;
|
||||
char* path;
|
||||
char * fullname;
|
||||
enum bufferStatus_e state;
|
||||
int dirty; /**< Has this buffer been modified since last save */
|
||||
};
|
||||
|
||||
/**
|
||||
* \struct editorConfig
|
||||
* \brief Containing our editor state.
|
||||
*/
|
||||
struct editorConfig {
|
||||
int cursor_x, cursor_y; /**< Cursor position */
|
||||
int screenrows; /**< Terminal height*/
|
||||
int screencols; /**< Terminal width*/
|
||||
struct editorConfig
|
||||
{
|
||||
int cursor_x, cursor_y; /**< Cursor position */
|
||||
int screenrows; /**< Terminal height*/
|
||||
int screencols; /**< Terminal width*/
|
||||
|
||||
ScreenLayout layout;
|
||||
ScreenLayout layout;
|
||||
|
||||
row_t *rows; /**< Store all the rows printed */
|
||||
LspClient* lsp_client;
|
||||
CompletionPopup lsp_completion;
|
||||
DiagnosticList lsp_diagnostics;
|
||||
|
||||
ContextBuffer* context_buffers;
|
||||
int dirty;
|
||||
|
||||
int dirty;
|
||||
char* status_msg;
|
||||
time_t status_msg_time;
|
||||
struct termios orig_termios; /**< Terminal communication interface */
|
||||
|
||||
char *status_msg;
|
||||
time_t status_msg_time;
|
||||
struct termios orig_termios; /**< Terminal communication interface */
|
||||
struct const_t constantes;
|
||||
int quit_times_buffer;
|
||||
|
||||
struct const_t constantes;
|
||||
int quit_times_buffer;
|
||||
char* init_file_path;
|
||||
FILE* fd_init_file;
|
||||
Lisp env;
|
||||
LispContext ctx; /** Lisp context */
|
||||
Lisp ctx_data; /** Lisp data context */
|
||||
LispError ctx_error; /** Lisp ctx error */
|
||||
|
||||
char *init_file_path;
|
||||
FILE *fd_init_file;
|
||||
Lisp env;
|
||||
LispContext ctx; /** Lisp context */
|
||||
Lisp ctx_data; /** Lisp data context */
|
||||
LispError ctx_error; /** Lisp ctx error */
|
||||
struct keyBind_t* key_binds;
|
||||
int number_of_keybinds;
|
||||
|
||||
struct keyBind_t *key_binds;
|
||||
int number_of_keybinds;
|
||||
struct prefix_t* prefix;
|
||||
int number_of_prefix;
|
||||
int prefix_state;
|
||||
|
||||
struct prefix_t *prefix;
|
||||
int number_of_prefix;
|
||||
int prefix_state;
|
||||
struct buffer_t buffers[64];
|
||||
int number_of_buffer;
|
||||
|
||||
struct buffer_t buffers[64];
|
||||
int number_of_buffer;
|
||||
|
||||
theme_t theme;
|
||||
theme_t theme;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -156,9 +258,10 @@ struct editorConfig {
|
||||
* \brief Contains text to add before writing to screen.
|
||||
* */
|
||||
|
||||
struct abuf {
|
||||
char *b; /**< Text that will be printed */
|
||||
int len; /**< Length of the text */
|
||||
struct abuf
|
||||
{
|
||||
char* b; /**< Text that will be printed */
|
||||
int len; /**< Length of the text */
|
||||
};
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
@@ -12,8 +12,17 @@
|
||||
#define ERASE_END_LINE "\x1b[K"
|
||||
#define TAB "\t"
|
||||
#define SPACE "\x20"
|
||||
|
||||
/* Uncomment to see debug logs on stderr */
|
||||
#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,
|
||||
@@ -25,6 +34,7 @@ enum editorKey_e {
|
||||
END_LINE,
|
||||
PAGE_UP,
|
||||
PAGE_DOWN,
|
||||
LSP_WAKE_KEY = 2000
|
||||
};
|
||||
|
||||
#define ABUF_INIT {NULL, 0}
|
||||
|
||||
+3
-4
@@ -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()
|
||||
@@ -15,4 +11,7 @@ void initBuiltins();
|
||||
|
||||
void initEditor();
|
||||
|
||||
void deInitEditor();
|
||||
|
||||
|
||||
#endif // INIT_H_
|
||||
|
||||
+2
-2
@@ -1163,7 +1163,7 @@ static Lisp sch_string_ref(Lisp args, LispError* e, LispContext ctx)
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
return lisp_make_char((int)lisp_string_ref(str, lisp_int(index)));
|
||||
return lisp_make_char(lisp_string_ref(str, lisp_int(index)));
|
||||
}
|
||||
|
||||
static Lisp sch_string_set(Lisp args, LispError* e, LispContext ctx)
|
||||
@@ -1737,7 +1737,7 @@ static Lisp sch_pseudo_rand(Lisp args, LispError* e, LispContext ctx)
|
||||
|
||||
static Lisp sch_univeral_time(Lisp args, LispError* e, LispContext ctx)
|
||||
{
|
||||
return lisp_make_int((LispInt)time(NULL));
|
||||
return lisp_make_int(time(NULL));
|
||||
}
|
||||
|
||||
static Lisp sch_is_table(Lisp args, LispError* e, LispContext ctx)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -59,4 +59,8 @@ ScreenLayout *splitScreenGetLayout(void);
|
||||
*/
|
||||
EditorPane *splitScreenGetActivePane(void);
|
||||
|
||||
void freePane(EditorPane *pane);
|
||||
void freeScreenLayout(ScreenLayout *layout);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,7 @@ int utf8Encode(uint32_t cp, char *buf);
|
||||
int utf8Seqlen(unsigned char c);
|
||||
int codepointWidth(uint32_t codepoint);
|
||||
uint32_t utf8Decode(const char** s);
|
||||
int is_word_char(const char *s);
|
||||
|
||||
|
||||
#endif //BELUGA_UTF8_H
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#define _BSD_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <libgen.h>
|
||||
|
||||
#include "include/buffer.h"
|
||||
#include "include/split_screen.h"
|
||||
#include <string.h>
|
||||
@@ -19,31 +21,49 @@
|
||||
#include "include/input.h"
|
||||
#include "include/editor_op.h"
|
||||
#include "include/terminal.h"
|
||||
#include "include/completion.h"
|
||||
#include <signal.h>
|
||||
|
||||
|
||||
struct editorConfig E;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
char * splash_screen = (char *) calloc(256, sizeof(char));
|
||||
char * splash_screen = strdup(getenv("HOME"));
|
||||
int home_path_len = (int) strlen(splash_screen);
|
||||
char * splash_screen_relative_path = strdup("/.beluga/assets/beluga.txt");
|
||||
int splash_screen_relative_path_len = (int) strlen(splash_screen_relative_path);
|
||||
|
||||
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);
|
||||
} else {
|
||||
strcat(splash_screen, getenv("HOME"));
|
||||
strcat(splash_screen, "/.beluga/assets/beluga.txt");
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf;
|
||||
|
||||
splash_screen = realloc(splash_screen, sizeof(char) * (home_path_len + splash_screen_relative_path_len + 1));
|
||||
strcat(splash_screen, "/.beluga/assets/beluga.txt");
|
||||
free(splash_screen_relative_path);
|
||||
|
||||
appDebug("splash : %s\n", splash_screen);
|
||||
active->buffer_id = bufferCreate(splash_screen, READ_ONLY);
|
||||
|
||||
if (argc >= 2) {
|
||||
if (E.constantes.LSP) {
|
||||
|
||||
}
|
||||
active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE);
|
||||
|
||||
buf = &E.buffers[active->buffer_id];
|
||||
|
||||
appDebug("peoject root : %s\n", dirname(buf->fullname));
|
||||
|
||||
|
||||
appDebug("splash : %s\n", splash_screen);
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
active->buffer_id = bufferCreate(splash_screen, READ_ONLY);
|
||||
}
|
||||
free(splash_screen);
|
||||
|
||||
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
|
||||
|
||||
|
||||
while (1) {
|
||||
editorRefreshScreen();
|
||||
editorProcessKeypress();
|
||||
|
||||
+6
-4
@@ -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,13 +24,14 @@ 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'
|
||||
)
|
||||
|
||||
# Executable
|
||||
executable('beluga',
|
||||
src_files,
|
||||
dependencies: [m]
|
||||
)
|
||||
dependencies: [m]
|
||||
dependencies: [m, thread_dep]
|
||||
)
|
||||
|
||||
+1
-1
@@ -11,4 +11,4 @@ void abAppend(struct abuf *ab, const char *s, int len) {
|
||||
ab->len += len;
|
||||
}
|
||||
|
||||
void abFree(struct abuf *ab) { free(ab->b); }
|
||||
void abFree(const struct abuf *ab) { free(ab->b); }
|
||||
|
||||
+23
-8
@@ -8,11 +8,14 @@
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/data.h"
|
||||
#include "include/split_screen.h"
|
||||
#include <_string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <libgen.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "include/completion.h"
|
||||
#include "include/input.h"
|
||||
|
||||
|
||||
@@ -55,18 +58,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)
|
||||
{
|
||||
appDebug("Creating new buffer");
|
||||
char *filename = basename((char *) path);
|
||||
// Check if file is already open
|
||||
const int existing_id = bufferFindByFilename(filename);
|
||||
const int existing_id = bufferFindByFilename(path);
|
||||
if (existing_id != -1)
|
||||
{
|
||||
return bufferSwitch(existing_id);
|
||||
}
|
||||
|
||||
// Check if we have space for more buffers
|
||||
if (E.number_of_buffer >= 64)
|
||||
{
|
||||
@@ -78,16 +81,27 @@ int bufferCreate(const char* filename, enum bufferStatus_e state)
|
||||
struct buffer_t* new_buf = &E.buffers[E.number_of_buffer];
|
||||
new_buf->buffer_id = E.number_of_buffer;
|
||||
new_buf->filename = strdup(filename);
|
||||
new_buf->fullname = malloc(1024 * sizeof(char));
|
||||
realpath(path, new_buf->fullname);
|
||||
new_buf->path = dirname(new_buf->fullname);
|
||||
new_buf->type = FILE_BUFF;
|
||||
new_buf->state = state;
|
||||
new_buf->x = 0;
|
||||
new_buf->y = 0;
|
||||
new_buf->dirty = 0; // New file starts clean
|
||||
new_buf->b_lsp_open = 0;
|
||||
|
||||
// Load file content using existing editorOpen
|
||||
editorOpen(new_buf);
|
||||
|
||||
E.number_of_buffer++;
|
||||
if (new_buf->filename[strlen(new_buf->filename) - 1] == 'c')
|
||||
{
|
||||
if (E.lsp_client->state == LSP_SHUTDOWN)
|
||||
lspStart(E.lsp_client, dirname(new_buf->path));
|
||||
while (E.lsp_client->state != LSP_READY)
|
||||
;
|
||||
lspDidOpen(E.lsp_client, new_buf);
|
||||
}
|
||||
|
||||
editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id);
|
||||
return new_buf->buffer_id;
|
||||
@@ -338,8 +352,8 @@ 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].size = (int) len;
|
||||
buffer->row[at].cap = (int) len + 1;
|
||||
buffer->row[at].chars = malloc(len + 1);
|
||||
if (!buffer->row[at].chars)
|
||||
return;
|
||||
@@ -400,6 +414,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)
|
||||
@@ -462,7 +477,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 */
|
||||
|
||||
+342
-252
@@ -15,12 +15,14 @@
|
||||
#include "../include/input.h"
|
||||
#include "../include/terminal.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include "../include/completion.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "include/completion.h"
|
||||
#include "include/init.h"
|
||||
|
||||
/**
|
||||
* @brief Finds a prefix configuration by name
|
||||
* @details Searches the prefix array for a prefix matching the given name.
|
||||
@@ -29,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];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,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 = realloc(
|
||||
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));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,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;
|
||||
free(E.prefix);
|
||||
for (i = 0; i < E.number_of_keybinds; ++i)
|
||||
{
|
||||
free(E.key_binds[i].key_sequence);
|
||||
}
|
||||
free(E.buffers[i].row);
|
||||
}
|
||||
free(E.key_binds);
|
||||
// bFree layout
|
||||
free(E.layout.panes);
|
||||
|
||||
free(E.init_file_path);
|
||||
fclose(E.fd_init_file);
|
||||
// bFree 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);
|
||||
}
|
||||
free(E.buffers[i].row);
|
||||
}
|
||||
|
||||
free(E.init_file_path);
|
||||
fclose(E.fd_init_file);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,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);
|
||||
lspShutdown(E.lsp_client);
|
||||
lisp_shutdown(E.ctx);
|
||||
deInitEditor();
|
||||
disableRawMode();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,20 +202,23 @@ 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) {
|
||||
|
||||
editorSave();
|
||||
|
||||
return lisp_null();
|
||||
Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx)
|
||||
{
|
||||
editorSave();
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,11 +230,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,12 +246,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,11 +265,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,11 +282,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,9 +299,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,14 +316,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,19 +339,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,16 +368,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);
|
||||
}
|
||||
free(filename);
|
||||
|
||||
return lisp_null();
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,10 +392,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,46 +410,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 = 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);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -425,12 +467,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,12 +485,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,16 +503,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,17 +530,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -505,57 +554,98 @@ 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)
|
||||
{
|
||||
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 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();
|
||||
if (!E.constantes.LSP) {
|
||||
return lisp_null();
|
||||
}
|
||||
// createContextBuffer(E.cursor_x - 2, E.cursor_y + 1, "hello");
|
||||
appDebug("editor-auto-complete\n");
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
struct buffer_t* buffer = bufferFindById(active->buffer_id);
|
||||
E.lsp_completion.visible = 1;
|
||||
lspRequestCompletion(
|
||||
E.lsp_client,
|
||||
buffer,
|
||||
buffer->y, // file line
|
||||
buffer->x, // file col
|
||||
active->cursor_x + active->origin_x + GUTTER_WIDTH, // screen x
|
||||
active->cursor_y + active->origin_y // screen y
|
||||
);
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
Lisp lspDefinition(Lisp args, LispError* e, LispContext ctx)
|
||||
{
|
||||
if (!E.constantes.LSP) {
|
||||
return lisp_null();
|
||||
}
|
||||
(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();
|
||||
}
|
||||
+3207
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,594 @@
|
||||
//
|
||||
// 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/split_screen.h"
|
||||
#include "include/terminal.h"
|
||||
|
||||
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
|
||||
appDebug("[LSP →] Content-Length: %d | %s\n", body_len, json);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
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 = malloc(content_length + 1);
|
||||
int total = 0;
|
||||
while (total < content_length)
|
||||
{
|
||||
int n = read(fd, body + total, content_length - total);
|
||||
if (n <= 0)
|
||||
{
|
||||
free(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)
|
||||
{
|
||||
appDebug("[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");
|
||||
appDebug("[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;
|
||||
appDebug("[LSP ←] NOTIF: %s\n", m);
|
||||
|
||||
if (strcmp(m, "textDocument/publishDiagnostics") == 0)
|
||||
{
|
||||
// Find which buffer this diagnostic belongs to
|
||||
cJSON* params = cJSON_GetObjectItem(root, "params");
|
||||
cJSON* uri = cJSON_GetObjectItem(params, "uri");
|
||||
int buf_id = splitScreenGetActivePane()->buffer_id;
|
||||
|
||||
appDebug("[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");
|
||||
appDebug("[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;
|
||||
appDebug("[LSP ←] RESPONSE id=%d\n", response_id);
|
||||
|
||||
// initialize response → send initialized + mark ready
|
||||
if (lsp->state == LSP_INITIALIZING)
|
||||
{
|
||||
appDebug("[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);
|
||||
appDebug("[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");
|
||||
appDebug(" [%d] kind=%-2d %-40s %s\n",
|
||||
i++,
|
||||
kind ? kind->valueint : 0,
|
||||
label ? label->valuestring : "(no label)",
|
||||
detail ? detail->valuestring : "");
|
||||
if (i >= 10)
|
||||
{
|
||||
appDebug(" ... (%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;
|
||||
|
||||
appDebug("[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;
|
||||
appDebug("[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;
|
||||
}
|
||||
|
||||
appDebug("[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);
|
||||
free(msg);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ─── lifecycle ───────────────────────────────────────────────────────────────
|
||||
|
||||
int lspStart(LspClient *lsp, const char *project_root)
|
||||
{
|
||||
// ── Pipes ─────────────────────────────────────────────────────────────────
|
||||
int to_clangd[2], from_clangd[2];
|
||||
|
||||
if (pipe(to_clangd) < 0 || pipe(from_clangd) < 0) {
|
||||
fprintf(stderr, "[LSP] pipe() failed\n");
|
||||
free(lsp);
|
||||
return 0;
|
||||
}
|
||||
if (pipe(lsp->wake_pipe) < 0) {
|
||||
fprintf(stderr, "[LSP] wake pipe() failed\n");
|
||||
free(lsp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── Fork clangd ───────────────────────────────────────────────────────────
|
||||
lsp->pid = fork();
|
||||
if (lsp->pid < 0) {
|
||||
fprintf(stderr, "[LSP] fork() failed\n");
|
||||
free(lsp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lsp->pid == 0) {
|
||||
// Child — become clangd
|
||||
dup2(to_clangd[0], STDIN_FILENO);
|
||||
dup2(from_clangd[1], STDOUT_FILENO);
|
||||
close(to_clangd[1]);
|
||||
close(from_clangd[0]);
|
||||
close(lsp->wake_pipe[0]);
|
||||
close(lsp->wake_pipe[1]);
|
||||
execlp("clangd", "clangd",
|
||||
"--log=error",
|
||||
"--completion-style=detailed",
|
||||
NULL);
|
||||
fprintf(stderr, "[LSP] execlp failed — is clangd installed?\n");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Parent — keep write end of to_clangd, read end of from_clangd
|
||||
close(to_clangd[0]);
|
||||
close(from_clangd[1]);
|
||||
lsp->write_fd = to_clangd[1];
|
||||
lsp->read_fd = from_clangd[0];
|
||||
lsp->next_id = 1;
|
||||
lsp->state = LSP_INITIALIZING;
|
||||
|
||||
// ── Threading ─────────────────────────────────────────────────────────────
|
||||
pthread_mutex_init(&lsp->lock, NULL);
|
||||
pthread_cond_init (&lsp->ready_cond, NULL);
|
||||
|
||||
// Start reader thread BEFORE sending initialize
|
||||
// so it can handle the response
|
||||
pthread_create(&lsp->reader_thread, NULL, lsp_reader, lsp);
|
||||
|
||||
// ── Send initialize ───────────────────────────────────────────────────────
|
||||
char abs_root[PATH_MAX];
|
||||
if (realpath(project_root, abs_root) == NULL)
|
||||
strncpy(abs_root, project_root, PATH_MAX - 1);
|
||||
|
||||
cJSON *req = cJSON_CreateObject();
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON *caps = cJSON_CreateObject();
|
||||
cJSON *td_caps = cJSON_CreateObject();
|
||||
cJSON *comp_caps = cJSON_CreateObject();
|
||||
cJSON *comp_item = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
|
||||
cJSON_AddStringToObject(req, "method", "initialize");
|
||||
|
||||
// rootUri
|
||||
char root_uri[PATH_MAX + 8];
|
||||
snprintf(root_uri, sizeof(root_uri), "file://%s", abs_root);
|
||||
cJSON_AddNumberToObject(params, "processId", getpid());
|
||||
cJSON_AddStringToObject(params, "rootUri", root_uri);
|
||||
|
||||
// Capabilities — tell clangd what we support
|
||||
cJSON_AddBoolToObject (comp_item, "snippetSupport", 0);
|
||||
cJSON_AddBoolToObject (comp_item, "commitCharactersSupport", 0);
|
||||
cJSON_AddItemToObject (comp_caps, "completionItem", comp_item);
|
||||
cJSON_AddItemToObject (td_caps, "completion", comp_caps);
|
||||
cJSON_AddItemToObject (td_caps, "hover", cJSON_CreateObject());
|
||||
cJSON_AddItemToObject (td_caps, "definition", cJSON_CreateObject());
|
||||
cJSON_AddItemToObject (td_caps, "publishDiagnostics", cJSON_CreateObject());
|
||||
cJSON_AddItemToObject (caps, "textDocument", td_caps);
|
||||
cJSON_AddItemToObject (params, "capabilities", caps);
|
||||
cJSON_AddItemToObject (req, "params", params);
|
||||
|
||||
char *msg = cJSON_PrintUnformatted(req);
|
||||
lsp_send(lsp->write_fd, msg);
|
||||
free(msg);
|
||||
cJSON_Delete(req);
|
||||
|
||||
// ── Wait for LSP_READY ────────────────────────────────────────────────────
|
||||
// Reader thread will handle the initialize response,
|
||||
// send "initialized", and signal ready_cond
|
||||
pthread_mutex_lock(&lsp->lock);
|
||||
while (lsp->state != LSP_READY) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += 5; // 5 second timeout — clangd should respond fast
|
||||
int rc = pthread_cond_timedwait(&lsp->ready_cond, &lsp->lock, &ts);
|
||||
if (rc == ETIMEDOUT) {
|
||||
fprintf(stderr, "[LSP] timeout waiting for initialize response\n");
|
||||
pthread_mutex_unlock(&lsp->lock);
|
||||
// Don't kill clangd — it might still come up, just return what we have
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&lsp->lock);
|
||||
|
||||
fprintf(stderr, "[LSP] ready — clangd initialized at %s\n", abs_root);
|
||||
return 1;
|
||||
}
|
||||
// ─── document sync ───────────────────────────────────────────────────────────
|
||||
|
||||
// Build the full buffer text into a bAlloc'd string
|
||||
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 = malloc(total + 1);
|
||||
char* p = text;
|
||||
for (int i = 0; i < buf->numrows; i++)
|
||||
{
|
||||
memcpy(p, buf->row[i].chars, buf->row[i].size);
|
||||
p += buf->row[i].size;
|
||||
*p++ = '\n';
|
||||
}
|
||||
*p = '\0';
|
||||
return text;
|
||||
}
|
||||
|
||||
void lspDidOpen(LspClient* lsp, struct buffer_t* buf)
|
||||
{
|
||||
appDebug("[LSP] opening file");
|
||||
if (lsp->state != LSP_READY || buf->b_lsp_open) return;
|
||||
|
||||
|
||||
char uri[PATH_MAX + 8];
|
||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||
|
||||
const char* lang = "c";
|
||||
if (strstr(buf->filename, ".cpp") || strstr(buf->filename, ".cc"))
|
||||
lang = "cpp";
|
||||
|
||||
char* raw = buffer_to_text(buf);
|
||||
|
||||
// Let cJSON handle ALL escaping — don't touch the text yourself
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON* 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);
|
||||
|
||||
free(msg);
|
||||
free(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 uri[PATH_MAX + 8];
|
||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||
|
||||
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);
|
||||
|
||||
free(msg);
|
||||
free(raw);
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
void lspDidClose(LspClient* lsp, struct buffer_t* buf)
|
||||
{
|
||||
|
||||
char uri[PATH_MAX + 8];
|
||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||
|
||||
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);
|
||||
free(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;
|
||||
lsp->completion_cursor_y = screen_y;
|
||||
|
||||
appDebug("LSP REQUEST COMP");
|
||||
|
||||
char* msg;
|
||||
char uri[PATH_MAX + 8];
|
||||
snprintf(uri, sizeof(uri), "file://%s", buf->fullname);
|
||||
appDebug("FULLNAME : %s\n", buf->fullname);
|
||||
|
||||
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);
|
||||
free(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);
|
||||
free(msg);
|
||||
}
|
||||
|
||||
void lspShutdown(LspClient *lsp)
|
||||
{
|
||||
if (!lsp || lsp->state == LSP_SHUTDOWN) return;
|
||||
|
||||
lsp->state = LSP_SHUTDOWN;
|
||||
|
||||
// 1. Send didClose for all open buffers
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
if (E.buffers[i].b_lsp_open)
|
||||
lspDidClose(lsp, &E.buffers[i]);
|
||||
}
|
||||
|
||||
// 2. 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);
|
||||
free(msg);
|
||||
cJSON_Delete(req);
|
||||
|
||||
// 3. Wait briefly for the shutdown response (2s timeout)
|
||||
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);
|
||||
free(resp);
|
||||
}
|
||||
|
||||
// 4. Send exit notification
|
||||
lsp_send(lsp->write_fd,
|
||||
"{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}");
|
||||
|
||||
// 5. Close write pipe first — reader thread will get EOF and exit
|
||||
close(lsp->write_fd);
|
||||
lsp->write_fd = -1;
|
||||
|
||||
// 6. Wake the main loop so it doesn't stay blocked in select()
|
||||
write(lsp->wake_pipe[1], "q", 1);
|
||||
|
||||
// 7. Wait for reader thread to finish
|
||||
pthread_join(lsp->reader_thread, NULL);
|
||||
|
||||
// 8. Close remaining fds
|
||||
close(lsp->read_fd);
|
||||
lsp->read_fd = -1;
|
||||
close(lsp->wake_pipe[0]);
|
||||
close(lsp->wake_pipe[1]);
|
||||
|
||||
// 9. Destroy synchronization primitives
|
||||
pthread_mutex_destroy(&lsp->lock);
|
||||
pthread_cond_destroy (&lsp->ready_cond);
|
||||
|
||||
// 10. Reap the clangd process
|
||||
waitpid(lsp->pid, NULL, 0);
|
||||
|
||||
free(lsp);
|
||||
}
|
||||
@@ -73,6 +73,8 @@ int editorMoveCursor(int key) {
|
||||
buf->x = buf->row[buf->y].size;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
+10
-6
@@ -17,11 +17,9 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
/**
|
||||
* @brief Closes the current file and resets editor state
|
||||
@@ -54,7 +52,7 @@ void editorCloseFile(void) {
|
||||
* @details Loads file content into editor rows, one line per row. If another
|
||||
* file is already open, it is closed first (without saving). File is opened in
|
||||
* a+ (read/append) mode to allow both reading and modification.
|
||||
* @param filename Path to the file to open (relative or absolute)
|
||||
* @param buffer Path to the file to open (relative or absolute)
|
||||
* @note Updates global editor state E
|
||||
* @note Calls die() on file open failure
|
||||
* @note Newline characters are stripped from loaded lines
|
||||
@@ -62,8 +60,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");
|
||||
|
||||
@@ -110,11 +114,11 @@ void editorSave() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
fd = open(buffer->filename, O_RDWR | O_CREAT, 0644);
|
||||
fd = open(buffer->fullname, O_RDWR | O_CREAT, 0644);
|
||||
if (fd != -1) {
|
||||
for (int i = 0; i < buffer->numrows; ++i)
|
||||
{
|
||||
len = strlen(buffer->row[i].chars);
|
||||
len = (int) strlen(buffer->row[i].chars);
|
||||
if (write(fd, buffer->row[i].chars, len) != len) {
|
||||
close(fd);
|
||||
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
|
||||
|
||||
+23
-2
@@ -49,6 +49,9 @@ void initBuiltins() {
|
||||
registerBuiltin("editor-cut-end-line", editorCutEndLine);
|
||||
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() {
|
||||
@@ -71,6 +74,8 @@ void initConfig() {
|
||||
void initTheme() {
|
||||
E.constantes.THEME = (char *)lisp_string(
|
||||
lisp_eval(lisp_read("THEME", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx));
|
||||
E.constantes.LSP = lisp_bool(lisp_eval(lisp_read("LSP", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx));
|
||||
appDebug("LSP ON : %d", E.constantes.LSP);
|
||||
if (strcmp(E.constantes.THEME, "dark") == 0) {
|
||||
E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(40, 44, 52);
|
||||
E.theme.COLOR_KEYWORD = ANSI_FG_RGB(198, 120, 221);
|
||||
@@ -95,7 +100,7 @@ void initEditor() {
|
||||
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
|
||||
die("getWindowSize");
|
||||
}
|
||||
appDebug("%d %d\n", E.screenrows, E.screencols);
|
||||
appDebug("%d %d", E.screenrows, E.screencols);
|
||||
E.screenrows -= 2;
|
||||
|
||||
|
||||
@@ -132,8 +137,10 @@ void initEditor() {
|
||||
strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
|
||||
E.prefix_state = 0;
|
||||
|
||||
initConfig();
|
||||
E.lsp_client = (LspClient*)malloc(sizeof(LspClient));
|
||||
E.lsp_client->state = LSP_SHUTDOWN;
|
||||
|
||||
initConfig();
|
||||
initTheme();
|
||||
|
||||
// To modify
|
||||
@@ -149,3 +156,17 @@ void initEditor() {
|
||||
|
||||
E.quit_times_buffer = E.constantes.QUIT_TIMES;
|
||||
}
|
||||
|
||||
void deInitEditor()
|
||||
{
|
||||
freeScreenLayout(&E.layout);
|
||||
free(E.lsp_client);
|
||||
free(E.status_msg);
|
||||
free(E.init_file_path);
|
||||
free(E.key_binds);
|
||||
for (int i = 0; i < E.number_of_keybinds; i++)
|
||||
{
|
||||
free(E.key_binds[i].key_sequence);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+78
-20
@@ -2,29 +2,26 @@
|
||||
#include "../include/define.h"
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/output.h"
|
||||
#include "include/data.h"
|
||||
#include "include/buffer.h"
|
||||
#include "include/builtins.h"
|
||||
#include "../include/completion.h"
|
||||
#include "include/data.h"
|
||||
#include "include/split_screen.h"
|
||||
#include <ctype.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "include/terminal.h"
|
||||
#include "include/utf8.h"
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
/**
|
||||
* @file input.c
|
||||
* @brief Input handling module for the Beluga text editor
|
||||
* @details Manages user input processing, key bindings, cursor movement, and file path completion
|
||||
* @details Manages user input processing, key bindings, cursor movement, and
|
||||
* file path completion
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -33,11 +30,12 @@ extern struct editorConfig E;
|
||||
* the first file or directory entry that matches the filename prefix.
|
||||
* Appends a trailing slash for directory entries.
|
||||
* @param path The file path to complete (can be relative or absolute)
|
||||
* @return Pointer to the completed file path (dynamically allocated), or NULL if:
|
||||
* @return Pointer to the completed file path (dynamically allocated), or NULL
|
||||
* if:
|
||||
* - path ends with '/' (already a directory)
|
||||
* - no matching entries found
|
||||
* - directory cannot be opened
|
||||
* @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) {
|
||||
@@ -60,7 +58,7 @@ const char *fileCompletion(const char *path) {
|
||||
if (last_slash) {
|
||||
dir_len = last_slash - path + 1; // length of dir_path
|
||||
strncpy(directory, path, dir_len);
|
||||
predict_len = strlen(path) - dir_len;
|
||||
predict_len = (int)(strlen(path) - dir_len);
|
||||
strncpy(predict, last_slash + 1, predict_len);
|
||||
directory[dir_len] = '\0';
|
||||
predict[predict_len] = '\0';
|
||||
@@ -99,16 +97,16 @@ const char *fileCompletion(const char *path) {
|
||||
|
||||
/**
|
||||
* @brief Displays an interactive prompt and returns user input
|
||||
* @details Allows the user to enter text in a prompt with optional path completion
|
||||
* via Tab key. Supports backspace, delete, and escape key handling. Dynamically
|
||||
* allocates memory for the input buffer.
|
||||
* @details Allows the user to enter text in a prompt with optional path
|
||||
* completion via Tab key. Supports backspace, delete, and escape key handling.
|
||||
* Dynamically allocates memory for the input buffer.
|
||||
* @param prompt The prompt message format string (printf-style)
|
||||
* @param placeHolder Initial text to display in the input buffer
|
||||
* @param bPathMode If non-zero, enables Tab key file path completion
|
||||
* @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) {
|
||||
@@ -151,8 +149,7 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||
strcpy(path, buf);
|
||||
}
|
||||
memset(buf, 0, 256);
|
||||
buf_len = 0;
|
||||
char * buf_complete = (char *) fileCompletion(path);
|
||||
char *buf_complete = (char *)fileCompletion(path);
|
||||
strcpy(buf, buf_complete);
|
||||
free(buf_complete);
|
||||
buf_len = strlen(buf);
|
||||
@@ -163,14 +160,12 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||
buf_size *= 2;
|
||||
buf = realloc(buf, buf_size);
|
||||
}
|
||||
buf[buf_len++] = c;
|
||||
buf[buf_len++] = (char)c;
|
||||
buf[buf_len] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Executes the command bound to a key sequence
|
||||
* @details Searches the keybinding table for a matching key sequence and
|
||||
@@ -207,11 +202,64 @@ int executeKeyBind(char *key_sequence) {
|
||||
* and either executes the bound command or inserts the character. Resets
|
||||
* the quit buffer counter on successful key processing.
|
||||
* @note Updates global editor state E
|
||||
* @note Calls editorReadKey() to get input and editorInsertChar() for unbound keys
|
||||
* @note Calls editorReadKey() to get input and editorInsertChar() for unbound
|
||||
* keys
|
||||
*/
|
||||
void editorProcessKeypress() {
|
||||
int c = editorReadKey();
|
||||
char key_sequence[8];
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
|
||||
if (E.constantes.LSP && buf->b_lsp_open) {
|
||||
if (c == LSP_WAKE_KEY)
|
||||
return;
|
||||
if (E.lsp_client && E.lsp_completion.visible) {
|
||||
if (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];
|
||||
|
||||
|
||||
// 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;
|
||||
@@ -219,5 +267,15 @@ void editorProcessKeypress() {
|
||||
int seq_len = utf8Encode(c, key_sequence);
|
||||
appDebug("key seq : %s\n", key_sequence);
|
||||
bufferInsertBytes(key_sequence, seq_len);
|
||||
if (buf->b_lsp_open && is_word_char(key_sequence)) {
|
||||
|
||||
if (E.lsp_client && E.lsp_client->state == LSP_READY) {
|
||||
lspDidChange(E.lsp_client, buf);
|
||||
|
||||
E.lsp_client->completion_just_arrived = 0; // consume the flag
|
||||
}
|
||||
buf->b_has_changed = 0;
|
||||
editorAutoComplete(lisp_null(), &E.ctx_error, E.ctx);
|
||||
}
|
||||
E.quit_times_buffer = E.constantes.QUIT_TIMES;
|
||||
}
|
||||
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
//
|
||||
// Created by Giorgio on 27/05/2026.
|
||||
//
|
||||
|
||||
#include "include/lsp_ui.h"
|
||||
#include "include/data.h"
|
||||
#include <stdio.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"
|
||||
#include "include/terminal.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) {
|
||||
appDebug("[DRAW] visible=%d count=%d origin=(%d,%d)\n",
|
||||
popup->visible, popup->count,
|
||||
popup->origin_x, popup->origin_y);
|
||||
if (!popup->visible || popup->count == 0) return;
|
||||
|
||||
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':
|
||||
case '\r': // ESC — dismiss
|
||||
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 + 1;
|
||||
popup->origin_y = screen_y + 2; // one row below cursor
|
||||
|
||||
cJSON *root = cJSON_Parse(json);
|
||||
if (!root) return;
|
||||
cJSON *result = cJSON_GetObjectItem(root, "result");
|
||||
if (!result) { cJSON_Delete(root); return; }
|
||||
// result can be a list or {isIncomplete, items:[…]}
|
||||
cJSON *items = cJSON_IsArray(result)
|
||||
? result
|
||||
: 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);
|
||||
|
||||
if (!kind)
|
||||
ci->kind = 0;
|
||||
else
|
||||
ci->kind = kind->valueint;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const char *raw = msg ? msg->valuestring : "";
|
||||
int i = 0;
|
||||
while (raw[i] && raw[i] != '\n' && i < 255)
|
||||
{
|
||||
diag->message[i] = raw[i];
|
||||
i++;
|
||||
}
|
||||
diag->message[i] = '\0';
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
+78
-36
@@ -15,13 +15,12 @@
|
||||
#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>
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
/**
|
||||
* @brief Renders a single pane with its buffer content
|
||||
*/
|
||||
@@ -52,7 +51,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,6 +69,9 @@ static void editorDrawPane(struct abuf* ab, EditorPane* pane)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (E.constantes.LSP) {
|
||||
lspUiDrawGutter(ab, &E.lsp_diagnostics, pane->buffer_id, file_row);
|
||||
}
|
||||
|
||||
|
||||
if (buf->filename[strlen(buf->filename) - 1] == 'c' || buf->filename[strlen(buf->filename) - 1] == 'h')
|
||||
@@ -130,7 +132,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 +142,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 +182,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 +196,8 @@ void editorScroll()
|
||||
if (active->cursor_x == active->width - 1)
|
||||
{
|
||||
active->x_offset++;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
active->cursor_x++;
|
||||
}
|
||||
@@ -204,7 +209,8 @@ void editorScroll()
|
||||
if (active->cursor_y == 0 && active->y_offset)
|
||||
{
|
||||
active->y_offset--;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
active->cursor_y--;
|
||||
}
|
||||
@@ -215,25 +221,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;
|
||||
}
|
||||
}
|
||||
@@ -296,11 +302,8 @@ void editorDrawStatusBar(struct abuf* ab)
|
||||
abAppend(ab, render_status, render_len);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
abAppend(ab, " ", 1);
|
||||
++len;
|
||||
}
|
||||
abAppend(ab, " ", 1);
|
||||
++len;
|
||||
}
|
||||
|
||||
abAppend(ab, "\x1b[m", 3); // normal text mode
|
||||
@@ -316,7 +319,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)
|
||||
{
|
||||
@@ -328,7 +331,6 @@ void editorDrawMessageBar(struct abuf* ab)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Performs complete screen refresh and buffer synchronization
|
||||
* @details Clears screen, redraws all visible content (rows, status bar,
|
||||
@@ -348,27 +350,67 @@ 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
|
||||
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
struct buffer_t* buffer = bufferFindById(active->buffer_id);
|
||||
|
||||
editorDrawAllPanes(&ab);
|
||||
if (E.constantes.LSP) {
|
||||
|
||||
// ── LSP: draw completion popup every frame while visible ──────────────────
|
||||
appDebug("[REFRESH] lsp_completion.visible=%d\n",
|
||||
E.lsp_completion.visible);
|
||||
while (E.lsp_client->completion_requested && !E.lsp_client->completion_just_arrived);
|
||||
// reset flags
|
||||
E.lsp_client->completion_just_arrived = 0;
|
||||
E.lsp_client->completion_requested = 0;
|
||||
|
||||
// ── LSP: diagnostic for current line in status bar ────────────────────────
|
||||
const char* diag = lspUiDiagnosticAtCursor(
|
||||
&E.lsp_diagnostics,
|
||||
active->buffer_id,
|
||||
buffer->y);
|
||||
if (diag) {
|
||||
char single_line[512];
|
||||
int i = 0;
|
||||
|
||||
// Copy until newline, \0, or screen width
|
||||
while (diag[i] && diag[i] != '\n' && i < E.screencols - 4)
|
||||
{
|
||||
single_line[i] = diag[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
// If message was truncated, add ellipsis
|
||||
if (diag[i] != '\0' && diag[i] != '\n')
|
||||
{
|
||||
single_line[i++] = '.';
|
||||
single_line[i++] = '.';
|
||||
single_line[i++] = '.';
|
||||
}
|
||||
single_line[i] = '\0';
|
||||
editorSetStatusMessage("● %s", single_line);
|
||||
}
|
||||
|
||||
}
|
||||
editorDrawStatusBar(&ab);
|
||||
editorDrawMessageBar(&ab);
|
||||
// editorDrawContextBuffer(&ab);
|
||||
|
||||
// Position cursor in active pane
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
if (active != NULL)
|
||||
if (E.constantes.LSP && (E.lsp_client && E.lsp_client->state == LSP_READY))
|
||||
{
|
||||
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));
|
||||
lspUiDrawCompletion(&ab, &E.lsp_completion);
|
||||
}
|
||||
|
||||
|
||||
// ── 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 + (E.constantes.LSP ? GUTTER_WIDTH : 0));
|
||||
abAppend(&ab, buf, (int)strlen(buf));
|
||||
|
||||
abAppend(&ab, SHOW_CURSOR, 6);
|
||||
write(STDOUT_FILENO, ab.b, ab.len);
|
||||
abFree(&ab);
|
||||
|
||||
+13
-2
@@ -9,6 +9,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
/**
|
||||
@@ -48,7 +49,7 @@ int splitScreenVertical(int buffer_id_left, int buffer_id_right) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reallocate panes array
|
||||
// bReallocate panes array
|
||||
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
|
||||
E.layout.mode = SPLIT_VERTICAL;
|
||||
E.layout.num_panes = 2;
|
||||
@@ -99,7 +100,7 @@ int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reallocate panes array
|
||||
// bReallocate panes array
|
||||
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
|
||||
E.layout.mode = SPLIT_HORIZONTAL;
|
||||
E.layout.num_panes = 2;
|
||||
@@ -216,3 +217,13 @@ EditorPane *splitScreenGetActivePane(void) {
|
||||
if (E.layout.num_panes == 0) return NULL;
|
||||
return &E.layout.panes[E.layout.active_pane];
|
||||
}
|
||||
|
||||
void freeScreenLayout(ScreenLayout *layout)
|
||||
{
|
||||
free(layout->panes);
|
||||
}
|
||||
|
||||
void freePane(EditorPane *pane)
|
||||
{
|
||||
free(pane);
|
||||
}
|
||||
|
||||
+25
-25
@@ -5,6 +5,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
const char *c_keywords[] = {
|
||||
@@ -32,6 +33,30 @@ static int utf8_char_len(const char *s)
|
||||
return 1; // continuation byte or invalid — advance 1 to avoid infinite loop
|
||||
}
|
||||
|
||||
int is_word_char(const char *s)
|
||||
{
|
||||
uint32_t cp = utf8Decode(&s);
|
||||
|
||||
if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') ||
|
||||
(cp >= '0' && cp <= '9') || cp == '_' || cp == '#')
|
||||
return 1;
|
||||
|
||||
if (cp == 0xFFFD) return 0;
|
||||
|
||||
if (cp >= 0x00C0 && cp <= 0x017F) return 1;
|
||||
if (cp >= 0x0370 && cp <= 0x03FF) return 1;
|
||||
if (cp >= 0x0400 && cp <= 0x04FF) return 1;
|
||||
if (cp >= 0x0600 && cp <= 0x06FF) return 1;
|
||||
if (cp >= 0x05D0 && cp <= 0x05EA) return 1;
|
||||
if (cp >= 0x0900 && cp <= 0x097F) return 1;
|
||||
if (cp >= 0x4E00 && cp <= 0x9FFF) return 1;
|
||||
if ((cp >= 0x3040 && cp <= 0x309F) ||
|
||||
(cp >= 0x30A0 && cp <= 0x30FF)) return 1;
|
||||
if (cp >= 0xAC00 && cp <= 0xD7A3) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy one full UTF-8 character from src+i into dst+pos, advance both indices.
|
||||
static void copy_utf8_char(char *dst, int *dst_pos, const char *src, int *src_pos)
|
||||
{
|
||||
@@ -41,31 +66,6 @@ static void copy_utf8_char(char *dst, int *dst_pos, const char *src, int *src_po
|
||||
}
|
||||
|
||||
// Check if character is alphanumeric or underscore
|
||||
int is_word_char(const char *s)
|
||||
{
|
||||
uint32_t cp = utf8Decode(&s);
|
||||
|
||||
if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') ||
|
||||
(cp >= '0' && cp <= '9') || cp == '_' || cp == '#')
|
||||
return 1;
|
||||
|
||||
if (cp == 0xFFFD) return 0;
|
||||
|
||||
if (cp >= 0x00C0 && cp <= 0x017F) return 1;
|
||||
if (cp >= 0x0370 && cp <= 0x03FF) return 1;
|
||||
if (cp >= 0x0400 && cp <= 0x04FF) return 1;
|
||||
if (cp >= 0x0600 && cp <= 0x06FF) return 1;
|
||||
if (cp >= 0x05D0 && cp <= 0x05EA) return 1;
|
||||
if (cp >= 0x0900 && cp <= 0x097F) return 1;
|
||||
if (cp >= 0x4E00 && cp <= 0x9FFF) return 1;
|
||||
if ((cp >= 0x3040 && cp <= 0x309F) ||
|
||||
(cp >= 0x30A0 && cp <= 0x30FF)) return 1;
|
||||
if (cp >= 0xAC00 && cp <= 0xD7A3) return 1;
|
||||
if ((cp >= 0x0660 && cp <= 0x0669) ||
|
||||
(cp >= 0x06F0 && cp <= 0x06F9)) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if string is a keyword
|
||||
int is_keyword(const char *word) {
|
||||
|
||||
+272
-189
@@ -10,229 +10,312 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "include/utf8.h"
|
||||
#include "../include/buffer.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include "../include/utf8.h"
|
||||
|
||||
void die(const char *s) {
|
||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
||||
lisp_shutdown(E.ctx);
|
||||
perror(s);
|
||||
exit(1);
|
||||
void die(const char* s)
|
||||
{
|
||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
||||
lisp_shutdown(E.ctx);
|
||||
perror(s);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void disableRawMode() {
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) {
|
||||
die("tcsetattr");
|
||||
}
|
||||
void disableRawMode()
|
||||
{
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1)
|
||||
{
|
||||
die("tcsetattr");
|
||||
}
|
||||
}
|
||||
|
||||
void enableRawMode() {
|
||||
if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) {
|
||||
die("tcgetattr");
|
||||
}
|
||||
void enableRawMode()
|
||||
{
|
||||
if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1)
|
||||
{
|
||||
die("tcgetattr");
|
||||
}
|
||||
|
||||
struct termios raw = E.orig_termios;
|
||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
raw.c_cflag |= (CS8);
|
||||
raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
|
||||
raw.c_cc[VMIN] = 0;
|
||||
raw.c_cc[VTIME] = 1;
|
||||
struct termios raw = E.orig_termios;
|
||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
raw.c_cflag |= (CS8);
|
||||
raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
|
||||
raw.c_cc[VMIN] = 0;
|
||||
raw.c_cc[VTIME] = 1;
|
||||
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
|
||||
die("tcgetattr");
|
||||
}
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
|
||||
{
|
||||
die("tcgetattr");
|
||||
}
|
||||
}
|
||||
|
||||
#include <ctype.h> /* isprint */
|
||||
|
||||
char *keyToString(int key) {
|
||||
static char key_str[32];
|
||||
char* keyToString(int key)
|
||||
{
|
||||
static char key_str[32];
|
||||
|
||||
if (key == '\r') {
|
||||
strcpy(key_str, "ENTER");
|
||||
} else if (key == 0x09) {
|
||||
strcpy(key_str, "TAB");
|
||||
} else if (key >= 1 && key <= 26) {
|
||||
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
|
||||
} else {
|
||||
switch (key) {
|
||||
case ARROW_UP:
|
||||
strcpy(key_str, "ARROW-UP");
|
||||
break;
|
||||
case ARROW_DOWN:
|
||||
strcpy(key_str, "ARROW-DOWN");
|
||||
break;
|
||||
case ARROW_LEFT:
|
||||
strcpy(key_str, "ARROW-LEFT");
|
||||
break;
|
||||
case ARROW_RIGHT:
|
||||
strcpy(key_str, "ARROW-RIGHT");
|
||||
break;
|
||||
case PAGE_UP:
|
||||
strcpy(key_str, "PAGE-UP");
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
strcpy(key_str, "PAGE-DOWN");
|
||||
break;
|
||||
case DEL_KEY:
|
||||
strcpy(key_str, "DEL");
|
||||
break;
|
||||
case BACKSPACE:
|
||||
strcpy(key_str, "BACKSPACE");
|
||||
break;
|
||||
case BEG_LINE:
|
||||
strcpy(key_str, "HOME");
|
||||
break;
|
||||
case END_LINE:
|
||||
strcpy(key_str, "END");
|
||||
break;
|
||||
case '\x1b':
|
||||
strcpy(key_str, "ESCAPE");
|
||||
break;
|
||||
default:
|
||||
if (key > 127) {
|
||||
/* UTF-8 code point — re-encode into the buffer */
|
||||
char buf[5] = {0};
|
||||
int n = utf8Encode((uint32_t)key, buf);
|
||||
snprintf(key_str, sizeof(key_str), "%.*s", n, buf);
|
||||
} else if (isprint(key)) {
|
||||
snprintf(key_str, sizeof(key_str), "%c", key);
|
||||
} else {
|
||||
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
|
||||
}
|
||||
if (key == '\r')
|
||||
{
|
||||
strcpy(key_str, "ENTER");
|
||||
}
|
||||
}
|
||||
return key_str;
|
||||
}
|
||||
|
||||
int editorReadKey() {
|
||||
char c;
|
||||
/* read first byte — may be start of UTF-8 or escape */
|
||||
while (read(STDIN_FILENO, &c, 1) != 1)
|
||||
;
|
||||
appDebug("f : %hhu %ld\r\n", c, 0x200);
|
||||
|
||||
if (c == '\x1b') {
|
||||
char seq[6];
|
||||
/* try to read escape sequence */
|
||||
if (read(STDIN_FILENO, &seq[0], 1) != 1)
|
||||
return '\x1b';
|
||||
if (read(STDIN_FILENO, &seq[1], 1) != 1)
|
||||
return '\x1b';
|
||||
appDebug("f2 : %s\r\n", seq);
|
||||
|
||||
if (seq[0] == '[') {
|
||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||
if (read(STDIN_FILENO, &seq[2], 1) != 1)
|
||||
return '\x1b';
|
||||
if (seq[2] == '~') {
|
||||
switch (seq[1]) {
|
||||
case '1':
|
||||
return BEG_LINE;
|
||||
case '3':
|
||||
return DEL_KEY;
|
||||
case '4':
|
||||
return END_LINE;
|
||||
case '5':
|
||||
return PAGE_UP;
|
||||
case '6':
|
||||
return PAGE_DOWN;
|
||||
case '7':
|
||||
return BEG_LINE;
|
||||
case '8':
|
||||
return END_LINE;
|
||||
}
|
||||
else if (key == 0x09)
|
||||
{
|
||||
strcpy(key_str, "TAB");
|
||||
}
|
||||
else if (key >= 1 && key <= 26)
|
||||
{
|
||||
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case ARROW_UP:
|
||||
strcpy(key_str, "ARROW-UP");
|
||||
break;
|
||||
case ARROW_DOWN:
|
||||
strcpy(key_str, "ARROW-DOWN");
|
||||
break;
|
||||
case ARROW_LEFT:
|
||||
strcpy(key_str, "ARROW-LEFT");
|
||||
break;
|
||||
case ARROW_RIGHT:
|
||||
strcpy(key_str, "ARROW-RIGHT");
|
||||
break;
|
||||
case PAGE_UP:
|
||||
strcpy(key_str, "PAGE-UP");
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
strcpy(key_str, "PAGE-DOWN");
|
||||
break;
|
||||
case DEL_KEY:
|
||||
strcpy(key_str, "DEL");
|
||||
break;
|
||||
case BACKSPACE:
|
||||
strcpy(key_str, "BACKSPACE");
|
||||
break;
|
||||
case BEG_LINE:
|
||||
strcpy(key_str, "HOME");
|
||||
break;
|
||||
case END_LINE:
|
||||
strcpy(key_str, "END");
|
||||
break;
|
||||
case '\x1b':
|
||||
strcpy(key_str, "ESCAPE");
|
||||
break;
|
||||
default:
|
||||
if (key > 127)
|
||||
{
|
||||
/* UTF-8 code point — re-encode into the buffer */
|
||||
char buf[5] = {0};
|
||||
int n = utf8Encode((uint32_t)key, buf);
|
||||
snprintf(key_str, sizeof(key_str), "%.*s", n, buf);
|
||||
}
|
||||
else if (isprint(key))
|
||||
{
|
||||
snprintf(key_str, sizeof(key_str), "%c", key);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (seq[1]) {
|
||||
case 'A':
|
||||
return ARROW_UP;
|
||||
case 'B':
|
||||
return ARROW_DOWN;
|
||||
case 'C':
|
||||
return ARROW_RIGHT;
|
||||
case 'D':
|
||||
return ARROW_LEFT;
|
||||
case 'H':
|
||||
return BEG_LINE;
|
||||
case 'F':
|
||||
return END_LINE;
|
||||
}
|
||||
return key_str;
|
||||
}
|
||||
|
||||
int editorReadKey()
|
||||
{
|
||||
char c;
|
||||
int nread;
|
||||
|
||||
while (1)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
|
||||
int max_fd = STDIN_FILENO;
|
||||
|
||||
// Only watch wake pipe if LSP is ready AND active buffer is open
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
struct buffer_t* buf = active ? bufferFindById(active->buffer_id) : NULL;
|
||||
int lsp_active = E.lsp_client
|
||||
&& E.lsp_client->wake_pipe[0] > 0
|
||||
&& E.lsp_client->state == LSP_READY
|
||||
&& buf
|
||||
&& buf->b_lsp_open; // ← only if this buffer is tracked
|
||||
|
||||
if (lsp_active)
|
||||
{
|
||||
FD_SET(E.lsp_client->wake_pipe[0], &fds);
|
||||
if (E.lsp_client->wake_pipe[0] > max_fd)
|
||||
max_fd = E.lsp_client->wake_pipe[0];
|
||||
}
|
||||
|
||||
int ready = select(max_fd + 1, &fds, NULL, NULL, NULL);
|
||||
if (ready <= 0) continue;
|
||||
|
||||
if (lsp_active && FD_ISSET(E.lsp_client->wake_pipe[0], &fds))
|
||||
{
|
||||
char tmp[16];
|
||||
read(E.lsp_client->wake_pipe[0], tmp, sizeof(tmp));
|
||||
return LSP_WAKE_KEY;
|
||||
}
|
||||
|
||||
if (FD_ISSET(STDIN_FILENO, &fds))
|
||||
{
|
||||
nread = read(STDIN_FILENO, &c, 1);
|
||||
if (nread == 1) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '\x1b';
|
||||
}
|
||||
|
||||
/* multi-byte UTF-8: read remaining bytes */
|
||||
int seqlen = utf8Seqlen((unsigned char)c);
|
||||
if (seqlen > 1) {
|
||||
/* pack into a pseudo-codepoint just to pass bytes through;
|
||||
we handle encoding/decoding at the row level */
|
||||
char buf[4] = {c, 0, 0, 0};
|
||||
for (int i = 1; i < seqlen; i++)
|
||||
if (read(STDIN_FILENO, &buf[i], 1) != 1)
|
||||
break;
|
||||
/* decode and return as uint32, but we need int — use high range */
|
||||
const char *p = buf;
|
||||
uint32_t cp = utf8Decode(&p);
|
||||
return (int)cp; /* caller re-encodes when inserting */
|
||||
}
|
||||
|
||||
return (unsigned char)c;
|
||||
appDebug("f : %hhu %ld\r\n", c, 0x200);
|
||||
|
||||
if (c == '\x1b')
|
||||
{
|
||||
char seq[6];
|
||||
/* try to read escape sequence */
|
||||
if (read(STDIN_FILENO, &seq[0], 1) != 1)
|
||||
return '\x1b';
|
||||
if (read(STDIN_FILENO, &seq[1], 1) != 1)
|
||||
return '\x1b';
|
||||
appDebug("f2 : %s\r\n", seq);
|
||||
|
||||
if (seq[0] == '[')
|
||||
{
|
||||
if (seq[1] >= '0' && seq[1] <= '9')
|
||||
{
|
||||
if (read(STDIN_FILENO, &seq[2], 1) != 1)
|
||||
return '\x1b';
|
||||
if (seq[2] == '~')
|
||||
{
|
||||
switch (seq[1])
|
||||
{
|
||||
case '1':
|
||||
return BEG_LINE;
|
||||
case '3':
|
||||
return DEL_KEY;
|
||||
case '4':
|
||||
return END_LINE;
|
||||
case '5':
|
||||
return PAGE_UP;
|
||||
case '6':
|
||||
return PAGE_DOWN;
|
||||
case '7':
|
||||
return BEG_LINE;
|
||||
case '8':
|
||||
return END_LINE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (seq[1])
|
||||
{
|
||||
case 'A':
|
||||
return ARROW_UP;
|
||||
case 'B':
|
||||
return ARROW_DOWN;
|
||||
case 'C':
|
||||
return ARROW_RIGHT;
|
||||
case 'D':
|
||||
return ARROW_LEFT;
|
||||
case 'H':
|
||||
return BEG_LINE;
|
||||
case 'F':
|
||||
return END_LINE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '\x1b';
|
||||
}
|
||||
|
||||
/* multi-byte UTF-8: read remaining bytes */
|
||||
int seqlen = utf8Seqlen((unsigned char)c);
|
||||
if (seqlen > 1)
|
||||
{
|
||||
/* pack into a pseudo-codepoint just to pass bytes through;
|
||||
we handle encoding/decoding at the row level */
|
||||
char buf[4] = {c, 0, 0, 0};
|
||||
for (int i = 1; i < seqlen; i++)
|
||||
if (read(STDIN_FILENO, &buf[i], 1) != 1)
|
||||
break;
|
||||
/* decode and return as uint32, but we need int — use high range */
|
||||
const char* p = buf;
|
||||
uint32_t cp = utf8Decode(&p);
|
||||
return (int)cp; /* caller re-encodes when inserting */
|
||||
}
|
||||
|
||||
return (unsigned char)c;
|
||||
}
|
||||
|
||||
int getCursorPosition(int *rows, int *cols) {
|
||||
char buf[32];
|
||||
unsigned int i = 0;
|
||||
int getCursorPosition(int* rows, int* cols)
|
||||
{
|
||||
char buf[32];
|
||||
unsigned int i = 0;
|
||||
|
||||
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (i < sizeof(buf) - 1) {
|
||||
if (read(STDIN_FILENO, &buf[i], 1) != 1) {
|
||||
break;
|
||||
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (buf[i] == 'R') {
|
||||
break;
|
||||
|
||||
while (i < sizeof(buf) - 1)
|
||||
{
|
||||
if (read(STDIN_FILENO, &buf[i], 1) != 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (buf[i] == 'R')
|
||||
{
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
buf[i] = '\0';
|
||||
buf[i] = '\0';
|
||||
|
||||
if (buf[0] != '\x1b' || buf[1] != '[') {
|
||||
return -1;
|
||||
}
|
||||
if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) {
|
||||
return -1;
|
||||
}
|
||||
if (buf[0] != '\x1b' || buf[1] != '[')
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (sscanf(&buf[2], "%d;%d", rows, cols) != 2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getWindowSize(int *rows, int *cols) {
|
||||
struct winsize ws;
|
||||
int getWindowSize(int* rows, int* cols)
|
||||
{
|
||||
struct winsize ws;
|
||||
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
||||
if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) {
|
||||
return -1;
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0)
|
||||
{
|
||||
if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return getCursorPosition(rows, cols);
|
||||
}
|
||||
return getCursorPosition(rows, cols);
|
||||
} else {
|
||||
*cols = ws.ws_col;
|
||||
*rows = ws.ws_row;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void appDebug(const char *fmt, ...) {
|
||||
void appDebug(const char* fmt, ...)
|
||||
{
|
||||
#ifdef APP_DEBUG
|
||||
va_list ap;
|
||||
char message[256];
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(message, 256, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(stderr, "%s\n", message);
|
||||
va_list ap;
|
||||
char message[1024];
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(message, 1024, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(stderr, "%s\n", message);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
int main() {
|
||||
printf("hello");
|
||||
}
|
||||
Reference in New Issue
Block a user