31 Commits

Author SHA1 Message Date
arthur 23f7701eb9 overflow errors 2026-06-03 14:09:01 +02:00
arthur 564292b03e linux lib compatibility 2026-06-03 09:20:00 +02:00
arthur cec92cacd1 working for starting screen 2026-06-02 19:06:51 +02:00
arthur 2738bc8042 popup and diagnose ui 2026-06-02 13:00:13 +02:00
arthur a8b2960eb4 First completion level is working (LSP connected) 2026-05-28 18:28:59 +02:00
arthur 8eeef59a98 clean working dir 2026-05-26 10:32:23 +02:00
arthur 6b6b3d1766 meson error
Build project / build (push) Has been cancelled
2026-05-26 08:31:59 +02:00
arthur 3d49a0e2eb copy paste functions
Build project / build (push) Has been cancelled
2026-05-26 08:28:38 +02:00
arthur 777dc5cb74 Version update
Build project / build (push) Successful in 28s
2026-05-23 15:44:05 +02:00
arthur c4d772e465 READ ONLY state for buffers
Build project / build (push) Successful in 36s
2026-05-23 15:42:17 +02:00
arthur 310498d582 lisp changes
Build project / build (push) Successful in 1m26s
2026-05-22 14:37:32 +02:00
arthur edb5384f0e temp commit 2026-05-22 13:40:03 +02:00
arthur bc66e673fa Merge branch 'refs/heads/char_encode'
# Conflicts:
#	include/builtins.h
#	include/data.h
#	include/editor_op.h
#	include/file_io.h
#	include/input.h
#	include/row_op.h
#	install.sh
#	main.c
#	meson.build
#	src/builtins.c
#	src/editor_op.c
#	src/file_io.c
#	src/init.c
#	src/input.c
#	src/output.c
#	src/row_op.c
2026-05-04 00:31:48 +02:00
arthur 8e1b4d2f86 utf8 processing without struct 2026-05-03 23:32:40 +02:00
Arthur Barraux 87746f09ce Merge remote-tracking branch 'gitea/main'
Build project / build (push) Successful in 56s
2026-01-24 19:47:59 +01:00
Arthur Barraux 5a7b073041 Adding splitting screen and buffer switching 2026-01-24 19:44:34 +01:00
Arthur Barraux 557fc8894a Adding splitting screen and buffer switching
Build project / build (push) Successful in 52s
2026-01-24 19:29:46 +01:00
Arthur Barraux b039cf3ded Forget delete file
Build project / build (push) Successful in 24s
2026-01-22 14:07:13 +01:00
Arthur Barraux af1835d75b config correct
Build project / build (push) Failing after 1m0s
2026-01-22 14:03:59 +01:00
Arthur Barraux f3bd5dd1c9 Color theming 2026-01-22 14:00:47 +01:00
Arthur Barraux fa7f8d39d8 update splash screen
Build project / build (push) Successful in 23s
2026-01-14 20:38:33 +01:00
Arthur Barraux 23036cf3d3 warranty 0 memoey leak
Build project / build (push) Successful in 58s
2026-01-14 20:24:37 +01:00
Arthur Barraux d0afd4f304 patch file_completion crash
Build project / build (push) Successful in 1m0s
2026-01-13 15:20:18 +01:00
Arthur Barraux de4fa78221 Merge remote-tracking branch 'gitea/main'
Build project / build (push) Successful in 48s
2026-01-11 19:41:57 +01:00
Arthur Barraux fc93832130 Syntax highlighting and comment 2026-01-11 19:41:30 +01:00
arthur 39e07902f4 Update README.md
Build project / build (push) Successful in 24s
2026-01-09 15:03:08 +01:00
Arthur Barraux 815114923d Add prefix feature
Build project / build (push) Successful in 56s
2026-01-09 14:54:07 +01:00
arthur 410f382592 Add go to end of word function
Build project / build (push) Successful in 1m22s
2025-12-24 14:50:14 +01:00
arthur 80c0bf73e0 Update README.md
Build project / build (push) Successful in 1m1s
2025-12-08 00:25:05 +01:00
arthur d4ee0d86c2 Update .gitea/workflows/build.yml
Build project / build (push) Failing after 2m18s
2025-12-04 01:34:33 +01:00
Arthur Barraux eae85c32ca add utf8_char_t struct 2025-11-19 10:37:41 +01:00
44 changed files with 8598 additions and 1326 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: giorgio-runner
steps:
- name: Checkout code
+6 -7
View File
@@ -1,7 +1,6 @@
# Beluga
Beluga is a project of CLI text editor that uses lisp as configuration language.
It's abviously only working for **Linux**.
## Requirements
@@ -11,7 +10,7 @@ You will only need **meson** and a **C compiler** to compile the editor.
### From source
Here is the installation line for development version:
```git clone https://homelinuxserver.ddns.net/git/arthur/beluga.git ~/.beluga && cd ~/.beluga && meson setup build && meson compile -C build```
```git clone https://git.giorgio-nas.fr/arthur/beluga.git && cd beluga && meson setup build && meson compile -C build```
The executable file will be `build/beluga`. Feel free to add it to your path.
@@ -26,8 +25,8 @@ To open an existing file just type :
Here is some few command that you will need :
| keybind| command |
|--------|------------------|
| Ctrl-Q | leave the editor |
| Ctrl-S | Save a file |
| Ctrl-O | open file |
| keybind | command |
|---------------|------------------|
| Ctrl-x Ctrl-c | leave the editor |
| Ctrl-x Ctrl-s | Save a file |
| Ctrl-x f | open file |
+39 -39
View File
@@ -1,43 +1,43 @@
**#%#*****###%**
*##+--------------------=##*
#*=----------------------------=*#*
#*------------------------------------*#
%+----------------------------------------=#*
#+---------------------------------------------##
*#-------------------------------------------------=##
*##+--------------------=##*
#*=----------------------------=*#*
#*------------------------------------*#
%+----------------------------------------=#*
#+---------------------------------------------##
*#-------------------------------------------------=##
*#----------------------------------------------------:-##
#----------------------------------------------------------##
#=--------------------------------------------------------------##
+--------------------------+@#-%*-----------------------------------#*
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 1.1
#----------------------------------------------------------##
#=--------------------------------------------------------------##
+--------------------------+@#-%*-----------------------------------#*
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.4
*-=-------------------------#@@*---------------------------------------=%
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
*%%=-=--------------------------------------------------------------------=# CTRL-q leave
%=--------------------------------------------------------------------------#* CTRL-s save
%-----------------------------------------------------------------------------** CTRL-o open-file
*+--=---===----=---------------=*-----------------------------------------------**
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
*%**=-----------==== ==---------------------------------=+#+-----------------=#
*%=----------------------------------------------------------=#*---------------#
##=----------------------------------=------------------------+%=------------+#
#%+---------------------------------=*------------------------+%------------#
*#%*=-------------=-----------------#-------------------------#+----------#
**#%#*******+=======-------------#=------------------------#----------#
#===#*=======------------------#*----=-----------=--=##*-----------#
-====##=------------------------*%+------------=*#+=====----------#
--=====+#*=----------------------=-=+*#####***+=======-----------=*
%------=====*%*=-------------------------========-----------------+*
*-=--------====%%###+=--------------------------=-----------------#
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
%=-------#* #%*=-----------------+#
*%+--=## ##=-----------------=#*
** #+----=-------------------#*
%+----------------------------#*
*%-------------==----------------+#
##--------------==------------------#
*#--------------===%-----------------=%
##---------------=-##*-----------------+#
*#---------------==#+=#%-----------------%
*%---------------+# %*---------------#*
*#------------=+#* #%*=-----------#*
#****##****** *#%%##+=----%
*%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave
%=--------------------------------------------------------------------------#* CTRL-x CTRL-s save
%-----------------------------------------------------------------------------** CTRL-x f open-file
*+--=---===----=---------------=*-----------------------------------------------**
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
*%**=-----------==== ==---------------------------------=+#+-----------------=#
*%=----------------------------------------------------------=#*---------------#
##=----------------------------------=------------------------+%=------------+#
#%+---------------------------------=*------------------------+%------------#
*#%*=-------------=-----------------#-------------------------#+----------#
**#%#*******+=======-------------#=------------------------#----------#
#===#*=======------------------#*----=-----------=--=##*-----------#
-====##=------------------------*%+------------=*#+=====----------#
--=====+#*=----------------------=-=+*#####***+=======-----------=*
%------=====*%*=-------------------------========-----------------+*
*-=--------====%%###+=--------------------------=-----------------#
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
%=-------#* #%*=-----------------+#
*%+--=## ##=-----------------=#*
** #+----=-------------------#*
%+----------------------------#*
*%-------------==----------------+#
##--------------==------------------#
*#--------------===%-----------------=%
##---------------=-##*-----------------+#
*#---------------==#+=#%-----------------%
*%---------------+# %*---------------#*
*#------------=+#* #%*=-----------#*
#****##****** *#%%##+=----%
+78 -17
View File
@@ -2,6 +2,13 @@
(define TAB-LENGTH 4)
(define QUIT-TIMES 1)
(define THEME "dark")
(define LSP #t)
;; PACKAGES
;; First git clone it
;; (add-package "smart_delimiters")
;; FUNCTIONS
@@ -13,22 +20,76 @@
)
;; KEY MAPPING
(define (char-between ch lo hi)
(if (char>=? ch lo)
(char<=? ch hi)
#f))
(map-key "CTRL-q" editor-quit)
(map-key "CTRL-d" editor-save)
(map-key "ARROW-UP" '(move-cursor "up"))
(map-key "ARROW-DOWN" '(move-cursor "down"))
(map-key "ARROW-RIGHT" '(move-cursor "right"))
(map-key "ARROW-LEFT" '(move-cursor "left"))
(map-key "ENTER" editor-insert-new-line)
(map-key "CTRL-a" move-cursor-beg-line)
(map-key "CTRL-e" move-cursor-end-line)
(map-key "BACKSPACE" editor-delete-previous-char)
(map-key "DEL" editor-delete-next-char)
(map-key "PAGE-UP" move-cursor-page-up)
(map-key "PAGE-DOWN" move-cursor-page-down)
(map-key "CTRL-o" editor-open-file)
(map-key "CTRL-k" editor-del-row)
(map-key "CTRL-s" editor-find)
(define (alphanumericp ch)
(if ch
(if (char-between ch #\a #\z)
#t
(if (char-between ch #\A #\Z)
#t
(if (char-between ch #\0 #\9)
#t
#f)))
#f))
(define (word-char-p ch)
(if (alphanumericp ch)
#t
#f))
(define editor-move-to-end-of-word (lambda () (
(if (word-char-p (editor-read-char))
((move-cursor "right")
(editor-move-to-end-of-word))
))
))
(define enter-and-tab
(lambda ()
(editor-insert-new-line)
(let ((is-in (move-cursor "up")))
(do ((ch (editor-read-char) (editor-read-char)))
((and (not (char=? ch #\space)) is-in) #f)
(move-cursor "down")
(editor-insert-char " ")
(set! is-in (move-cursor "up")))
(move-cursor "down"))))
(add-prefix "user")
(map-key "CTRL-x" '(editor-set-prefix "user") "no-prefix")
(map-key "CTRL-g" '(editor-set-prefix "no-prefix") "user")
(map-key "CTRL-c" editor-quit "user")
(map-key "CTRL-s" editor-save "user")
(map-key "ARROW-UP" '(move-cursor "up") "no-prefix")
(map-key "ARROW-DOWN" '(move-cursor "down") "no-prefix")
(map-key "ARROW-RIGHT" '(move-cursor "right") "no-prefix")
(map-key "ARROW-LEFT" '(move-cursor "left") "no-prefix")
(map-key "ENTER" enter-and-tab "no-prefix")
(map-key "CTRL-a" move-cursor-beg-line "no-prefix")
(map-key "CTRL-e" move-cursor-end-line "no-prefix")
(map-key "BACKSPACE" editor-delete-previous-char "no-prefix")
(map-key "DEL" editor-delete-next-char "no-prefix")
(map-key "PAGE-UP" move-cursor-page-up "no-prefix")
(map-key "PAGE-DOWN" move-cursor-page-down "no-prefix")
(map-key "f" editor-open-file "user")
(map-key "TAB" editor-insert-tab "no-prefix")
(map-key "CTRL-s" buffer-find "no-prefix")
(map-key "CTRL-r" buffer-find-reverse "no-prefix")
(map-key "ARROW-RIGHT" editor-switch-next-buffer "user")
(map-key "\"" editor-split-screen-vertical "user")
(map-key "o" editor-switch-next-pane "user")
(map-key "*" editor-unify-panes "user")
(map-key "CTRL-y" editor-paste "no-prefix")
(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")
+69
View File
@@ -0,0 +1,69 @@
/**
* @file buffer.h
* @brief Buffer management for handling multiple open files
*/
#ifndef BUFFER_H_
#define BUFFER_H_
#include "data.h"
/**
* @brief Creates a new buffer for a file
* @param filename Path to the file
* @return Buffer ID on success, -1 on failure
*/
int bufferCreate(const char *path, enum bufferStatus_e state);
/**
* @brief Switches to a specific buffer by ID
* @param buffer_id The buffer ID to switch to
* @return 0 on success, -1 on failure
*/
int bufferSwitch(int buffer_id);
struct buffer_t *bufferFindById(int buffer_id);
/**
* @brief Closes a buffer by ID
* @param buffer_id The buffer ID to close
* @return 0 on success, -1 on failure
*/
int bufferClose(int buffer_id);
/**
* @brief Gets the current active buffer
* @return Pointer to current buffer_t, NULL if none
*/
struct buffer_t *bufferGetCurrent(void);
/**
* @brief Lists all open buffers
* @return Number of open buffers
*/
int bufferListAll(void);
/**
* @brief Saves current buffer to disk
* @return 0 on success, -1 on failure
*/
int bufferSave(void);
/**
* @brief Saves all buffers to disk
* @return 0 on success, -1 on failure
*/
int bufferSaveAll(void);
void bufferFind(struct buffer_t *buf);
void bufferFindReverse(struct buffer_t* buf);
void bufferInsertNewLine();
void bufferInsertBytes(const char *src, int n);
void bufferDelBytes();
void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at, const char *src, int n);
void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n);
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len);
void bufferFreeRow(row_t *row);
#endif
+46 -24
View File
@@ -3,38 +3,60 @@
#include "lisp.h"
// Mouvement
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx);
Lisp mapKey(Lisp args, LispError *e, LispContext ctx);
void registerBuiltin(char * key_sequence, LispCFunc f);
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx);
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx);
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx);
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx);
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx);
// Text editing
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx);
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx);
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx);
Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx);
// Editor
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx);
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx);
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx);
Lisp mapKey(Lisp args, LispError *e, LispContext ctx);
void registerBuiltin(char * key_sequence, LispCFunc f);
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx);
Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
// Buffer
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx);
Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx);
Lisp bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx);
// Pane
Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx);
Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx);
Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx);
Lisp editorPaste(Lisp args, LispError *e, LispContext ctx);
Lisp editorCutEndLine(Lisp args, LispError *e, LispContext ctx);
Lisp editorMoveBegBuffer(Lisp args, LispError *e, LispContext ctx);
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);
#endif
+306
View File
@@ -0,0 +1,306 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 19
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* Limits the length of circular references can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_CIRCULAR_LIMIT
#define CJSON_CIRCULAR_LIMIT 10000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable address area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
#define cJSON_SetBoolValue(object, boolValue) ( \
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
cJSON_Invalid\
)
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif
+123
View File
@@ -0,0 +1,123 @@
#ifndef COLOR_H_
#define COLOR_H_
// ============================================================================
// TEXT STYLES / ATTRIBUTES
// ============================================================================
#define ANSI_RESET "\x1b[0m" // Reset all attributes
#define ANSI_BOLD "\x1b[1m" // Bold text
#define ANSI_DIM "\x1b[2m" // Dim/faint text
#define ANSI_ITALIC "\x1b[3m" // Italic text
#define ANSI_UNDERLINE "\x1b[4m" // Underline text
#define ANSI_BLINK "\x1b[5m" // Blinking text
#define ANSI_REVERSE "\x1b[7m" // Reverse video (invert colors)
#define ANSI_HIDDEN "\x1b[8m" // Hidden/invisible text
#define ANSI_STRIKETHROUGH "\x1b[9m" // Strikethrough text
// ============================================================================
// FOREGROUND COLORS (30-37 standard, 90-97 bright)
// ============================================================================
#define ANSI_BLACK "\x1b[30m"
#define ANSI_RED "\x1b[31m"
#define ANSI_GREEN "\x1b[32m"
#define ANSI_YELLOW "\x1b[33m"
#define ANSI_BLUE "\x1b[34m"
#define ANSI_MAGENTA "\x1b[35m"
#define ANSI_CYAN "\x1b[36m"
#define ANSI_WHITE "\x1b[37m"
// Bright/Light foreground colors (90-97)
#define ANSI_BRIGHT_BLACK "\x1b[90m"
#define ANSI_BRIGHT_RED "\x1b[91m"
#define ANSI_BRIGHT_GREEN "\x1b[92m"
#define ANSI_BRIGHT_YELLOW "\x1b[93m"
#define ANSI_BRIGHT_BLUE "\x1b[94m"
#define ANSI_BRIGHT_MAGENTA "\x1b[95m"
#define ANSI_BRIGHT_CYAN "\x1b[96m"
#define ANSI_BRIGHT_WHITE "\x1b[97m"
// ============================================================================
// BACKGROUND COLORS (40-47 standard, 100-107 bright)
// ============================================================================
#define ANSI_BG_BLACK "\x1b[40m"
#define ANSI_BG_RED "\x1b[41m"
#define ANSI_BG_GREEN "\x1b[42m"
#define ANSI_BG_YELLOW "\x1b[43m"
#define ANSI_BG_BLUE "\x1b[44m"
#define ANSI_BG_MAGENTA "\x1b[45m"
#define ANSI_BG_CYAN "\x1b[46m"
#define ANSI_BG_WHITE "\x1b[47m"
// Bright/Light background colors (100-107)
#define ANSI_BG_BRIGHT_BLACK "\x1b[100m"
#define ANSI_BG_BRIGHT_RED "\x1b[101m"
#define ANSI_BG_BRIGHT_GREEN "\x1b[102m"
#define ANSI_BG_BRIGHT_YELLOW "\x1b[103m"
#define ANSI_BG_BRIGHT_BLUE "\x1b[104m"
#define ANSI_BG_BRIGHT_MAGENTA "\x1b[105m"
#define ANSI_BG_BRIGHT_CYAN "\x1b[106m"
#define ANSI_BG_BRIGHT_WHITE "\x1b[107m"
// ============================================================================
// 256-COLOR MODE (for more colors)
// ============================================================================
// Foreground: \x1b[38;5;Nm where N is 0-255
// Background: \x1b[48;5;Nm where N is 0-255
// Example macros:
#define ANSI_FG_256(N) "\x1b[38;5;" #N "m"
#define ANSI_BG_256(N) "\x1b[48;5;" #N "m"
// ============================================================================
// TRUE COLOR / 24-BIT COLOR (RGB)
// ============================================================================
// Foreground: \x1b[38;2;R;G;Bm
// Background: \x1b[48;2;R;G;Bm
// Example macros:
#define ANSI_FG_RGB(R,G,B) "\x1b[38;2;" #R ";" #G ";" #B "m"
#define ANSI_BG_RGB(R,G,B) "\x1b[48;2;" #R ";" #G ";" #B "m"
// ============================================================================
// CURSOR MOVEMENT
// ============================================================================
#define ANSI_CURSOR_UP(N) "\x1b[" #N "A" // Move up N lines
#define ANSI_CURSOR_DOWN(N) "\x1b[" #N "B" // Move down N lines
#define ANSI_CURSOR_RIGHT(N) "\x1b[" #N "C" // Move right N columns
#define ANSI_CURSOR_LEFT(N) "\x1b[" #N "D" // Move left N columns
#define ANSI_CURSOR_HOME "\x1b[H" // Move to home (0,0)
#define ANSI_CURSOR_HIDE "\x1b[?25l" // Hide cursor
#define ANSI_CURSOR_SHOW "\x1b[?25h" // Show cursor
// ============================================================================
// SCREEN CONTROL
// ============================================================================
#define ANSI_CLEAR_SCREEN "\x1b[2J" // Clear entire screen
#define ANSI_CLEAR_LINE "\x1b[K" // Clear from cursor to end of line
#define ANSI_CLEAR_UP "\x1b[1J" // Clear from cursor to start
#define ANSI_CLEAR_DOWN "\x1b[0J" // Clear from cursor to end
// ============================================================================
// EXAMPLE USAGE
// ============================================================================
/*
#include <stdio.h>
int main(void) {
// Simple color example
printf("%sHello in Red%s\n", ANSI_RED, ANSI_RESET);
// Bold and colored
printf("%s%sBold Green%s\n", ANSI_BOLD, ANSI_GREEN, ANSI_RESET);
// Background color
printf("%s%sYellow on Blue%s\n", ANSI_YELLOW, ANSI_BG_BLUE, ANSI_RESET);
// Combine multiple styles
printf("%s%s%sUnderlined Bold Cyan%s\n",
ANSI_UNDERLINE, ANSI_BOLD, ANSI_CYAN, ANSI_RESET);
return 0;
}
*/
#endif
+26
View File
@@ -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
+228 -45
View File
@@ -5,70 +5,252 @@
#include <termios.h>
#include <time.h>
#include "define.h"
#include "lisp.h"
typedef struct lsp_client_t LspClient;
/**
* \struct erow
* \struct row_t
* \brief Store one editor row
* \param
* */
typedef struct erow {
int size; /**< Size of the line */
int rsize; /**< Size of the render line */
char *chars; /**< Characters of the line */
char *render; /**< The actual line we will print */
} erow;
typedef struct row
{
int size; /**< Size of the line */
int cap; /**< Size of the render line */
char* chars; /**< Characters of the line */
} row_t;
enum editorStatus_e {
IDLE,
READ_ONLY,
READ_AND_WRITE,
typedef struct context_buffer_t
{
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
} 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
int origin_x, origin_y;
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
} EditorPane;
/**
* @brief Screen layout manager
*/
typedef struct
{
SplitMode mode;
EditorPane* panes;
int num_panes;
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;
} theme_t;
enum bufferStatus_e
{
IDLE,
READ_ONLY,
READ_AND_WRITE,
};
struct const_t {
int TAB_LENGTH;
int QUIT_TIMES;
struct const_t
{
int TAB_LENGTH;
int QUIT_TIMES;
char* THEME;
int LSP;
};
struct keyBind_t {
char *key_sequence;
Lisp command;
struct prefix_t
{
char prefix_name[64];
int prefix_id;
};
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 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 rx; /**< Position in the render*/
int row_offset; /**< Position scroll of lines */
int col_offset; /**< Position scroll of colomns*/
int screenrows; /**< Terminal height*/
int screencols; /**< Terminal width*/
int numrows; /**< Number of rows contained */
erow *row; /**< Store all the rows printed */
int dirty;
char *filename;
enum editorStatus_e state;
char status_msg[80];
time_t status_msg_time;
struct termios orig_termios; /**< Terminal communication interface */
struct editorConfig
{
int cursor_x, cursor_y; /**< Cursor position */
int screenrows; /**< Terminal height*/
int screencols; /**< Terminal width*/
struct const_t constantes;
int quit_times_buffer;
ScreenLayout layout;
FILE *fd_init_file;
Lisp env;
LispContext ctx; /** Lisp context */
Lisp ctx_data; /** Lisp data context */
LispError ctx_error; /** Lisp ctx error */
LspClient* lsp_client;
CompletionPopup lsp_completion;
DiagnosticList lsp_diagnostics;
struct keyBind_t* key_binds;
int number_of_keybinds;
int dirty;
char* status_msg;
time_t status_msg_time;
struct termios orig_termios; /**< Terminal communication interface */
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 */
struct keyBind_t* key_binds;
int number_of_keybinds;
struct prefix_t* prefix;
int number_of_prefix;
int prefix_state;
struct buffer_t buffers[64];
int number_of_buffer;
theme_t theme;
};
/**
@@ -76,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;
+40 -13
View File
@@ -2,28 +2,55 @@
#define DEFINE_H_
#define CTRL_KEY(k) ((k) & 0x1f)
#define ALT(k) ((k) | 0x200) // set bit 9 to mark as Option combo
#define ESCAPE '\x1b'
#define CURSOR_TOP_LEFT "\x1b[H"
#define HIDE_CURSOR "\x1b[?25l"
#define SHOW_CURSOR "\x1b[?25h"
#define ERASE_END_LINE "\x1b[K"
#define TAB "\t"
#define SPACE "\x20"
enum editorKey {
BACKSPACE = 127,
ARROW_LEFT = 1000,
ARROW_RIGHT,
ARROW_UP,
ARROW_DOWN,
DEL_KEY,
BEG_LINE,
END_LINE,
PAGE_UP,
PAGE_DOWN,
};
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
/* 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,
ARROW_RIGHT,
ARROW_UP,
ARROW_DOWN,
DEL_KEY,
BEG_LINE,
END_LINE,
PAGE_UP,
PAGE_DOWN,
LSP_WAKE_KEY = 2000
};
#define ABUF_INIT {NULL, 0}
#define BELUGA_VERSION "1.1"
#define BELUGA_VERSION "2.4"
#ifdef __APPLE__
#define CLIPBOARD_COPY_CMD "pbcopy"
#define CLIPBOARD_PASTE_CMD "pbpaste"
#else
#define CLIPBOARD_COPY_CMD "xclip -selection clipboard"
#define CLIPBOARD_PASTE_CMD "xclip -selection clipboard -o"
#endif
#endif // DEFINE_H_
+5 -3
View File
@@ -2,10 +2,12 @@
#define EDITOR_OP_H_
#include "data.h"
void editorInsertChar(int c);
void editorInsertNewLine();
void editorSetStatusMessage(const char *fmt, ...);
int editorRowCharCount(row_t *row, int x);
int editorRowCxToByte(const row_t *row, int cursor_x);
char *editorGetClipboard(void);
void editorSetClipboard(const char *text, int len);
void editorDelChar();
#endif // EDITOR_OP_H_
+1 -8
View File
@@ -2,21 +2,14 @@
#define FILE_IO_H_
#include "data.h"
#include "row_op.h"
#include "terminal.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
char *editorRowsToString(int *buffer_len);
void editorCloseFile(void);
void editorOpen(char *filename);
void editorOpen(struct buffer_t *buffer);
void editorSave();
void editorFind();
#endif // FILE_IO_H_
+3 -4
View File
@@ -1,10 +1,6 @@
#ifndef INIT_H_
#define INIT_H_
#include "builtins.h"
#include "data.h"
#include "terminal.h"
#include <stdio.h>
/**
* \fn void initEditor()
@@ -15,4 +11,7 @@ void initBuiltins();
void initEditor();
void deInitEditor();
#endif // INIT_H_
+2 -2
View File
@@ -22,9 +22,9 @@
char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
char *key_to_string(int key);
const char *fileCompletion(const char *path);
void editorMoveCursor(int key);
int editorMoveCursor(int key);
int executeKeyBind(char *key_sequence);
+16 -13
View File
@@ -1861,8 +1861,8 @@ static Lisp parse_symbol_(Lexer* lex, LispContext ctx)
char scratch[LISP_IDENTIFIER_MAX];
size_t length = lexer_copy_token(lex, 0, LISP_IDENTIFIER_MAX, scratch);
// always convert symbols to uppercase
for (int i = 0; i < length; ++i)
scratch[i] = toupper(scratch[i]);
// for (int i = 0; i < length; ++i)
// scratch[i] = toupper(scratch[i]);
return symbol_intern_(ctx.p->symbols, scratch, length, ctx);
}
@@ -2075,8 +2075,10 @@ static Lisp parse(Lexer* lex, LispError* out_error, LispContext ctx)
result = lisp_cons(get_sym(SYM_BEGIN, ctx), lisp_list_reverse(result), ctx);
}
if (out_error) *out_error = error;
if (out_error)
*out_error = error;
return result;
}
Lisp lisp_read(const char *program, LispError* out_error, LispContext ctx)
@@ -2122,6 +2124,7 @@ void* fread_all_(FILE* file, size_t* out_size) {
return NULL;
}
data = new_data;
memset(data + *out_size, 0, cap - *out_size);
}
size_t read = fread(data + *out_size, 1, BLOCK_SIZE, file);
@@ -3183,17 +3186,17 @@ LispContext lisp_init(void)
ctx.p->macros = lisp_make_table(ctx);
Lisp* c = ctx.p->symbol_cache;
c[SYM_IF] = lisp_make_symbol("IF", ctx);
c[SYM_BEGIN] = lisp_make_symbol("BEGIN", ctx);
c[SYM_QUOTE] = lisp_make_symbol("QUOTE", ctx);
c[SYM_QUASI_QUOTE] = lisp_make_symbol("QUASIQUOTE", ctx);
c[SYM_UNQUOTE] = lisp_make_symbol("UNQUOTE", ctx);
c[SYM_UNQUOTE_SPLICE] = lisp_make_symbol("UNQUOTESPLICE", ctx);
c[SYM_DEFINE] = lisp_make_symbol("_DEF", ctx);
c[SYM_DEFINE_MACRO] = lisp_make_symbol("DEFINE-MACRO", ctx);
c[SYM_SET] = lisp_make_symbol("_SET!", ctx);
c[SYM_IF] = lisp_make_symbol("if", ctx);
c[SYM_BEGIN] = lisp_make_symbol("begin", ctx);
c[SYM_QUOTE] = lisp_make_symbol("quote", ctx);
c[SYM_QUASI_QUOTE] = lisp_make_symbol("quasiquote", ctx);
c[SYM_UNQUOTE] = lisp_make_symbol("unquote", ctx);
c[SYM_UNQUOTE_SPLICE] = lisp_make_symbol("unquotesplice", ctx);
c[SYM_DEFINE] = lisp_make_symbol("_def", ctx);
c[SYM_DEFINE_MACRO] = lisp_make_symbol("define-macro", ctx);
c[SYM_SET] = lisp_make_symbol("_set!", ctx);
c[SYM_LAMBDA] = lisp_make_symbol("/\\_", ctx);
c[SYM_CONS] = lisp_make_symbol("CONS", ctx);
c[SYM_CONS] = lisp_make_symbol("cons", ctx);
return ctx;
}
+202 -205
View File
@@ -36,7 +36,7 @@ static const char* lib_0_sequences_src_ =
(if (pair? args) \n\
(if (pair? (cdr args)) \n\
(if (pair? (cdr (cdr args))) \n\
`(/\\_ ,(car args) ,(cons 'BEGIN (cdr args))) \n\
`(/\\_ ,(car args) ,(cons 'begin (cdr args))) \n\
`(/\\_ ,(car args) ,(car (cdr args)))) \n\
(syntax-error \"lambda missing body expressions: (lambda (args) body)\")) \n\
(syntax-error \"lambda missing argument: (lambda (args) body)\")))) \n\
@@ -44,18 +44,18 @@ static const char* lib_0_sequences_src_ =
(define-macro set! (lambda (var x) \n\
(begin \n\
(if (not (symbol? var)) (syntax-error \"set! not a variable\")) \n\
`(_SET! ,var ,x)))) \n\
`(_set! ,var ,x)))) \n\
\n\
(define-macro define \n\
(lambda (var . exprs) \n\
(if (symbol? var) \n\
(if (pair? (cdr exprs)) \n\
(syntax-error \"define: (define var x)\") \n\
`(_DEF ,var ,(car exprs))) \n\
`(_def ,var ,(car exprs))) \n\
(if (pair? var) \n\
`(_DEF ,(car var) \n\
(LAMBDA ,(cdr var) \n\
,(if (null? (cdr exprs)) (car exprs) (cons 'BEGIN exprs)))) \n\
`(_def ,(car var) \n\
(lambda ,(cdr var) \n\
,(if (null? (cdr exprs)) (car exprs) (cons 'begin exprs)))) \n\
(syntax-error \"define: not a symbol\") )))) \n\
\n\
(define (first x) (car x)) \n\
@@ -98,13 +98,13 @@ static const char* lib_0_sequences_src_ =
(define (_expand-shorthand-body path) \n\
(if (null? path) (cons 'pair '()) \n\
(list (if (char=? (car path) #\\A) \n\
(cons 'CAR (_expand-shorthand-body (cdr path))))))) \n\
(cons 'car (_expand-shorthand-body (cdr path))))))) \n\
\n\
(define (_expand-shorthand text) \n\
(cons 'DEFINE (cons (list (string->symbol (string-append \"C\" text \"R\")) 'pair) \n\
(cons 'define (cons (list (string->symbol (string-append \"C\" text \"R\")) 'pair) \n\
(_expand-shorthand-body (string->list text))))) \n\
\n\
(define-macro _shorthand-accessors (lambda args (cons 'BEGIN (map1 _expand-shorthand args)))) \n\
(define-macro _shorthand-accessors (lambda args (cons 'begin (map1 _expand-shorthand args)))) \n\
\n\
(define (vector . args) (list->vector args)) \n\
\n\
@@ -124,14 +124,14 @@ static const char* lib_0_sequences_src_ =
static const char* lib_1_forms_src_ =
"(define (_make-lambda args body) \n\
(list 'LAMBDA args (if (null? (cdr body)) (car body) (cons 'BEGIN body)))) \n\
(list 'lambda args (if (null? (cdr body)) (car body) (cons 'begin body)))) \n\
\n\
\n\
; (LET <name> ((<var0> <expr0>) ... (<varN> <expr1>)) <body0> ... <bodyN>) \n\
; => ((LAMBDA (<var0> ... <varN>) (BEGIN <body0> ... <bodyN>)) <expr0> ... <expr1>) \n\
; (let <name> ((<var0> <expr0>) ... (<varN> <expr1>)) <body0> ... <bodyN>) \n\
; => ((lambda (<var0> ... <varN>) (begin <body0> ... <bodyN>)) <expr0> ... <expr1>) \n\
; => named \n\
; ((lambda () \n\
; (define <name> (LAMBDA (<var0> ... <varN>) (BEGIN <body0> ... <bodyN>))) \n\
; (define <name> (lambda (<var0> ... <varN>) (begin <body0> ... <bodyN>))) \n\
; (<name> <expr0> ... <exprN>))) \n\
\n\
(define (_check-binding-list bindings) \n\
@@ -145,7 +145,7 @@ static const char* lib_1_forms_src_ =
(define initial-args (map1 (lambda (entry) (second entry)) bindings)) \n\
(if (null? var) \n\
(cons body-func initial-args) \n\
(list (_make-lambda '() (list (list 'DEFINE var body-func) (cons var initial-args)))))) \n\
(list (_make-lambda '() (list (list 'define var body-func) (cons var initial-args)))))) \n\
\n\
(define-macro let (lambda args \n\
(if (pair? (first args)) \n\
@@ -153,8 +153,8 @@ static const char* lib_1_forms_src_ =
(_let->combination (first args) (second args) (cdr (cdr args)))))) \n\
\n\
(define (_let*-helper bindings body) \n\
(if (null? bindings) (if (null? (cdr body)) (car body) (cons 'BEGIN body)) \n\
(list 'LET (list (car bindings)) (_let*-helper (cdr bindings) body)))) \n\
(if (null? bindings) (if (null? (cdr body)) (car body) (cons 'begin body)) \n\
(list 'let (list (car bindings)) (_let*-helper (cdr bindings) body)))) \n\
\n\
(define-macro let* (lambda (bindings . body) \n\
(_check-binding-list bindings) \n\
@@ -163,17 +163,17 @@ static const char* lib_1_forms_src_ =
(define-macro letrec (lambda (bindings . body) \n\
(_check-binding-list bindings) \n\
(cons (_make-lambda (map1 (lambda (entry) (first entry)) bindings) \n\
(append (map1 (lambda (entry) (list 'SET! (first entry) (second entry))) \n\
(append (map1 (lambda (entry) (list 'set! (first entry) (second entry))) \n\
bindings) body)) \n\
(map1 (lambda (entry) '()) bindings)))) \n\
\n\
\n\
; (COND (<pred0> <expr0>) \n\
; (cond (<pred0> <expr0>) \n\
; (<pred1> <expr1>) \n\
; ... \n\
; (else <expr-1>)) \n\
; => \n\
; (IF <pred0> <expr0> \n\
; (if <pred0> <expr0> \n\
; (if <pred1> <expr1> \n\
; .... \n\
; (if <predN> <exprN> <expr-1>)) ... ) \n\
@@ -187,11 +187,11 @@ static const char* lib_1_forms_src_ =
\n\
(define (_cond-helper clauses) \n\
(if (null? clauses) '() \n\
(if (eq? (car (car clauses)) 'ELSE) \n\
(cons 'BEGIN (cdr (car clauses))) \n\
(list 'IF \n\
(if (eq? (car (car clauses)) 'else) \n\
(cons 'begin (cdr (car clauses))) \n\
(list 'if \n\
(car (car clauses)) \n\
(cons 'BEGIN (cdr (car clauses))) \n\
(cons 'begin (cdr (car clauses))) \n\
(_cond-helper (cdr clauses)))))) \n\
\n\
(define-macro cond (lambda clauses \n\
@@ -206,26 +206,26 @@ static const char* lib_2_forms_src_ =
(cond ((null? preds) #t) \n\
((null? (cdr preds)) (car preds)) \n\
(else \n\
`(IF ,(car preds) ,(_and-helper (cdr preds)) #f)))) \n\
`(if ,(car preds) ,(_and-helper (cdr preds)) #f)))) \n\
(define-macro and (lambda preds (_and-helper preds))) \n\
\n\
(define (_or-helper preds var) \n\
(cond ((null? preds) #f) \n\
((null? (cdr preds)) (car preds)) \n\
(else \n\
`(BEGIN (SET! ,var ,(car preds)) \n\
(IF ,var ,var ,(_or-helper (cdr preds) var)))))) \n\
`(begin (set! ,var ,(car preds)) \n\
(if ,var ,var ,(_or-helper (cdr preds) var)))))) \n\
\n\
(define-macro or (lambda preds \n\
(let ((var (gensym))) \n\
`(LET ((,var '())) ,(_or-helper preds var))))) \n\
`(let ((,var '())) ,(_or-helper preds var))))) \n\
\n\
(define-macro case (lambda (key . clauses) \n\
(let ((expr (gensym))) \n\
`(LET ((,expr ,key)) \n\
,(cons 'COND (map1 (lambda (entry) \n\
`(let ((,expr ,key)) \n\
,(cons 'cond (map1 (lambda (entry) \n\
(cons (if (pair? (car entry)) \n\
`(MEMV ,expr (quote ,(car entry))) \n\
`(memv ,expr (quote ,(car entry))) \n\
(car entry)) \n\
(cdr entry))) clauses)))))) \n\
\n\
@@ -234,23 +234,24 @@ static const char* lib_2_forms_src_ =
`(begin (set! ,l (cons ,v ,l)) ,l))) \n\
\n\
; (DO ((<var0> <init0> <step0>) ...) (<test> <result>) <body>) \n\
(define-macro do \n\
(lambda (vars loop-check . loops) \n\
(let ( (names '()) (inits '()) (steps '()) (f (gensym)) ) \n\
(for-each1 (lambda (var) \n\
(push (car var) names) \n\
(set! var (cdr var)) \n\
(push (car var) inits) \n\
(set! var (cdr var)) \n\
(push (car var) steps)) vars) \n\
`((LAMBDA () \n\
(DEFINE ,f (LAMBDA ,names \n\
(IF ,(car loop-check) \n\
,(if (pair? (cdr loop-check)) (car (cdr loop-check)) '()) \n\
,(cons 'BEGIN (append loops (list (cons f steps)))) ))) \n\
,(cons f inits) \n\
)) ))) \n\
\n\
(define-macro do \n\
(lambda (vars loop-check . loops) \n\
(let ( (names '()) (inits '()) (steps '()) (f (gensym)) ) \n\
(for-each1 (lambda (var-spec) \n\
(push (car var-spec) names) \n\
(push (car (cdr var-spec)) inits) \n\
(if (pair? (cdr (cdr var-spec))) \n\
(push (car (cdr (cdr var-spec))) steps) \n\
(push (car var-spec) steps))) vars) \n\
`((lambda () \n\
(define ,f (lambda ,names \n\
(if ,(car loop-check) \n\
,(if (pair? (cdr loop-check)) (car (cdr loop-check)) '()) \n\
,(cons 'begin (append loops (list (cons f steps)))) ))) \n\
,(cons f inits) \n\
)) ))) \n\
\n\
(define-macro dotimes \n\
(lambda (form body) \n\
(apply (lambda (i n . result) \n\
@@ -262,18 +263,18 @@ static const char* lib_2_forms_src_ =
(define-macro swap! \n\
(lambda (x y) \n\
(let ((temp (gensym))) \n\
`(LET ((,temp ,x)) \n\
(SET! ,temp ,x) \n\
(SET! ,x ,y) \n\
(SET! ,y ,temp))))) \n\
`(let ((,temp ,x)) \n\
(set! ,temp ,x) \n\
(set! ,x ,y) \n\
(set! ,y ,temp))))) \n\
\n\
(define-macro inc! ; CL incf \n\
(lambda (x) \n\
`(SET! ,x (+ ,x 1)))) \n\
`(set! ,x (+ ,x 1)))) \n\
\n\
(define-macro dec! ; CL decf \n\
(lambda (x) \n\
`(SET! ,x (- ,x 1))))";
`(set! ,x (- ,x 1))))";
static const char* lib_3_math_src_ =
"(define (number? x) (real? x)) \n\
@@ -445,7 +446,7 @@ static const char* lib_4_sequences_src_ =
static const char* lib_5_streams_src_ =
"(define-macro delay (lambda (expr) \n\
`(make-promise ,(cons 'LAMBDA \n\
`(make-promise ,(cons 'lambda \n\
(cons '() \n\
(cons expr '())))))) \n\
\n\
@@ -1162,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)
@@ -1736,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)
@@ -1926,182 +1927,178 @@ static Lisp sch_is_cont(Lisp args, LispError* e, LispContext ctx)
static const LispFuncDef lib_cfunc_defs[] = {
{ "ERROR", sch_error },
{ "SYNTAX-ERROR", sch_syntax_error },
{ "error", sch_error },
{ "syntax-error", sch_syntax_error },
// Output Procedures https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Output-Procedures.html
{ "_WRITE", sch_write },
{ "_DISPLAY", sch_display },
{ "_WRITE-CHAR", sch_write_char },
{ "_FLUSH-OUTPUT-PORT", sch_flush },
{ "_READ", sch_read },
{ "INPUT-PORT?", sch_is_port_in },
{ "OUTPUT-PORT?", sch_is_port_out },
{ "OPEN-INPUT-FILE", sch_open_input },
{ "OPEN-OUTPUT-FILE", sch_open_output },
{ "CLOSE-INPUT-PORT", sch_port_close },
{ "CLOSE-OUTPUT-PORT", sch_port_close },
{ "EOF-OBJECT?", sch_is_eof },
// Output Procedures
{ "_write", sch_write },
{ "_display", sch_display },
{ "_write-char", sch_write_char },
{ "_flush-output-port", sch_flush },
{ "_read", sch_read },
{ "input-port?", sch_is_port_in },
{ "output-port?", sch_is_port_out },
{ "open-input-file", sch_open_input },
{ "open-output-file", sch_open_output },
{ "close-input-port", sch_port_close },
{ "close-output-port", sch_port_close },
{ "eof-object?", sch_is_eof },
// Universal Time https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Universal-Time.html
{ "GET-UNIVERSAL-TIME", sch_univeral_time },
{ "PRINT-GC-STATISTICS", sch_print_gc_stats },
// Universal Time
{ "get-universal-time", sch_univeral_time },
{ "print-gc-statistics", sch_print_gc_stats },
{ "MACROEXPAND", sch_macroexpand },
{ "macroexpand", sch_macroexpand },
// Equivalence Predicates https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Equivalence-Predicates.html
{ "EQ?", sch_exact_eq },
{ "EQV?", sch_equal },
{ "EQUAL?", sch_equal_r },
// Equivalence Predicates
{ "eq?", sch_exact_eq },
{ "eqv?", sch_equal },
{ "equal?", sch_equal_r },
// Booleans https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Booleans.html
{ "BOOLEAN?", sch_is_boolean },
{ "NOT", sch_not },
// Booleans
{ "boolean?", sch_is_boolean },
{ "not", sch_not },
// PAIRS
{ "CONS", sch_cons },
{ "CAR", sch_car },
{ "CDR", sch_cdr },
{ "SET-CAR!", sch_set_car },
{ "SET-CDR!", sch_set_cdr },
{ "NULL?", sch_is_null },
{ "PAIR?", sch_is_pair },
{ "cons", sch_cons },
{ "car", sch_car },
{ "cdr", sch_cdr },
{ "set-car!", sch_set_car },
{ "set-cdr!", sch_set_cdr },
{ "null?", sch_is_null },
{ "pair?", sch_is_pair },
// Lists https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_8.html
{ "LIST", sch_list },
{ "LIST?", sch_is_list },
{ "MAKE-LIST", sch_make_list },
{ "LIST-COPY", sch_list_copy },
{ "LENGTH", sch_length },
{ "APPEND", sch_append },
{ "APPEND-REVERSE!", sch_append_reverse },
{ "LIST-REF", sch_list_ref },
{ "NTHCDR", sch_list_advance },
// Lists
{ "list", sch_list },
{ "list?", sch_is_list },
{ "make-list", sch_make_list },
{ "list-copy", sch_list_copy },
{ "length", sch_length },
{ "append", sch_append },
{ "append-reverse!", sch_append_reverse },
{ "list-ref", sch_list_ref },
{ "nthcdr", sch_list_advance },
// Vectors https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_9.html#SEC82
{ "VECTOR?", sch_is_vector },
{ "MAKE-VECTOR", sch_make_vector },
{ "VECTOR-GROW", sch_vector_grow },
{ "VECTOR-LENGTH", sch_vector_length },
{ "VECTOR-SET!", sch_vector_set },
{ "VECTOR-SWAP!", sch_vector_swap },
{ "VECTOR-REF", sch_vector_ref },
{ "VECTOR-FILL!", sch_vector_fill },
{ "VECTOR-ASSQ", sch_vector_assq },
{ "SUBVECTOR", sch_subvector },
{ "LIST->VECTOR", sch_list_to_vector },
{ "VECTOR->LIST", sch_vector_to_list },
// Vectors
{ "vector?", sch_is_vector },
{ "make-vector", sch_make_vector },
{ "vector-grow", sch_vector_grow },
{ "vector-length", sch_vector_length },
{ "vector-set!", sch_vector_set },
{ "vector-swap!", sch_vector_swap },
{ "vector-ref", sch_vector_ref },
{ "vector-fill!", sch_vector_fill },
{ "vector-assq", sch_vector_assq },
{ "subvector", sch_subvector },
{ "list->vector", sch_list_to_vector },
{ "vector->list", sch_vector_to_list },
// Strings https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_7.html#SEC61
{ "STRING?", sch_is_string },
{ "MAKE-STRING", sch_make_string },
{ "STRING=?", sch_equal_r },
{ "STRING<?", sch_string_less },
{ "SUBSTRING", sch_substring },
{ "STRING-NULL?", sch_string_is_null },
{ "STRING-LENGTH", sch_string_length },
{ "STRING-REF", sch_string_ref },
{ "STRING-SET!", sch_string_set },
{ "STRING-UPCASE", sch_string_upcase },
{ "STRING-DOWNCASE", sch_string_downcase },
{ "STRING-APPEND", sch_string_append },
{ "STRING->LIST", sch_string_to_list },
{ "LIST->STRING", sch_list_to_string },
{ "STRING->NUMBER", sch_string_to_number },
{ "NUMBER->STRING", sch_number_to_string },
// Strings
{ "string?", sch_is_string },
{ "make-string", sch_make_string },
{ "string=?", sch_equal_r },
{ "string<?", sch_string_less },
{ "substring", sch_substring },
{ "string-null?", sch_string_is_null },
{ "string-length", sch_string_length },
{ "string-ref", sch_string_ref },
{ "string-set!", sch_string_set },
{ "string-upcase", sch_string_upcase },
{ "string-downcase", sch_string_downcase },
{ "string-append", sch_string_append },
{ "string->list", sch_string_to_list },
{ "list->string", sch_list_to_string },
{ "string->number", sch_string_to_number },
{ "number->string", sch_number_to_string },
// Characters https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Characters.html#Characters
{ "CHAR?", sch_is_char },
{ "CHAR=?", sch_equals },
{ "CHAR<?", sch_char_less },
// Characters
{ "char?", sch_is_char },
{ "char=?", sch_equals },
{ "char<?", sch_char_less },
{ "CHAR-UPCASE", sch_char_upcase },
{ "CHAR-DOWNCASE", sch_char_downcase },
{ "CHAR-WHITESPACE?", sch_char_is_white },
{ "CHAR-ALPHANUMERIC?", sch_char_is_alphanum },
{ "CHAR-ALPHABETIC?", sch_char_is_alpha },
{ "CHAR-NUMERIC?", sch_char_is_number },
{ "CHAR->INTEGER", sch_char_to_int },
{ "char-upcase", sch_char_upcase },
{ "char-downcase", sch_char_downcase },
{ "char-whitespace?", sch_char_is_white },
{ "char-alphanumeric?", sch_char_is_alphanum },
{ "char-alphabetic?", sch_char_is_alpha },
{ "char-numeric?", sch_char_is_number },
{ "char->integer", sch_char_to_int },
// Association Lists https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Association-Lists.html
// Numerical operations https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Numerical-operations.html
// Numerical operations
{ "=", sch_equals },
{ "+", sch_add },
{ "-", sch_sub },
{ "*", sch_mult },
{ "/", sch_divide },
{ "<", sch_less },
{ "INTEGER?", sch_is_int },
{ "EVEN?", sch_is_even },
{ "REAL?", sch_is_real },
{ "EXP", sch_exp },
{ "EXPT", sch_power },
{ "LOG", sch_log },
{ "SIN", sch_sin },
{ "COS", sch_cos },
{ "TAN", sch_tan },
{ "ATAN", sch_atan },
{ "SQRT", sch_sqrt },
{ "ROUND", sch_round },
{ "FLOOR", sch_floor },
{ "CEILING", sch_ceiling },
{ "QUOTIENT", sch_quotient },
{ "REMAINDER", sch_remainder },
{ "MODULO", sch_modulo },
{ "ABS", sch_abs },
{ "MAGNITUDE", sch_abs },
{ "EXACT?", sch_is_int },
{ "EXACT->INEXACT", sch_to_inexact },
{ "INEXACT->EXACT", sch_to_exact },
{ "integer?", sch_is_int },
{ "even?", sch_is_even },
{ "real?", sch_is_real },
{ "exp", sch_exp },
{ "expt", sch_power },
{ "log", sch_log },
{ "sin", sch_sin },
{ "cos", sch_cos },
{ "tan", sch_tan },
{ "atan", sch_atan },
{ "sqrt", sch_sqrt },
{ "round", sch_round },
{ "floor", sch_floor },
{ "ceiling", sch_ceiling },
{ "quotient", sch_quotient },
{ "remainder", sch_remainder },
{ "modulo", sch_modulo },
{ "abs", sch_abs },
{ "magnitude", sch_abs },
{ "exact?", sch_is_int },
{ "exact->inexact", sch_to_inexact },
{ "inexact->exact", sch_to_exact },
// Symbols https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Symbols.html
{ "SYMBOL?", sch_is_symbol },
{ "SYMBOL<?", sch_symbol_less },
{ "STRING->SYMBOL", sch_string_to_symbol },
{ "SYMBOL->STRING", sch_symbol_to_string },
{ "GENERATE-UNINTERNED-SYMBOL", sch_gensym },
{ "GENSYM", sch_gensym },
// Symbols
{ "symbol?", sch_is_symbol },
{ "symbol<?", sch_symbol_less },
{ "string->symbol", sch_string_to_symbol },
{ "symbol->string", sch_symbol_to_string },
{ "generate-uninterned-symbol", sch_gensym },
{ "gensym", sch_gensym },
// Environments https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_14.html
{ "EVAL", sch_eval },
{ "SCHEME-REPORT-ENVIRONMENT", sch_system_env },
{ "INTERACTION-ENVIRONMENT", sch_user_env },
// { "THE-ENVIRONMENT", sch_current_env },
// Environments
{ "eval", sch_eval },
{ "scheme-report-environment", sch_system_env },
{ "interaction-environment", sch_user_env },
// Hash Tables https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Basic-Hash-Table-Operations.html#Basic-Hash-Table-Operations
{ "HASH-TABLE?", sch_is_table },
{ "MAKE-HASH-TABLE", sch_table_make },
{ "HASH-TABLE-SET!", sch_table_set },
{ "HASH-TABLE-REF", sch_table_get },
{ "HASH-TABLE-SIZE", sch_table_size },
{ "HASH-TABLE->ALIST", sch_table_to_alist },
// Hash Tables
{ "hash-table?", sch_is_table },
{ "make-hash-table", sch_table_make },
{ "hash-table-set!", sch_table_set },
{ "hash-table-ref", sch_table_get },
{ "hash-table-size", sch_table_size },
{ "hash-table->alist", sch_table_to_alist },
{ "PROMISE?", sch_is_promise },
{ "MAKE-PROMISE", sch_make_promise },
{ "_PROMISE-PROCEDURE", sch_promise_proc },
{ "_PROMISE-STORE!", sch_promise_store },
{ "PROMISE-VALUE", sch_promise_val },
{ "PROMISE-FORCED?", sch_promise_forced },
{ "promise?", sch_is_promise },
{ "make-promise", sch_make_promise },
{ "_promise-procedure", sch_promise_proc },
{ "_promise-store!", sch_promise_store },
{ "promise-value", sch_promise_val },
{ "promise-forced?", sch_promise_forced },
// Procedures https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Procedure-Operations.html#Procedure-Operations
{ "APPLY", sch_apply },
{ "COMPILED-PROCEDURE?", sch_is_func },
{ "COMPOUND-PROCEDURE?", sch_is_lambda },
{ "PROCEDURE-ENVIRONMENT", sch_lambda_env },
// TOOD: Almost standard
{ "PROCEDURE-BODY", sch_lambda_body },
{ "CALL/CC", sch_call_cc },
{ "CONTINUATION?", sch_is_cont },
// Procedures
{ "apply", sch_apply },
{ "compiled-procedure?", sch_is_func },
{ "compound-procedure?", sch_is_lambda },
{ "procedure-environment", sch_lambda_env },
{ "procedure-body", sch_lambda_body },
{ "call/cc", sch_call_cc },
{ "continuation?", sch_is_cont },
// Random Numbers https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Random-Numbers.html
{ "RANDOM", sch_pseudo_rand },
{ "RANDOM-SEED!", sch_pseudo_seed },
// Random Numbers
{ "random", sch_pseudo_rand },
{ "random-seed!", sch_pseudo_seed },
// Garbage Collection https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-user/Garbage-Collection.html
{ "GC-FLIP", sch_gc_flip },
// Garbage Collection
{ "gc-flip", sch_gc_flip },
{ NULL, NULL }
};
void lisp_lib_load(LispContext ctx)
@@ -2111,14 +2108,14 @@ void lisp_lib_load(LispContext ctx)
lisp_table_set(
table,
lisp_make_symbol("_CURRENT-OUTPUT-PORT", ctx),
lisp_make_symbol("_current-output-port", ctx),
lisp_make_port(stdout, 0),
ctx
);
lisp_table_set(
table,
lisp_make_symbol("_CURRENT-INPUT-PORT", ctx),
lisp_make_symbol("_current-input-port", ctx),
lisp_make_port(stdin, 1),
ctx
);
+35
View File
@@ -0,0 +1,35 @@
//
// Created by Giorgio on 27/05/2026.
//
#ifndef BELUGA_LSP_UI_H
#define BELUGA_LSP_UI_H
#include "data.h"
#include "buffer.h" // for abuf
// ── Public API ────────────────────────────────────────────────────────────────
// Call from editorRefreshScreen() — appends popup/underlines to abuf
void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup);
void lspUiDrawDiagnostics(struct abuf *ab, DiagnosticList *diags,
EditorPane *pane, struct buffer_t *buf);
// Input handling — returns 1 if the key was consumed by the popup
int lspUiHandleKey(CompletionPopup *popup, int key);
// Parse clangd JSON into our structs
void lspParseCompletion(const char *json, CompletionPopup *popup,
int screen_x, int screen_y);
void lspParseDiagnostics(const char *json, DiagnosticList *diags, int buffer_id);
void lspUiDrawGutter(struct abuf *ab, DiagnosticList *diags,
int buf_id, int line);
const char *lspUiDiagnosticAtCursor(DiagnosticList *diags,
int buffer_id, int cursor_line);
#endif
-8
View File
@@ -1,11 +1,7 @@
#ifndef OUTPUT_H_
#define OUTPUT_H_
#include "append_buffer.h"
#include "data.h"
#include "define.h"
#include "row_op.h"
#include <stdio.h>
#include <unistd.h>
/**
@@ -13,8 +9,6 @@
* \brief Draws left rows of the editor.
*/
void editorDrawRows(struct abuf *ab);
void editorRefreshScreen();
void editorScroll();
@@ -23,6 +17,4 @@ void editorDrawStatusBar(struct abuf *ab);
void editorDrawMessageBar(struct abuf *ab);
void editorSetStatusMessage(const char *fmt, ...);
#endif // OUTPUT_H_
-14
View File
@@ -8,22 +8,8 @@
#include <time.h>
#include <unistd.h>
int editorRowCxToRx(erow *row, int cursor_x);
int editorRowRxToCx(erow *row, int rx);
void editorUpdateRow(erow *row);
void editorInsertRow(int at, char *s, size_t len);
void editorFreeRow(erow *row);
void editorDelRow(int at);
void editorRowInsertChar(erow *row, int at, int c);
void editorRowAppendString(erow *row, char *s, size_t len);
void editorRowDelchar(erow *row, int at);
#endif // ROW_OP_H_
+66
View File
@@ -0,0 +1,66 @@
/**
* @file split_screen.h
* @brief Split screen management for displaying multiple buffers
*/
#ifndef SPLIT_SCREEN_H_
#define SPLIT_SCREEN_H_
#include "data.h"
/**
* @brief Initializes split screen system
*/
void splitScreenInit(void);
/**
* @brief Splits screen vertically (left-right)
* @param buffer_id_left Buffer ID for left pane
* @param buffer_id_right Buffer ID for right pane
* @return 0 on success, -1 on failure
*/
int splitScreenVertical(int buffer_id_left, int buffer_id_right);
/**
* @brief Splits screen horizontally (top-bottom)
* @param buffer_id_top Buffer ID for top pane
* @param buffer_id_bottom Buffer ID for bottom pane
* @return 0 on success, -1 on failure
*/
int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom);
/**
* @brief Returns to single buffer fullscreen
*/
void splitScreenUnify(void);
/**
* @brief Switches active pane (focus moves between splits)
* @return 0 on success, -1 on failure
*/
int splitScreenSwitchPane(void);
/**
* @brief Updates the active pane's buffer
* @param buffer_id New buffer ID for active pane
* @return 0 on success, -1 on failure
*/
int splitScreenSetPaneBuffer(int buffer_id);
/**
* @brief Gets current screen layout
* @return Pointer to current ScreenLayout
*/
ScreenLayout *splitScreenGetLayout(void);
/**
* @brief Gets active pane
* @return Pointer to active EditorPane
*/
EditorPane *splitScreenGetActivePane(void);
void freePane(EditorPane *pane);
void freeScreenLayout(ScreenLayout *layout);
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef SYNTAX_HIGHLIGHTER_H_
#define SYNTAX_HIGHLIGHTER_H_
// Color codes that only affect foreground, preserving your background color
#define COLOR_RESET "\x1b[39m" // Reset all (4 bytes)
// Token types
typedef enum {
TOKEN_KEYWORD,
TOKEN_TYPE,
TOKEN_STRING,
TOKEN_COMMENT,
TOKEN_NUMBER,
TOKEN_OPERATOR,
TOKEN_DEFAULT
} TokenType;
char *highlight_line(const char * line, int *length);
#endif
+4 -2
View File
@@ -4,8 +4,6 @@
/* includes */
#include "data.h"
#include "define.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
@@ -31,4 +29,8 @@ int getCursorPosition(int *rows, int *cols);
int getWindowSize(int *rows, int *cols);
char *keyToString(int key);
void appDebug(const char *fmt, ...);
#endif
+17
View File
@@ -0,0 +1,17 @@
//
// Created by Giorgio on 01/05/2026.
//
#ifndef BELUGA_UTF8_H
#define BELUGA_UTF8_H
#include <stdint.h>
uint32_t readUtf8Char(void);
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
Executable → Regular
+3 -1
View File
@@ -16,8 +16,10 @@ fi
echo "Create config files ..."
mkdir -pv ~/.beluga/
cp -rv ./assets/ ~/.beluga/
mkdir -pv ~/.beluga/assets/
cp -rv ./assets/beluga.txt ~/.beluga/assets/
mkdir -pv ~/.beluga/packages/
mkdir -pv ~/.beluga/config/
read -p "Do you want to replace your config file or keep it (init.lisp.bak) / (init.lisp.new) ? (Y/n)" confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
+45 -27
View File
@@ -5,47 +5,65 @@
* interactions. \version 0.1 \date 21 septembre 2024
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _GNU_SOURCE
#include <libgen.h>
#include "include/buffer.h"
#include "include/split_screen.h"
#include <string.h>
#include <unistd.h>
#include "include/data.h"
#include "include/file_io.h"
#include "include/init.h"
#include "include/input.h"
#include "include/output.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[]) {
// Get HOME with NULL check
const char *home = getenv("HOME");
if (!home) {
fprintf(stderr, "Error: HOME environment variable not set\n");
return 1;
}
char * splash_screen = (char *) calloc(256, sizeof(char));
enableRawMode();
initEditor();
if (argc >= 2) {
E.state = READ_AND_WRITE;
editorOpen(argv[1]);
} else {
strcat(splash_screen, getenv("HOME"));
strcat(splash_screen, "/.beluga/assets/beluga.txt");
fprintf(stderr, "%s\n", splash_screen);
editorOpen(splash_screen);
}
free(splash_screen);
// Allocate and build splash screen path safely
char *splash_screen;
if (asprintf(&splash_screen, "%s/.beluga/assets/beluga.txt", home) == -1) {
fprintf(stderr, "Error: Failed to allocate splash screen path\n");
return 1;
}
editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit");
signal(SIGPIPE, SIG_IGN);
enableRawMode();
initEditor();
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf;
while (1) {
editorRefreshScreen();
editorProcessKeypress();
}
return 0;
appDebug("splash : %s\n", splash_screen);
active->buffer_id = bufferCreate(splash_screen, READ_ONLY);
if (argc >= 2) {
active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE);
buf = &E.buffers[active->buffer_id];
appDebug("project root : %s\n", dirname(buf->fullname));
}
free(splash_screen); // Now guaranteed safe
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
while (1) {
editorRefreshScreen();
editorProcessKeypress();
}
return 0;
}
+11 -3
View File
@@ -1,18 +1,20 @@
project('beluga', 'c',
version : '1.1',
version : '2.3',
default_options : [
'c_std=none',
]
)
cc = meson.get_compiler('c')
m = cc.find_library('m', required: false)
m = cc.find_library('m', required: false)
thread_dep = dependency('threads')
# Source files
src_files = files(
'main.c',
'src/append_buffer.c',
'src/editor_op.c',
'src/syntax_highlighter.c',
'src/file_io.c',
'src/init.c',
'src/input.c',
@@ -20,10 +22,16 @@ src_files = files(
'src/row_op.c',
'src/terminal.c',
'src/builtins.c',
'src/buffer.c',
'src/split_screen.c',
'src/utf8.c',
'src/completion.c',
'src/lsp_ui.c',
'src/cJSON.c'
)
# Executable
executable('beluga',
src_files,
dependencies: [m]
dependencies: [m, thread_dep]
)
+5 -3
View File
@@ -1,7 +1,5 @@
#include "../include/append_buffer.h"
extern struct editorConfig E;
void abAppend(struct abuf *ab, const char *s, int len) {
char *new = realloc(ab->b, ab->len + len);
@@ -13,4 +11,8 @@ void abAppend(struct abuf *ab, const char *s, int len) {
ab->len += len;
}
void abFree(struct abuf *ab) { free(ab->b); }
void abFree(struct abuf *ab) {
free(ab->b);
ab->b = NULL;
ab->len = 0;
}
+524
View File
@@ -0,0 +1,524 @@
/**
* @file buffer.c
* @brief Buffer management implementation for multiple open files
*/
#include "../include/buffer.h"
#include "../include/file_io.h"
#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"
/**
* @brief Finds a buffer by filename
* @param filename The filename to search for
* @return Buffer ID if found, -1 if not found
*/
static int bufferFindByFilename(const char* filename)
{
for (int i = 0; i < E.number_of_buffer; i++)
{
if (E.buffers[i].filename != NULL &&
strcmp(E.buffers[i].filename, filename) == 0)
{
return E.buffers[i].buffer_id;
}
}
return -1;
}
/**
* @brief Finds a buffer by ID
* @param buffer_id The buffer ID to find
* @return Pointer to buffer, NULL if not found
*/
struct buffer_t* bufferFindById(int buffer_id)
{
for (int i = 0; i < E.number_of_buffer; i++)
{
if (E.buffers[i].buffer_id == buffer_id)
{
return &E.buffers[i];
}
}
return NULL;
}
/**
* @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.
* @return Buffer ID on success, -1 on failure
*/
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(path);
if (existing_id != -1)
{
return bufferSwitch(existing_id);
}
// Check if we have space for more buffers
if (E.number_of_buffer >= 64)
{
editorSetStatusMessage("Error: maximum buffers reached (64)");
return -1;
}
// Initialize new buffer
struct buffer_t* new_buf = &E.buffers[E.number_of_buffer];
new_buf->buffer_id = E.number_of_buffer;
new_buf->filename = strdup(filename);
if (!new_buf->filename)
{
return -1;
}
new_buf->fullname = malloc(1024 * sizeof(char));
if (!new_buf->fullname)
{
free(new_buf->filename);
return -1;
}
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;
}
/**
* @brief Switches to a specific buffer by ID
* @details Saves current buffer state, loads target buffer content and state.
* @param buffer_id The buffer ID to switch to
* @return 0 on success, -1 on failure
*/
int bufferSwitch(int buffer_id)
{
struct buffer_t* target = bufferFindById(buffer_id);
if (target == NULL)
{
E.layout.panes[E.layout.active_pane].buffer_id = buffer_id;
editorSetStatusMessage("Error: buffer not found");
return -1;
}
editorSetStatusMessage("Switched to: %s (buffer %d)",
target->filename, buffer_id);
return 0;
}
/**
* @brief Closes a buffer by ID
* @details Frees buffer resources and removes from buffer list.
* Prompts to save unsaved changes. If closing current buffer,
* switches to next available buffer.
* @param buffer_id The buffer ID to close
* @return 0 on success, -1 on failure
*/
int bufferClose(int buffer_id)
{
struct buffer_t* buf = bufferFindById(buffer_id);
if (buf == NULL)
{
editorSetStatusMessage("Error: buffer not found");
return -1;
}
EditorPane* active = splitScreenGetActivePane();
// If this is the current buffer, find next buffer to switch to
if (active->buffer_id == buffer_id)
{
int next_id = -1;
// Try to switch to next buffer
for (int i = 0; i < E.number_of_buffer; i++)
{
if (E.buffers[i].buffer_id != buffer_id)
{
next_id = E.buffers[i].buffer_id;
break;
}
}
if (next_id != -1)
{
bufferSwitch(next_id);
}
else
{
// No other buffers, clear editor
editorCloseFile();
}
}
// Free buffer resources
free(buf->filename);
buf->filename = NULL;
buf->buffer_id = -1;
// Remove from buffer list by shifting
for (int i = 0; i < E.number_of_buffer; i++)
{
if (E.buffers[i].buffer_id == buffer_id)
{
for (int j = i; j < E.number_of_buffer - 1; j++)
{
E.buffers[j] = E.buffers[j + 1];
}
E.number_of_buffer--;
break;
}
}
editorSetStatusMessage("Closed buffer %d", buffer_id);
return 0;
}
/**
* @brief Gets the current active buffer
* @return Pointer to current buffer_t, NULL if none
*/
struct buffer_t* bufferGetCurrent(void)
{
EditorPane* active = splitScreenGetActivePane();
return bufferFindById(active->buffer_id);
}
/**
* @brief Lists all open buffers
* @details Prints buffer information to status message
* @return Number of open buffers
*/
int bufferListAll(void)
{
if (E.number_of_buffer == 0)
{
editorSetStatusMessage("No buffers open");
return 0;
}
char buf[256] = "";
int offset = 0;
EditorPane* active = splitScreenGetActivePane();
for (int i = 0; i < E.number_of_buffer; i++)
{
struct buffer_t* b = &E.buffers[i];
char marker = (b->buffer_id == active->buffer_id) ? '*' : ' ';
offset += snprintf(&buf[offset], sizeof(buf) - offset,
"%c%d:%s ", marker, b->buffer_id,
b->filename ? b->filename : "[No Name]");
}
editorSetStatusMessage("Buffers: %s", buf);
return E.number_of_buffer;
}
/**
* @brief Saves current buffer to disk
* @return 0 on success, -1 on failure
*/
int bufferSave(void)
{
EditorPane* active = splitScreenGetActivePane();
if (active->buffer_id == -1)
{
editorSetStatusMessage("Error: no buffer active");
return -1;
}
editorSave();
return 0;
}
/**
* @brief Saves all buffers to disk
* @details Iterates through all buffers, saves each one
* @return 0 on success, -1 on failure
*/
int bufferSaveAll(void)
{
int saved = 0;
int failed = 0;
for (int i = 0; i < E.number_of_buffer; i++)
{
if (bufferSwitch(E.buffers[i].buffer_id) == 0)
{
if (E.dirty && bufferSave() == 0)
{
saved++;
}
}
else
{
failed++;
}
}
editorSetStatusMessage("Saved %d buffers (%d failed)", saved, failed);
return (failed == 0) ? 0 : -1;
}
/**
* @brief Searches for a string in the document
* @details Prompts user for a search query, then searches forward from current
* cursor position. Updates cursor position to the first match found.
* @note Updates global editor state E (cursor position, row_offset)
* @note Search is case-sensitive and operates on rendered line content
* @note Searches begin from the line after current cursor position
* @see editorPrompt()
* @see editorRowRxToCx()
*/
void bufferFind(struct buffer_t* buf)
{
appDebug("searching\n");
char* query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
if (query == NULL)
return;
int i;
for (i = buf->y + 1; i < buf->numrows; i++)
{
row_t* row = &buf->row[i];
char* match = strstr(row->chars, query);
if (match)
{
buf->y = i;
break;
}
}
free(query);
}
void bufferFindReverse(struct buffer_t* buf)
{
appDebug("searching\n");
char* query = editorPrompt("Reverse search: %s (ESC to cancel)", "", 0);
if (query == NULL)
return;
int i;
if (!buf->y)
return;
for (i = buf->y - 1; i >= 0; i--)
{
row_t* row = &buf->row[i];
char* match = strstr(row->chars, query);
if (match)
{
buf->y = i;
break;
}
}
free(query);
}
void bufferInsertRow(struct buffer_t* buffer, int at, char* s, size_t len)
{
if (at < 0 || at > buffer->numrows)
return;
row_t* tmp = realloc(buffer->row, sizeof(row_t) * (buffer->numrows + 1));
if (!tmp)
return;
buffer->row = tmp;
/* Shift existing rows to make room at 'at' — no at++ */
if (at < buffer->numrows)
{
memmove(&buffer->row[at + 1], &buffer->row[at],
sizeof(row_t) * (buffer->numrows - at)); /* not -at+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;
memcpy(buffer->row[at].chars, s, len);
buffer->row[at].chars[len] = '\0'; /* always NUL-terminate */
buffer->numrows++;
buffer->dirty++;
}
void bufferFreeRow(row_t* row) { free(row->chars); }
/**
* \fn editorRowInsertChar(erow *row, int at, int c)
* \param at Index of where we want to insert the char */
void bufferRowInsertBytes(struct buffer_t* buffer, row_t* row, int at,
const char* src, int n)
{
if (buffer->state == READ_ONLY)
return;
if (at < 0 || at > row->size)
{
return;
}
if (row->size + n + 1 > row->cap)
{
size_t new_cap = row->cap == 0 ? 128 : row->cap * 2;
while (new_cap < row->size + n + 1) {
new_cap *= 2;
}
char *new_chars = realloc(row->chars, new_cap);
if (!new_chars) {
return; // Allocation failed
}
row->chars = new_chars;
row->cap = (int) new_cap;
}
memmove(row->chars + at + n, row->chars + at, row->size - at);
// Copy new bytes
memcpy(row->chars + at, src, n);
row->size += n;
row->chars[row->size] = '\0';
++buffer->dirty;
}
/**
* \fn bufferRowDelChar(struct bufferConfig *E, frow *frow, int at)
* \brief Delete the a char at the chosen position on the given row
* \param at Index of the char to delete
* \param row Row on operation is made */
void bufferRowDelByte(struct buffer_t* buffer, row_t* row, int at, int n)
{
if (buffer->state == READ_ONLY)
return;
if (at < 0 || at >= row->size)
return;
memmove(row->chars + at, row->chars + at + n, row->size - at - n);
row->size -= n;
row->chars[row->size] = '\0';
buffer->x -= n;
++buffer->dirty;
}
void bufferInsertBytes(const char* src, int n)
{
appDebug("bufferInsertBytes \r\n");
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buf = bufferFindById(active->buffer_id);
if (buf->y == buf->numrows)
{
bufferInsertRow(buf, buf->numrows, "", 0);
}
bufferRowInsertBytes(buf, &buf->row[buf->y], buf->x, src, n);
buf->x += n;
buf->b_has_changed = 1;
}
void bufferDelBytes(void)
{
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buf = bufferFindById(active->buffer_id);
/* Nothing to delete */
if (buf->numrows == 0) return;
if (buf->x == 0 && buf->y == 0) return;
/* Use row_offset, not col_offset, for row indexing */
row_t* r = &buf->row[buf->y];
if (buf->x > 0)
{
int byte_end = editorRowCxToByte(r, buf->x);
int byte_start = editorRowCxToByte(r, buf->x - 1);
int char_width = byte_end - byte_start; /* byte width of the character */
bufferRowDelByte(buf, r, byte_start, char_width);
E.dirty = 1;
}
else
{
/* Merge current row into the previous one */
row_t* prev = &buf->row[buf->y - 1]; // FIX: was buf->y (same as r)
int prev_char_count = editorRowCharCount(prev, prev->size);
bufferRowInsertBytes(buf, prev, prev->size, r->chars, r->size);
free(r->chars);
r->chars = NULL;
memmove(&buf->row[buf->y],
&buf->row[buf->y + 1],
sizeof(row_t) * (buf->numrows - buf->y - 1));
buf->numrows--;
active->cursor_x = prev_char_count;
buf->x = prev_char_count;
active->cursor_y--;
buf->y--;
E.dirty = 1;
}
}
void bufferInsertNewLine(void)
{
appDebug("Inserting new line\n");
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buf = bufferFindById(active->buffer_id);
appDebug("buf x %d\n", buf->x);
if (buf->y >= buf->numrows)
{
/* Cursor is past the last row: just append a blank line */
bufferInsertRow(buf, buf->numrows, "", 0);
}
else
{
row_t* row = &buf->row[buf->y];
/* 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: bRealloc inside bufferInsertRow may have moved the array */
row = &buf->row[buf->y];
/* Truncate the current row at the cursor */
row->size = buf->x;
row->chars[row->size] = '\0';
/* Do NOT touch row->cap — the allocation is still valid */
}
buf->y++;
buf->x = 0;
appDebug("Insert new line done\n");
}
+616 -129
View File
@@ -1,174 +1,661 @@
/**
* @file builtins.c
* @brief Built-in Lisp functions for editor operations
* @details Provides Lisp bindings for core editor functionality including
* cursor movement, file operations, keybinding management, and text
* manipulation
*/
#include "../include/builtins.h"
#include "../include/define.h"
#include "../include/input.h"
#include "../include/file_io.h"
#include "../include/editor_op.h"
#include "../include/row_op.h"
#include "../include/buffer.h"
#include "../include/data.h"
#include "../include/define.h"
#include "../include/editor_op.h"
#include "../include/file_io.h"
#include "../include/input.h"
#include "../include/terminal.h"
#include "../include/split_screen.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
const char *key_sequence = lisp_string(lisp_car(args));
args = lisp_cdr(args);
// second argument
Lisp func = lisp_car(args);
#include "include/completion.h"
#include "include/init.h"
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));
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
E.key_binds[E.number_of_keybinds - 1].command = func;
return lisp_null();
/**
* @brief Finds a prefix configuration by name
* @details Searches the prefix array for a prefix matching the given name.
* Returns the first prefix (default) if no match is found.
* @param prefix_name Name of the prefix to search for (max 64 chars)
* @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];
}
}
return E.prefix[0];
}
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
const char *direction = lisp_string(lisp_car(args));
switch (direction[0]) {
case 'u':
editorMoveCursor(ARROW_UP);
break;
case 'd':
editorMoveCursor(ARROW_DOWN);
break;
case 'r':
editorMoveCursor(ARROW_RIGHT);
break;
case 'l':
editorMoveCursor(ARROW_LEFT);
break;
}
/**
* @brief Lisp function to bind a command to a key sequence
* @details Registers a keybinding with an associated Lisp command and optional
* prefix modifier. Dynamically extends the keybind array.
* @param args Lisp list of 3 arguments: (key-sequence command prefix-name)
* - key_sequence (string): The key or key combination to bind
* - command (Lisp): The Lisp command/function to execute
* - prefix_name (string): Prefix modifier name
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @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));
void* memory_temp;
args = lisp_cdr(args);
// second argument
const Lisp func = lisp_car(args);
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));
return lisp_null();
}
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
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);
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
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();
}
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode();
exit(0);
return lisp_null();
}
Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx) {
editorSave();
return lisp_null();
/**
* @brief Lisp function to move cursor in a specified direction
* @details Moves the editor cursor up, down, left, or right based on direction
* string.
* @param args Lisp list with one argument: direction string
* - "u": up, "d": down, "r": right, "l": left
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return Lisp boolean indicating whether movement was valid
* @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;
default:
break;
}
appDebug("move lisp %d\n", is_in);
return lisp_make_bool(is_in);
}
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx) {
/**
* @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 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);
E.key_binds[i].key_sequence = NULL;
}
free(E.key_binds);
E.key_binds = NULL;
E.number_of_keybinds = 0;
editorInsertNewLine();
return lisp_null();
// Similar fix for buffers
for (i = 0; i < E.number_of_buffer; ++i) {
free(E.buffers[i].filename);
E.buffers[i].filename = NULL;
for (j = 0; j < E.buffers[i].numrows; ++j) {
free(E.buffers[i].row[j].chars);
E.buffers[i].row[j].chars = NULL;
}
free(E.buffers[i].row);
E.buffers[i].row = NULL;
}
// bFree layout
free(E.layout.panes);
free(E.init_file_path);
fclose(E.fd_init_file);
}
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
E.cursor_x = 0;
return lisp_null();
/**
* @brief Lisp function to quit the editor
* @details Closes editor with unsaved changes protection. Prompts user to
* confirm quit after multiple attempts if file is dirty. Cleans up resources
* and restores terminal.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null() (never returns on successful exit)
* @note Calls exit(0) to terminate program
* @note Updates quit_times_buffer counter
* @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();
}
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);
}
Lisp moveCursorEndLine(Lisp args, LispError* e, LispContext ctx) {
if (E.cursor_y < E.numrows) {
E.cursor_x = E.row[E.cursor_y].size;
}
return lisp_null();
/**
* @brief Lisp function to save the current file
* @details Wrapper around editorSave() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @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 deletePreviousChar(Lisp args, LispError* e, LispContext ctx) {
editorDelChar();
return lisp_null();
Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx)
{
editorSave();
return lisp_null();
}
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) {
E.cursor_y = E.row_offset;
int times = E.screenrows;
while (--times) {
editorMoveCursor(ARROW_UP);
}
return lisp_null();
/**
* @brief Lisp function to insert a new line at cursor
* @details Wrapper around editorInsertNewLine() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @see editorInsertNewLine()
*/
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx)
{
bufferInsertNewLine();
return lisp_null();
}
Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx) {
E.cursor_y = E.row_offset + E.screenrows - 1;
if (E.cursor_y > E.numrows) {
E.cursor_y = E.numrows;
}
int times = E.screenrows;
while (--times) {
editorMoveCursor(ARROW_DOWN);
}
/**
* @brief Lisp function to insert a tab (spaces) at cursor
* @details Inserts TAB_LENGTH spaces at the current cursor position.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @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);
}
return lisp_null();
return lisp_null();
}
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename)
editorOpen(filename);
return lisp_null();
/**
* @brief Lisp function to move cursor to beginning of line
* @details Moves cursor to column 0 of the current line.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @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 editorPrintC(Lisp args, LispError *e, LispContext ctx) {
char c = lisp_string(lisp_car(args))[0];
editorInsertChar(c);
return lisp_null();
/**
* @brief Lisp function to move cursor to end of line
* @details Moves cursor to the end of the current line content.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @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 addPackage(Lisp args, LispError *e, LispContext ctx) {
const char *package_name = lisp_string(lisp_car(args));
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");
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();
/**
* @brief Lisp function to delete character before cursor
* @details Wrapper around editorDelChar() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @see editorDelChar()
*/
Lisp deletePreviousChar(Lisp args, LispError* e, LispContext ctx)
{
bufferDelBytes();
return lisp_null();
}
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) {
editorDelRow(E.cursor_y);
return lisp_null();
/**
* @brief Lisp function to move cursor up by one screen
* @details Scrolls up one full screen height, moving cursor to top of visible
* area.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @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 editorFind_L(Lisp args, LispError *e, LispContext ctx) {
editorFind();
return lisp_null();
/**
* @brief Lisp function to move cursor down by one screen
* @details Scrolls down one full screen height, moving cursor to bottom of
* visible area.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @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);
}
return lisp_null();
}
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
fprintf(stderr, "char read : %c\n", E.row[E.cursor_y].render[E.cursor_x]);
return lisp_make_char(E.row[E.cursor_y].render[E.cursor_x]);
/**
* @brief Lisp function to open a file
* @details Prompts user for filename and opens the file for editing.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
* @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);
return lisp_null();
}
/**
* @brief Lisp function to insert a character
* @details Extracts a character from Lisp string argument and inserts it at
* cursor.
* @param args Lisp list with one string argument
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @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, (int) strlen(src));
return lisp_null();
}
/**
* @brief Lisp function to load and execute a package
* @details Loads a Lisp package from the user's packages directory
* (~/.beluga/packages/<package_name>/init.lisp) and evaluates it.
* @param args Lisp list with one argument: package name (string)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @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);
const char *home = getenv("HOME");
if (!home) {
return lisp_null();
}
char *package_dir;
if (asprintf(&package_dir, "%s/.beluga/packages/%s/init.lisp", home, package_name) == -1) {
return lisp_null();
}
FILE* fd_package = fopen(package_dir, "r");
if (!fd_package) {
free(package_dir);
return lisp_null();
}
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error, E.ctx);
fclose(fd_package);
free(package_dir);
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;
}
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();
}
/**
* @brief Lisp function to search for text
* @details Wrapper around editorFind() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @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();
}
/**
* @brief Lisp function to search for text
* @details Wrapper around editorFind() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @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();
}
/**
* @brief Lisp function to read character at cursor
* @details Returns the character at the current cursor position as a Lisp
* character. Returns 'a' if at end of line.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @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;
}
/**
* @brief Lisp function to set the current prefix mode
* @details Changes the editor's prefix state to a named prefix, affecting which
* keybindings are active. Updates status message to show active prefix.
* @param args Lisp list with one argument: prefix name (string)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @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);
return lisp_null();
}
/**
* @brief Lisp function to define a new prefix modifier
* @details Registers a named prefix modifier that can be used with keybindings
* to create context-aware command sequences (e.g., Ctrl-X as a prefix).
* @param args Lisp list with one argument: prefix name (string, max 64 chars)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @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 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();
}
Lisp editorCutEndLine(Lisp args, LispError* e, LispContext ctx)
{
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)
{
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)
{
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)
{
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
View File
File diff suppressed because it is too large Load Diff
+594
View File
@@ -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);
}
+130 -35
View File
@@ -1,47 +1,142 @@
#include "../include/editor_op.h"
#include <stdarg.h>
#include "../include/row_op.h"
#include "../include/buffer.h"
#include "../include/data.h"
#include "../include/split_screen.h"
#include "../include/terminal.h"
#include "../include/utf8.h"
extern struct editorConfig E;
void editorInsertChar(int c) {
if (E.cursor_y == E.numrows) {
editorInsertRow(E.numrows, "", 0);
/**
* @brief Sets a temporary status message for display
* @details Formats and stores a message that will be displayed in the message
* bar for 5 seconds. Uses printf-style variable argument formatting.
* @param fmt Printf-style format string
* @param ... Variable arguments for format string
* @note Updates global editor state E (status_msg, status_msg_time)
* @see editorDrawMessageBar()
*/
void editorSetStatusMessage(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(E.status_msg, E.screencols, fmt, ap);
va_end(ap);
E.status_msg_time = time(NULL);
}
/**
* @brief Moves the cursor based on arrow key input
* @details Updates cursor position (E.cursor_x, E.cursor_y) based on the given
* key direction. Handles line wrapping and boundary conditions. Prevents cursor
* from exceeding line lengths.
* @param key The arrow key code (ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT)
* @return 1 if cursor movement was valid, 0 if cursor was constrained to line boundary
* @note Updates global editor state E
*/
int editorMoveCursor(int key) {
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
row_t *row = &buf->row[buf->y];
switch (key) {
case ARROW_RIGHT:
if (row && buf->x < row->size) {
int len = utf8Seqlen(row->chars[buf->x]);
buf->x += len;
} else if (row && buf->y < buf->numrows - 1) {
buf->y++;
buf->x = 0;
}
break;
case ARROW_DOWN:
if (buf->y < buf->numrows - 1) {
buf->y++;
}
break;
case ARROW_UP:
if (buf->y != 0) {
--buf->y;
}
break;
case ARROW_LEFT:
if (buf->x != 0) {
--buf->x;
} else if (buf->y > 0) {
--buf->y;
buf->x = buf->row[buf->y].size;
}
break;
default:
break;
}
editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, c);
E.cursor_x++;
return 1;
}
void editorInsertNewLine() {
erow *row;
if (!E.cursor_x) {
editorInsertRow(E.cursor_y, "", 0);
} else {
row = &E.row[E.cursor_y];
editorInsertRow(E.cursor_y + 1, &row->chars[E.cursor_x],
row->size - E.cursor_x);
row = &E.row[E.cursor_y];
row->size = E.cursor_x;
row->chars[row->size] = '\0';
editorUpdateRow(row);
}
++E.cursor_y;
E.cursor_x = 0;
char *editorGetClipboard(void) {
FILE *pipe = popen(CLIPBOARD_PASTE_CMD, "r");
if (!pipe) return NULL;
size_t cap = 4096;
size_t len = 0;
char *buf = malloc(cap);
if (!buf) {
pclose(pipe);
return NULL;
}
int c;
while ((c = fgetc(pipe)) != EOF) {
if (len + 1 >= cap) {
cap *= 2;
char *new_buf = realloc(buf, cap);
if (!new_buf) {
free(buf);
pclose(pipe);
return NULL;
}
buf = new_buf;
}
buf[len++] = (char)c;
}
buf[len] = '\0';
pclose(pipe);
return buf;
}
void editorDelChar() {
erow *row;
if (E.cursor_y == E.numrows || !(E.cursor_x || E.cursor_y)) {
return;
}
row = &E.row[E.cursor_y];
if (E.cursor_x > 0) {
editorRowDelchar(row, E.cursor_x - 1);
--E.cursor_x;
} else {
E.cursor_x = E.row[E.cursor_y - 1].size;
editorRowAppendString(&E.row[E.cursor_y - 1], row->chars, row->size);
editorDelRow(E.cursor_y);
--E.cursor_y;
}
void editorSetClipboard(const char *text, int len) {
FILE *pipe = popen(CLIPBOARD_COPY_CMD, "w");
if (!pipe) return;
fwrite(text, 1, len, pipe);
pclose(pipe);
}
int editorRowCxToByte(const row_t *row, int cursor_x) {
int i = 0, col = 0;
while (col < cursor_x && i < row->size) {
int sl = utf8Seqlen((unsigned char)row->chars[i]);
if (sl < 1)
sl = 1;
col++;
i += sl;
}
return i;
}
int editorRowCharCount(row_t *row, int x) {
int n = 0, i = 0;
while (i < x && i < row->size) {
int sl = utf8Seqlen((unsigned char)row->chars[i]);
if (sl < 1)
sl = 1;
n++;
i += sl;
}
return n;
}
+104 -108
View File
@@ -1,130 +1,126 @@
/**
* @file file_io.c
* @brief File I/O operations module for the Beluga text editor
* @details Handles file loading, saving, searching, and buffer management.
* Provides functionality for opening/closing files, persisting changes to disk,
* and searching for text patterns within the document.
*/
#include "../include/file_io.h"
#include "../include/editor_op.h"
#include "../include/input.h"
#include "../include/output.h"
#include "../include/buffer.h"
#include "../include/data.h"
#include "../include/split_screen.h"
#include "../include/row_op.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
extern char *strdup(const char *);
extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
FILE *restrict stream);
extern int ftruncate(int fd, off_t length);
extern struct editorConfig E;
char *editorRowsToString(int *buffer_len) {
int tot_len = 0;
int j;
char *buf;
char *p;
for (j = 0; j < E.numrows; ++j) {
tot_len += E.row[j].size + 1;
}
*buffer_len = tot_len;
buf = malloc(tot_len);
p = buf;
for (j = 0; j < E.numrows; ++j) {
memcpy(p, E.row[j].chars, E.row[j].size);
p += E.row[j].size;
*p = '\n';
p++;
}
return buf;
}
/**
* @brief Closes the current file and resets editor state
* @details Clears all rows, resets cursor position, scroll offsets, and file
* metadata. Does not prompt to save unsaved changes.
* @note Updates global editor state E
*/
void editorCloseFile(void) {
E.cursor_x = 0;
E.cursor_y = 0;
E.rx = 0;
E.row_offset = 0;
E.col_offset = 0;
E.numrows = 0;
E.row = NULL;
E.dirty = 0;
E.filename = NULL;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
active->cursor_x = 0;
active->cursor_y = 0;
active->x_offset = 0;
active->y_offset = 0;
for (int i = 0; i < buf->numrows; ++i) {
free(buf->row[i].chars);
}
buf->numrows = 0;
free(buf->row);
buf->row = NULL;
buf->dirty = 0;
free(buf->filename);
buf->filename = NULL;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
}
void editorOpen(char *filename) {
FILE *fp;
/**
* @brief Opens a file for editing
* @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 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
* @see editorInsertRow()
*/
void editorOpen(struct buffer_t* buffer) {
FILE *fp;
fp = fopen(buffer->fullname, "a+");
if (!fp)
die("fopen");
// Test if a file is already open
if (E.filename != NULL) {
editorCloseFile();
E.state = READ_AND_WRITE;
}
char *line = NULL;
size_t line_cap = 0;
ssize_t line_len;
free(E.filename);
E.filename = strdup(filename);
rewind(fp);
fp = fopen(filename, "a+");
if (!fp)
die("fopen");
char *line = NULL;
size_t line_cap = 0;
ssize_t line_len;
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
while (line_len > 0 &&
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
--line_len;
}
editorInsertRow(E.numrows, line, line_len);
}
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
while (line_len > 0 &&
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
--line_len;
}
bufferInsertRow(buffer, buffer->numrows, line, line_len);
free(line);
fclose(fp);
E.dirty = 0;
line = NULL;
line_cap = 0; // Reset for next iteration
}
fclose(fp);
E.dirty = 0;
}
/**
* @brief Saves the current file to disk
* @details Prompts for filename if not set, converts all rows to a buffer,
* writes to disk using open/ftruncate/write, and updates dirty flag.
* Displays status messages on success or failure.
* @note Updates global editor state E (dirty flag)
* @note If no filename is set, prompts user via editorPrompt()
* @note Uses O_RDWR | O_CREAT with mode 0644
* @see editorRowsToString()
*/
void editorSave() {
int len;
char *buf;
int fd;
if (E.filename == NULL) {
E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (E.filename == NULL) {
editorSetStatusMessage("Save aborted");
return;
}
}
buf = editorRowsToString(&len);
fd = open(E.filename, O_RDWR | O_CREAT, 0644);
if (fd != -1) {
if (ftruncate(fd, len) != -1) {
if (write(fd, buf, len) == len) {
close(fd);
free(buf);
E.dirty = 0;
editorSetStatusMessage("%d bytes written to disk", len);
return;
}
}
close(fd);
}
free(buf);
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
}
void editorFind() {
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
if (query == NULL) return;
int i;
for (i = E.cursor_y + 1; i < E.numrows; i++) {
erow *row = &E.row[i];
char *match = strstr(row->render, query);
if (match) {
E.cursor_y = i;
E.cursor_x = editorRowRxToCx(row, match - row->render);
E.row_offset = E.numrows;
break;
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
int len;
int fd;
if (buffer->filename == NULL) {
buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (buffer->filename == NULL) {
editorSetStatusMessage("Save aborted");
return;
}
}
free(query);
}
fd = open(buffer->fullname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd != -1) {
for (int i = 0; i < buffer->numrows; ++i)
{
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));
return;
}
write(fd, "\n", 1);
}
buffer->dirty = 0;
close(fd);
}
editorSetStatusMessage("File saved");
}
+148 -47
View File
@@ -1,68 +1,63 @@
#include "../include/init.h"
#include "../include/builtins.h"
#include "../include/color.h"
#include "../include/data.h"
#include "../include/terminal.h"
#include "../include/builtins.h"
#include "../include/split_screen.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LISP_IMPLEMENTATION
#include "../include/lisp.h"
#include "../include/lisp_lib.h"
extern struct editorConfig;
struct editorConfig;
void registerBuiltin(char *key_sequence, LispCFunc f) {
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
lisp_make_func(f), E.ctx);
}
void initBuiltins() {
// move cursor
registerBuiltin("MOVE-CURSOR", moveCursor);
registerBuiltin("MAP-KEY", mapKey);
registerBuiltin("EDITOR-QUIT", editorQuit);
registerBuiltin("EDITOR-SAVE", l_editorSave);
registerBuiltin("EDITOR-INSERT-NEW-LINE", l_editorInsertNewLine);
registerBuiltin("MOVE-CURSOR-BEG-LINE", moveCursorBeginLine);
registerBuiltin("MOVE-CURSOR-END-LINE", moveCursorEndLine);
registerBuiltin("EDITOR-DELETE-PREVIOUS-CHAR", deletePreviousChar);
registerBuiltin("MOVE-CURSOR-PAGE-UP", editorMoveCursorPageUp);
registerBuiltin("MOVE-CURSOR-PAGE-DOWN", editorMoveCursorPageDown);
registerBuiltin("EDITOR-OPEN-FILE", editorOpenFile);
registerBuiltin("EDITOR-INSERT-CHAR", editorPrintC);
registerBuiltin("ADD-PACKAGE", addPackage);
registerBuiltin("EDITOR-DEL-ROW", editorDelRow_L);
registerBuiltin("EDITOR-FIND", editorFind_L);
registerBuiltin("EDITOR-READ-CHAR", editorReadChar_L);
// Registering C functions as lisp macro
registerBuiltin("move-cursor", moveCursor);
registerBuiltin("map-key", mapKey);
registerBuiltin("editor-quit", editorQuit);
registerBuiltin("editor-save", l_editorSave);
registerBuiltin("editor-insert-new-line", l_editorInsertNewLine);
registerBuiltin("move-cursor-beg-line", moveCursorBeginLine);
registerBuiltin("move-cursor-end-line", moveCursorEndLine);
registerBuiltin("editor-delete-previous-char", deletePreviousChar);
registerBuiltin("move-cursor-page-up", editorMoveCursorPageUp);
registerBuiltin("move-cursor-page-down", editorMoveCursorPageDown);
registerBuiltin("editor-open-file", editorOpenFile);
registerBuiltin("editor-insert-char", editorPrintC);
registerBuiltin("add-package", addPackage);
registerBuiltin("buffer-find", bufferFind_L);
registerBuiltin("buffer-find-reverse", bufferFindReverse_L);
registerBuiltin("editor-read-char", editorReadChar_L);
registerBuiltin("add-prefix", editorPrefix);
registerBuiltin("editor-set-prefix", editorSetPrefix);
registerBuiltin("editor-insert-tab", l_editorInserTab);
registerBuiltin("editor-switch-next-buffer", editorSwitchNextBuffer);
registerBuiltin("editor-split-screen-vertical", l_editorSplitScreenVertical);
registerBuiltin("editor-switch-next-pane", editorSwitchNextPane);
registerBuiltin("editor-unify-panes", editorUnifiedPanes);
registerBuiltin("editor-paste", editorPaste);
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 initEditor() {
char * init_file_path = (char *) calloc(256, sizeof(char));
E.cursor_x = 0;
E.cursor_y = 0;
E.rx = 0;
E.row_offset = 0;
E.col_offset = 0;
E.numrows = 0;
E.row = NULL;
E.dirty = 0;
E.filename = NULL;
E.state = READ_ONLY;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
die("getWindowSize");
}
E.screenrows -= 2;
void initConfig() {
E.number_of_keybinds = 0;
strcat(init_file_path, getenv("HOME"));
strcat(init_file_path, "/.beluga/config/init.lisp");
printf("%s\n", init_file_path);
E.fd_init_file = fopen(init_file_path, "r");
E.ctx = lisp_init();
E.ctx.p->err_port = fopen("lisp_log.err", "w");
E.env = lisp_env(E.ctx);
lisp_lib_load(E.ctx);
// Init builtins lisp functions
@@ -70,12 +65,96 @@ void initEditor() {
// Read config file
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
free(init_file_path);
if (E.ctx_error != LISP_ERROR_NONE) {
die("init failed");
}
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
}
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);
E.theme.COLOR_TYPE = ANSI_FG_RGB(97, 175, 239);
E.theme.COLOR_STRING = ANSI_FG_RGB(152, 195, 121);
E.theme.COLOR_COMMENT = ANSI_FG_RGB(92, 99, 112);
E.theme.COLOR_NUMBER = ANSI_FG_RGB(209, 154, 102);
E.theme.COLOR_DEFAULT = ANSI_FG_RGB(171, 178, 191);
} else {
E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(250, 251, 252);
E.theme.COLOR_KEYWORD = ANSI_FG_RGB(166, 38, 164);
E.theme.COLOR_TYPE = ANSI_FG_RGB(4, 90, 180);
E.theme.COLOR_STRING = ANSI_FG_RGB(80, 161, 79);
E.theme.COLOR_COMMENT = ANSI_FG_RGB(152, 152, 152);
E.theme.COLOR_NUMBER = ANSI_FG_RGB(206, 102, 54);
E.theme.COLOR_DEFAULT = ANSI_FG_RGB(86, 89, 90);
}
}
void initEditor() {
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
die("getWindowSize");
}
appDebug("%d %d", E.screenrows, E.screencols);
E.screenrows -= 2;
// Init graphics variables
splitScreenInit();
EditorPane *active = splitScreenGetActivePane();
E.cursor_x = 0;
E.cursor_y = 0;
active->cursor_x = 0;
active->cursor_y = 0;
active->origin_x = 0;
active->origin_y = 0;
active->width = E.screencols;
active->height = E.screenrows;
const char *home = getenv("HOME");
if (!home) {
die("HOME environment variable not set");
}
if (asprintf(&E.init_file_path, "%s/.beluga/config/init.lisp", home) == -1) {
die("asprintf failed");
}
E.fd_init_file = fopen(E.init_file_path, "r");
if (!E.fd_init_file) {
// File might not exist, that's okay for some cases
// Handle accordingly
}
E.status_msg = calloc(E.screencols, sizeof(char));
if (!E.status_msg) {
die("malloc failed");
}
E.status_msg[0] = '\0';
E.status_msg[0] = '\0';
E.status_msg_time = 0;
// Key binds
E.number_of_keybinds = 0;
E.number_of_prefix = 0;
// General prefix is 0 (no prefix)
E.prefix = (struct prefix_t *)malloc(sizeof(struct prefix_t));
E.prefix[0].prefix_id = 0;
strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
E.prefix_state = 0;
E.lsp_client = (LspClient*)malloc(sizeof(LspClient));
E.lsp_client->state = LSP_SHUTDOWN;
initConfig();
initTheme();
// To modify
E.constantes.TAB_LENGTH =
@@ -89,3 +168,25 @@ 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);
}
for (int i = 0; i < E.number_of_buffer; ++i)
{
free(E.buffers[i].filename);
free(E.buffers[i].path);
free(E.buffers[i].fullname);
free(E.buffers[i].row->chars);
free(E.buffers[i].row);
}
}
+215 -187
View File
@@ -1,84 +1,126 @@
#include "../include/input.h"
#include "../include/define.h"
#include "../include/editor_op.h"
#include "../include/output.h"
#include "../include/define.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 <unistd.h>
#include <sys/stat.h>
extern struct editorConfig E;
#include "include/terminal.h"
#include "include/utf8.h"
char * file_completion(const char *path) {
DIR * dir;
struct dirent *entry;
char directory[128];
char predict[128];
int predict_len = 0;
/**
* @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
*/
/**
* @brief Returns the first file completion match for the given path
* @details Searches the directory containing the given path prefix and returns
* 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:
* - path ends with '/' (already a directory)
* - no matching entries found
* - directory cannot be opened
* @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) {
DIR *dir;
struct dirent *entry;
char directory[256];
char predict[128];
const char *last_slash;
int predict_len = 0;
size_t dir_len;
// path is a directory
if (path[strlen(path) - 1] == '/') {
return path;
appDebug("[FILE COMP] is dir\n");
strncpy(directory, path, 256);
}
// Find dir name
char * last_slash = strrchr(path, '/');
if (last_slash) {
size_t dir_len = last_slash - path + 1; // length of dir_path
strncpy(directory, path, dir_len);
predict_len = strlen(path) - dir_len - 1;
strncpy(predict, last_slash + 1, predict_len);
directory[dir_len] = '\0';
predict[predict_len] = '\0';
fprintf(stderr, "%s %s\n", directory, predict);
} else {
return NULL;
}
// Find dir name
last_slash = strrchr(path, '/');
if (last_slash) {
dir_len = last_slash - path + 1; // length of dir_path
strncpy(directory, 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';
appDebug("%s %s\n", directory, predict);
} else {
appDebug("[FILE COMP] dir not found\n");
return strdup(path);
}
dir = opendir(directory);
if (!dir)
return NULL;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, predict, predict_len) == 0) {
static char full_path[128];
snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name);
return strdup(path);
struct stat st;
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
strcat(full_path, "/"); // add slash for directories
}
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, predict, predict_len) == 0) {
static char full_path[1024];
snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name);
return strdup(full_path);
}
struct stat st;
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
strcat(full_path, "/"); // add slash for directories
}
closedir(dir);
return strdup(full_path);
}
}
// Cleanup when no more entries
closedir(dir);
dir = NULL;
return NULL;
// Cleanup when no more entries
closedir(dir);
dir = NULL;
free(entry);
appDebug("[FILE COMP] no entries\n");
return strdup(path);
}
/**
* \fn char * editorPrompt(struct editorConfig *E, char *prompt, char bPathMode)
* \brief Return user input in a prompt when enter is hit. */
char *editorPrompt(char *prompt, char * placeHolder, char bPathMode) {
size_t buf_size = 128;
* @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.
* @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 bFreeing the returned string
* @note Uses editorReadKey() for input and editorRefreshScreen() for display
*/
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
size_t buf_size = 256;
appDebug("[FILE COMP] %s %d\n", placeHolder, strlen(placeHolder));
char *buf = malloc(buf_size);
size_t buf_len = 0;
int c = 0;
buf[0] = '\0';
strcpy(buf, placeHolder);
buf_len = strlen(placeHolder);
strcpy(buf, placeHolder);
buf_len = strlen(placeHolder);
while (1) {
editorSetStatusMessage(prompt, buf);
editorSetStatusMessage(prompt, buf);
editorRefreshScreen();
c = editorReadKey();
if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
@@ -95,162 +137,148 @@ char *editorPrompt(char *prompt, char * placeHolder, char bPathMode) {
return buf;
}
} else if (bPathMode && c == '\t') {
char path[128];
char * pwd;
if (buf[0] != '/') {
pwd = getenv("PWD");
fprintf(stderr, "%s\n", pwd);
memcpy(path, pwd, strlen(pwd));
path[strlen(pwd)] = '/';
strncat(path, buf, buf_len);
} else {
strcpy(path, buf);
}
memset(buf, 0, 128);
buf_len = 0;
strcpy(buf, file_completion(path));
buf_len = strlen(buf);
buf[buf_len] = '\0';
} else if (!iscntrl(c) && c < 128) {
char path[256];
char *pwd;
if (buf[0] != '/') {
pwd = getenv("PWD");
appDebug("%s\n", pwd);
memcpy(path, pwd, strlen(pwd));
path[strlen(pwd)] = '/';
strncat(path, buf, buf_len);
} else {
strcpy(path, buf);
}
memset(buf, 0, 256);
char *buf_complete = (char *)fileCompletion(path);
strcpy(buf, buf_complete);
free(buf_complete);
buf_len = strlen(buf);
buf[buf_len] = '\0';
} else if (!iscntrl(c) && c < 256) {
if (buf_len == buf_size - 1) {
buf_size *= 2;
buf = realloc(buf, buf_size);
}
buf[buf_len++] = c;
buf[buf_len] = '\0';
}
}
}
char *key_to_string(int key) {
static char key_str[32];
char tmp[10];
sprintf(tmp, "%d", key);
// First test enter key
if (key == '\r') {
strcpy(key_str, "ENTER");
} else if (key >= 1 && key <= 26) { // CTRL keys
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");
fprintf(stderr, "pagr up\n");
break;
case PAGE_DOWN:
strcpy(key_str, "PAGE-DOWN");
break;
case DEL_KEY:
fprintf(stderr, "delete key\n");
strcpy(key_str, "DEL");
break;
case BACKSPACE:
strcpy(key_str, "BACKSPACE");
break;
case '\r':
strcpy(key_str, "ENTER");
break;
case '\x1b':
strcpy(key_str, "ESCAPE");
break;
case BEG_LINE:
strcpy(key_str, "HOME");
break;
case END_LINE:
strcpy(key_str, "END");
break;
default:
// For regular characters
if (isprint(key)) {
snprintf(key_str, sizeof(key_str), "%c", key);
} else {
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
char *new_buf = realloc(buf, buf_size);
if (!new_buf) {
free(buf);
return NULL;
}
buf = new_buf;
}
}
}
return key_str;
}
void editorMoveCursor(int key) {
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
int row_len;
switch (key) {
case ARROW_RIGHT:
if (row && E.cursor_x < row->size) {
++E.cursor_x;
} else if (row && E.cursor_x == row->size) {
E.cursor_y++;
E.cursor_x = 0;
}
break;
case ARROW_DOWN:
if (E.cursor_y < E.numrows) {
++E.cursor_y;
}
break;
case ARROW_UP:
if (E.cursor_y != 0) {
--E.cursor_y;
}
break;
case ARROW_LEFT:
if (E.cursor_x != 0) {
--E.cursor_x;
} else if (E.cursor_y > 0) {
--E.cursor_y;
E.cursor_x = E.row[E.cursor_y].size;
}
break;
}
row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
row_len = row ? row->size : 0;
if (E.cursor_x > row_len) {
E.cursor_x = row_len;
}
}
/**
* @brief Executes the command bound to a key sequence
* @details Searches the keybinding table for a matching key sequence and
* prefix state, then evaluates the associated Lisp command.
* @param key_sequence The string representation of the key sequence
* @return 1 if a matching keybinding was found and executed, 0 otherwise
* @note Updates global editor state E (prefix_state)
* @note Uses Lisp interpreter to evaluate bound commands
*/
int executeKeyBind(char *key_sequence) {
int i;
int previous_state = 0;
appDebug("pressed %s\n", key_sequence);
for (i = 0; i < E.number_of_keybinds; ++i) {
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
fprintf(stderr, "lisp function %s\n", key_sequence);
// It's a symbol, create a function call
lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx),
&E.ctx_error, E.ctx);
return 1;
if (E.prefix_state != E.key_binds[i].prefix_id) {
continue;
}
previous_state = E.prefix_state;
// It's a symbol, create a function call
lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx),
&E.ctx_error, E.ctx);
if (E.prefix_state == previous_state)
E.prefix_state = 0;
return 1;
}
}
return 0;
}
/**
* @brief Processes a single keypress from the user
* @details Reads a key, checks if it matches any registered keybinding,
* 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
*/
void editorProcessKeypress() {
int c = editorReadKey();
char key_sequence[8];
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
if (executeKeyBind(key_to_string(c))) {
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;
}
editorInsertChar(c);
E.quit_times_buffer = E.constantes.QUIT_TIMES;
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
View File
@@ -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);
}
+399 -119
View File
@@ -1,137 +1,417 @@
/**
* @file output.c
* @brief Screen rendering and output module for the Beluga text editor
* @details Handles all screen updates, cursor positioning, status bar
* rendering, and display synchronization using ANSI escape sequences
*/
#include "../include/output.h"
#include <stdarg.h>
#include "../include/append_buffer.h"
#include "../include/buffer.h"
#include "../include/editor_op.h"
#include "../include/data.h"
#include "../include/define.h"
#include "../include/row_op.h"
#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
*/
static void editorDrawPane(struct abuf* ab, EditorPane* pane)
{
int file_row;
char pos_buf[32];
int pos_len;
int byte_len_to_print;
int bytes_to_print;
char* highlighted;
void editorDrawRows(struct abuf *ab) {
int y;
char welcome[80];
int welcome_len;
int padding;
int len;
int file_row;
for (y = 0; y < E.screenrows; ++y) {
file_row = y + E.row_offset;
if (file_row >= E.numrows) {
if (E.numrows == 0 && y == E.screenrows / 3) {
welcome_len =
snprintf(welcome, sizeof(welcome),
"Beluga text editor -- version %s", BELUGA_VERSION);
if (welcome_len > E.screencols) {
welcome_len = E.screencols;
if (pane == NULL || pane->buffer_id < 0)
return;
const struct buffer_t* buf = bufferFindById(pane->buffer_id);
if (buf == NULL)
return;
// Draw all pane
for (int y = 0; y < pane->height; y++)
{
file_row = y + pane->y_offset;
// Set cursor at start of pane row
pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH",
pane->origin_y + y + 1, pane->origin_x + 1);
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));
// pane line is out of buffer
if (file_row >= buf->numrows)
{
// Empty line - show tilde
abAppend(ab, "~", 1);
for (int i = 0;
i < pane->width - 1;
++i)
{
abAppend(ab, " ", 1);
}
}
padding = (E.screencols - welcome_len) / 2;
if (padding) {
abAppend(ab, "~", 1);
--padding;
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')
{
// Render line with syntax highlighting, constrain to pane width
highlighted = highlight_line(
&buf->row[file_row].chars[pane->x_offset], &byte_len_to_print);
// Print only up to pane width
abAppend(ab, highlighted, byte_len_to_print);
free(highlighted);
}
else
{
// Render basic line
bytes_to_print =
buf->row[file_row].size - pane->x_offset < pane->width
? buf->row[file_row].size - pane->x_offset
: pane->width;
abAppend(ab, &buf->row[file_row].chars[pane->x_offset], bytes_to_print);
}
// Fill remaining space with background color to pane width
for (int i = 0;
i < pane->width - editorRowCharCount(&buf->row[file_row], pane->width) + pane->x_offset;
++i)
{
abAppend(ab, " ", 1);
}
}
while (padding--) {
abAppend(ab, " ", 1);
}
}
/**
* @brief Renders all panes based on current split configuration
*/
static void editorDrawAllPanes(struct abuf* ab)
{
const ScreenLayout* layout = splitScreenGetLayout();
if (layout->num_panes == 1)
{
// Single pane fullscreen
editorDrawPane(ab, &layout->panes[0]);
}
else if (layout->num_panes == 2)
{
// Draw both panes
for (int i = 0; i < 2; i++)
{
editorDrawPane(ab, &layout->panes[i]);
// Draw pane border/divider if not the last pane
if (layout->mode == SPLIT_VERTICAL && i == 0)
{
// Draw vertical divider
int divider_col = layout->panes[0].width;
for (int y = 0; y < layout->panes[0].height; y++)
{
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, "\x1b[1m|\x1b[0m", 9); // Bold pipe divider
}
}
else if (layout->mode == SPLIT_HORIZONTAL && i == 0)
{
// Draw horizontal divider
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));
for (int x = 0; x < E.screencols; x++)
{
abAppend(ab, "\x1b[1m-\x1b[0m", 9); // Bold dash divider
}
}
}
abAppend(ab, welcome, welcome_len);
} else {
abAppend(ab, "~", 1);
}
} else {
len = E.row[file_row].rsize - E.col_offset;
if (len < 0)
len = 0;
if (len > E.screencols)
}
}
/**
* @brief Updates scroll offsets to keep cursor visible on screen
* @details Adjusts E.row_offset and E.col_offset to ensure the cursor remains
* within the visible viewport. Also updates E.rx (rendered x-coordinate).
* @note Updates global editor state E
* @see editorRowCxToRx()
*/
void editorScroll()
{
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buf = bufferFindById(active->buffer_id);
int rel_x, rel_y;
// compute relative coordinates
rel_x = editorRowCharCount(&buf->row[buf->y], buf->x);
appDebug("counting %d\n", rel_x);
rel_y = buf->y;
appDebug("%d %d / %d %d\n", active->cursor_x, active->x_offset,
active->cursor_y, active->y_offset);
while (rel_x != active->cursor_x + active->x_offset ||
rel_y != active->cursor_y + active->y_offset)
{
if (rel_x < active->cursor_x + active->x_offset)
{
// LEFT
if (active->cursor_x == 0 && active->x_offset)
{
active->x_offset--;
}
else
{
active->cursor_x--;
}
}
else
{
// RIGHT
if (rel_x > active->cursor_x + active->x_offset)
{
if (active->cursor_x == active->width - 1)
{
active->x_offset++;
}
else
{
active->cursor_x++;
}
}
}
if (rel_y < active->cursor_y + active->y_offset)
{
if (active->cursor_y == 0 && active->y_offset)
{
active->y_offset--;
}
else
{
active->cursor_y--;
}
}
if (rel_y > active->cursor_y + active->y_offset)
{
if (active->cursor_y == active->height - 1)
{
active->y_offset++;
}
else
{
active->cursor_y++;
}
}
}
}
char* basename(char* path)
{
int len = (int)strlen(path);
for (int i = len - 1; i > 0; i--)
{
if (path[i] == '/')
{
path = path + i + 1;
break;
}
}
return path;
}
/**
* @brief Renders the status bar at the bottom of the screen
* @details Displays filename, line count, dirty flag, and current cursor
* position in an inverted color bar. Right-aligns the cursor position
* indicator.
* @param ab Pointer to append buffer structure for accumulating output
* @note Uses ANSI escape codes for color inversion
*/
void editorDrawStatusBar(struct abuf* ab)
{
// TO MODIFY
int len, render_len;
char status[E.screencols], render_status[E.screencols * 4];
EditorPane* active = splitScreenGetActivePane();
struct buffer_t* buf = bufferFindById(active->buffer_id);
abAppend(ab, "\x1b[7m", 4); // inverting colors
const char* mode_str = "";
ScreenLayout* layout = splitScreenGetLayout();
if (layout->mode == SPLIT_VERTICAL)
mode_str = " [V-SPLIT]";
else if (layout->mode == SPLIT_HORIZONTAL)
mode_str = " [H-SPLIT]";
// Build buffer status showing all buffers with dirty indicators
char buf_status[1024] = "";
int offset = 0;
for (int i = 0; i < E.number_of_buffer; i++)
{
struct buffer_t* b = &E.buffers[i];
char marker = (b->buffer_id == active->buffer_id) ? '>' : ' ';
char dirty_marker = b->dirty ? '*' : ' ';
offset += snprintf(&buf_status[offset], sizeof(buf_status) - offset,
"%c%d:%s%c ", marker, b->buffer_id,
b->filename ? basename(b->filename) : "[No Name]", dirty_marker);
}
len = snprintf(status, sizeof(status), "%s%s", buf_status, mode_str);
render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
active->cursor_y + 1, buf->numrows);
if (len > E.screencols)
{
len = E.screencols;
abAppend(ab, &E.row[file_row].render[E.col_offset], len);
}
abAppend(ab, ERASE_END_LINE, 3);
abAppend(ab, status, len);
while (len < E.screencols)
{
if (E.screencols - len == render_len + 1)
{
abAppend(ab, render_status, render_len);
break;
}
abAppend(ab, " ", 1);
++len;
}
abAppend(ab, "\x1b[m", 3); // normal text mode
abAppend(ab, "\r\n", 2);
}
}
void editorScroll() {
E.rx = E.cursor_x;
if (E.cursor_y < E.numrows) {
E.rx = editorRowCxToRx(&E.row[E.cursor_y], E.cursor_x);
}
if (E.cursor_y < E.row_offset) {
E.row_offset = E.cursor_y;
}
if (E.cursor_y >= E.row_offset + E.screenrows) {
E.row_offset = E.cursor_y - E.screenrows + 1;
}
if (E.rx < E.col_offset) {
E.col_offset = E.rx;
}
if (E.rx >= E.col_offset + E.screencols) {
E.col_offset = E.rx - E.screencols + 1;
}
}
void editorDrawStatusBar(struct abuf *ab) {
int len, render_len;
char status[80], render_status[80];
abAppend(ab, "\x1b[7m", 4); // inverting colors
len = snprintf(status, sizeof(status), "%.20s - %d lines%s",
E.filename ? E.filename : "[No Name]", E.numrows,
E.dirty ? "*" : "");
render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
E.cursor_y + 1, E.numrows);
if (len > E.screencols) {
len = E.screencols;
}
abAppend(ab, status, len);
while (len < E.screencols) {
if (E.screencols - len == render_len) {
abAppend(ab, render_status, render_len);
break;
} else {
abAppend(ab, " ", 1);
++len;
/**
* @brief Renders the message bar below the status bar
* @details Displays temporary status messages for a limited time (5 seconds).
* Only displays message if within time window and within screen width.
* @param ab Pointer to append buffer structure for accumulating output
* @note Messages are set by editorSetStatusMessage()
*/
void editorDrawMessageBar(struct abuf* ab)
{
int msg_len = (int)strlen(E.status_msg);
abAppend(ab, ERASE_END_LINE, 3);
if (msg_len > E.screencols)
{
msg_len = E.screencols;
}
if (msg_len && time(NULL) - E.status_msg_time < 5)
{
abAppend(ab, E.status_msg, msg_len);
}
}
abAppend(ab, "\x1b[m", 3); // normal text mode
abAppend(ab, "\r\n", 2);
}
void editorDrawMessageBar(struct abuf *ab) {
int msg_len = strlen(E.status_msg);
abAppend(ab, ERASE_END_LINE, 3);
if (msg_len > E.screencols) {
msg_len = E.screencols;
}
if (msg_len && time(NULL) - E.status_msg_time < 5) {
abAppend(ab, E.status_msg, msg_len);
}
}
void editorRefreshScreen() {
editorScroll();
struct abuf ab = ABUF_INIT;
char buf[32];
abAppend(&ab, HIDE_CURSOR, 6);
abAppend(&ab, CURSOR_TOP_LEFT, 3);
editorDrawRows(&ab);
editorDrawStatusBar(&ab);
editorDrawMessageBar(&ab);
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cursor_y - E.row_offset) + 1,
(E.rx - E.col_offset) + 1);
abAppend(&ab, buf, strlen(buf));
abAppend(&ab, SHOW_CURSOR, 6);
write(STDOUT_FILENO, ab.b, ab.len);
abFree(&ab);
}
void editorSetStatusMessage(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
va_end(ap);
E.status_msg_time = time(NULL);
/**
* @brief Performs complete screen refresh and buffer synchronization
* @details Clears screen, redraws all visible content (rows, status bar,
* message bar), positions cursor, and writes accumulated buffer to stdout. This
* is the main rendering function called each frame.
* @note Updates global editor state E (via editorScroll())
* @see editorDrawRows()
* @see editorDrawStatusBar()
* @see editorDrawMessageBar()
*/
void editorRefreshScreen()
{
struct abuf ab = ABUF_INIT;
char buf[32];
abAppend(&ab, HIDE_CURSOR, 6);
abAppend(&ab, CURSOR_TOP_LEFT, 3);
abAppend(&ab, E.theme.BACKGROUND_COLOR,
(int)strlen(E.theme.BACKGROUND_COLOR));
editorScroll();
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);
if (E.constantes.LSP && (E.lsp_client && E.lsp_client->state == LSP_READY))
{
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);
}
+2 -141
View File
@@ -1,4 +1,6 @@
#include "../include/row_op.h"
#include "../include/data.h"
#include "../include/utf8.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -6,146 +8,5 @@
extern struct editorConfig E;
int editorRowCxToRx(erow *row, int cursor_x) {
int render_x = 0;
int i;
for (i = 0; i < cursor_x; ++i) {
if (row->chars[i] == '\t') {
render_x += (E.constantes.TAB_LENGTH - 1) - (render_x % E.constantes.TAB_LENGTH);
}
render_x++;
}
return render_x;
}
int editorRowRxToCx(erow *row, int rx) {
int cur_rx = 0;
int cx;
for (cx = 0; cx < row->size; cx++) {
if (row->chars[cx] == '\t')
cur_rx += (E.constantes.TAB_LENGTH - 1) - (cur_rx % E.constantes.TAB_LENGTH);
cur_rx++;
if (cur_rx > rx) return cx;
}
return cx;
}
/**
* \fn editorUpdateRow(erow *row)
* \brief Copy content of \p row in \p row->render.
* */
void editorUpdateRow(erow *row) {
int i, i_render;
int tabs = 0;
// counting number of tabs
for (i = 0; i < row->size; ++i) {
tabs +=
(row->chars[i] == '\t'); /**< increment tabs of 1 if chars[i] is one. */
}
free(row->render);
row->render = malloc(row->size + tabs * (E.constantes.TAB_LENGTH - 1) +
1); /**< Tabs needs E.constantes.TAB_LENGTH chars so E.constantes.TAB_LENGTH - 1
more than the first already counted. */
// end of counting
i_render = 0;
for (i = 0; i < row->size; ++i) {
if (row->chars[i] == '\t') {
row->render[i_render++] = ' ';
while (i_render % E.constantes.TAB_LENGTH) {
row->render[i_render++] =
' '; /**< Addind the right amount of spaces for tabs */
}
} else {
row->render[i_render++] = row->chars[i];
}
}
row->render[i_render] = '\0'; // Don't forget the end of string character.
row->rsize = i_render;
}
void editorInsertRow(int at, char *s, size_t len) {
if (at < 0 || at > E.numrows) {
return;
}
erow *tmp = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1));
if (!tmp) {
return;
}
E.row = tmp;
memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));
E.row[at].size = len;
E.row[at].chars = malloc(len + 1);
memcpy(E.row[at].chars, s, len);
E.row[at].chars[len] = '\0';
E.row[at].rsize = 0;
E.row[at].render = NULL;
editorUpdateRow(&E.row[at]);
++E.numrows;
++E.dirty;
}
void editorFreeRow(erow *row) {
free(row->render);
free(row->chars);
}
void editorDelRow(int at) {
if (at < 0 || at >= E.numrows) {
return;
}
editorFreeRow(&E.row[at]);
memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1));
--E.numrows;
++E.dirty;
}
/**
* \fn editorRowInsertChar(erow *row, int at, int c)
* \param at Index of where we want to insert the char */
void editorRowInsertChar(erow *row, int at, int c) {
if (E.state == READ_ONLY)
return;
if (at < 0 || at > row->size) {
at = row->size;
}
row->chars = realloc(row->chars, row->size + 2);
memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
++row->size;
row->chars[at] = c;
editorUpdateRow(row);
++E.dirty;
}
void editorRowAppendString(erow *row, char *s, size_t len) {
row->chars = realloc(row->chars, row->size + len + 1);
memcpy(&row->chars[row->size], s, len);
row->size += len;
row->chars[row->size] = '\0';
editorUpdateRow(row);
++E.dirty;
}
/**
* \fn editorRowDelChar(struct editorConfig *E, erow *erow, int at)
* \brief Delete the a char at the chosen position on the given row
* \param at Index of the char to delete
* \param row Row on operation is made */
void editorRowDelchar(erow *row, int at) {
if (at < 0 || at >= row->size) {
return;
}
memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
--row->size;
editorUpdateRow(row);
++E.dirty;
}
+236
View File
@@ -0,0 +1,236 @@
/**
* @file split_screen.c
* @brief Split screen implementation
*/
#include "../include/split_screen.h"
#include "../include/buffer.h"
#include "../include/editor_op.h"
#include <stdlib.h>
#include <string.h>
extern struct editorConfig E;
/**
* @brief Initializes split screen system with single fullscreen pane
*/
void splitScreenInit(void) {
E.layout.mode = SPLIT_NONE;
E.layout.num_panes = 1;
E.layout.active_pane = 0;
EditorPane *new_panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
if (!new_panes) {
editorSetStatusMessage("Error: realloc failed");
return;
}
E.layout.panes = new_panes;
// Initialize single fullscreen pane
E.layout.panes[0].buffer_id = -1; // No buffer for now
E.layout.panes[0].origin_y = 0;
E.layout.panes[0].origin_x = 0;
E.layout.panes[0].height = E.screenrows - 2; // Leave room for status bar
E.layout.panes[0].width = E.screencols;
E.layout.panes[0].cursor_x = 0;
E.layout.panes[0].cursor_y = 0;
E.layout.panes[0].y_offset = 0;
E.layout.panes[0].x_offset = 0;
E.layout.panes[0].is_active = 1;
}
/**
* @brief Splits screen vertically (left-right)
* @param buffer_id_left Buffer ID for left pane
* @param buffer_id_right Buffer ID for right pane
* @return 0 on success, -1 on failure
*/
int splitScreenVertical(int buffer_id_left, int buffer_id_right) {
// Verify both buffers exist
if (bufferFindById(buffer_id_left) == NULL ||
bufferFindById(buffer_id_right) == NULL) {
editorSetStatusMessage("Error: invalid buffer IDs");
return -1;
}
// bReallocate panes array
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
E.layout.mode = SPLIT_VERTICAL;
E.layout.num_panes = 2;
E.layout.active_pane = 0;
int half_width = E.screencols / 2;
int pane_height = E.screenrows; // Leave room for status bar
// Left pane
E.layout.panes[0].buffer_id = buffer_id_left;
E.layout.panes[0].origin_y = 0;
E.layout.panes[0].origin_x = 0;
E.layout.panes[0].height = pane_height;
E.layout.panes[0].width = half_width;
E.layout.panes[0].cursor_x = 0;
E.layout.panes[0].cursor_y = 0;
E.layout.panes[0].y_offset = 0;
E.layout.panes[0].x_offset = 0;
E.layout.panes[0].is_active = 1;
// Right pane
E.layout.panes[1].buffer_id = buffer_id_right;
E.layout.panes[1].origin_y = 0;
E.layout.panes[1].origin_x = half_width;
E.layout.panes[1].height = pane_height;
E.layout.panes[1].width = E.screencols - half_width;
E.layout.panes[1].cursor_x = 0;
E.layout.panes[1].cursor_y = 0;
E.layout.panes[1].y_offset = 0;
E.layout.panes[1].x_offset = 0;
E.layout.panes[1].is_active = 0;
editorSetStatusMessage("Vertical split: %d | %d", buffer_id_left, buffer_id_right);
return 0;
}
/**
* @brief Splits screen horizontally (top-bottom)
* @param buffer_id_top Buffer ID for top pane
* @param buffer_id_bottom Buffer ID for bottom pane
* @return 0 on success, -1 on failure
*/
int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom) {
// Verify both buffers exist
if (bufferFindById(buffer_id_top) == NULL ||
bufferFindById(buffer_id_bottom) == NULL) {
editorSetStatusMessage("Error: invalid buffer IDs");
return -1;
}
// bReallocate panes array
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
E.layout.mode = SPLIT_HORIZONTAL;
E.layout.num_panes = 2;
E.layout.active_pane = 0;
int half_height = (E.screenrows - 2) / 2; // Account for status bar
// Top pane
E.layout.panes[0].buffer_id = buffer_id_top;
E.layout.panes[0].origin_y = 0;
E.layout.panes[0].origin_x = 0;
E.layout.panes[0].height = half_height;
E.layout.panes[0].width = E.screencols;
E.layout.panes[0].cursor_x = 0;
E.layout.panes[0].cursor_y = 0;
E.layout.panes[0].y_offset = 0;
E.layout.panes[0].x_offset = 0;
E.layout.panes[0].is_active = 1;
// Bottom pane
E.layout.panes[1].buffer_id = buffer_id_bottom;
E.layout.panes[1].origin_y = half_height;
E.layout.panes[1].origin_x = 0;
E.layout.panes[1].height = E.screenrows - 2 - half_height;
E.layout.panes[1].width = E.screencols;
E.layout.panes[1].cursor_x = 0;
E.layout.panes[1].cursor_y = 0;
E.layout.panes[1].x_offset = 0;
E.layout.panes[1].y_offset = 0;
E.layout.panes[1].is_active = 0;
editorSetStatusMessage("Horizontal split: %d / %d", buffer_id_top, buffer_id_bottom);
return 0;
}
/**
* @brief Returns to single buffer fullscreen
*/
void splitScreenUnify(void) {
E.layout.mode = SPLIT_NONE;
E.layout.num_panes = 1;
E.layout.active_pane = 0;
E.layout.panes[0].origin_x = 0;
E.layout.panes[0].origin_y = 0;
E.layout.panes[0].height = E.screenrows;
E.layout.panes[0].width = E.screencols;
E.layout.panes[0].is_active = 1;
editorSetStatusMessage("Unified view");
}
/**
* @brief Switches active pane (focus moves between splits)
* @return 0 on success, -1 on failure
*/
int splitScreenSwitchPane(void) {
if (E.layout.num_panes < 2) {
editorSetStatusMessage("No split to switch");
return -1;
}
// Deactivate current pane
E.layout.panes[E.layout.active_pane].is_active = 0;
// Move to next pane
E.layout.active_pane = (E.layout.active_pane + 1) % E.layout.num_panes;
// Activate new pane
E.layout.panes[E.layout.active_pane].is_active = 1;
editorSetStatusMessage("Switched to pane %d (buffer %d)",
E.layout.active_pane,
E.layout.panes[E.layout.active_pane].buffer_id);
return 0;
}
/**
* @brief Updates the active pane's buffer
* @param buffer_id New buffer ID for active pane
* @return 0 on success, -1 on failure
*/
int splitScreenSetPaneBuffer(int buffer_id) {
if (bufferFindById(buffer_id) == NULL) {
editorSetStatusMessage("Error: invalid buffer ID");
return -1;
}
EditorPane *active = &E.layout.panes[E.layout.active_pane];
active->buffer_id = buffer_id;
active->cursor_x = 0;
active->cursor_y = 0;
active->origin_x = 0;
active->origin_y = 0;
editorSetStatusMessage("Pane %d now showing buffer %d",
E.layout.active_pane, buffer_id);
return 0;
}
/**
* @brief Gets current screen layout
* @return Pointer to current ScreenLayout
*/
ScreenLayout *splitScreenGetLayout(void) {
return &E.layout;
}
/**
* @brief Gets active pane
* @return Pointer to active EditorPane
*/
EditorPane *splitScreenGetActivePane(void) {
if (E.layout.num_panes == 0) return NULL;
return &E.layout.panes[E.layout.active_pane];
}
void freeScreenLayout(ScreenLayout *layout) {
if (layout) {
free(layout->panes);
layout->panes = NULL;
}
}
void freePane(EditorPane *pane)
{
free(pane);
}
+252
View File
@@ -0,0 +1,252 @@
#include "../include/syntax_highlighter.h"
#include "../include/data.h"
#include "../include/utf8.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern struct editorConfig E;
const char *c_keywords[] = {
"if", "else", "while", "for", "do", "switch",
"case", "break", "#include", "#define", "#if", "#endif", "#ifndef", "continue", "return",
"goto", "struct", "union", "enum", "typedef", "static",
"extern", "const", "volatile", "sizeof", "auto", "register",
"inline", "restrict", NULL};
// C types
const char *c_types[] = {"int", "char", "float", "double",
"void", "long", "short", "unsigned",
"signed", "bool", NULL};
// Returns the byte length of the UTF-8 character starting at s.
// Never returns 0 for a non-NUL byte, so callers won't infinite-loop.
static int utf8_char_len(const char *s)
{
unsigned char c = (unsigned char)*s;
if (c == 0) return 0;
if (c < 0x80) return 1;
if ((c & 0xE0) == 0xC0) return 2;
if ((c & 0xF0) == 0xE0) return 3;
if ((c & 0xF8) == 0xF0) return 4;
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)
{
int len = utf8_char_len(&src[*src_pos]);
for (int b = 0; b < len; b++)
dst[(*dst_pos)++] = src[(*src_pos)++];
}
// Check if character is alphanumeric or underscore
// Check if string is a keyword
int is_keyword(const char *word) {
for (int i = 0; c_keywords[i] != NULL; i++) {
if (strcmp(word, c_keywords[i]) == 0)
return 1;
}
return 0;
}
// Check if string is a type
int is_type(const char *word) {
for (int i = 0; c_types[i] != NULL; i++) {
if (strcmp(word, c_types[i]) == 0)
return 1;
}
return 0;
}
// Get color code for token type
const char *get_color(TokenType type) {
switch (type) {
case TOKEN_KEYWORD:
return E.theme.COLOR_KEYWORD;
case TOKEN_TYPE:
return E.theme.COLOR_TYPE;
case TOKEN_STRING:
return E.theme.COLOR_STRING;
case TOKEN_COMMENT:
return E.theme.COLOR_COMMENT;
case TOKEN_NUMBER:
return E.theme.COLOR_NUMBER;
default:
return E.theme.COLOR_DEFAULT;
}
}
int comment_section = 0;
// Highlight a line of C code and return the highlighted string
// Returns a newly allocated string that must be freed by the caller
char *highlight_line(const char *line, int *length) {
// Each byte can expand to at most (color_prefix + 4 bytes + color_reset).
// Allocate generously based on line length to avoid overflow.
int line_len = (int) strlen(line);
int buf_size = line_len * 32 + 256;
char *result = malloc(buf_size);
if (!result) {
*length = 0;
return NULL;
}
int result_pos = 0;
int i = 0;
while (line[i] != '\0' && line[i] != '\n') {
// Skip whitespace — copy full UTF-8 char (whitespace is always ASCII,
// but using copy_utf8_char keeps the pattern consistent)
if (line[i] == ' ' || line[i] == '\t') {
copy_utf8_char(result, &result_pos, line, &i);
continue;
}
if (comment_section) {
result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_COMMENT);
while (line[i] != '\0' && line[i] != '\n') {
if (line[i] == '*' && line[i + 1] == '/') {
comment_section = 0;
}
copy_utf8_char(result, &result_pos, line, &i);
}
result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue;
}
// Handle line comments
if (line[i] == '/' && line[i + 1] == '/') {
result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_COMMENT);
while (line[i] != '\0' && line[i] != '\n') {
copy_utf8_char(result, &result_pos, line, &i);
}
result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue;
}
// Handle block comments
if (line[i] == '/' && line[i + 1] == '*') {
comment_section = 1;
result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_COMMENT);
result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
while (line[i] != '\0') {
if (line[i] == '*' && line[i + 1] == '/') {
result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
comment_section = 0;
break;
}
copy_utf8_char(result, &result_pos, line, &i);
}
result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue;
}
// Handle strings
if (line[i] == '"') {
result_pos += snprintf(&result[result_pos], 10, "%s\"", E.theme.COLOR_STRING);
i++;
while (line[i] != '\0' && line[i] != '"') {
if (line[i] == '\\') {
result[result_pos++] = line[i++];
copy_utf8_char(result, &result_pos, line, &i);
} else {
copy_utf8_char(result, &result_pos, line, &i);
}
}
if (line[i] == '"')
result[result_pos++] = line[i++];
result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue;
}
// Handle character literals
if (line[i] == '\'') {
result_pos += snprintf(&result[result_pos], 10, "%s'", E.theme.COLOR_STRING);
i++;
while (line[i] != '\0' && line[i] != '\'') {
if (line[i] == '\\') {
result[result_pos++] = line[i++];
copy_utf8_char(result, &result_pos, line, &i);
} else {
copy_utf8_char(result, &result_pos, line, &i);
}
}
if (line[i] == '\'')
result[result_pos++] = line[i++];
result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue;
}
// Handle numbers
if (line[i] >= '0' && line[i] <= '9') {
result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_NUMBER);
while (is_word_char(&line[i]) || line[i] == '.') {
copy_utf8_char(result, &result_pos, line, &i);
}
result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
continue;
}
// Handle identifiers and keywords
if (is_word_char(&line[i])) {
int start = i;
// Advance by full UTF-8 character widths, not single bytes
while (line[i] != '\0' && is_word_char(&line[i]))
i += utf8_char_len(&line[i]);
char word[256];
int word_len = i - start;
if (word_len >= (int)sizeof(word))
word_len = (int)sizeof(word) - 1;
strncpy(word, &line[start], word_len);
word[word_len] = '\0';
TokenType type = TOKEN_DEFAULT;
if (is_keyword(word))
type = TOKEN_KEYWORD;
else if (is_type(word))
type = TOKEN_TYPE;
result_pos += snprintf(&result[result_pos], 100, "%s%s%s", get_color(type),
word, COLOR_RESET);
continue;
}
// Handle operators and other characters (including non-ASCII multi-byte)
result_pos += snprintf(&result[result_pos], 10, "%s", E.theme.COLOR_DEFAULT);
copy_utf8_char(result, &result_pos, line, &i);
result_pos += snprintf(&result[result_pos], 10, "%s", COLOR_RESET);
}
result[result_pos] = '\0';
*length = result_pos + 1;
return result;
}
+293 -121
View File
@@ -1,149 +1,321 @@
#include "../include/terminal.h"
#include <ctype.h>
#include "../include/data.h"
#include "../include/define.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.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);
#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 disableRawMode() {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) {
die("tcsetattr");
}
}
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;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
die("tcgetattr");
}
}
int editorReadKey() {
int nread;
char c;
char seq[3];
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
if (nread == -1 && errno != EAGAIN) {
die("read");
void disableRawMode()
{
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1)
{
die("tcsetattr");
}
}
}
if (c == '\x1b') {
if (read(STDIN_FILENO, &seq[0], 1) != 1 ||
read(STDIN_FILENO, &seq[1], 1) != 1) {
return '\x1b';
void enableRawMode()
{
if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1)
{
die("tcgetattr");
}
if (seq[0] == '[') {
if (seq[1] >= '0' && seq[1] <= '9') {
if (read(STDIN_FILENO, &seq[2], 1) != 1) {
return '\x1b';
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");
}
}
#include <ctype.h> /* isprint */
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 (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;
}
}
} else if (seq[0] == 'O') {
switch (seq[1]) {
case 'H':
return BEG_LINE;
case 'F':
return END_LINE;
}
}
return '\x1b';
} else {
return c;
}
return key_str;
}
int getCursorPosition(int *rows, int *cols) {
char buf[32];
unsigned int i = 0;
int editorReadKey()
{
char c;
int nread;
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) {
return -1;
}
while (1)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
while (i < sizeof(buf) - 1) {
if (read(STDIN_FILENO, &buf[i], 1) != 1) {
break;
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;
}
}
if (buf[i] == 'R') {
break;
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';
}
++i;
}
buf[i] = '\0';
if (buf[0] != '\x1b' || buf[1] != '[') {
return -1;
}
if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) {
return -1;
}
/* 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 0;
return (unsigned char)c;
}
int getWindowSize(int *rows, int *cols) {
struct winsize ws;
int getCursorPosition(int* rows, int* cols)
{
char buf[32];
unsigned int i = 0;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) {
return -1;
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 (buf[i] == 'R')
{
break;
}
++i;
}
buf[i] = '\0';
if (buf[0] != '\x1b' || buf[1] != '[')
{
return -1;
}
if (sscanf(&buf[2], "%d;%d", rows, cols) != 2)
{
return -1;
}
return 0;
}
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;
}
return getCursorPosition(rows, cols);
}
return getCursorPosition(rows, cols);
} else {
*cols = ws.ws_col;
*rows = ws.ws_row;
return 0;
}
}
void appDebug(const char* fmt, ...)
{
#ifdef APP_DEBUG
va_list ap;
char message[1024];
va_start(ap, fmt);
vsnprintf(message, 1024, fmt, ap);
va_end(ap);
fprintf(stderr, "%s\n", message);
#endif
}
+147
View File
@@ -0,0 +1,147 @@
/**
* @file utf8.c
*/
#include "../include/utf8.h"
#include <stdint.h>
#include <unistd.h>
uint32_t readUtf8Char(void)
{
unsigned char buf[4];
read(STDIN_FILENO, &buf[0], 1);
int extra;
uint32_t cp;
if (buf[0] < 0x80)
{
cp = buf[0];
extra = 0;
}
else if (buf[0] < 0xC0) { return 0xFFFD; } // stray continuation
else if (buf[0] < 0xE0)
{
cp = buf[0] & 0x1F;
extra = 1;
}
else if (buf[0] < 0xF0)
{
cp = buf[0] & 0x0F;
extra = 2;
}
else
{
cp = buf[0] & 0x07;
extra = 3;
}
if (extra > 0)
{
read(STDIN_FILENO, &buf[1], extra); // read remaining bytes at once
for (int i = 0; i < extra; i++)
cp = (cp << 6) | (buf[1 + i] & 0x3F);
}
return cp;
}
uint32_t utf8Decode(const char** s)
{
unsigned char c = (unsigned char)**s;
uint32_t cp;
int extra;
if (c < 0x80)
{
cp = c;
extra = 0;
}
else if (c < 0xC0)
{
(*s)++;
return 0xFFFD;
}
else if (c < 0xE0)
{
cp = c & 0x1F;
extra = 1;
}
else if (c < 0xF0)
{
cp = c & 0x0F;
extra = 2;
}
else
{
cp = c & 0x07;
extra = 3;
}
(*s)++;
while (extra--)
{
c = (unsigned char)**s;
if ((c & 0xC0) != 0x80) return 0xFFFD;
cp = (cp << 6) | (c & 0x3F);
(*s)++;
}
return cp;
}
// buf must have at least 4 bytes; returns bytes written
int utf8Encode(uint32_t cp, char* buf)
{
if (cp < 0x80)
{
buf[0] = cp;
return 1;
}
if (cp < 0x800)
{
buf[0] = 0xC0 | (cp >> 6);
buf[1] = 0x80 | (cp & 0x3F);
return 2;
}
if (cp < 0x10000)
{
buf[0] = 0xE0 | (cp >> 12);
buf[1] = 0x80 | ((cp >> 6) & 0x3F);
buf[2] = 0x80 | (cp & 0x3F);
return 3;
}
buf[0] = 0xF0 | (cp >> 18);
buf[1] = 0x80 | ((cp >> 12) & 0x3F);
buf[2] = 0x80 | ((cp >> 6) & 0x3F);
buf[3] = 0x80 | (cp & 0x3F);
return 4;
}
int utf8Seqlen(unsigned char c)
{
if (c < 0x80) return 1;
if (c < 0xC0) return 0; /* continuation — shouldn't be leading */
if (c < 0xE0) return 2;
if (c < 0xF0) return 3;
return 4;
}
/**
* @param codepoint utf8 codepoint of a char
* @return length of the codepoint
*/
int codepointWidth(uint32_t codepoint)
{
if (codepoint < 0x20 || codepoint == 0x7F) return 0;
/* rough double-width ranges */
if ((codepoint >= 0x1100 && codepoint <= 0x115F) ||
(codepoint >= 0x2E80 && codepoint <= 0x303E) ||
(codepoint >= 0x3041 && codepoint <= 0x33BF) ||
(codepoint >= 0xAC00 && codepoint <= 0xD7AF) ||
(codepoint >= 0xF900 && codepoint <= 0xFAFF) ||
(codepoint >= 0xFF01 && codepoint <= 0xFF60) ||
(codepoint >= 0x1F300 && codepoint <= 0x1FAFF))
return 2;
return 1;
}
+3
View File
@@ -0,0 +1,3 @@
int main() {
printf("hello");
}