1 Commits

Author SHA1 Message Date
Giorgio ar work cce1ffa903 mistral gagnant 2026-02-04 16:16:36 +01:00
50 changed files with 12917 additions and 17081 deletions
+2876 -2876
View File
File diff suppressed because it is too large Load Diff
+32 -32
View File
@@ -1,32 +1,32 @@
# Beluga # Beluga
Beluga is a project of CLI text editor that uses lisp as configuration language. Beluga is a project of CLI text editor that uses lisp as configuration language.
## Requirements ## Requirements
You will only need **meson** and a **C compiler** to compile the editor. You will only need **meson** and a **C compiler** to compile the editor.
## Installation ## Installation
### From source ### From source
Here is the installation line for development version: Here is the installation line for development version:
```git clone https://git.giorgio-nas.fr/arthur/beluga.git && 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. The executable file will be `build/beluga`. Feel free to add it to your path.
### From installation script ( prefered ) ### From installation script ( prefered )
Just clone the repo and execute the script `install.sh`. It will automatically add beluga to your path. Just clone the repo and execute the script `install.sh`. It will automatically add beluga to your path.
## Getting start ## Getting start
To open an existing file just type : To open an existing file just type :
```./build/beluga path_to_my_file``` ```./build/beluga path_to_my_file```
Here is some few command that you will need : Here is some few command that you will need :
| keybind | command | | keybind | command |
|---------------|------------------| |---------------|------------------|
| Ctrl-x Ctrl-c | leave the editor | | Ctrl-x Ctrl-c | leave the editor |
| Ctrl-x Ctrl-s | Save a file | | Ctrl-x Ctrl-s | Save a file |
| Ctrl-x f | open file | | Ctrl-x f | open file |
+43 -43
View File
@@ -1,43 +1,43 @@
**#%#*****###%** **#%#*****###%**
*##+--------------------=##* *##+--------------------=##*
#*=----------------------------=*#* #*=----------------------------=*#*
#*------------------------------------*# #*------------------------------------*#
%+----------------------------------------=#* %+----------------------------------------=#*
#+---------------------------------------------## #+---------------------------------------------##
*#-------------------------------------------------=## *#-------------------------------------------------=##
*#----------------------------------------------------:-## *#----------------------------------------------------:-##
#----------------------------------------------------------## #----------------------------------------------------------##
#=--------------------------------------------------------------## #=--------------------------------------------------------------##
+--------------------------+@#-%*-----------------------------------#* +--------------------------+@#-%*-----------------------------------#*
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.4 +--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.3
*-=-------------------------#@@*---------------------------------------=% *-=-------------------------#@@*---------------------------------------=%
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS ----- %*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
*%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave *%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave
%=--------------------------------------------------------------------------#* CTRL-x CTRL-s save %=--------------------------------------------------------------------------#* CTRL-x CTRL-s save
%-----------------------------------------------------------------------------** CTRL-x f open-file %-----------------------------------------------------------------------------** CTRL-x f open-file
*+--=---===----=---------------=*-----------------------------------------------** *+--=---===----=---------------=*-----------------------------------------------**
#--=## *#%#*+==----==+**+----------------------= ***=---------------------% #--=## *#%#*+==----==+**+----------------------= ***=---------------------%
*%**=-----------==== ==---------------------------------=+#+-----------------=# *%**=-----------==== ==---------------------------------=+#+-----------------=#
*%=----------------------------------------------------------=#*---------------# *%=----------------------------------------------------------=#*---------------#
##=----------------------------------=------------------------+%=------------+# ##=----------------------------------=------------------------+%=------------+#
#%+---------------------------------=*------------------------+%------------# #%+---------------------------------=*------------------------+%------------#
*#%*=-------------=-----------------#-------------------------#+----------# *#%*=-------------=-----------------#-------------------------#+----------#
**#%#*******+=======-------------#=------------------------#----------# **#%#*******+=======-------------#=------------------------#----------#
#===#*=======------------------#*----=-----------=--=##*-----------# #===#*=======------------------#*----=-----------=--=##*-----------#
-====##=------------------------*%+------------=*#+=====----------# -====##=------------------------*%+------------=*#+=====----------#
--=====+#*=----------------------=-=+*#####***+=======-----------=* --=====+#*=----------------------=-=+*#####***+=======-----------=*
%------=====*%*=-------------------------========-----------------+* %------=====*%*=-------------------------========-----------------+*
*-=--------====%%###+=--------------------------=-----------------# *-=--------====%%###+=--------------------------=-----------------#
#-----------=% +*##%%%%%%@@%%%%####*==---------------------** #-----------=% +*##%%%%%%@@%%%%####*==---------------------**
%=-------#* #%*=-----------------+# %=-------#* #%*=-----------------+#
*%+--=## ##=-----------------=#* *%+--=## ##=-----------------=#*
** #+----=-------------------#* ** #+----=-------------------#*
%+----------------------------#* %+----------------------------#*
*%-------------==----------------+# *%-------------==----------------+#
##--------------==------------------# ##--------------==------------------#
*#--------------===%-----------------=% *#--------------===%-----------------=%
##---------------=-##*-----------------+# ##---------------=-##*-----------------+#
*#---------------==#+=#%-----------------% *#---------------==#+=#%-----------------%
*%---------------+# %*---------------#* *%---------------+# %*---------------#*
*#------------=+#* #%*=-----------#* *#------------=+#* #%*=-----------#*
#****##****** *#%%##+=----% #****##****** *#%%##+=----%
+91 -94
View File
@@ -1,94 +1,91 @@
;; MACROS ;; MACROS
(define TAB-LENGTH 4) (define TAB-LENGTH 4)
(define QUIT-TIMES 1) (define QUIT-TIMES 1)
(define THEME "dark") (define THEME "dark")
;; PACKAGES ;; PACKAGES
;; First git clone it ;; First git clone it
;; (add-package "smart_delimiters") ;; (add-package "smart_delimiters")
;; FUNCTIONS ;; FUNCTIONS
(define editor-delete-next-char (lambda () ( (define editor-delete-next-char (lambda () (
(move-cursor "right") (move-cursor "right")
(editor-delete-previous-char) (editor-delete-previous-char)
) )
) )
) )
(define (char-between ch lo hi) (define (char-between ch lo hi)
(if (char>=? ch lo) (if (char>=? ch lo)
(char<=? ch hi) (char<=? ch hi)
#f)) #f))
(define (alphanumericp ch) (define (alphanumericp ch)
(if ch (if ch
(if (char-between ch #\a #\z) (if (char-between ch #\a #\z)
#t #t
(if (char-between ch #\A #\Z) (if (char-between ch #\A #\Z)
#t #t
(if (char-between ch #\0 #\9) (if (char-between ch #\0 #\9)
#t #t
#f))) #f)))
#f)) #f))
(define (word-char-p ch) (define (word-char-p ch)
(if (alphanumericp ch) (if (alphanumericp ch)
#t #t
#f)) #f))
(define editor-move-to-end-of-word (lambda () ( (define editor-move-to-end-of-word (lambda () (
(if (word-char-p (editor-read-char)) (if (word-char-p (editor-read-char))
((move-cursor "right") ((move-cursor "right")
(editor-move-to-end-of-word)) (editor-move-to-end-of-word))
)) ))
)) ))
(define enter-and-tab (define enter-and-tab
(lambda () (lambda ()
(editor-insert-new-line) (editor-insert-new-line)
(let ((is-in (move-cursor "up"))) (let ((is-in (move-cursor "up")))
(do ((ch (editor-read-char) (editor-read-char))) (do ((ch (editor-read-char) (editor-read-char)))
((and (not (char=? ch #\space)) is-in) #f) ((and (not (char=? ch #\space)) is-in) #f)
(move-cursor "down") (move-cursor "down")
(editor-insert-char " ") (editor-insert-char " ")
(set! is-in (move-cursor "up"))) (set! is-in (move-cursor "up")))
(move-cursor "down")))) (move-cursor "down"))))
(add-prefix "user") (add-prefix "user")
(map-key "CTRL-x" '(editor-set-prefix "user") "no-prefix") (map-key "CTRL-x" '(editor-set-prefix "user") "no-prefix")
(map-key "CTRL-g" '(editor-set-prefix "no-prefix") "user") (map-key "CTRL-g" '(editor-set-prefix "no-prefix") "user")
(map-key "CTRL-c" editor-quit "user") (map-key "CTRL-c" editor-quit "user")
(map-key "CTRL-s" editor-save "user") (map-key "CTRL-s" editor-save "user")
(map-key "ARROW-UP" '(move-cursor "up") "no-prefix") (map-key "ARROW-UP" '(move-cursor "up") "no-prefix")
(map-key "ARROW-DOWN" '(move-cursor "down") "no-prefix") (map-key "ARROW-DOWN" '(move-cursor "down") "no-prefix")
(map-key "ARROW-RIGHT" '(move-cursor "right") "no-prefix") (map-key "ARROW-RIGHT" '(move-cursor "right") "no-prefix")
(map-key "ARROW-LEFT" '(move-cursor "left") "no-prefix") (map-key "ARROW-LEFT" '(move-cursor "left") "no-prefix")
(map-key "ENTER" enter-and-tab "no-prefix") (map-key "ENTER" enter-and-tab "no-prefix")
(map-key "CTRL-a" move-cursor-beg-line "no-prefix") (map-key "CTRL-a" move-cursor-beg-line "no-prefix")
(map-key "CTRL-e" move-cursor-end-line "no-prefix") (map-key "CTRL-e" move-cursor-end-line "no-prefix")
(map-key "BACKSPACE" editor-delete-previous-char "no-prefix") (map-key "BACKSPACE" editor-delete-previous-char "no-prefix")
(map-key "DEL" editor-delete-next-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-UP" move-cursor-page-up "no-prefix")
(map-key "PAGE-DOWN" move-cursor-page-down "no-prefix") (map-key "PAGE-DOWN" move-cursor-page-down "no-prefix")
(map-key "f" editor-open-file "user") (map-key "f" editor-open-file "user")
(map-key "TAB" editor-insert-tab "no-prefix") (map-key "TAB" editor-insert-tab "no-prefix")
(map-key "CTRL-s" buffer-find "no-prefix") (map-key "CTRL-k" editor-del-row "no-prefix")
(map-key "CTRL-r" buffer-find-reverse "no-prefix") (map-key "CTRL-s" buffer-find "no-prefix")
(map-key "ARROW-RIGHT" editor-switch-next-buffer "user") (map-key "CTRL-r" editor-move-to-end-of-word "no-prefix")
(map-key "\"" editor-split-screen-vertical "user") (map-key "ARROW-RIGHT" editor-switch-next-buffer "user")
(map-key "o" editor-switch-next-pane "user") (map-key "\"" editor-split-screen-vertical "user")
(map-key "*" editor-unify-panes "user") (map-key "o" editor-switch-next-pane "user")
(map-key "CTRL-y" editor-paste "no-prefix") (map-key "*" editor-unify-panes "user")
(map-key "CTRL-k" editor-cut-end-line "no-prefix") (map-key "t" editor-create-terminal "user")
(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")
+12 -12
View File
@@ -1,12 +1,12 @@
#ifndef APPEND_BUFFER_H_ #ifndef APPEND_BUFFER_H_
#define APPEND_BUFFER_H_ #define APPEND_BUFFER_H_
#include "data.h" #include "data.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
void abAppend(struct abuf *ab, const char *s, int len); void abAppend(struct abuf *ab, const char *s, int len);
void abFree(struct abuf *ab); void abFree(struct abuf *ab);
#endif // APPEND_BUFFER_H_ #endif // APPEND_BUFFER_H_
+72 -69
View File
@@ -1,69 +1,72 @@
/** /**
* @file buffer.h * @file buffer.h
* @brief Buffer management for handling multiple open files * @brief Buffer management for handling multiple open files
*/ */
#ifndef BUFFER_H_ #ifndef BUFFER_H_
#define BUFFER_H_ #define BUFFER_H_
#include "data.h" #include "data.h"
/** /**
* @brief Creates a new buffer for a file * @brief Creates a new buffer for a file
* @param filename Path to the file * @param filename Path to the file
* @return Buffer ID on success, -1 on failure * @return Buffer ID on success, -1 on failure
*/ */
int bufferCreate(const char *path, enum bufferStatus_e state); int bufferCreate(const char *filename);
/** /**
* @brief Switches to a specific buffer by ID * @brief Switches to a specific buffer by ID
* @param buffer_id The buffer ID to switch to * @param buffer_id The buffer ID to switch to
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
int bufferSwitch(int buffer_id); int bufferSwitch(int buffer_id);
struct buffer_t *bufferFindById(int buffer_id); struct buffer_t *bufferFindById(int buffer_id);
/** /**
* @brief Closes a buffer by ID * @brief Closes a buffer by ID
* @param buffer_id The buffer ID to close * @param buffer_id The buffer ID to close
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
int bufferClose(int buffer_id); int bufferClose(int buffer_id);
/** /**
* @brief Gets the current active buffer * @brief Gets the current active buffer
* @return Pointer to current buffer_t, NULL if none * @return Pointer to current buffer_t, NULL if none
*/ */
struct buffer_t *bufferGetCurrent(void); struct buffer_t *bufferGetCurrent(void);
/** /**
* @brief Lists all open buffers * @brief Lists all open buffers
* @return Number of open buffers * @return Number of open buffers
*/ */
int bufferListAll(void); int bufferListAll(void);
/** /**
* @brief Saves current buffer to disk * @brief Saves current buffer to disk
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
int bufferSave(void); int bufferSave(void);
/** /**
* @brief Saves all buffers to disk * @brief Saves all buffers to disk
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
int bufferSaveAll(void); int bufferSaveAll(void);
void bufferFind(struct buffer_t *buf); /**
void bufferFindReverse(struct buffer_t* buf); * @brief Creates a new terminal buffer
void bufferInsertNewLine(); * @return Buffer ID on success, -1 on failure
void bufferInsertBytes(const char *src, int n); */
void bufferDelBytes(); int bufferCreateTerminal(void);
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); * @brief Executes a command in the terminal buffer
void bufferFreeRow(row_t *row); * @param buffer_id The terminal buffer ID
* @param command The command to execute
* @return 0 on success, -1 on failure
#endif */
int bufferExecuteTerminalCommand(int buffer_id, const char *command);
#endif
+60 -62
View File
@@ -1,62 +1,60 @@
#ifndef BUILTINS_H_ #ifndef BUILTINS_H_
#define BUILTINS_H_ #define BUILTINS_H_
#include "lisp.h" #include "lisp.h"
// Mouvement // Mouvement
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx); Lisp moveCursor(Lisp args, LispError *e, LispContext ctx);
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx); Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx);
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx); Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx);
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx); Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx); Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx);
// Text editing // Text editing
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx); Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx);
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx); Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx);
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx); Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx); Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx);
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx); Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx);
// Editor
// Editor
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx); Lisp editorQuit(Lisp args, LispError *e, LispContext ctx);
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx); Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx);
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx); Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx);
Lisp mapKey(Lisp args, LispError *e, LispContext ctx); Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx);
void registerBuiltin(char * key_sequence, LispCFunc f); Lisp mapKey(Lisp args, LispError *e, LispContext ctx);
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx); void registerBuiltin(char * key_sequence, LispCFunc f);
Lisp addPackage(Lisp args, LispError *e, LispContext ctx); Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx);
Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
// Buffer
// Buffer
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx);
Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx); Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx);
Lisp bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx); Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx);
// Pane // Pane
Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx); Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx);
Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx); Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx);
Lisp editorUnifiedPanes(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
// Other void free_structs(void);
Lisp editorAutoComplete(Lisp args, LispError *e, LispContext ctx); // Terminal
// Lisp lspComplete(Lisp args, LispError *e, LispContext ctx);
Lisp lspDefinition(Lisp args, LispError *e, LispContext ctx); Lisp editorCreateTerminal(Lisp args, LispError *e, LispContext ctx);
void free_structs(void); #endif
#endif
-306
View File
@@ -1,306 +0,0 @@
/*
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 -123
View File
@@ -1,123 +1,123 @@
#ifndef COLOR_H_ #ifndef COLOR_H_
#define COLOR_H_ #define COLOR_H_
// ============================================================================ // ============================================================================
// TEXT STYLES / ATTRIBUTES // TEXT STYLES / ATTRIBUTES
// ============================================================================ // ============================================================================
#define ANSI_RESET "\x1b[0m" // Reset all attributes #define ANSI_RESET "\x1b[0m" // Reset all attributes
#define ANSI_BOLD "\x1b[1m" // Bold text #define ANSI_BOLD "\x1b[1m" // Bold text
#define ANSI_DIM "\x1b[2m" // Dim/faint text #define ANSI_DIM "\x1b[2m" // Dim/faint text
#define ANSI_ITALIC "\x1b[3m" // Italic text #define ANSI_ITALIC "\x1b[3m" // Italic text
#define ANSI_UNDERLINE "\x1b[4m" // Underline text #define ANSI_UNDERLINE "\x1b[4m" // Underline text
#define ANSI_BLINK "\x1b[5m" // Blinking text #define ANSI_BLINK "\x1b[5m" // Blinking text
#define ANSI_REVERSE "\x1b[7m" // Reverse video (invert colors) #define ANSI_REVERSE "\x1b[7m" // Reverse video (invert colors)
#define ANSI_HIDDEN "\x1b[8m" // Hidden/invisible text #define ANSI_HIDDEN "\x1b[8m" // Hidden/invisible text
#define ANSI_STRIKETHROUGH "\x1b[9m" // Strikethrough text #define ANSI_STRIKETHROUGH "\x1b[9m" // Strikethrough text
// ============================================================================ // ============================================================================
// FOREGROUND COLORS (30-37 standard, 90-97 bright) // FOREGROUND COLORS (30-37 standard, 90-97 bright)
// ============================================================================ // ============================================================================
#define ANSI_BLACK "\x1b[30m" #define ANSI_BLACK "\x1b[30m"
#define ANSI_RED "\x1b[31m" #define ANSI_RED "\x1b[31m"
#define ANSI_GREEN "\x1b[32m" #define ANSI_GREEN "\x1b[32m"
#define ANSI_YELLOW "\x1b[33m" #define ANSI_YELLOW "\x1b[33m"
#define ANSI_BLUE "\x1b[34m" #define ANSI_BLUE "\x1b[34m"
#define ANSI_MAGENTA "\x1b[35m" #define ANSI_MAGENTA "\x1b[35m"
#define ANSI_CYAN "\x1b[36m" #define ANSI_CYAN "\x1b[36m"
#define ANSI_WHITE "\x1b[37m" #define ANSI_WHITE "\x1b[37m"
// Bright/Light foreground colors (90-97) // Bright/Light foreground colors (90-97)
#define ANSI_BRIGHT_BLACK "\x1b[90m" #define ANSI_BRIGHT_BLACK "\x1b[90m"
#define ANSI_BRIGHT_RED "\x1b[91m" #define ANSI_BRIGHT_RED "\x1b[91m"
#define ANSI_BRIGHT_GREEN "\x1b[92m" #define ANSI_BRIGHT_GREEN "\x1b[92m"
#define ANSI_BRIGHT_YELLOW "\x1b[93m" #define ANSI_BRIGHT_YELLOW "\x1b[93m"
#define ANSI_BRIGHT_BLUE "\x1b[94m" #define ANSI_BRIGHT_BLUE "\x1b[94m"
#define ANSI_BRIGHT_MAGENTA "\x1b[95m" #define ANSI_BRIGHT_MAGENTA "\x1b[95m"
#define ANSI_BRIGHT_CYAN "\x1b[96m" #define ANSI_BRIGHT_CYAN "\x1b[96m"
#define ANSI_BRIGHT_WHITE "\x1b[97m" #define ANSI_BRIGHT_WHITE "\x1b[97m"
// ============================================================================ // ============================================================================
// BACKGROUND COLORS (40-47 standard, 100-107 bright) // BACKGROUND COLORS (40-47 standard, 100-107 bright)
// ============================================================================ // ============================================================================
#define ANSI_BG_BLACK "\x1b[40m" #define ANSI_BG_BLACK "\x1b[40m"
#define ANSI_BG_RED "\x1b[41m" #define ANSI_BG_RED "\x1b[41m"
#define ANSI_BG_GREEN "\x1b[42m" #define ANSI_BG_GREEN "\x1b[42m"
#define ANSI_BG_YELLOW "\x1b[43m" #define ANSI_BG_YELLOW "\x1b[43m"
#define ANSI_BG_BLUE "\x1b[44m" #define ANSI_BG_BLUE "\x1b[44m"
#define ANSI_BG_MAGENTA "\x1b[45m" #define ANSI_BG_MAGENTA "\x1b[45m"
#define ANSI_BG_CYAN "\x1b[46m" #define ANSI_BG_CYAN "\x1b[46m"
#define ANSI_BG_WHITE "\x1b[47m" #define ANSI_BG_WHITE "\x1b[47m"
// Bright/Light background colors (100-107) // Bright/Light background colors (100-107)
#define ANSI_BG_BRIGHT_BLACK "\x1b[100m" #define ANSI_BG_BRIGHT_BLACK "\x1b[100m"
#define ANSI_BG_BRIGHT_RED "\x1b[101m" #define ANSI_BG_BRIGHT_RED "\x1b[101m"
#define ANSI_BG_BRIGHT_GREEN "\x1b[102m" #define ANSI_BG_BRIGHT_GREEN "\x1b[102m"
#define ANSI_BG_BRIGHT_YELLOW "\x1b[103m" #define ANSI_BG_BRIGHT_YELLOW "\x1b[103m"
#define ANSI_BG_BRIGHT_BLUE "\x1b[104m" #define ANSI_BG_BRIGHT_BLUE "\x1b[104m"
#define ANSI_BG_BRIGHT_MAGENTA "\x1b[105m" #define ANSI_BG_BRIGHT_MAGENTA "\x1b[105m"
#define ANSI_BG_BRIGHT_CYAN "\x1b[106m" #define ANSI_BG_BRIGHT_CYAN "\x1b[106m"
#define ANSI_BG_BRIGHT_WHITE "\x1b[107m" #define ANSI_BG_BRIGHT_WHITE "\x1b[107m"
// ============================================================================ // ============================================================================
// 256-COLOR MODE (for more colors) // 256-COLOR MODE (for more colors)
// ============================================================================ // ============================================================================
// Foreground: \x1b[38;5;Nm where N is 0-255 // Foreground: \x1b[38;5;Nm where N is 0-255
// Background: \x1b[48;5;Nm where N is 0-255 // Background: \x1b[48;5;Nm where N is 0-255
// Example macros: // Example macros:
#define ANSI_FG_256(N) "\x1b[38;5;" #N "m" #define ANSI_FG_256(N) "\x1b[38;5;" #N "m"
#define ANSI_BG_256(N) "\x1b[48;5;" #N "m" #define ANSI_BG_256(N) "\x1b[48;5;" #N "m"
// ============================================================================ // ============================================================================
// TRUE COLOR / 24-BIT COLOR (RGB) // TRUE COLOR / 24-BIT COLOR (RGB)
// ============================================================================ // ============================================================================
// Foreground: \x1b[38;2;R;G;Bm // Foreground: \x1b[38;2;R;G;Bm
// Background: \x1b[48;2;R;G;Bm // Background: \x1b[48;2;R;G;Bm
// Example macros: // Example macros:
#define ANSI_FG_RGB(R,G,B) "\x1b[38;2;" #R ";" #G ";" #B "m" #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" #define ANSI_BG_RGB(R,G,B) "\x1b[48;2;" #R ";" #G ";" #B "m"
// ============================================================================ // ============================================================================
// CURSOR MOVEMENT // CURSOR MOVEMENT
// ============================================================================ // ============================================================================
#define ANSI_CURSOR_UP(N) "\x1b[" #N "A" // Move up N lines #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_DOWN(N) "\x1b[" #N "B" // Move down N lines
#define ANSI_CURSOR_RIGHT(N) "\x1b[" #N "C" // Move right N columns #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_LEFT(N) "\x1b[" #N "D" // Move left N columns
#define ANSI_CURSOR_HOME "\x1b[H" // Move to home (0,0) #define ANSI_CURSOR_HOME "\x1b[H" // Move to home (0,0)
#define ANSI_CURSOR_HIDE "\x1b[?25l" // Hide cursor #define ANSI_CURSOR_HIDE "\x1b[?25l" // Hide cursor
#define ANSI_CURSOR_SHOW "\x1b[?25h" // Show cursor #define ANSI_CURSOR_SHOW "\x1b[?25h" // Show cursor
// ============================================================================ // ============================================================================
// SCREEN CONTROL // SCREEN CONTROL
// ============================================================================ // ============================================================================
#define ANSI_CLEAR_SCREEN "\x1b[2J" // Clear entire screen #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_LINE "\x1b[K" // Clear from cursor to end of line
#define ANSI_CLEAR_UP "\x1b[1J" // Clear from cursor to start #define ANSI_CLEAR_UP "\x1b[1J" // Clear from cursor to start
#define ANSI_CLEAR_DOWN "\x1b[0J" // Clear from cursor to end #define ANSI_CLEAR_DOWN "\x1b[0J" // Clear from cursor to end
// ============================================================================ // ============================================================================
// EXAMPLE USAGE // EXAMPLE USAGE
// ============================================================================ // ============================================================================
/* /*
#include <stdio.h> #include <stdio.h>
int main(void) { int main(void) {
// Simple color example // Simple color example
printf("%sHello in Red%s\n", ANSI_RED, ANSI_RESET); printf("%sHello in Red%s\n", ANSI_RED, ANSI_RESET);
// Bold and colored // Bold and colored
printf("%s%sBold Green%s\n", ANSI_BOLD, ANSI_GREEN, ANSI_RESET); printf("%s%sBold Green%s\n", ANSI_BOLD, ANSI_GREEN, ANSI_RESET);
// Background color // Background color
printf("%s%sYellow on Blue%s\n", ANSI_YELLOW, ANSI_BG_BLUE, ANSI_RESET); printf("%s%sYellow on Blue%s\n", ANSI_YELLOW, ANSI_BG_BLUE, ANSI_RESET);
// Combine multiple styles // Combine multiple styles
printf("%s%s%sUnderlined Bold Cyan%s\n", printf("%s%s%sUnderlined Bold Cyan%s\n",
ANSI_UNDERLINE, ANSI_BOLD, ANSI_CYAN, ANSI_RESET); ANSI_UNDERLINE, ANSI_BOLD, ANSI_CYAN, ANSI_RESET);
return 0; return 0;
} }
*/ */
#endif #endif
-29
View File
@@ -1,29 +0,0 @@
//
// Created by Giorgio on 25/05/2026.
//
#ifndef BELUGA_COMPLETION_H
#define BELUGA_COMPLETION_H
#include "data.h"
void createContextBuffer(const int x, const int y, const char * text);
int lspStart(LspClient *lsp, const char *project_root);
void lspShutdown(LspClient *lsp);
// Document sync — call these from your buffer open/save/edit hooks
void lspDidOpen(LspClient *lsp, struct buffer_t *buf);
void lspDidChange(LspClient *lsp, struct buffer_t *buf);
void lspDidClose(LspClient *lsp, struct buffer_t *buf);
// Requests
void lspRequestCompletion(LspClient *lsp, struct buffer_t *buf,
int line, int col,
int screen_x, int screen_y);
void lspRequestHover(LspClient *lsp, struct buffer_t *buf, int line, int col);
void lspRequestDefinition(LspClient *lsp, struct buffer_t *buf, int line, int col);
#endif //BELUGA_COMPLETION_H
+172 -258
View File
@@ -1,258 +1,172 @@
#ifndef DATA_H_ #ifndef DATA_H_
#define DATA_H_ #define DATA_H_
#include <stdio.h> #include <stdio.h>
#include <termios.h> #include <time.h>
#include <time.h>
#ifdef _WIN32
#include "define.h" #include "termiWin.h"
#else
#include "lisp.h" #include <termios.h>
#endif
typedef struct lsp_client_t LspClient;
#include "lisp.h"
/**
* \struct row_t /**
* \brief Store one editor row * \struct erow
* \param * \brief Store one editor row
* */ * \param
* */
typedef struct row {
int size; /**< Size of the line */ typedef struct frow {
int cap; /**< Size of the render line */ int size; /**< Size of the line */
char *chars; /**< Characters of the line */ int rsize; /**< Size of the render line */
} row_t; char *chars; /**< Characters of the line */
char *render; /**< The actual line we will print */
typedef struct context_buffer_t } frow;
{
int editor_x, editor_y;
int width, height; /**
row_t *rows; * @brief Split modes for screen layout
} ContextBuffer; */
typedef enum {
/** SPLIT_NONE = 0, // Single buffer fullscreen
* @brief Split modes for screen layout SPLIT_VERTICAL, // Left-right split
*/ SPLIT_HORIZONTAL // Top-bottom split
typedef enum { } SplitMode;
SPLIT_NONE = 0, // Single buffer fullscreen
SPLIT_VERTICAL, // Left-right split /**
SPLIT_HORIZONTAL // Top-bottom split * @brief Represents an editor viewport/pane
} SplitMode; */
typedef struct {
/** int buffer_id; // Which buffer this pane displays
* @brief Represents an editor viewport/pane int start_row; // Starting row on screen
*/ int start_col; // Starting column on screen
typedef struct { int height; // Height of this pane
int buffer_id; // Which buffer this pane displays int width; // Width of this pane
int height; // Height of this pane int cursor_x; // Local cursor x in this pane
int width; // Width of this pane int cursor_y; // Local cursor y in this pane
int origin_x, origin_y; int rx, ry;
int cursor_x; // Local cursor x in this pane int row_offset; // Scroll offset for rows
int cursor_y; // Local cursor y in this pane int col_offset; // Scroll offset for columns
int x_offset, y_offset; int is_active; // Is this pane currently active
int is_active; // Is this pane currently active } EditorPane;
} EditorPane;
/**
/** * @brief Screen layout manager
* @brief Screen layout manager */
*/ typedef struct {
typedef struct { SplitMode mode;
SplitMode mode; EditorPane *panes;
EditorPane *panes; int num_panes;
int num_panes; int active_pane; // Index of active pane
int active_pane; // Index of active pane } ScreenLayout;
} ScreenLayout;
typedef struct theme {
typedef struct theme { char *BACKGROUND_COLOR;
char *BACKGROUND_COLOR; char *COLOR_KEYWORD;
char *COLOR_KEYWORD; char *COLOR_TYPE;
char *COLOR_TYPE; char *COLOR_STRING;
char *COLOR_STRING; char *COLOR_COMMENT;
char *COLOR_COMMENT; char *COLOR_NUMBER;
char *COLOR_NUMBER; char *COLOR_DEFAULT;
char *COLOR_DEFAULT;
} theme_t;
} theme_t;
enum bufferStatus_e {
enum bufferStatus_e { IDLE,
IDLE, READ_ONLY,
READ_ONLY, READ_AND_WRITE,
READ_AND_WRITE, };
};
struct const_t {
struct const_t { int TAB_LENGTH;
int TAB_LENGTH; int QUIT_TIMES;
int QUIT_TIMES; char *THEME;
char *THEME; };
};
struct prefix_t {
struct prefix_t { char prefix_name[64];
char prefix_name[64]; int prefix_id;
int prefix_id; };
};
struct keyBind_t {
struct keyBind_t { char *key_sequence;
char *key_sequence; int prefix_id;
int prefix_id; Lisp command;
Lisp command; };
};
enum buffer_type { FILE_BUFF, TERMINAL_BUFF };
// In data.h — add these
struct buffer_t {
#include <pthread.h> enum buffer_type type;
int buffer_id;
#define LSP_MAX_PENDING 64 int width, height;
int x, y; /**< Position in the buffer (cursor) */
typedef enum { frow *row;
LSP_NOT_STARTED = 0, int numrows;
LSP_INITIALIZING, char *filename;
LSP_READY, enum bufferStatus_e state;
LSP_SHUTDOWN, int dirty; /**< Has this buffer been modified since last save */
} LspState; int row_offset; /**< Scroll offset for rows in this buffer */
int col_offset; /**< Scroll offset for columns in this buffer */
typedef struct {
int id; // Terminal-specific data
void (*callback)(struct lsp_client_t *lsp, const char *json); char *terminal_command; /**< Current command being executed */
} LspPending; int terminal_pid; /**< Process ID of running terminal command */
char *terminal_output; /**< Captured terminal output */
typedef struct lsp_client_t { int terminal_output_size; /**< Size of terminal output */
// ── Process ─────────────────────────────────────────────────────────────── };
pid_t pid;
int write_fd; /**
int read_fd; * \struct editorConfig
int completion_just_arrived; * \brief Containing our editor state.
int completion_requested; */
struct editorConfig {
int screenrows; /**< Terminal height*/
int screencols; /**< Terminal width*/
int wake_pipe[2]; // [0] = read end (main loop), [1] = write end (reader thread)
ScreenLayout layout;
// ── State ───────────────────────────────────────────────────────────────── int dirty;
LspState state; int prefix_state;
int next_id; char status_msg[80];
time_t status_msg_time;
// ── Pending requests ────────────────────────────────────────────────────── struct termios orig_termios; /**< Terminal communication interface */
LspPending pending[LSP_MAX_PENDING];
int pending_count; struct const_t constantes;
int quit_times_buffer;
// ── Threading ─────────────────────────────────────────────────────────────
pthread_t reader_thread; char *init_file_path;
pthread_mutex_t lock; FILE *fd_init_file;
pthread_cond_t ready_cond; // signaled when state → LSP_READY Lisp env;
LispContext ctx; /** Lisp context */
// ── Completion context ──────────────────────────────────────────────────── Lisp ctx_data; /** Lisp data context */
int completion_cursor_x; // screen position when LispError ctx_error; /** Lisp ctx error */
int completion_cursor_y; // completion was requested
} LspClient; struct keyBind_t *key_binds;
int number_of_keybinds;
typedef struct {
char label[128]; // display text e.g. "printf" struct prefix_t *prefix;
char detail[64]; // type/sig hint e.g. "int (const char *, ...)" int number_of_prefix;
int kind; // LSP CompletionItemKind (1=Text,2=Method,3=Function…)
} CompletionItem; struct buffer_t buffers[64];
int number_of_buffer;
typedef struct {
CompletionItem items[COMPLETION_MAX_ITEMS]; theme_t theme;
int count; };
int selected; // currently highlighted row
int visible; // is the popup shown? /**
int origin_x; // screen col where popup appears * \struct abuf
int origin_y; // screen row where popup appears * \brief Contains text to add before writing to screen.
} CompletionPopup; * */
typedef enum { DIAG_ERROR = 1, DIAG_WARNING, DIAG_HINT } DiagSeverity; struct abuf {
char *b; /**< Text that will be printed */
typedef struct { int len; /**< Length of the text */
int buffer_id; };
int line; // 0-based
int col_start; // 0-based extern struct editorConfig E;
int col_end;
DiagSeverity severity; #endif
char message[256];
} Diagnostic;
typedef struct {
Diagnostic entries[DIAG_MAX];
int count;
} DiagnosticList;
enum buffer_type { FILE_BUFF, TERMINAL_BUFF };
struct buffer_t {
enum buffer_type type;
int buffer_id;
int b_lsp_open;
int x, y; /**< Position in the file */
row_t *row;
int numrows;
int b_has_changed;
char *filename;
char *path;
enum bufferStatus_e state;
int dirty; /**< Has this buffer been modified since last save */
};
/**
* \struct editorConfig
* \brief Containing our editor state.
*/
struct editorConfig {
int cursor_x, cursor_y; /**< Cursor position */
int screenrows; /**< Terminal height*/
int screencols; /**< Terminal width*/
ScreenLayout layout;
row_t *rows; /**< Store all the rows printed */
ContextBuffer* context_buffers;
LspClient *lsp_client;
CompletionPopup lsp_completion;
DiagnosticList lsp_diagnostics;
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;
};
/**
* \struct abuf
* \brief Contains text to add before writing to screen.
* */
struct abuf {
char *b; /**< Text that will be printed */
int len; /**< Length of the text */
};
extern struct editorConfig E;
#endif
+29 -49
View File
@@ -1,49 +1,29 @@
#ifndef DEFINE_H_ #ifndef DEFINE_H_
#define DEFINE_H_ #define DEFINE_H_
#define CTRL_KEY(k) ((k) & 0x1f) #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 ESCAPE '\x1b' #define HIDE_CURSOR "\x1b[?25l"
#define CURSOR_TOP_LEFT "\x1b[H" #define SHOW_CURSOR "\x1b[?25h"
#define HIDE_CURSOR "\x1b[?25l" #define ERASE_END_LINE "\x1b[K"
#define SHOW_CURSOR "\x1b[?25h"
#define ERASE_END_LINE "\x1b[K" enum editorKey {
#define TAB "\t" BACKSPACE = 127,
#define SPACE "\x20" ARROW_LEFT = 1000,
#define APP_DEBUG ARROW_RIGHT,
ARROW_UP,
#define COMPLETION_MAX_ITEMS 16 ARROW_DOWN,
#define COMPLETION_MAX_WIDTH 40 DEL_KEY,
#define COMPLETION_POPUP_H 8 // max visible rows BEG_LINE,
#define DIAG_MAX 128 END_LINE,
PAGE_UP,
#define GUTTER_WIDTH 2 PAGE_DOWN,
};
enum editorKey_e {
BACKSPACE = 127, #define ABUF_INIT {NULL, 0}
ARROW_LEFT = 1000,
ARROW_RIGHT, #define BELUGA_VERSION "1.1"
ARROW_UP,
ARROW_DOWN, #endif // DEFINE_H_
DEL_KEY,
BEG_LINE,
END_LINE,
PAGE_UP,
PAGE_DOWN,
};
#define ABUF_INIT {NULL, 0}
#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_
+13 -13
View File
@@ -1,13 +1,13 @@
#ifndef EDITOR_OP_H_ #ifndef EDITOR_OP_H_
#define EDITOR_OP_H_ #define EDITOR_OP_H_
#include "data.h" #include "data.h"
void bufferInsertChar(int c);
void editorSetStatusMessage(const char *fmt, ...);
int editorRowCharCount(row_t *row, int x); void bufferInsertNewLine();
int editorRowCxToByte(const row_t *row, int cursor_x);
char *editorGetClipboard(void); void bufferDelChar();
void editorSetClipboard(const char *text, int len);
void editorSetStatusMessage(const char *fmt, ...);
#endif // EDITOR_OP_H_ #endif // EDITOR_OP_H_
+22 -15
View File
@@ -1,15 +1,22 @@
#ifndef FILE_IO_H_ #ifndef FILE_IO_H_
#define FILE_IO_H_ #define FILE_IO_H_
#include "data.h" #include "data.h"
#include <stdio.h> #include "row_op.h"
#include <stdlib.h> #include "terminal.h"
#include <sys/types.h> #include <stdio.h>
#include <stdlib.h>
void editorCloseFile(void); #include <sys/types.h>
void editorOpen(struct buffer_t *buffer); char *bufferRowsToString(struct buffer_t *buf,int *buffer_len);
void editorSave();
void editorCloseFile(void);
#endif // FILE_IO_H_
void editorOpen(struct buffer_t *buffer);
void editorSave();
void bufferFind(struct buffer_t *buf);
#endif // FILE_IO_H_
+18 -14
View File
@@ -1,14 +1,18 @@
#ifndef INIT_H_ #ifndef INIT_H_
#define INIT_H_ #define INIT_H_
#include "builtins.h"
/** #include "data.h"
* \fn void initEditor() #include "terminal.h"
* \brief Job's function is to initialize all the fields of editorConfig. #include <stdio.h>
* */
/**
void initBuiltins(); * \fn void initEditor()
* \brief Job's function is to initialize all the fields of editorConfig.
void initEditor(); * */
#endif // INIT_H_ void initBuiltins();
void initEditor();
#endif // INIT_H_
+38 -38
View File
@@ -1,38 +1,38 @@
#ifndef INPUT_H_ #ifndef INPUT_H_
#define INPUT_H_ #define INPUT_H_
#include "data.h" #include "data.h"
#include "define.h" #include "define.h"
#include "output.h" #include "output.h"
#include "terminal.h" #include "terminal.h"
#include "builtins.h" #include "builtins.h"
#include <unistd.h> #include <unistd.h>
// KEYS keycode // KEYS keycode
// //
// ARROW_UP \x1b[A // ARROW_UP \x1b[A
// ARROW_DOWN \x1b[B // ARROW_DOWN \x1b[B
// ARROW_RIGHT \x1b[C // ARROW_RIGHT \x1b[C
// ARROW_LEFT \x1b[D // ARROW_LEFT \x1b[D
// PAGE_UP \x1b[5~ // PAGE_UP \x1b[5~
// PAGE_DOWN \x1b[6~ // PAGE_DOWN \x1b[6~
// HOME \x1b[1~ || <esc>[7~ || <esc>[H || <esc>OH // HOME \x1b[1~ || <esc>[7~ || <esc>[H || <esc>OH
// END \x1b[4~ || <esc>[8~ || <esc>[F || <esc>OF // END \x1b[4~ || <esc>[8~ || <esc>[F || <esc>OF
// DELETE \x1b[3~ // DELETE \x1b[3~
char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode); char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
const char *fileCompletion(const char *path); char *key_to_string(int key);
int editorMoveCursor(int key); int editorMoveCursor(int key);
int executeKeyBind(char *key_sequence); int executeKeyBind(char *key_sequence);
/** /**
* \fn void editorProcessKeypress() * \fn void editorProcessKeypress()
* \brief Get the last key input and do the proper action. * \brief Get the last key input and do the proper action.
*/ */
void editorProcessKeypress(); void editorProcessKeypress();
#endif // INPUT_H_ #endif // INPUT_H_
+3220 -3218
View File
File diff suppressed because it is too large Load Diff
+2177 -2177
View File
File diff suppressed because it is too large Load Diff
-35
View File
@@ -1,35 +0,0 @@
//
// 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
+22 -20
View File
@@ -1,20 +1,22 @@
#ifndef OUTPUT_H_ #ifndef OUTPUT_H_
#define OUTPUT_H_ #define OUTPUT_H_
#include "data.h" #include "data.h"
#include <unistd.h> #include <unistd.h>
/** /**
* \fn void editorDrawRows(struct editorConfig *E, struct abuf *ab) * \fn void editorDrawRows(struct editorConfig *E, struct abuf *ab)
* \brief Draws left rows of the editor. * \brief Draws left rows of the editor.
*/ */
void editorRefreshScreen(); void editorDrawRows(struct abuf *ab);
void editorScroll(); void editorRefreshScreen();
void editorDrawStatusBar(struct abuf *ab); void editorScroll();
void editorDrawMessageBar(struct abuf *ab); void editorDrawStatusBar(struct abuf *ab);
#endif // OUTPUT_H_ void editorDrawMessageBar(struct abuf *ab);
#endif // OUTPUT_H_
+29 -15
View File
@@ -1,15 +1,29 @@
#ifndef ROW_OP_H_ #ifndef ROW_OP_H_
#define ROW_OP_H_ #define ROW_OP_H_
#include "data.h" #include "data.h"
#include "define.h" #include "define.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
int bufferRowCxToRx(frow *row, int cursor_x);
int bufferRowRxToCx(frow *row, int rx);
#endif // ROW_OP_H_ void bufferUpdatfrow(frow *row);
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len);
void bufferFrefrow(frow *row);
void bufferDelRow(struct buffer_t *buffer, int at);
void bufferRowInsertChar(struct buffer_t *buffer, frow *row, int at, int c);
void bufferRowAppendString(struct buffer_t *buffer, frow *row, char *s, size_t len);
void bufferRowDelchar(struct buffer_t *buffer, frow *row, int at);
#endif // ROW_OP_H_
+62 -62
View File
@@ -1,62 +1,62 @@
/** /**
* @file split_screen.h * @file split_screen.h
* @brief Split screen management for displaying multiple buffers * @brief Split screen management for displaying multiple buffers
*/ */
#ifndef SPLIT_SCREEN_H_ #ifndef SPLIT_SCREEN_H_
#define SPLIT_SCREEN_H_ #define SPLIT_SCREEN_H_
#include "data.h" #include "data.h"
/** /**
* @brief Initializes split screen system * @brief Initializes split screen system
*/ */
void splitScreenInit(void); void splitScreenInit(void);
/** /**
* @brief Splits screen vertically (left-right) * @brief Splits screen vertically (left-right)
* @param buffer_id_left Buffer ID for left pane * @param buffer_id_left Buffer ID for left pane
* @param buffer_id_right Buffer ID for right pane * @param buffer_id_right Buffer ID for right pane
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
int splitScreenVertical(int buffer_id_left, int buffer_id_right); int splitScreenVertical(int buffer_id_left, int buffer_id_right);
/** /**
* @brief Splits screen horizontally (top-bottom) * @brief Splits screen horizontally (top-bottom)
* @param buffer_id_top Buffer ID for top pane * @param buffer_id_top Buffer ID for top pane
* @param buffer_id_bottom Buffer ID for bottom pane * @param buffer_id_bottom Buffer ID for bottom pane
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom); int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom);
/** /**
* @brief Returns to single buffer fullscreen * @brief Returns to single buffer fullscreen
*/ */
void splitScreenUnify(void); void splitScreenUnify(void);
/** /**
* @brief Switches active pane (focus moves between splits) * @brief Switches active pane (focus moves between splits)
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
int splitScreenSwitchPane(void); int splitScreenSwitchPane(void);
/** /**
* @brief Updates the active pane's buffer * @brief Updates the active pane's buffer
* @param buffer_id New buffer ID for active pane * @param buffer_id New buffer ID for active pane
* @return 0 on success, -1 on failure * @return 0 on success, -1 on failure
*/ */
int splitScreenSetPaneBuffer(int buffer_id); int splitScreenSetPaneBuffer(int buffer_id);
/** /**
* @brief Gets current screen layout * @brief Gets current screen layout
* @return Pointer to current ScreenLayout * @return Pointer to current ScreenLayout
*/ */
ScreenLayout *splitScreenGetLayout(void); ScreenLayout *splitScreenGetLayout(void);
/** /**
* @brief Gets active pane * @brief Gets active pane
* @return Pointer to active EditorPane * @return Pointer to active EditorPane
*/ */
EditorPane *splitScreenGetActivePane(void); EditorPane *splitScreenGetActivePane(void);
#endif #endif
+24 -21
View File
@@ -1,21 +1,24 @@
#ifndef SYNTAX_HIGHLIGHTER_H_ #ifndef SYNTAX_HIGHLIGHTER_H_
#define SYNTAX_HIGHLIGHTER_H_ #define SYNTAX_HIGHLIGHTER_H_
#include "color.h"
// Color codes that only affect foreground, preserving your background color
#define COLOR_RESET "\x1b[39m" // Reset all (4 bytes) // Color codes that only affect foreground, preserving your background color
#define COLOR_RESET "\x1b[39m" // Reset all (4 bytes)
// Token types
typedef enum { // Token types
TOKEN_KEYWORD, typedef enum {
TOKEN_TYPE, TOKEN_KEYWORD,
TOKEN_STRING, TOKEN_DATATYPE,
TOKEN_COMMENT, TOKEN_STRING,
TOKEN_NUMBER, TOKEN_COMMENT,
TOKEN_OPERATOR, TOKEN_NUMBER,
TOKEN_DEFAULT TOKEN_OPERATOR,
} TokenType; TOKEN_DEFAULT
} SyntaxTokenType;
char *highlight_line(const char * line, int *length);
const char *get_color(SyntaxTokenType type);
#endif char *highlight_line(const char * line, int *length);
#endif
+217
View File
@@ -0,0 +1,217 @@
/* termiWin.h
*
* Copyright (C) 2017 Christian Visintin - christian.visintin1997@gmail.com
*
* This file is part of "termiWin: a termios porting for Windows"
*
* termiWin is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* termiWin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with termiWin. If not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#ifndef TERMIWIN_H_
#define TERMIWIN_H_
#define TERMIWIN_VERSION "1.2.0"
#define TERMIWIN_MAJOR_VERSION 1
#define TERMIWIN_MINOR_VERSION 2
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#ifdef _MSC_VER
#pragma comment(lib, "Ws2_32.lib")
#endif
/*Redefining functions from winsock to termiWin. This is very important since winsock2 defines functions such as close as closesocket we have to redefine it*/
#ifdef __MINGW32__
#define TERMIWIN_DONOTREDEFINE
#endif
#ifndef TERMIWIN_DONOTREDEFINE
#define read read_serial
#define write serial_write
#define open open_serial
#define close close_serial
#define select select_serial
#endif
#if !defined(SSIZE_MAX)
// ssize_t
#if SIZE_MAX == UINT_MAX
typedef int ssize_t; /* common 32 bit case */
#define SSIZE_MAX INT_MAX
#elif SIZE_MAX == ULONG_MAX
typedef long ssize_t; /* linux 64 bits */
#define SSIZE_MAX LONG_MAX
#elif SIZE_MAX == ULLONG_MAX
typedef long long ssize_t; /* windows 64 bits */
#define SSIZE_MAX LLONG_MAX
#endif
#endif
//Serial options - Linux -> Windows
/*setAttr flags - ~ in front of flags -> deny them*/
//iFlag
#define INPCK 0x00004000 /*If this bit is set, input parity checking is enabled. If it is not set, no checking at all is done for parity errors on input; the characters are simply passed through to the application.*/
#define IGNPAR 0x00001000 /*If this bit is set, any byte with a framing or parity error is ignored. This is only useful if INPCK is also set.*/
#define PARMRK 0x00040000 /*If this bit is set, input bytes with parity or framing errors are marked when passed to the program. This bit is meaningful only when INPCK is set and IGNPAR is not set.*/
#define ISTRIP 0x00008000 /*If this bit is set, valid input bytes are stripped to seven bits; otherwise, all eight bits are available for programs to read. */
#define IGNBRK 0x00000400 /*If this bit is set, break conditions are ignored. */
#define BRKINT 0x00000100 /*If this bit is set and IGNBRK is not set, a break condition clears the terminal input and output queues and raises a SIGINT signal for the foreground process group associated with the terminal. */
#define IGNCR 0x00000800 /*If this bit is set, carriage return characters ('\r') are discarded on input. Discarding carriage return may be useful on terminals that send both carriage return and linefeed when you type the RET key. */
#define ICRNL 0x00000200 /*If this bit is set and IGNCR is not set, carriage return characters ('\r') received as input are passed to the application as newline characters ('\n').*/
#define INLCR 0x00002000 /*If this bit is set, newline characters ('\n') received as input are passed to the application as carriage return characters ('\r').*/
#define IXOFF 0x00010000 /*If this bit is set, start/stop control on input is enabled. In other words, the computer sends STOP and START characters as necessary to prevent input from coming in faster than programs are reading it. The idea is that the actual terminal hardware that is generating the input data responds to a STOP character by suspending transmission, and to a START character by resuming transmission.*/
#define IXON 0x00020000 /*If this bit is set, start/stop control on output is enabled. In other words, if the computer receives a STOP character, it suspends output until a START character is received. In this case, the STOP and START characters are never passed to the application program. If this bit is not set, then START and STOP can be read as ordinary characters.*/
//lFlag
#define ICANON 0x00001000 /*This bit, if set, enables canonical input processing mode. Otherwise, input is processed in noncanonical mode. */
#define ECHO 0x00000100 /*If this bit is set, echoing of input characters back to the terminal is enabled.*/
#define ECHOE 0x00000200 /*If this bit is set, echoing indicates erasure of input with the ERASE character by erasing the last character in the current line from the screen. Otherwise, the character erased is re-echoed to show what has happened (suitable for a printing terminal). */
#define ECHOK 0x00000400 /*This bit enables special display of the KILL character by moving to a new line after echoing the KILL character normally. The behavior of ECHOKE (below) is nicer to look at.*/
#define ECHONL 0x00000800 /*If this bit is set and the ICANON bit is also set, then the newline ('\n') character is echoed even if the ECHO bit is not set. */
#define ISIG 0x00004000 /*This bit controls whether the INTR, QUIT, and SUSP characters are recognized. The functions associated with these characters are performed if and only if this bit is set. Being in canonical or noncanonical input mode has no effect on the interpretation of these characters. */
#define IEXTEN 0x00002000 /*On BSD systems and GNU/Linux and GNU/Hurd systems, it enables the LNEXT and DISCARD characters.*/
#define NOFLSH 0x00008000 /*Normally, the INTR, QUIT, and SUSP characters cause input and output queues for the terminal to be cleared. If this bit is set, the queues are not cleared. */
#define TOSTOP 0x00010000 /*If this bit is set and the system supports job control, then SIGTTOU signals are generated by background processes that attempt to write to the terminal.*/
//cFlag
#define CSTOPB 0x00001000 /*If this bit is set, two stop bits are used. Otherwise, only one stop bit is used. */
#define PARENB 0x00004000 /*If this bit is set, generation and detection of a parity bit are enabled*/
#define PARODD 0x00008000 /*This bit is only useful if PARENB is set. If PARODD is set, odd parity is used, otherwise even parity is used. */
#define CSIZE 0x00000c00 /*This is a mask for the number of bits per character. */
#define CS5 0x00000000 /*This specifies five bits per byte. */
#define CS6 0x00000400 /*This specifies six bits per byte. */
#define CS7 0x00000800 /*This specifies seven bits per byte. */
#define CS8 0x00000c00 /*This specifies eight bits per byte. */
#define CLOCAL 0x00000000 /*Ignore modem control lines -> ignore data carrier detected - not implementable in windows*/
#define CREAD 0x00000000 /*Enable receiver - if is not set no character will be received*/
//oFlag
#define OPOST 0x00000100 /*If this bit is set, output data is processed in some unspecified way so that it is displayed appropriately on the terminal device. This typically includes mapping newline characters ('\n') onto carriage return and linefeed pairs. */
//cc
#define VEOF 0
#define VEOL 1
#define VERASE 2
#define VINTR 3
#define VKILL 4
#define VMIN 5 /*If set to 0, serial communication is NOT-BLOCKING, otherwise is BLOCKING*/
#define VQUIT 6
#define VSTART 7
#define VSTOP 8
#define VSUSP 9
#define VTIME 10
//END OF setAttr flags
/*Controls*/
#define TIOMBIC DTR_CONTROL_DISABLE
#define TIOMBIS DTR_CONTROL_ENABLE
#define CRTSCTS RTS_CONTROL_ENABLE
/*Others*/
#define NCCS 11
//Baud speed
#define B110 CBR_110
#define B300 CBR_300
#define B600 CBR_600
#define B1200 CBR_2400
#define B2400 CBR_2400
#define B4800 CBR_4800
#define B9600 CBR_9600
#define B19200 CBR_19200
#define B38400 CBR_38400
#define B57600 CBR_57600
#define B115200 CBR_115200
/*Attributes optional_actions*/
#define TCSANOW 0
#define TCSADRAIN 1
#define TCSAFLUSH 2
/*TCFLUSH options*/
#define TCIFLUSH 0
#define TCOFLUSH 1
#define TCIOFLUSH 2
/*TCFLOW optons*/
#define TCOOFF 0
#define TCOON 1
#define TCIOFF 2
#define TCION 3
//typdef
typedef unsigned tcflag_t; /*This is an unsigned integer type used to represent the various bit masks for terminal flags.*/
typedef unsigned cc_t; /*This is an unsigned integer type used to represent characters associated with various terminal control functions.*/
typedef unsigned speed_t; /*used for terminal baud rates*/
typedef struct termios
{
tcflag_t c_iflag; /*input modes*/
tcflag_t c_oflag; /*output modes*/
tcflag_t c_cflag; /*control modes*/
tcflag_t c_lflag; /*local modes*/
cc_t c_cc[NCCS]; /*special character*/
} termios;
//Serial configuration functions
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
int tcsendbreak(int fd, int duration);
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
void cfmakeraw(struct termios *termios_p);
speed_t cfgetispeed(const struct termios *termios_p);
speed_t cfgetospeed(const struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
int cfsetspeed(struct termios * termios_p, speed_t speed);
//Write/Read/Open/Close/Select Functions
ssize_t read_serial(int fd, void* buffer, size_t count);
ssize_t write_serial(int fd, const void* buffer, size_t count);
int open_serial(const char* portname, int opt);
int close_serial(int fd);
int select_serial(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//get Handle out of the COM structure
HANDLE getHandle();
#endif
#ifndef _WIN32
#pragma message("-Warning: termiWin requires a Windows system!")
#endif
#endif
+33 -36
View File
@@ -1,36 +1,33 @@
#ifndef TERMINAL_H_ #ifndef TERMINAL_H_
#define TERMINAL_H_ #define TERMINAL_H_
/* includes */ /* includes */
#include <errno.h> #include "data.h"
#include <stdio.h> #include "define.h"
#include <stdlib.h> #include <errno.h>
#include <sys/ioctl.h> #include <stdio.h>
#include <termios.h> #include <stdlib.h>
#include <unistd.h> // #include <sys/ioctl.h>
#include <unistd.h>
/**
* \fn void die(const char *s) /**
* \brief Exit program and return s error message. * \fn void die(const char *s)
* \param *s Error string * \brief Exit program and return s error message.
* */ * \param *s Error string
* */
void die(const char *s);
void die(const char *s);
void disableRawMode();
void disableRawMode();
void enableRawMode();
void enableRawMode();
int editorReadKey();
int editorReadKey();
int getCursorPosition(int *rows, int *cols);
int getCursorPosition(int *rows, int *cols);
int getWindowSize(int *rows, int *cols);
int getWindowSize(int *rows, int *cols);
char *keyToString(int key);
#endif
void appDebug(const char *fmt, ...);
#endif
+32
View File
@@ -0,0 +1,32 @@
/* termios.h
*
* Copyright (C) 2017 Christian Visintin - christian.visintin1997@gmail.com
*
* This file is part of "termiWin: a termios porting for Windows"
*
* termiWin is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* termiWin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with termiWin. If not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#ifdef _WIN32
#ifndef TERMIOS_H
#define TERMIOS_H
#include "termiWin.h"
#endif // TERMIOS_H
#endif // _WIN32
-16
View File
@@ -1,16 +0,0 @@
//
// 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);
#endif //BELUGA_UTF8_H
-16
View File
@@ -1,16 +0,0 @@
//
// Created by Giorgio on 28/05/2026.
//
#ifndef BELUGA_UTILS_H
#define BELUGA_UTILS_H
#include <sys/_types/_size_t.h>
extern int beluga_alloc_counter;
void * bAlloc(size_t size);
void * bRealloc(void * ptr, size_t size);
void * bFree(void * ptr);
#endif //BELUGA_UTILS_H
Regular → Executable
+44 -44
View File
@@ -1,44 +1,44 @@
#!/bin/bash #!/bin/bash
echo "--- Welcome to Beluga installer ---" echo "--- Welcome to Beluga installer ---"
read -p "Do you want to start the installation ? (Y/n)" confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1 read -p "Do you want to start the installation ? (Y/n)" confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
# Check dependencies # Check dependencies
if ! command -v "meson" &>/dev/null; then if ! command -v "meson" &>/dev/null; then
echo "❌ Error: meson not found. Please install it first." echo "❌ Error: meson not found. Please install it first."
exit 1 exit 1
fi fi
# Create config files # Create config files
echo "Create config files ..." echo "Create config files ..."
mkdir -pv ~/.beluga/ mkdir -pv ~/.beluga/
mkdir -pv ~/.beluga/assets/ mkdir -pv ~/.beluga/assets/
cp -rv ./assets/beluga.txt ~/.beluga/assets/ cp -rv ./assets/beluga.txt ~/.beluga/assets/
mkdir -pv ~/.beluga/packages/ mkdir -pv ~/.beluga/packages/
mkdir -pv ~/.beluga/config/ 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 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 if [[ "$confirm" =~ ^[Yy]$ ]]; then
mv ~/.beluga/config/init.lisp ~/.beluga/config/init.lisp.bak mv ~/.beluga/config/init.lisp ~/.beluga/config/init.lisp.bak
cp -rv ./config/init.lisp ~/.beluga/config/ cp -rv ./config/init.lisp ~/.beluga/config/
else else
cp -rv ./config/init.lisp ~/.beluga/config/init.lisp.new cp -rv ./config/init.lisp ~/.beluga/config/init.lisp.new
fi fi
# Compile the project # Compile the project
echo "Start compilation ..." echo "Start compilation ..."
meson setup build/ meson setup build/
meson compile -C build/ meson compile -C build/
# Add to path # Add to path
echo "Adding beluga to the path" echo "Adding beluga to the path"
sudo cp -f ./build/beluga /usr/local/bin/ sudo cp -f ./build/beluga /usr/local/bin/
echo "Installation finish" echo "Installation finish"
echo "Check ~/.beluga/config/init.lisp for customization" echo "Check ~/.beluga/config/init.lisp for customization"
+54 -77
View File
@@ -1,77 +1,54 @@
/** /**
* \file main.c * \file main.c
* \author Arthur Barraux * \author Arthur Barraux
* \brief Base file of Beluga text editor. Contain fonctions for terminal * \brief Base file of Beluga text editor. Contain fonctions for terminal
* interactions. \version 0.1 \date 21 septembre 2024 * interactions. \version 0.1 \date 21 septembre 2024
*/ */
#define _DEFAULT_SOURCE #include "include/buffer.h"
#define _BSD_SOURCE #include "include/split_screen.h"
#define _GNU_SOURCE #include <stdio.h>
#include <stdlib.h>
#include "include/utils.h" #include <string.h>
#include <unistd.h>
#include "include/buffer.h"
#include "include/split_screen.h" #define _DEFAULT_SOURCE
#include <string.h> #define _BSD_SOURCE
#include <unistd.h> #define _GNU_SOURCE
#include "include/data.h" #include "include/data.h"
#include "include/init.h" #include "include/file_io.h"
#include "include/input.h" #include "include/init.h"
#include "include/editor_op.h" #include "include/input.h"
#include "include/terminal.h" #include "include/editor_op.h"
#include "include/completion.h" #include "include/terminal.h"
#include <signal.h>
struct editorConfig E;
#include "include/utils.h"
int main(int argc, char *argv[]) {
struct editorConfig E;
char * splash_screen = (char *) calloc(256, sizeof(char));
int main(int argc, char *argv[]) {
enableRawMode();
char * splash_screen = bAlloc(sizeof(char) * 512); initEditor();
if (argc >= 2) {
signal(SIGPIPE, SIG_IGN); // don't die on broken pipe, just get EPIPE from write() EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(argv[1]);
enableRawMode(); } else {
initEditor(); strcat(splash_screen, getenv("HOME"));
if (argc >= 2) { strcat(splash_screen, "/.beluga/assets/beluga.txt");
EditorPane *active = splitScreenGetActivePane(); fprintf(stderr, "%s\n", splash_screen);
active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE); EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(splash_screen);
char project_root[512]; }
realpath(argv[1], project_root); //free(splash_screen);
char *slash = strrchr(project_root, '/');
if (slash) *slash = '\0'; editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
appDebug("peoject root : %s\n", project_root);
while (1) {
lspStart(E.lsp_client, project_root); editorRefreshScreen();
struct buffer_t *buf = &E.buffers[active->buffer_id]; editorProcessKeypress();
lspDidOpen(E.lsp_client, buf); }
return 0;
} else { }
strcat(splash_screen, getenv("HOME"));
strcat(splash_screen, "/.beluga/assets/beluga.txt");
appDebug("splash : %s\n", splash_screen);
EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(splash_screen, READ_ONLY);
struct buffer_t *buf = &E.buffers[active->buffer_id];
lspStart(E.lsp_client, splash_screen);
lspDidOpen(E.lsp_client, buf);
}
free(splash_screen);
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
appDebug("allocation : %d\n", beluga_alloc_counter);
while (1) {
editorRefreshScreen();
editorProcessKeypress();
}
return 0;
}
+37 -38
View File
@@ -1,38 +1,37 @@
project('beluga', 'c', project('beluga', 'c',
version : '2.3', version : '2.3',
default_options : [ default_options : [
'c_std=none', 'c_std=none',
] ]
) )
cc = meson.get_compiler('c') 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
# Source files src_files = files(
src_files = files( 'main.c',
'main.c', 'src/append_buffer.c',
'src/append_buffer.c', 'src/editor_op.c',
'src/editor_op.c', 'src/syntax_highlighter.c',
'src/syntax_highlighter.c', 'src/file_io.c',
'src/file_io.c', 'src/init.c',
'src/init.c', 'src/input.c',
'src/input.c', 'src/output.c',
'src/output.c', 'src/row_op.c',
'src/row_op.c', 'src/terminal.c',
'src/terminal.c', 'src/builtins.c',
'src/builtins.c', 'src/buffer.c',
'src/buffer.c', 'src/split_screen.c'
'src/split_screen.c', )
'src/utf8.c',
'src/completion.c', # Add termiWin.c for Windows builds
'src/lsp_ui.c', if host_machine.system() == 'windows'
'src/cJSON.c', src_files += files('src/termiWin.c')
'src/utils.c' endif
)
# Executable
# Executable executable('beluga',
executable('beluga', src_files,
src_files, dependencies: [m]
dependencies: [m, thread_dep] )
)
+16 -15
View File
@@ -1,15 +1,16 @@
#include "../include/append_buffer.h" #include "../include/append_buffer.h"
#include "include/utils.h"
extern struct editorConfig E;
void abAppend(struct abuf *ab, const char *s, int len) {
char *new = bRealloc(ab->b, ab->len + len); void abAppend(struct abuf *ab, const char *s, int len) {
char *new = realloc(ab->b, ab->len + len);
if (new == NULL) {
return; if (new == NULL) {
} return;
memcpy(&new[ab->len], s, len); }
ab->b = new; memcpy(&new[ab->len], s, len);
ab->len += len; ab->b = new;
} ab->len += len;
}
void abFree(struct abuf *ab) { bFree(ab->b); }
void abFree(struct abuf *ab) { free(ab->b); }
+336 -481
View File
@@ -1,481 +1,336 @@
/** /**
* @file buffer.c * @file buffer.c
* @brief Buffer management implementation for multiple open files * @brief Buffer management implementation for multiple open files
*/ */
#include "../include/buffer.h" #include "../include/buffer.h"
#include "../include/file_io.h" #include "../include/file_io.h"
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/data.h" #include "../include/data.h"
#include "include/split_screen.h" #include "include/split_screen.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <libgen.h> #include <string.h>
#include <string.h>
#include <sys/stat.h> extern struct editorConfig E;
#include "include/input.h" /**
#include "include/utils.h" * @brief Finds a buffer by filename
* @param filename The filename to search for
* @return Buffer ID if found, -1 if not found
/** */
* @brief Finds a buffer by filename static int bufferFindByFilename(const char *filename) {
* @param filename The filename to search for for (int i = 0; i < E.number_of_buffer; i++) {
* @return Buffer ID if found, -1 if not found if (E.buffers[i].filename != NULL &&
*/ strcmp(E.buffers[i].filename, filename) == 0) {
static int bufferFindByFilename(const char* filename) return E.buffers[i].buffer_id;
{ }
for (int i = 0; i < E.number_of_buffer; i++) }
{ return -1;
if (E.buffers[i].filename != NULL && }
strcmp(E.buffers[i].filename, filename) == 0)
{ /**
return E.buffers[i].buffer_id; * @brief Finds a buffer by ID
} * @param buffer_id The buffer ID to find
} * @return Pointer to buffer, NULL if not found
return -1; */
} 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) {
* @brief Finds a buffer by ID return &E.buffers[i];
* @param buffer_id The buffer ID to find }
* @return Pointer to buffer, NULL if not found }
*/ return NULL;
struct buffer_t* bufferFindById(int buffer_id) }
{
for (int i = 0; i < E.number_of_buffer; i++) /**
{ * @brief Creates a new buffer for a file
if (E.buffers[i].buffer_id == buffer_id) * @details Allocates buffer slot, loads file content, saves buffer metadata.
{ * If file is already open, switches to existing buffer instead.
return &E.buffers[i]; * @param filename Path to the file
} * @return Buffer ID on success, -1 on failure
} */
return NULL; int bufferCreate(const char *filename) {
} // Check if file is already open
int existing_id = bufferFindByFilename(filename);
/** if (existing_id != -1) {
* @brief Creates a new buffer for a file return bufferSwitch(existing_id);
* @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 // Check if we have space for more buffers
*/ if (E.number_of_buffer >= 64) {
int bufferCreate(const char* path, enum bufferStatus_e state) editorSetStatusMessage("Error: maximum buffers reached (64)");
{ return -1;
char *filename = basename((char *) path); }
// Check if file is already open
const int existing_id = bufferFindByFilename(path); // Initialize new buffer
path = dirname((char *) path); struct buffer_t *new_buf = &E.buffers[E.number_of_buffer];
if (existing_id != -1) new_buf->buffer_id = E.number_of_buffer;
{ new_buf->filename = strdup(filename);
return bufferSwitch(existing_id); new_buf->type = FILE_BUFF;
} new_buf->state = READ_AND_WRITE;
// Check if we have space for more buffers new_buf->x = 0;
if (E.number_of_buffer >= 64) new_buf->y = 0;
{ new_buf->row_offset = 0;
editorSetStatusMessage("Error: maximum buffers reached (64)"); new_buf->col_offset = 0;
return -1; new_buf->dirty = 0; // New file starts clean
}
// Load file content using existing editorOpen
// Initialize new buffer editorOpen(new_buf);
struct buffer_t* new_buf = &E.buffers[E.number_of_buffer];
new_buf->buffer_id = E.number_of_buffer; E.number_of_buffer++;
new_buf->filename = strdup(filename);
new_buf->type = FILE_BUFF; editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id);
new_buf->state = state; return new_buf->buffer_id;
new_buf->x = 0; }
new_buf->y = 0;
new_buf->dirty = 0; // New file starts clean /**
new_buf->path = strdup(path); * @brief Switches to a specific buffer by ID
* @details Saves current buffer state, loads target buffer content and state.
// Load file content using existing editorOpen * @param buffer_id The buffer ID to switch to
editorOpen(new_buf); * @return 0 on success, -1 on failure
*/
E.number_of_buffer++; int bufferSwitch(int buffer_id) {
struct buffer_t *target = bufferFindById(buffer_id);
editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id); if (target == NULL) {
return new_buf->buffer_id; E.layout.panes[E.layout.active_pane].buffer_id = buffer_id;
} editorSetStatusMessage("Error: buffer not found");
return -1;
/** }
* @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 editorSetStatusMessage("Switched to: %s (buffer %d)",
* @return 0 on success, -1 on failure target->filename, buffer_id);
*/ return 0;
int bufferSwitch(int buffer_id) }
{
struct buffer_t* target = bufferFindById(buffer_id); /**
if (target == NULL) * @brief Closes a buffer by ID
{ * @details Frees buffer resources and removes from buffer list.
E.layout.panes[E.layout.active_pane].buffer_id = buffer_id; * Prompts to save unsaved changes. If closing current buffer,
editorSetStatusMessage("Error: buffer not found"); * switches to next available buffer.
return -1; * @param buffer_id The buffer ID to close
} * @return 0 on success, -1 on failure
*/
int bufferClose(int buffer_id) {
editorSetStatusMessage("Switched to: %s (buffer %d)", struct buffer_t *buf = bufferFindById(buffer_id);
target->filename, buffer_id); if (buf == NULL) {
return 0; editorSetStatusMessage("Error: buffer not found");
} return -1;
}
/**
* @brief Closes a buffer by ID EditorPane * active = splitScreenGetActivePane();
* @details Frees buffer resources and removes from buffer list.
* Prompts to save unsaved changes. If closing current buffer, // If this is the current buffer, find next buffer to switch to
* switches to next available buffer. if (active->buffer_id == buffer_id) {
* @param buffer_id The buffer ID to close int next_id = -1;
* @return 0 on success, -1 on failure
*/ // Try to switch to next buffer
int bufferClose(int buffer_id) for (int i = 0; i < E.number_of_buffer; i++) {
{ if (E.buffers[i].buffer_id != buffer_id) {
struct buffer_t* buf = bufferFindById(buffer_id); next_id = E.buffers[i].buffer_id;
if (buf == NULL) break;
{ }
editorSetStatusMessage("Error: buffer not found"); }
return -1;
} if (next_id != -1) {
bufferSwitch(next_id);
EditorPane* active = splitScreenGetActivePane(); } else {
// No other buffers, clear editor
// If this is the current buffer, find next buffer to switch to editorCloseFile();
if (active->buffer_id == buffer_id) }
{ }
int next_id = -1;
// Free buffer resources
// Try to switch to next buffer free(buf->filename);
for (int i = 0; i < E.number_of_buffer; i++) buf->filename = NULL;
{ buf->buffer_id = -1;
if (E.buffers[i].buffer_id != buffer_id)
{ // Remove from buffer list by shifting
next_id = E.buffers[i].buffer_id; for (int i = 0; i < E.number_of_buffer; i++) {
break; 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];
}
if (next_id != -1) E.number_of_buffer--;
{ break;
bufferSwitch(next_id); }
} }
else
{ editorSetStatusMessage("Closed buffer %d", buffer_id);
// No other buffers, clear editor return 0;
editorCloseFile(); }
}
} /**
* @brief Gets the current active buffer
// Free buffer resources * @return Pointer to current buffer_t, NULL if none
bFree(buf->filename); */
buf->filename = NULL; struct buffer_t *bufferGetCurrent(void) {
buf->buffer_id = -1; EditorPane * active = splitScreenGetActivePane();
return bufferFindById(active->buffer_id);
// Remove from buffer list by shifting }
for (int i = 0; i < E.number_of_buffer; i++)
{ /**
if (E.buffers[i].buffer_id == buffer_id) * @brief Lists all open buffers
{ * @details Prints buffer information to status message
for (int j = i; j < E.number_of_buffer - 1; j++) * @return Number of open buffers
{ */
E.buffers[j] = E.buffers[j + 1]; int bufferListAll(void) {
} if (E.number_of_buffer == 0) {
E.number_of_buffer--; editorSetStatusMessage("No buffers open");
break; return 0;
} }
}
char buf[256] = "";
editorSetStatusMessage("Closed buffer %d", buffer_id); int offset = 0;
return 0; EditorPane * active = splitScreenGetActivePane();
}
for (int i = 0; i < E.number_of_buffer; i++) {
/** struct buffer_t *b = &E.buffers[i];
* @brief Gets the current active buffer char marker = (b->buffer_id == active->buffer_id) ? '*' : ' ';
* @return Pointer to current buffer_t, NULL if none offset += snprintf(&buf[offset], sizeof(buf) - offset,
*/ "%c%d:%s ", marker, b->buffer_id,
struct buffer_t* bufferGetCurrent(void) b->filename ? b->filename : "[No Name]");
{ }
EditorPane* active = splitScreenGetActivePane();
return bufferFindById(active->buffer_id); editorSetStatusMessage("Buffers: %s", buf);
} return E.number_of_buffer;
}
/**
* @brief Lists all open buffers /**
* @details Prints buffer information to status message * @brief Saves current buffer to disk
* @return Number of open buffers * @return 0 on success, -1 on failure
*/ */
int bufferListAll(void) int bufferSave(void) {
{ EditorPane * active = splitScreenGetActivePane();
if (E.number_of_buffer == 0) if (active->buffer_id == -1) {
{ editorSetStatusMessage("Error: no buffer active");
editorSetStatusMessage("No buffers open"); return -1;
return 0; }
}
editorSave();
char buf[256] = ""; return 0;
int offset = 0; }
EditorPane* active = splitScreenGetActivePane();
/**
for (int i = 0; i < E.number_of_buffer; i++) * @brief Saves all buffers to disk
{ * @details Iterates through all buffers, saves each one
struct buffer_t* b = &E.buffers[i]; * @return 0 on success, -1 on failure
char marker = (b->buffer_id == active->buffer_id) ? '*' : ' '; */
offset += snprintf(&buf[offset], sizeof(buf) - offset, int bufferSaveAll(void) {
"%c%d:%s ", marker, b->buffer_id, int saved = 0;
b->filename ? b->filename : "[No Name]"); int failed = 0;
}
for (int i = 0; i < E.number_of_buffer; i++) {
editorSetStatusMessage("Buffers: %s", buf); if (bufferSwitch(E.buffers[i].buffer_id) == 0) {
return E.number_of_buffer; if (E.dirty && bufferSave() == 0) {
} saved++;
}
/** } else {
* @brief Saves current buffer to disk failed++;
* @return 0 on success, -1 on failure }
*/ }
int bufferSave(void)
{ editorSetStatusMessage("Saved %d buffers (%d failed)", saved, failed);
EditorPane* active = splitScreenGetActivePane(); return (failed == 0) ? 0 : -1;
if (active->buffer_id == -1) }
{
editorSetStatusMessage("Error: no buffer active"); /**
return -1; * @brief Creates a new terminal buffer
} * @return Buffer ID on success, -1 on failure
*/
editorSave(); int bufferCreateTerminal(void) {
return 0; // Check if we have space for more buffers
} if (E.number_of_buffer >= 64) {
editorSetStatusMessage("Error: maximum buffers reached (64)");
/** return -1;
* @brief Saves all buffers to disk }
* @details Iterates through all buffers, saves each one
* @return 0 on success, -1 on failure // Initialize new terminal buffer
*/ struct buffer_t *new_buf = &E.buffers[E.number_of_buffer];
int bufferSaveAll(void) new_buf->buffer_id = E.number_of_buffer;
{ new_buf->filename = strdup("[Terminal]");
int saved = 0; new_buf->type = TERMINAL_BUFF;
int failed = 0; new_buf->state = READ_AND_WRITE;
new_buf->x = 0;
for (int i = 0; i < E.number_of_buffer; i++) new_buf->y = 0;
{ new_buf->row_offset = 0;
if (bufferSwitch(E.buffers[i].buffer_id) == 0) new_buf->col_offset = 0;
{ new_buf->dirty = 0;
if (E.dirty && bufferSave() == 0) new_buf->numrows = 0;
{ new_buf->row = NULL;
saved++;
} // Initialize terminal-specific data
} new_buf->terminal_command = NULL;
else new_buf->terminal_pid = -1;
{ new_buf->terminal_output = NULL;
failed++; new_buf->terminal_output_size = 0;
}
} // Initialize terminal-specific data
new_buf->terminal_command = NULL;
editorSetStatusMessage("Saved %d buffers (%d failed)", saved, failed); new_buf->terminal_pid = -1;
return (failed == 0) ? 0 : -1; new_buf->terminal_output = NULL;
} new_buf->terminal_output_size = 0;
/** E.number_of_buffer++;
* @brief Searches for a string in the document
* @details Prompts user for a search query, then searches forward from current editorSetStatusMessage("Created terminal buffer %d", new_buf->buffer_id);
* cursor position. Updates cursor position to the first match found. return new_buf->buffer_id;
* @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() * @brief Executes a command in the terminal buffer
* @see editorRowRxToCx() * @param buffer_id The terminal buffer ID
*/ * @param command The command to execute
void bufferFind(struct buffer_t* buf) * @return 0 on success, -1 on failure
{ */
appDebug("searching\n"); int bufferExecuteTerminalCommand(int buffer_id, const char *command) {
char* query = editorPrompt("Search: %s (ESC to cancel)", "", 0); struct buffer_t *buf = bufferFindById(buffer_id);
if (buf == NULL || buf->type != TERMINAL_BUFF) {
if (query == NULL) editorSetStatusMessage("Error: buffer is not a terminal buffer");
return; return -1;
int i; }
for (i = buf->y+1; i < buf->numrows; i++)
{ // Free any existing terminal data
row_t* row = &buf->row[i]; if (buf->terminal_command) {
char* match = strstr(row->chars, query); free(buf->terminal_command);
if (match) }
{ if (buf->terminal_output) {
buf->y = i; free(buf->terminal_output);
break; }
}
} // Store the command
bFree(query); buf->terminal_command = strdup(command);
}
// For now, we'll simulate terminal output
void bufferFindReverse(struct buffer_t* buf) // In a real implementation, this would fork a process and capture output
{ char output[256];
appDebug("searching\n"); snprintf(output, sizeof(output), "$ %s\n[Command executed: %s]\n", command, command);
char* query = editorPrompt("Reverse search: %s (ESC to cancel)", "", 0);
buf->terminal_output = strdup(output);
if (query == NULL) buf->terminal_output_size = strlen(output);
return;
int i; // Update the buffer content to show terminal output
if (!buf->y) // Clear existing rows
return; for (int i = 0; i < buf->numrows; ++i) {
for (i = buf->y - 1; i >= 0; i--) free(buf->row[i].chars);
{ free(buf->row[i].render);
row_t* row = &buf->row[i]; }
char* match = strstr(row->chars, query); free(buf->row);
if (match) buf->row = NULL;
{ buf->numrows = 0;
buf->y = i;
break; // Add terminal output as rows
} // Make a copy for parsing since strtok modifies the string
} char *output_copy = strdup(buf->terminal_output);
bFree(query); char *line = strtok(output_copy, "\n");
} while (line != NULL) {
bufferInsertRow(buf, buf->numrows, line, strlen(line));
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) { line = strtok(NULL, "\n");
if (at < 0 || at > buffer->numrows) }
return; free(output_copy);
row_t *tmp = bRealloc(buffer->row, sizeof(row_t) * (buffer->numrows + 1)); buf->dirty = 1;
if (!tmp) editorSetStatusMessage("Executed: %s", command);
return; return 0;
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 = bAlloc(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) { bFree(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 (row->size + n + 1 > row->cap) {
row->cap = (row->size + n + 1) * 2;
row->chars = bRealloc(row->chars, row->cap);
}
memmove(row->chars + at + n, row->chars + at, row->size - at);
memcpy(row->chars + at, src, n);
row->size += n;
row->chars = bRealloc(row->chars, row->size + 2);
++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);
bFree(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");
}
+530 -647
View File
File diff suppressed because it is too large Load Diff
-3206
View File
File diff suppressed because it is too large Load Diff
-555
View File
@@ -1,555 +0,0 @@
//
// Created by Giorgio on 25/05/2026.
//
#include "../include/completion.h"
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/syslimits.h>
#include "include/append_buffer.h"
#include "include/cJSON.h"
#include "include/data.h"
#include "include/lsp_ui.h"
#include "include/terminal.h"
#include "include/utils.h"
void createContextBuffer(const int x, const int y, const char* text)
{
E.context_buffers = bAlloc(sizeof(ContextBuffer));
ContextBuffer* buffer = E.context_buffers;
buffer->editor_x = x;
buffer->editor_y = y;
buffer->height = 1;
buffer->rows = bAlloc(sizeof(struct row));
if (!buffer->rows) return;
buffer->rows[0].chars = strdup(text);
buffer->rows[0].size = strlen(text);
buffer->width = strlen(text);
}
static void lsp_send(int fd, const char* json)
{
int body_len = strlen(json);
char header[1024];
int header_len = snprintf(header, sizeof(header),
"Content-Length: %d\r\n\r\n", body_len);
// Write header + body atomically in two writes, no dprintf mixing
write(fd, header, header_len);
write(fd, json, body_len);
// Log to stderr for debugging
fprintf(stderr, "[LSP →] Content-Length: %d | %s\n", body_len, json);
fflush(stderr);
}
static int lsp_uri_to_buffer_id(const char* uri)
{
const char *path = uri;
if (strncmp(uri, "file://", 7) == 0)
path = uri + 7;
// path is now "/absolute/path" — realpath output matches this directly
for (int i = 0; i < E.number_of_buffer; i++) {
if (E.buffers[i].filename == NULL) continue;
char abs[PATH_MAX];
realpath(E.buffers[i].filename, abs);
fprintf(stderr, "[URI MATCH] comparing '%s' vs '%s'\n", abs, path);
if (strcmp(abs, path) == 0)
return E.buffers[i].buffer_id;
}
return -1;
}
static char* lsp_recv(int fd)
{
char header[1024];
int content_length = 0;
while (1)
{
int i = 0;
char c;
int n;
// Read until \n
while ((n = read(fd, &c, 1)) == 1 && c != '\n')
header[i++] = c;
if (n <= 0) return NULL;
header[i] = '\0';
// Strip \r explicitly
while (i > 0 && (header[i - 1] == '\r' || header[i - 1] == ' '))
header[--i] = '\0';
if (i == 0) break; // blank line = end of headers
if (strncmp(header, "Content-Length: ", 16) == 0)
content_length = atoi(header + 16);
}
if (content_length == 0) return NULL;
char* body = bAlloc(content_length + 1);
int total = 0;
while (total < content_length)
{
int n = read(fd, body + total, content_length - total);
if (n <= 0)
{
bFree(body);
return NULL;
}
total += n;
}
body[content_length] = '\0';
return body;
}
static void lsp_dispatch(LspClient* lsp, const char* json)
{
if (!json) return;
cJSON* root = cJSON_Parse(json);
if (!root)
{
fprintf(stderr, "[LSP ←] Failed to parse JSON: %.120s\n", json);
return;
}
cJSON* method = cJSON_GetObjectItem(root, "method");
cJSON* id = cJSON_GetObjectItem(root, "id");
cJSON* result = cJSON_GetObjectItem(root, "result");
cJSON* error = cJSON_GetObjectItem(root, "error");
// ── Error response ────────────────────────────────────────────────────────
if (error)
{
cJSON* msg = cJSON_GetObjectItem(error, "message");
fprintf(stderr, "[LSP ←] ERROR: %s\n",
msg ? msg->valuestring : "(no message)");
cJSON_Delete(root);
return;
}
// ── Notification (no id, has method) ─────────────────────────────────────
if (method && !id)
{
const char* m = method->valuestring;
fprintf(stderr, "[LSP ←] NOTIF: %s\n", m);
if (strcmp(m, "textDocument/publishDiagnostics") == 0)
{
// Find which buffer this diagnostic belongs to
cJSON* params = cJSON_GetObjectItem(root, "params");
cJSON* uri = cJSON_GetObjectItem(params, "uri");
int buf_id = lsp_uri_to_buffer_id(
uri ? uri->valuestring : "");
fprintf(stderr, "[LSP ←] Diagnostics for buffer %d\n", buf_id);
pthread_mutex_lock(&lsp->lock);
lspParseDiagnostics(json, &E.lsp_diagnostics, buf_id);
pthread_mutex_unlock(&lsp->lock);
write(lsp->wake_pipe[1], "d", 1);
}
else if (strcmp(m, "window/logMessage") == 0)
{
cJSON* params = cJSON_GetObjectItem(root, "params");
cJSON* message = cJSON_GetObjectItem(params, "message");
fprintf(stderr, "[LSP ←] LOG: %s\n",
message ? message->valuestring : "");
}
E.lsp_client->completion_just_arrived = 1;
cJSON_Delete(root);
return;
}
// ── Response (has id + result) ────────────────────────────────────────────
if (id && result)
{
int response_id = id->valueint;
fprintf(stderr, "[LSP ←] RESPONSE id=%d\n", response_id);
// initialize response → send initialized + mark ready
if (lsp->state == LSP_INITIALIZING)
{
fprintf(stderr, "[LSP ←] Initialize OK, sending initialized\n");
lsp_send(lsp->write_fd,
"{\"jsonrpc\":\"2.0\",\"method\":\"initialized\",\"params\":{}}");
pthread_mutex_lock(&lsp->lock);
lsp->state = LSP_READY;
pthread_cond_signal(&lsp->ready_cond);
pthread_mutex_unlock(&lsp->lock);
E.lsp_client->completion_just_arrived = 1;
cJSON_Delete(root);
return;
}
// completion response → parse items and show popup
cJSON* items = cJSON_GetObjectItem(result, "items");
if (items && cJSON_IsArray(items))
{
int count = cJSON_GetArraySize(items);
fprintf(stderr, "[LSP ←] Completion: %d items\n", count);
// Print each item to stderr for debugging
cJSON* item;
int i = 0;
cJSON_ArrayForEach(item, items)
{
cJSON* label = cJSON_GetObjectItem(item, "label");
cJSON* detail = cJSON_GetObjectItem(item, "detail");
cJSON* kind = cJSON_GetObjectItem(item, "kind");
fprintf(stderr, " [%d] kind=%-2d %-40s %s\n",
i++,
kind ? kind->valueint : 0,
label ? label->valuestring : "(no label)",
detail ? detail->valuestring : "");
if (i >= 10)
{
fprintf(stderr, " ... (%d more)\n", count - 10);
break;
}
}
pthread_mutex_lock(&lsp->lock);
lspParseCompletion(json, &E.lsp_completion,
lsp->completion_cursor_x,
lsp->completion_cursor_y);
pthread_mutex_unlock(&lsp->lock);
E.lsp_client->completion_just_arrived = 1;
fprintf(stderr, "[POPUP] visible=%d count=%d origin=(%d,%d)\n",
E.lsp_completion.visible,
E.lsp_completion.count,
E.lsp_completion.origin_x,
E.lsp_completion.origin_y);
write(lsp->wake_pipe[1], "c", 1);
cJSON_Delete(root);
return;
}
// definition response → jump to location
cJSON* uri_item = cJSON_GetObjectItem(result, "uri");
if (uri_item)
{
cJSON* range = cJSON_GetObjectItem(result, "range");
cJSON* start = cJSON_GetObjectItem(range, "start");
int line = cJSON_GetObjectItem(start, "line")->valueint;
int col = cJSON_GetObjectItem(start, "character")->valueint;
fprintf(stderr, "[LSP ←] Definition: %s:%d:%d\n",
uri_item->valuestring, line, col);
E.lsp_client->completion_just_arrived = 1;
// TODO: jump to that location
cJSON_Delete(root);
return;
}
fprintf(stderr, "[LSP ←] Unhandled response id=%d: %.80s\n",
response_id, json);
}
cJSON_Delete(root);
}
static void* lsp_reader(void* arg)
{
LspClient* lsp = (LspClient*)arg;
while (lsp->state != LSP_SHUTDOWN)
{
char* msg = lsp_recv(lsp->read_fd);
if (!msg) break; // ← pipe closed or error, exit cleanly
lsp_dispatch(lsp, msg);
bFree(msg);
}
return NULL;
}
// ─── lifecycle ───────────────────────────────────────────────────────────────
int lspStart(LspClient* lsp, const char* project_root)
{
int to_clangd[2], from_clangd[2];
pipe(to_clangd);
pipe(from_clangd);
pipe(lsp->wake_pipe);
lsp->pid = fork();
if (lsp->pid == 0)
{
// Child: become clangd
dup2(to_clangd[0], STDIN_FILENO);
dup2(from_clangd[1], STDOUT_FILENO);
close(to_clangd[1]);
close(from_clangd[0]);
execlp("clangd", "clangd", "--log=error", "--completion-style=detailed", NULL);
_exit(1); // clangd not found
}
close(to_clangd[0]);
close(from_clangd[1]);
lsp->write_fd = to_clangd[1];
lsp->read_fd = from_clangd[0];
lsp->next_id = 1;
lsp->state = LSP_INITIALIZING;
pthread_mutex_init(&lsp->lock, NULL);
// Send initialize
char buf[1024];
snprintf(buf, sizeof(buf),
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"initialize\","
"\"params\":{\"processId\":%d,\"rootUri\":\"file://%s\","
"\"capabilities\":{"
"\"textDocument\":{"
"\"completion\":{\"completionItem\":{\"snippetSupport\":false}},"
"\"hover\":{},"
"\"definition\":{},"
"\"publishDiagnostics\":{}"
"}"
"}}}",
lsp->next_id++, getpid(), project_root);
pthread_mutex_init(&lsp->lock, NULL);
pthread_cond_init(&lsp->ready_cond, NULL);
pthread_create(&lsp->reader_thread, NULL, lsp_reader, lsp);
lsp_send(lsp->write_fd, buf);
pthread_mutex_lock(&lsp->lock);
while (lsp->state != LSP_READY)
pthread_cond_wait(&lsp->ready_cond, &lsp->lock);
pthread_mutex_unlock(&lsp->lock);
return 0;
}
// ─── document sync ───────────────────────────────────────────────────────────
// Build the full buffer text into a bAlloc'd string
static char* buffer_to_text(struct buffer_t* buf)
{
int total = 0;
for (int i = 0; i < buf->numrows; i++)
total += buf->row[i].size + 1; // +1 for \n
char* text = bAlloc(total + 1);
char* p = text;
for (int i = 0; i < buf->numrows; i++)
{
memcpy(p, buf->row[i].chars, buf->row[i].size);
p += buf->row[i].size;
*p++ = '\n';
}
*p = '\0';
return text;
}
void lspDidOpen(LspClient* lsp, struct buffer_t* buf)
{
if (lsp->state != LSP_READY || buf->b_lsp_open) return;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8];
snprintf(uri, sizeof(uri), "file://%s", abs);
const char* lang = "c";
if (strstr(buf->filename, ".cpp") || strstr(buf->filename, ".cc"))
lang = "cpp";
char* raw = buffer_to_text(buf);
// Let cJSON handle ALL escaping — don't touch the text yourself
cJSON* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didOpen");
cJSON_AddStringToObject(td, "uri", uri);
cJSON_AddStringToObject(td, "languageId", lang);
cJSON_AddNumberToObject(td, "version", 1);
cJSON_AddStringToObject(td, "text", raw); // cJSON escapes this
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg);
bFree(msg);
bFree(raw);
cJSON_Delete(root);
buf->b_lsp_open = 1;
}
void lspDidChange(LspClient* lsp, struct buffer_t* buf)
{
if (lsp->state != LSP_READY || !buf->b_lsp_open) return;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8];
snprintf(uri, sizeof(uri), "file://%s", abs);
char* raw = buffer_to_text(buf);
cJSON* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON* changes = cJSON_CreateArray();
cJSON* change = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didChange");
cJSON_AddStringToObject(td, "uri", uri);
cJSON_AddNumberToObject(td, "version", buf->dirty);
cJSON_AddStringToObject(change, "text", raw); // full content sync
cJSON_AddItemToArray(changes, change);
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(params, "contentChanges", changes);
cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg);
bFree(msg);
bFree(raw);
cJSON_Delete(root);
}
void lspDidClose(LspClient* lsp, struct buffer_t* buf)
{
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8];
snprintf(uri, sizeof(uri), "file://%s", abs);
cJSON* root = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddStringToObject(root, "method", "textDocument/didClose");
cJSON_AddStringToObject(td, "uri", uri); // ← fixed
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(root, "params", params);
char* msg = cJSON_PrintUnformatted(root);
lsp_send(lsp->write_fd, msg);
bFree(msg);
cJSON_Delete(root);
buf->b_lsp_open = 0;
}
// ─── requests ────────────────────────────────────────────────────────────────
void lspRequestCompletion(LspClient* lsp, struct buffer_t* buf,
int line, int col,
int screen_x, int screen_y)
{
if (lsp->state != LSP_READY) return;
lsp->completion_cursor_x = screen_x; // ← add
lsp->completion_cursor_y = screen_y;
appDebug("LSP REQUEST COMP");
char* msg;
char abs[PATH_MAX];
realpath(buf->filename, abs);
char uri[PATH_MAX + 8];
snprintf(uri, sizeof(uri), "file://%s", abs);
cJSON* req = cJSON_CreateObject();
cJSON* params = cJSON_CreateObject();
cJSON* td = cJSON_CreateObject();
cJSON* position = cJSON_CreateObject();
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
cJSON_AddStringToObject(req, "method", "textDocument/completion");
cJSON_AddStringToObject(td, "uri", uri);
cJSON_AddNumberToObject(position, "line", line);
cJSON_AddNumberToObject(position, "character", col);
cJSON_AddItemToObject(params, "position", position);
cJSON_AddItemToObject(params, "textDocument", td);
cJSON_AddItemToObject(req, "params", params);
msg = cJSON_PrintUnformatted(req);
lsp_send(lsp->write_fd, msg);
E.lsp_client->completion_requested = 1;
cJSON_Delete(req);
bFree(msg);
}
void lspRequestDefinition(LspClient* lsp, struct buffer_t* buf, int line, int col)
{
if (lsp->state != LSP_READY) return;
char* msg;
asprintf(&msg,
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"textDocument/definition\","
"\"params\":{\"textDocument\":{\"uri\":\"file://%s\"},"
"\"position\":{\"line\":%d,\"character\":%d}}}",
lsp->next_id++, buf->filename, line, col);
lsp_send(lsp->write_fd, msg);
bFree(msg);
}
void lspShutdown(LspClient* lsp)
{
if (!lsp || lsp->state == LSP_SHUTDOWN) return;
lsp->state = LSP_SHUTDOWN;
// 1. Send shutdown request (clangd expects this before exit)
cJSON* req = cJSON_CreateObject();
cJSON_AddStringToObject(req, "jsonrpc", "2.0");
cJSON_AddNumberToObject(req, "id", lsp->next_id++);
cJSON_AddStringToObject(req, "method", "shutdown");
cJSON_AddNullToObject(req, "params");
char* msg = cJSON_PrintUnformatted(req);
lsp_send(lsp->write_fd, msg);
bFree(msg);
cJSON_Delete(req);
// 2. Wait briefly for the shutdown response
// (don't block forever — clangd has 2s to reply)
struct timeval tv = {.tv_sec = 2, .tv_usec = 0};
fd_set fds;
FD_ZERO(&fds);
FD_SET(lsp->read_fd, &fds);
if (select(lsp->read_fd + 1, &fds, NULL, NULL, &tv) > 0)
{
char* resp = lsp_recv(lsp->read_fd);
bFree(resp);
}
// 3. Send exit notification
lsp_send(lsp->write_fd,
"{\"jsonrpc\":\"2.0\",\"method\":\"exit\",\"params\":null}");
// 4. Close pipes — this signals the reader thread to stop
close(lsp->write_fd);
close(lsp->read_fd);
// 5. Wait for reader thread to finish
pthread_join(lsp->reader_thread, NULL);
pthread_mutex_destroy(&lsp->lock);
// 6. Reap the clangd process
waitpid(lsp->pid, NULL, 0);
bFree(lsp);
}
+82 -131
View File
@@ -1,131 +1,82 @@
#include "../include/editor_op.h" #include <stdarg.h>
#include <stdarg.h> #include "../include/editor_op.h"
#include "../include/row_op.h"
#include "../include/row_op.h" #include "include/buffer.h"
#include "../include/buffer.h" #include "include/data.h"
#include "../include/data.h" #include "include/split_screen.h"
#include "../include/split_screen.h"
#include "../include/terminal.h"
#include "../include/utf8.h" extern struct editorConfig E;
#include "include/utils.h"
/**
extern struct editorConfig E; * @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
* @brief Sets a temporary status message for display * @param ... Variable arguments for format string
* @details Formats and stores a message that will be displayed in the message * @note Updates global editor state E (status_msg, status_msg_time)
* bar for 5 seconds. Uses printf-style variable argument formatting. * @see editorDrawMessageBar()
* @param fmt Printf-style format string */
* @param ... Variable arguments for format string void editorSetStatusMessage(const char *fmt, ...) {
* @note Updates global editor state E (status_msg, status_msg_time) va_list ap;
* @see editorDrawMessageBar() va_start(ap, fmt);
*/ vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
void editorSetStatusMessage(const char* fmt, ...) va_end(ap);
{ E.status_msg_time = time(NULL);
va_list ap; }
va_start(ap, fmt);
vsnprintf(E.status_msg, E.screencols, fmt, ap);
va_end(ap); void bufferInsertChar(int c) {
E.status_msg_time = time(NULL); EditorPane *active = splitScreenGetActivePane();
} struct buffer_t *buf = bufferFindById(active->buffer_id);
if (active->cursor_y == buf->numrows) {
/** bufferInsertRow(buf, buf->numrows, "", 0);
* @brief Moves the cursor based on arrow key input }
* @details Updates cursor position (E.cursor_x, E.cursor_y) based on the given bufferRowInsertChar(buf, &buf->row[active->cursor_y], active->cursor_x, c);
* key direction. Handles line wrapping and boundary conditions. Prevents cursor active->cursor_x++;
* 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 void bufferInsertNewLine() {
* @note Updates global editor state E /*
*/ * Add new line and place the cursor at the beginning of it
int editorMoveCursor(int key) { */
EditorPane *active = splitScreenGetActivePane(); fprintf(stderr, "Inserting new line\n");
struct buffer_t *buf = bufferFindById(active->buffer_id); EditorPane *active = splitScreenGetActivePane();
row_t *row = &buf->row[buf->y]; struct buffer_t *buf = bufferFindById(active->buffer_id);
switch (key) { frow *row;
case ARROW_RIGHT: if (!active->cursor_x) {
if (row && buf->x < row->size) { bufferInsertRow(buf, active->cursor_y, "", 0);
int len = utf8Seqlen(row->chars[buf->x]); } else {
buf->x += len; row = &buf->row[active->cursor_y];
} else if (row && buf->y < buf->numrows) { bufferInsertRow(buf, active->cursor_y + 1, &row->chars[active->cursor_x],
buf->y++; row->size - active->cursor_x);
buf->x = 0; row = &buf->row[active->cursor_y];
} row->size = active->cursor_x;
break; row->chars[row->size] = '\0';
case ARROW_DOWN: bufferUpdatfrow(row);
if (buf->y < buf->numrows) { }
++active->cursor_y;
buf->y++; active->cursor_x = 0;
} fprintf(stderr, "Insert new line done\n");
break; }
case ARROW_UP:
if (buf->y != 0) { void bufferDelChar() {
--buf->y; frow *row;
} EditorPane *active = splitScreenGetActivePane();
break; struct buffer_t *buf = bufferFindById(active->buffer_id);
case ARROW_LEFT: if (active->cursor_y == buf->numrows || !(active->cursor_x || active->cursor_y)) {
if (buf->x != 0) { return;
--buf->x; }
} else if (buf->y > 0) { row = &buf->row[active->cursor_y];
--buf->y; if (active->cursor_x > 0) {
buf->x = buf->row[buf->y].size; bufferRowDelchar(buf, row, active->cursor_x - 1);
} --active->cursor_x;
break; } else {
} active->cursor_x = buf->row[active->cursor_y - 1].size;
return 1; bufferRowAppendString(buf, &buf->row[active->cursor_y - 1], row->chars, row->size);
} bufferDelRow(buf, active->cursor_y);
--active->cursor_y;
char *editorGetClipboard(void) { }
FILE *pipe = popen(CLIPBOARD_PASTE_CMD, "r"); }
if (!pipe) return NULL;
size_t cap = 4096;
size_t len = 0;
char *buf = bAlloc(cap);
int c;
while ((c = fgetc(pipe)) != EOF) {
if (len + 1 >= cap) {
cap *= 2;
buf = bRealloc(buf, cap);
}
buf[len++] = (char)c;
}
buf[len] = '\0';
pclose(pipe);
return buf; // caller must free
}
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;
}
+251 -138
View File
@@ -1,138 +1,251 @@
/** /**
* @file file_io.c * @file file_io.c
* @brief File I/O operations module for the Beluga text editor * @brief File I/O operations module for the Beluga text editor
* @details Handles file loading, saving, searching, and buffer management. * @details Handles file loading, saving, searching, and buffer management.
* Provides functionality for opening/closing files, persisting changes to disk, * Provides functionality for opening/closing files, persisting changes to disk,
* and searching for text patterns within the document. * and searching for text patterns within the document.
*/ */
#include "../include/file_io.h" #include "../include/file_io.h"
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/input.h" #include "../include/input.h"
#include "../include/buffer.h" #include "include/buffer.h"
#include "../include/data.h" #include "include/data.h"
#include "../include/split_screen.h" #include "include/split_screen.h"
#include "../include/row_op.h" #include <fcntl.h>
#include <fcntl.h> #include <stdio.h>
#include <stdio.h> #include <stdlib.h>
#include <stdlib.h> #include <string.h>
#include <string.h> #include <time.h>
#include <time.h> #include <unistd.h>
#include <unistd.h>
#include <errno.h> // Windows compatibility for getline
#ifdef _WIN32
#include "../include/utils.h" #include <io.h>
extern struct editorConfig E; ssize_t getline(char **lineptr, size_t *n, FILE *stream) {
char *bufptr = NULL;
/** char *p = bufptr;
* @brief Closes the current file and resets editor state size_t size;
* @details Clears all rows, resets cursor position, scroll offsets, and file int c;
* metadata. Does not prompt to save unsaved changes.
* @note Updates global editor state E if (lineptr == NULL) {
*/ return -1;
void editorCloseFile(void) { }
EditorPane *active = splitScreenGetActivePane(); if (stream == NULL) {
struct buffer_t *buf = bufferFindById(active->buffer_id); return -1;
active->cursor_x = 0; }
active->cursor_y = 0; if (n == NULL) {
active->x_offset = 0; return -1;
active->y_offset = 0; }
for (int i = 0; i < buf->numrows; ++i) { bufptr = *lineptr;
bFree(buf->row[i].chars); size = *n;
}
buf->numrows = 0; c = fgetc(stream);
bFree(buf->row); if (c == EOF) {
buf->row = NULL; return -1;
buf->dirty = 0; }
bFree(buf->filename);
buf->filename = NULL; if (bufptr == NULL) {
E.status_msg[0] = '\0'; bufptr = malloc(128);
E.status_msg_time = 0; if (bufptr == NULL) {
} return -1;
}
/** size = 128;
* @brief Opens a file for editing }
* @details Loads file content into editor rows, one line per row. If another p = bufptr;
* file is already open, it is closed first (without saving). File is opened in while(c != EOF) {
* a+ (read/append) mode to allow both reading and modification. if ((p - bufptr) > (size - 1)) {
* @param filename Path to the file to open (relative or absolute) size = size + 128;
* @note Updates global editor state E bufptr = realloc(bufptr, size);
* @note Calls die() on file open failure if (bufptr == NULL) {
* @note Newline characters are stripped from loaded lines return -1;
* @see editorInsertRow() }
*/ }
void editorOpen(struct buffer_t* buffer) { *p++ = c;
FILE *fp; if (c == '\n') {
char full_name[1024]; break;
strcpy(full_name, buffer->path); }
strcat(full_name, "/"); c = fgetc(stream);
strcat(full_name, buffer->filename); }
strcat(full_name, "\0");
appDebug("full name : %s", full_name); *p++ = '\0';
*lineptr = bufptr;
fp = fopen(full_name, "a+"); *n = size;
if (!fp)
die("fopen"); return p - bufptr - 1;
}
char *line = NULL; #endif
size_t line_cap;
ssize_t line_len; extern char *strdup(const char *);
extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
rewind(fp); FILE *restrict stream);
extern int ftruncate(int fd, off_t length);
while ((line_len = getline(&line, &line_cap, fp)) != -1) { extern struct editorConfig E;
while (line_len > 0 &&
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) { /**
--line_len; * @brief Converts all editor rows to a single string buffer
} * @details Concatenates all row content into a single allocated buffer with
appDebug("line %s\n", line); * newlines between rows. Useful for file saving and buffer operations.
bufferInsertRow(buffer, buffer->numrows, line, line_len); * @param buffer_len Pointer to integer where total buffer length will be stored
bFree(line); * @return Pointer to dynamically allocated buffer containing all row data.
line = NULL; * Rows are separated by newline characters.
} * @note Caller is responsible for freeing the returned buffer
bFree(line); */
fclose(fp); char *bufferRowsToString(struct buffer_t *buf, int *buffer_len) {
E.dirty = 0; int tot_len = 0;
} int j;
char *buffer;
/** char *p;
* @brief Saves the current file to disk
* @details Prompts for filename if not set, converts all rows to a buffer, for (j = 0; j < buf->numrows; ++j) {
* writes to disk using open/ftruncate/write, and updates dirty flag. tot_len += buf->row[j].size + 1;
* Displays status messages on success or failure. }
* @note Updates global editor state E (dirty flag) *buffer_len = tot_len;
* @note If no filename is set, prompts user via editorPrompt() buffer = malloc(tot_len);
* @note Uses O_RDWR | O_CREAT with mode 0644 p = buffer;
* @see editorRowsToString() for (j = 0; j < buf->numrows; ++j) {
*/ memcpy(p, buf->row[j].chars, buf->row[j].size);
void editorSave() { p += buf->row[j].size;
EditorPane *active = splitScreenGetActivePane(); *p = '\n';
struct buffer_t *buffer = bufferFindById(active->buffer_id); p++;
int len; }
int fd;
if (buffer->filename == NULL) { return buffer;
buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1); }
if (buffer->filename == NULL) {
editorSetStatusMessage("Save aborted"); /**
return; * @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.
fd = open(buffer->filename, O_RDWR | O_CREAT, 0644); * @note Updates global editor state E
if (fd != -1) { */
for (int i = 0; i < buffer->numrows; ++i) void editorCloseFile(void) {
{ EditorPane *active = splitScreenGetActivePane();
len = strlen(buffer->row[i].chars); struct buffer_t *buf = bufferFindById(active->buffer_id);
if (write(fd, buffer->row[i].chars, len) != len) { active->cursor_x = 0;
close(fd); active->cursor_y = 0;
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); active->rx = 0;
active->row_offset = 0;
return; active->col_offset = 0;
} for (int i = 0; i < buf->numrows; ++i) {
write(fd, "\n", 1); free(buf->row[i].chars);
} free(buf->row[i].render);
buffer->dirty = 0; }
close(fd); buf->numrows = 0;
} free(buf->row);
editorSetStatusMessage("File saved"); buf->row = NULL;
} buf->dirty = 0;
free(buf->filename);
buf->filename = NULL;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
}
/**
* @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 filename 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->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;
}
bufferInsertRow(buffer, buffer->numrows, line, line_len);
free(line);
line = NULL;
}
free(line);
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() {
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
int len;
char *buf;
int fd;
if (buffer->filename == NULL) {
buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (buffer->filename == NULL) {
editorSetStatusMessage("Save aborted");
return;
}
}
buf = bufferRowsToString(buffer, &len);
fd = open(buffer->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));
}
/**
* @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) {
fprintf(stderr, "searching\n");
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
EditorPane *active = splitScreenGetActivePane();
if (query == NULL)
return;
int i;
for (i = active->cursor_y + 1; i < buf->numrows; i++) {
frow *row = &buf->row[i];
char *match = strstr(row->render, query);
if (match) {
active->cursor_y = i;
active->cursor_x = bufferRowRxToCx(row, match - row->render);
buf->row_offset = buf->numrows;
break;
}
}
free(query);
}
+138 -156
View File
@@ -1,156 +1,138 @@
#include "../include/init.h" #include "../include/init.h"
#include "../include/builtins.h" #include "../include/builtins.h"
#include "../include/color.h" #include "../include/color.h"
#include "../include/data.h" #include "../include/data.h"
#include "../include/terminal.h" #include "../include/terminal.h"
#include "../include/split_screen.h" #include "include/split_screen.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#define LISP_IMPLEMENTATION #define LISP_IMPLEMENTATION
#include "../include/lisp.h" #include "../include/lisp.h"
#include "../include/lisp_lib.h" #include "../include/lisp_lib.h"
#include "include/utils.h"
struct editorConfig;
struct editorConfig;
void registerBuiltin(char *key_sequence, LispCFunc f) {
void registerBuiltin(char *key_sequence, LispCFunc f) { lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx), lisp_make_func(f), E.ctx);
lisp_make_func(f), E.ctx); }
} void initBuiltins() {
// Registering C functions as lisp macro
void initBuiltins() { registerBuiltin("move-cursor", moveCursor);
// Registering C functions as lisp macro registerBuiltin("map-key", mapKey);
registerBuiltin("move-cursor", moveCursor); registerBuiltin("editor-quit", editorQuit);
registerBuiltin("map-key", mapKey); registerBuiltin("editor-save", l_editorSave);
registerBuiltin("editor-quit", editorQuit); registerBuiltin("editor-insert-new-line", l_editorInsertNewLine);
registerBuiltin("editor-save", l_editorSave); registerBuiltin("move-cursor-beg-line", moveCursorBeginLine);
registerBuiltin("editor-insert-new-line", l_editorInsertNewLine); registerBuiltin("move-cursor-end-line", moveCursorEndLine);
registerBuiltin("move-cursor-beg-line", moveCursorBeginLine); registerBuiltin("editor-delete-previous-char", deletePreviousChar);
registerBuiltin("move-cursor-end-line", moveCursorEndLine); registerBuiltin("move-cursor-page-up", editorMoveCursorPageUp);
registerBuiltin("editor-delete-previous-char", deletePreviousChar); registerBuiltin("move-cursor-page-down", editorMoveCursorPageDown);
registerBuiltin("move-cursor-page-up", editorMoveCursorPageUp); registerBuiltin("editor-open-file", editorOpenFile);
registerBuiltin("move-cursor-page-down", editorMoveCursorPageDown); registerBuiltin("editor-insert-char", editorPrintC);
registerBuiltin("editor-open-file", editorOpenFile); registerBuiltin("add-package", addPackage);
registerBuiltin("editor-insert-char", editorPrintC); registerBuiltin("editor-del-row", editorDelRow_L);
registerBuiltin("add-package", addPackage); registerBuiltin("buffer-find", bufferFind_L);
registerBuiltin("buffer-find", bufferFind_L); registerBuiltin("editor-read-char", editorReadChar_L);
registerBuiltin("buffer-find-reverse", bufferFindReverse_L); registerBuiltin("add-prefix", editorPrefix);
registerBuiltin("editor-read-char", editorReadChar_L); registerBuiltin("editor-set-prefix", editorSetPrefix);
registerBuiltin("add-prefix", editorPrefix); registerBuiltin("editor-insert-tab", l_editorInserTab);
registerBuiltin("editor-set-prefix", editorSetPrefix); registerBuiltin("editor-switch-next-buffer", editorSwitchNextBuffer);
registerBuiltin("editor-insert-tab", l_editorInserTab); registerBuiltin("editor-split-screen-vertical", l_editorSplitScreenVertical);
registerBuiltin("editor-switch-next-buffer", editorSwitchNextBuffer); registerBuiltin("editor-switch-next-pane", editorSwitchNextPane);
registerBuiltin("editor-split-screen-vertical", l_editorSplitScreenVertical); registerBuiltin("editor-unify-panes", editorUnifiedPanes);
registerBuiltin("editor-switch-next-pane", editorSwitchNextPane); registerBuiltin("editor-create-terminal", editorCreateTerminal);
registerBuiltin("editor-unify-panes", editorUnifiedPanes); }
registerBuiltin("editor-paste", editorPaste);
registerBuiltin("editor-cut-end-line", editorCutEndLine); void initConfig() {
registerBuiltin("editor-move-cursor-beg-buffer", editorMoveBegBuffer);
registerBuiltin("editor-move-cursor-end-buffer", editorMoveEndBuffer); E.ctx = lisp_init();
registerBuiltin("editor-auto-complete", editorAutoComplete); E.env = lisp_env(E.ctx);
// registerBuiltin("lsp-complete", lspComplete); lisp_lib_load(E.ctx);
registerBuiltin("lsp-definition", lspDefinition); // Init builtins lisp functions
} initBuiltins();
void initConfig() { // Read config file
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
E.ctx = lisp_init(); if (E.ctx_error != LISP_ERROR_NONE) {
E.ctx.p->err_port = fopen("log.err", "w"); die("init failed");
E.env = lisp_env(E.ctx); }
lisp_lib_load(E.ctx); lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
// Init builtins lisp functions }
initBuiltins();
void init_theme() {
// Read config file E.constantes.THEME = (char *)lisp_string(
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx); lisp_eval(lisp_read("THEME", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx));
if (E.ctx_error != LISP_ERROR_NONE) { if (strcmp(E.constantes.THEME, "dark") == 0) {
die("init failed"); E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(40, 44, 52);
} E.theme.COLOR_KEYWORD = ANSI_FG_RGB(198, 120, 221);
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx); 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);
void initTheme() { E.theme.COLOR_NUMBER = ANSI_FG_RGB(209, 154, 102);
E.constantes.THEME = (char *)lisp_string( E.theme.COLOR_DEFAULT = ANSI_FG_RGB(171, 178, 191);
lisp_eval(lisp_read("THEME", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx)); } else {
if (strcmp(E.constantes.THEME, "dark") == 0) { E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(250, 251, 252);
E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(40, 44, 52); E.theme.COLOR_KEYWORD = ANSI_FG_RGB(166, 38, 164);
E.theme.COLOR_KEYWORD = ANSI_FG_RGB(198, 120, 221); E.theme.COLOR_TYPE = ANSI_FG_RGB(4, 90, 180);
E.theme.COLOR_TYPE = ANSI_FG_RGB(97, 175, 239); E.theme.COLOR_STRING = ANSI_FG_RGB(80, 161, 79);
E.theme.COLOR_STRING = ANSI_FG_RGB(152, 195, 121); E.theme.COLOR_COMMENT = ANSI_FG_RGB(152, 152, 152);
E.theme.COLOR_COMMENT = ANSI_FG_RGB(92, 99, 112); E.theme.COLOR_NUMBER = ANSI_FG_RGB(206, 102, 54);
E.theme.COLOR_NUMBER = ANSI_FG_RGB(209, 154, 102); E.theme.COLOR_DEFAULT = ANSI_FG_RGB(86, 89, 90);
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); void initEditor() {
E.theme.COLOR_TYPE = ANSI_FG_RGB(4, 90, 180);
E.theme.COLOR_STRING = ANSI_FG_RGB(80, 161, 79); if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
E.theme.COLOR_COMMENT = ANSI_FG_RGB(152, 152, 152); die("getWindowSize");
E.theme.COLOR_NUMBER = ANSI_FG_RGB(206, 102, 54); }
E.theme.COLOR_DEFAULT = ANSI_FG_RGB(86, 89, 90); E.screenrows -= 2;
}
}
// Init graphics variables
void initEditor() { splitScreenInit();
EditorPane *active = splitScreenGetActivePane();
if (getWindowSize(&E.screenrows, &E.screencols) == -1) { active->cursor_x = 0;
die("getWindowSize"); active->cursor_y = 0;
} active->rx = 0;
appDebug("%d %d\n", E.screenrows, E.screencols); active->row_offset = 0;
E.screenrows -= 2; active->col_offset = 0;
E.init_file_path = (char *)calloc(256, sizeof(char));
strcat(E.init_file_path, getenv("HOME"));
// Init graphics variables strcat(E.init_file_path, "/.beluga/config/init.lisp");
splitScreenInit(); E.fd_init_file = fopen(E.init_file_path, "r");
EditorPane *active = splitScreenGetActivePane();
E.cursor_x = 0; // Status bar
E.cursor_y = 0; E.status_msg[0] = '\0';
E.status_msg_time = 0;
active->cursor_x = 0;
active->cursor_y = 0; // Key binds
active->origin_x = 0; E.number_of_keybinds = 0;
active->origin_y = 0; E.number_of_prefix = 0;
active->width = E.screencols; // General prefix is 0 (no prefix)
active->height = E.screenrows; E.prefix = (struct prefix_t *)malloc(sizeof(struct prefix_t));
E.prefix[0].prefix_id = 0;
E.init_file_path = (char *)calloc(256, sizeof(char)); strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
strcat(E.init_file_path, getenv("HOME")); E.prefix_state = 0;
strcat(E.init_file_path, "/.beluga/config/init.lisp");
appDebug("%s\n", E.init_file_path); initConfig();
E.fd_init_file = fopen(E.init_file_path, "r");
init_theme();
// Status bar
E.status_msg = (char *)calloc(E.screencols, sizeof(char)); // To modify
E.status_msg[0] = '\0';
E.status_msg_time = 0; E.constantes.TAB_LENGTH =
(int)lisp_eval(lisp_read("TAB-LENGTH", &E.ctx_error, E.ctx), &E.ctx_error,
// Key binds E.ctx)
E.number_of_keybinds = 0; .val.int_val;
E.number_of_prefix = 0; E.constantes.QUIT_TIMES =
// General prefix is 0 (no prefix) (int)lisp_eval(lisp_read("QUIT-TIMES", &E.ctx_error, E.ctx), &E.ctx_error,
E.prefix = (struct prefix_t *)bAlloc(sizeof(struct prefix_t)); E.ctx)
E.prefix[0].prefix_id = 0; .val.int_val;
strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
E.prefix_state = 0; E.quit_times_buffer = E.constantes.QUIT_TIMES;
}
E.lsp_client = (LspClient*)bAlloc(sizeof(LspClient));
initConfig();
initTheme();
// To modify
E.constantes.TAB_LENGTH =
(int)lisp_eval(lisp_read("TAB-LENGTH", &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx)
.val.int_val;
E.constantes.QUIT_TIMES =
(int)lisp_eval(lisp_read("QUIT-TIMES", &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx)
.val.int_val;
E.quit_times_buffer = E.constantes.QUIT_TIMES;
}
+393 -272
View File
@@ -1,272 +1,393 @@
#include "../include/input.h" #include "../include/input.h"
#include "../include/define.h" #include "../include/define.h"
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/output.h" #include "../include/output.h"
#include "include/data.h" #include "include/buffer.h"
#include "include/buffer.h" #include "include/data.h"
#include "include/data.h" #include "include/split_screen.h"
#include "include/split_screen.h" #include <ctype.h>
#include "include/completion.h" #include <dirent.h>
#include "include/lsp_ui.h" #include <stdint.h>
#include <ctype.h> #include <stdio.h>
#include <sys/stat.h> #include <stdlib.h>
#include <dirent.h> #include <string.h>
#include <stdint.h> #include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h> extern struct editorConfig E;
#include <string.h>
#include <sys/stat.h> /**
#include <unistd.h> * @file input.c
* @brief Input handling module for the Beluga text editor
#include "include/terminal.h" * @details Manages user input processing, key bindings, cursor movement, and file path completion
#include "include/utf8.h" */
#include "include/utils.h"
/**
extern struct editorConfig E; * @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.
* @file input.c * Appends a trailing slash for directory entries.
* @brief Input handling module for the Beluga text editor * @param path The file path to complete (can be relative or absolute)
* @details Manages user input processing, key bindings, cursor movement, and file path completion * @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
* @brief Returns the first file completion match for the given path * @note Caller is responsible for freeing the returned string
* @details Searches the directory containing the given path prefix and returns * @note Uses static buffer internally; may return stale pointers across calls
* the first file or directory entry that matches the filename prefix. */
* Appends a trailing slash for directory entries. const char *file_completion(const char *path) {
* @param path The file path to complete (can be relative or absolute) DIR *dir;
* @return Pointer to the completed file path (dynamically allocated), or NULL if: struct dirent *entry;
* - path ends with '/' (already a directory) char directory[128];
* - no matching entries found char predict[128];
* - directory cannot be opened const char *last_slash;
* @note Caller is responsible for bFreeing the returned string int predict_len = 0;
* @note Uses static buffer internally; may return stale pointers across calls size_t dir_len;
*/
const char *fileCompletion(const char *path) { // path is a directory
DIR *dir; if (path[strlen(path) - 1] == '/') {
struct dirent *entry; fprintf(stderr, "[FILE COMP] is dir\n");
char directory[256]; strncpy(directory, path, 128);
char predict[128]; }
const char *last_slash;
int predict_len = 0; // Find dir name
size_t dir_len; last_slash = strrchr(path, '/');
if (last_slash) {
// path is a directory dir_len = last_slash - path + 1; // length of dir_path
if (path[strlen(path) - 1] == '/') { strncpy(directory, path, dir_len);
appDebug("[FILE COMP] is dir\n"); predict_len = strlen(path) - dir_len;
strncpy(directory, path, 256); strncpy(predict, last_slash + 1, predict_len);
} directory[dir_len] = '\0';
predict[predict_len] = '\0';
// Find dir name fprintf(stderr, "%s %s\n", directory, predict);
last_slash = strrchr(path, '/'); } else {
if (last_slash) { fprintf(stderr, "[FILE COMP] dir not found\n");
dir_len = last_slash - path + 1; // length of dir_path return strdup(path);
strncpy(directory, path, dir_len); }
predict_len = strlen(path) - dir_len;
strncpy(predict, last_slash + 1, predict_len); dir = opendir(directory);
directory[dir_len] = '\0'; if (!dir)
predict[predict_len] = '\0'; return strdup(path);
appDebug("%s %s\n", directory, predict);
} else { while ((entry = readdir(dir)) != NULL) {
appDebug("[FILE COMP] dir not found\n"); if (strncmp(entry->d_name, predict, predict_len) == 0) {
return strdup(path); static char full_path[512];
} snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name);
dir = opendir(directory); struct stat st;
if (!dir) if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
return strdup(path); strcat(full_path, "/"); // add slash for directories
}
while ((entry = readdir(dir)) != NULL) { closedir(dir);
if (strncmp(entry->d_name, predict, predict_len) == 0) {
static char full_path[1024]; return strdup(full_path);
snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name); }
}
struct stat st;
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) { // Cleanup when no more entries
strcat(full_path, "/"); // add slash for directories closedir(dir);
} dir = NULL;
closedir(dir); free(entry);
fprintf(stderr, "[FILE COMP] no entries\n");
return strdup(full_path); return strdup(path);
} }
}
/**
// Cleanup when no more entries * @brief Displays an interactive prompt and returns user input
closedir(dir); * @details Allows the user to enter text in a prompt with optional path completion
dir = NULL; * via Tab key. Supports backspace, delete, and escape key handling. Dynamically
bFree(entry); * allocates memory for the input buffer.
appDebug("[FILE COMP] no entries\n"); * @param prompt The prompt message format string (printf-style)
return strdup(path); * @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
* @brief Displays an interactive prompt and returns user input * - Input buffer is empty when Enter is pressed
* @details Allows the user to enter text in a prompt with optional path completion * @note Caller is responsible for freeing the returned string
* via Tab key. Supports backspace, delete, and escape key handling. Dynamically * @note Uses editorReadKey() for input and editorRefreshScreen() for display
* allocates memory for the input buffer. */
* @param prompt The prompt message format string (printf-style) char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
* @param placeHolder Initial text to display in the input buffer size_t buf_size = 128;
* @param bPathMode If non-zero, enables Tab key file path completion char *buf = malloc(buf_size);
* @return Pointer to the user-entered text (dynamically allocated), or NULL if: size_t buf_len = 0;
* - User pressed ESC to cancel int c = 0;
* - Input buffer is empty when Enter is pressed buf[0] = '\0';
* @note Caller is responsible for bFreeing the returned string strcpy(buf, placeHolder);
* @note Uses editorReadKey() for input and editorRefreshScreen() for display buf_len = strlen(placeHolder);
*/
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) { while (1) {
size_t buf_size = 256; editorSetStatusMessage(prompt, buf);
appDebug("[FILE COMP] %s %d\n", placeHolder, strlen(placeHolder)); editorRefreshScreen();
char *buf = bAlloc(buf_size); c = editorReadKey();
size_t buf_len = 0; if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
int c = 0; if (buf_len != 0) {
buf[0] = '\0'; buf[--buf_len] = '\0';
strcpy(buf, placeHolder); }
buf_len = strlen(placeHolder); } else if (c == ESCAPE) {
editorSetStatusMessage("");
while (1) { free(buf);
editorSetStatusMessage(prompt, buf); return NULL;
editorRefreshScreen(); } else if (c == '\r') {
c = editorReadKey(); if (buf_len != 0) {
if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { editorSetStatusMessage("");
if (buf_len != 0) { return buf;
buf[--buf_len] = '\0'; }
} } else if (bPathMode && c == '\t') {
} else if (c == ESCAPE) { char path[128];
editorSetStatusMessage(""); char *pwd;
bFree(buf); if (buf[0] != '/') {
return NULL; pwd = getenv("PWD");
} else if (c == '\r') { fprintf(stderr, "%s\n", pwd);
if (buf_len != 0) { memcpy(path, pwd, strlen(pwd));
editorSetStatusMessage(""); path[strlen(pwd)] = '/';
return buf; strncat(path, buf, buf_len);
} } else {
} else if (bPathMode && c == '\t') { strcpy(path, buf);
char path[256]; }
char *pwd; memset(buf, 0, 128);
if (buf[0] != '/') { buf_len = 0;
pwd = getenv("PWD"); char * buf_complete = (char *) file_completion(path);
appDebug("%s\n", pwd); strcpy(buf, buf_complete);
memcpy(path, pwd, strlen(pwd)); free(buf_complete);
path[strlen(pwd)] = '/'; buf_len = strlen(buf);
strncat(path, buf, buf_len); buf[buf_len] = '\0';
} else {
strcpy(path, buf); } else if (!iscntrl(c) && c < 128) {
} if (buf_len == buf_size - 1) {
memset(buf, 0, 256); buf_size *= 2;
buf_len = 0; buf = realloc(buf, buf_size);
char * buf_complete = (char *) fileCompletion(path); }
strcpy(buf, buf_complete); buf[buf_len++] = c;
bFree(buf_complete); buf[buf_len] = '\0';
buf_len = strlen(buf); }
buf[buf_len] = '\0'; }
}
} else if (!iscntrl(c) && c < 256) {
if (buf_len == buf_size - 1) { /**
buf_size *= 2; * @brief Converts a key code to its string representation
buf = bRealloc(buf, buf_size); * @details Translates raw key codes (including special keys, control keys,
} * and regular characters) into human-readable string formats suitable for
buf[buf_len++] = c; * display and keybinding configuration.
buf[buf_len] = '\0'; * @param key The key code to convert
} * @return Pointer to static buffer containing the string representation.
} * Examples: "ENTER", "ARROW-UP", "CTRL-a", "TAB", "DELETE", etc.
} * @note Returns pointer to static buffer; string is overwritten on next call
* @note Non-printable characters are formatted as "KEY-<number>"
*/
char *key_to_string(int key) {
/** static char key_str[32];
* @brief Executes the command bound to a key sequence
* @details Searches the keybinding table for a matching key sequence and char tmp[10];
* prefix state, then evaluates the associated Lisp command. sprintf(tmp, "%d", key);
* @param key_sequence The string representation of the key sequence
* @return 1 if a matching keybinding was found and executed, 0 otherwise // First test enter key
* @note Updates global editor state E (prefix_state)
* @note Uses Lisp interpreter to evaluate bound commands if (key == '\r') {
*/ strcpy(key_str, "ENTER");
int executeKeyBind(char *key_sequence) { } else if (key == '\t') {
int i; strcpy(key_str, "TAB");
int previous_state = 0; } else if (key >= 1 && key <= 26) { // CTRL keys
appDebug("pressed %s\n", key_sequence); snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
for (i = 0; i < E.number_of_keybinds; ++i) { } else {
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) { switch (key) {
if (E.prefix_state != E.key_binds[i].prefix_id) { case ARROW_UP:
continue; strcpy(key_str, "ARROW-UP");
} break;
previous_state = E.prefix_state; case ARROW_DOWN:
// It's a symbol, create a function call strcpy(key_str, "ARROW-DOWN");
lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx), break;
&E.ctx_error, E.ctx); case ARROW_LEFT:
if (E.prefix_state == previous_state) strcpy(key_str, "ARROW-LEFT");
E.prefix_state = 0; break;
return 1; case ARROW_RIGHT:
} strcpy(key_str, "ARROW-RIGHT");
} break;
return 0; case PAGE_UP:
} strcpy(key_str, "PAGE-UP");
fprintf(stderr, "pagr up\n");
/** break;
* @brief Processes a single keypress from the user case PAGE_DOWN:
* @details Reads a key, checks if it matches any registered keybinding, strcpy(key_str, "PAGE-DOWN");
* and either executes the bound command or inserts the character. Resets break;
* the quit buffer counter on successful key processing. case DEL_KEY:
* @note Updates global editor state E strcpy(key_str, "DEL");
* @note Calls editorReadKey() to get input and editorInsertChar() for unbound keys
*/ break;
void editorProcessKeypress() { case BACKSPACE:
int c = editorReadKey(); strcpy(key_str, "BACKSPACE");
char key_sequence[8]; break;
case '\r':
if (E.lsp_client && E.lsp_completion.visible) { strcpy(key_str, "ENTER");
if (c == ARROW_UP || c == CTRL_KEY('p')) { break;
if (E.lsp_completion.selected > 0) case '\x1b':
E.lsp_completion.selected--; strcpy(key_str, "ESCAPE");
return; // consumed, redraw on next loop break;
} case BEG_LINE:
if (c == ARROW_DOWN || c == CTRL_KEY('n')) { strcpy(key_str, "HOME");
if (E.lsp_completion.selected < E.lsp_completion.count - 1) break;
E.lsp_completion.selected++; case END_LINE:
return; strcpy(key_str, "END");
} break;
if (c == '\r') { default:
CompletionItem *item = &E.lsp_completion.items[E.lsp_completion.selected]; // For regular characters
EditorPane *active = splitScreenGetActivePane(); if (isprint(key)) {
struct buffer_t *buf = bufferFindById(active->buffer_id); snprintf(key_str, sizeof(key_str), "%c", key);
} else {
// Find how many chars the user already typed by looking at the snprintf(key_str, sizeof(key_str), "KEY-%d", key);
// 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]; }
return key_str;
// 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]) || * @brief Moves the cursor based on arrow key input
row->chars[word_start - 1] == '_')) * @details Updates cursor position (E.cursor_x, E.cursor_y) based on the given
word_start--; * key direction. Handles line wrapping and boundary conditions. Prevents cursor
* from exceeding line lengths.
int already_typed = file_col - word_start; // chars user already typed * @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
// Insert only the suffix — what comes after what's already typed * @note Updates global editor state E
const char *suffix = item->label + already_typed; */
for (int i = 0; suffix[i]; i++) int editorMoveCursor(int key) {
bufferInsertBytes(&suffix[i], 1); EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
E.lsp_completion.visible = 0; frow *row = (active->cursor_y + active->row_offset >= buf->numrows) ? NULL : &buf->row[active->cursor_y + active->row_offset];
return; int row_len;
} int x = active->cursor_x + active->col_offset;
if (c == ESCAPE) { int y = active->cursor_y + active->row_offset;
E.lsp_completion.visible = 0; switch (key) {
return; case ARROW_RIGHT:
} if (row && x < row->size) {
// Any other key: dismiss popup and fall through to normal handling active->cursor_x++;
E.lsp_completion.visible = 0;
} } else if (row && y == row->size) {
active->cursor_y++;
if (executeKeyBind(keyToString(c))) { active->cursor_x = 0;
return; }
} break;
int seq_len = utf8Encode(c, key_sequence); case ARROW_DOWN:
appDebug("key seq : %s\n", key_sequence); if (y < buf->numrows) {
bufferInsertBytes(key_sequence, seq_len);
E.quit_times_buffer = E.constantes.QUIT_TIMES; active->cursor_y++;
} }
break;
case ARROW_UP:
if (y != 0) {
--active->cursor_y;
}
break;
case ARROW_LEFT:
if (x != 0) {
--active->cursor_x;
} else if (y > 0) {
--active->cursor_y;
active->cursor_x = buf->row[y].size;
}
break;
}
return 1;
}
/**
* @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;
fprintf(stderr, "pressed %s\n", key_sequence);
for (i = 0; i < E.number_of_keybinds; ++i) {
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
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 Handles terminal-specific key processing
* @details Processes key presses for terminal buffers, handling command input
* and execution.
* @param c The key code
* @return 1 if key was handled, 0 otherwise
*/
int handleTerminalKeypress(int c) {
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
if (buf == NULL || buf->type != TERMINAL_BUFF) {
return 0;
}
// Handle terminal-specific keys
if (c == '\r') { // Enter key - execute command
if (buf->terminal_command && strlen(buf->terminal_command) > 0) {
bufferExecuteTerminalCommand(buf->buffer_id, buf->terminal_command);
return 1;
}
} else if (c == BACKSPACE || c == CTRL_KEY('h')) { // Backspace
if (buf->terminal_command && strlen(buf->terminal_command) > 0) {
buf->terminal_command[strlen(buf->terminal_command) - 1] = '\0';
return 1;
}
} else if (c == CTRL_KEY('c')) { // Ctrl+C - clear command
if (buf->terminal_command) {
free(buf->terminal_command);
buf->terminal_command = NULL;
}
return 1;
} else if (!iscntrl(c) && c < 128) { // Regular character input
if (buf->terminal_command == NULL) {
buf->terminal_command = malloc(2);
buf->terminal_command[0] = c;
buf->terminal_command[1] = '\0';
} else {
int len = strlen(buf->terminal_command);
buf->terminal_command = realloc(buf->terminal_command, len + 2);
buf->terminal_command[len] = c;
buf->terminal_command[len + 1] = '\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();
// First try terminal-specific handling
if (handleTerminalKeypress(c)) {
return;
}
if (executeKeyBind(key_to_string(c))) {
return;
}
bufferInsertChar(c);
E.quit_times_buffer = E.constantes.QUIT_TIMES;
}
-324
View File
@@ -1,324 +0,0 @@
//
// Created by Giorgio on 27/05/2026.
//
#include "include/lsp_ui.h"
#include "include/data.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// We use cJSON — drop cJSON.c + cJSON.h into src/ and include/
#include "include/cJSON.h"
#include "include/append_buffer.h"
// ─── ANSI helpers ─────────────────────────────────────────────────────────────
// Move terminal cursor to absolute (col, row) — 1-based
static void ab_move(struct abuf *ab, int row, int col) {
char esc[32];
int n = snprintf(esc, sizeof(esc), "\x1b[%d;%dH", row, col);
abAppend(ab, esc, n); // your existing abAppend
}
static void ab_sgr(struct abuf *ab, const char *codes) {
char esc[32];
int n = snprintf(esc, sizeof(esc), "\x1b[%sm", codes);
abAppend(ab, esc, n);
}
#define AB_RESET(ab) ab_sgr(ab, "0")
#define AB_BOLD(ab) ab_sgr(ab, "1")
#define AB_DIM(ab) ab_sgr(ab, "2")
#define AB_FG(ab, c) ab_sgr(ab, c) // e.g. "32" = green
#define AB_BG(ab, c) ab_sgr(ab, c) // e.g. "44" = blue bg
// ─── Completion item kind → short tag ────────────────────────────────────────
static const char *kind_tag(int k) {
switch (k) {
case 2: return "mth"; // Method
case 3: return "fn "; // Function
case 4: return "ctr"; // Constructor
case 5: return "fld"; // Field
case 6: return "var"; // Variable
case 7: return "cls"; // Class
case 9: return "ifc"; // Interface
case 14: return "kwd"; // Keyword
default: return " ";
}
}
static const char *kind_color(int k) {
switch (k) {
case 3: return "33"; // yellow — functions
case 2: return "36"; // cyan — methods
case 7: return "35"; // magenta — classes
case 14: return "34"; // blue — keywords
case 5:
case 6: return "32"; // green — fields/vars
default: return "37"; // white
}
}
// ─── Draw completion popup ───────────────────────────────────────────────────
//
// ┌─────────────────────────────────────┐
// │ fn printf int (const …) │ ← selected (bold, inverted)
// │ fn fprintf │
// │ fn sprintf │
// └─────────────────────────────────────┘
//
// Drawn using absolute cursor positioning — safe to call inside
// editorRefreshScreen() before the final write().
void lspUiDrawCompletion(struct abuf *ab, CompletionPopup *popup) {
fprintf(stderr, "[DRAW] visible=%d count=%d origin=(%d,%d)\n",
popup->visible, popup->count,
popup->origin_x, popup->origin_y);
if (!popup->visible || popup->count == 0) return;
int visible_count = popup->count < COMPLETION_POPUP_H
? popup->count : COMPLETION_POPUP_H;
int box_w = COMPLETION_MAX_WIDTH;
// Scroll window so selected item is always visible
int scroll = 0;
if (popup->selected >= COMPLETION_POPUP_H)
scroll = popup->selected - COMPLETION_POPUP_H + 1;
// ── top border ────────────────────────────────────────────────────────────
ab_move(ab, popup->origin_y, popup->origin_x);
ab_sgr(ab, "0;37;40"); // white on black
abAppend(ab, "", 3); // UTF-8 box chars (3 bytes each)
for (int i = 0; i < box_w - 2; i++) abAppend(ab, "", 3);
abAppend(ab, "", 3);
// ── rows ──────────────────────────────────────────────────────────────────
for (int i = 0; i < visible_count; i++) {
int idx = i + scroll;
CompletionItem *it = &popup->items[idx];
int is_sel = (idx == popup->selected);
ab_move(ab, popup->origin_y + 1 + i, popup->origin_x);
if (is_sel) ab_sgr(ab, "7"); // reverse video (highlight)
else ab_sgr(ab, "0;37;40");
abAppend(ab, "", 2);
// kind tag in color
if (!is_sel) ab_sgr(ab, kind_color(it->kind));
abAppend(ab, kind_tag(it->kind), 3);
abAppend(ab, " ", 1);
if (!is_sel) AB_RESET(ab);
if (!is_sel) ab_sgr(ab, "37;40");
// label — left column, fixed width 20
char label_col[21];
snprintf(label_col, sizeof(label_col), "%-20s", it->label);
if (is_sel) AB_BOLD(ab);
abAppend(ab, label_col, 20);
if (is_sel) ab_sgr(ab, "22"); // bold off
abAppend(ab, " ", 1);
// detail — right column, dimmed
if (!is_sel) AB_DIM(ab);
char detail_col[14];
snprintf(detail_col, sizeof(detail_col), "%-13s", it->detail);
abAppend(ab, detail_col, 13);
AB_RESET(ab);
if (is_sel) ab_sgr(ab, "7");
else ab_sgr(ab, "37;40");
abAppend(ab, "", 2);
AB_RESET(ab);
}
// ── scroll indicator on right border if needed ────────────────────────────
if (popup->count > COMPLETION_POPUP_H) {
int bar_row = popup->origin_y + 1 +
(popup->selected * (COMPLETION_POPUP_H - 1))
/ (popup->count - 1);
ab_move(ab, bar_row, popup->origin_x + box_w - 1);
ab_sgr(ab, "0;37;40");
abAppend(ab, "", 3);
AB_RESET(ab);
}
// ── bottom border ─────────────────────────────────────────────────────────
ab_move(ab, popup->origin_y + 1 + visible_count, popup->origin_x);
ab_sgr(ab, "0;37;40");
abAppend(ab, "", 3);
for (int i = 0; i < box_w - 2; i++) abAppend(ab, "", 3);
abAppend(ab, "", 3);
AB_RESET(ab);
}
// ─── Handle popup keys ────────────────────────────────────────────────────────
// Returns 1 if key was consumed, 0 if editor should handle it normally.
int lspUiHandleKey(CompletionPopup *popup, int key) {
if (!popup->visible) return 0;
switch (key) {
case '\x1b': // ESC — dismiss
popup->visible = 0;
return 1;
case '\r': // Enter — accept
// Caller should insert popup->items[popup->selected].label
popup->visible = 0;
return 1;
// Arrow up / Ctrl-p
case 'A' + 0x80:
case 0x10:
if (popup->selected > 0) popup->selected--;
return 1;
// Arrow down / Ctrl-n
case 'B' + 0x80:
case 0x0e:
if (popup->selected < popup->count - 1) popup->selected++;
return 1;
default:
// Any printable key: dismiss popup, let editor handle it
popup->visible = 0;
return 0;
}
}
static DiagSeverity diag_worst_on_line(DiagnosticList *diags,
int buffer_id, int line) {
DiagSeverity worst = 0;
for (int i = 0; i < diags->count; i++) {
Diagnostic *d = &diags->entries[i];
if (d->buffer_id == buffer_id && d->line == line) {
if (d->severity > worst) worst = d->severity; // ERROR > WARN > HINT
}
}
return worst;
}
// Call this once per screen row, right before you draw the row text.
// ab : your append buffer
// diags : &E.lsp_diagnostics
// buf_id : active buffer id
// line : file line number (0-based) for this screen row
void lspUiDrawGutter(struct abuf *ab, DiagnosticList *diags,
int buf_id, int line) {
DiagSeverity s = diag_worst_on_line(diags, buf_id, line);
switch (s) {
case DIAG_ERROR:
ab_sgr(ab, "1;31"); // bold red
abAppend(ab, "", 3);
break;
case DIAG_WARNING:
ab_sgr(ab, "1;33"); // bold yellow
abAppend(ab, "", 3);
break;
case DIAG_HINT:
ab_sgr(ab, "2;37"); // dim white
abAppend(ab, "·", 1);
break;
default:
abAppend(ab, " ", 1); // no diagnostic
break;
}
AB_RESET(ab);
abAppend(ab, " ", 1); // gutter padding
}
// Returns the diagnostic message for the line the cursor is on, or NULL.
const char *lspUiDiagnosticAtCursor(DiagnosticList *diags,
int buffer_id, int cursor_line) {
for (int i = 0; i < diags->count; i++) {
Diagnostic *d = &diags->entries[i];
if (d->buffer_id == buffer_id && d->line == cursor_line)
return d->message;
}
return NULL;
}
// ─── Parse clangd JSON → our structs ─────────────────────────────────────────
void lspParseCompletion(const char *json, CompletionPopup *popup,
int screen_x, int screen_y) {
popup->count = 0;
popup->selected = 0;
popup->origin_x = screen_x;
popup->origin_y = screen_y + 1; // one row below cursor
cJSON *root = cJSON_Parse(json);
if (!root) return;
cJSON *result = cJSON_GetObjectItem(root, "result");
if (!result) { cJSON_Delete(root); return; }
// result can be a list or {isIncomplete, items:[…]}
cJSON *items = cJSON_IsArray(result)
? result
: cJSON_GetObjectItem(result, "items");
if (!items) { cJSON_Delete(root); return; }
cJSON *item;
cJSON_ArrayForEach(item, items) {
if (popup->count >= COMPLETION_MAX_ITEMS) break;
CompletionItem *ci = &popup->items[popup->count++];
cJSON *label = cJSON_GetObjectItem(item, "label");
cJSON *detail = cJSON_GetObjectItem(item, "detail");
cJSON *kind = cJSON_GetObjectItem(item, "kind");
const char *raw_label = label ? label->valuestring : "";
if (raw_label[0] == ' ') raw_label++;
strncpy(ci->label, raw_label, 127);
strncpy(ci->detail, detail ? detail->valuestring : "", 63);
ci->kind = kind ? kind->valueint : 0;
}
popup->visible = (popup->count > 0);
cJSON_Delete(root);
}
void lspParseDiagnostics(const char *json, DiagnosticList *diags,
int buffer_id) {
cJSON *root = cJSON_Parse(json);
if (!root) return;
cJSON *params = cJSON_GetObjectItem(root, "params");
if (!params) { cJSON_Delete(root); return; }
cJSON *diag_arr = cJSON_GetObjectItem(params, "diagnostics");
if (!diag_arr) { cJSON_Delete(root); return; }
// Remove old entries for this buffer
int w = 0;
for (int i = 0; i < diags->count; i++)
if (diags->entries[i].buffer_id != buffer_id)
diags->entries[w++] = diags->entries[i];
diags->count = w;
cJSON *d;
cJSON_ArrayForEach(d, diag_arr) {
if (diags->count >= DIAG_MAX) break;
Diagnostic *diag = &diags->entries[diags->count++];
diag->buffer_id = buffer_id;
cJSON *range = cJSON_GetObjectItem(d, "range");
cJSON *start = cJSON_GetObjectItem(range, "start");
cJSON *end_pos = cJSON_GetObjectItem(range, "end");
cJSON *sev = cJSON_GetObjectItem(d, "severity");
cJSON *msg = cJSON_GetObjectItem(d, "message");
diag->line = cJSON_GetObjectItem(start, "line")->valueint;
diag->col_start = cJSON_GetObjectItem(start, "character")->valueint;
diag->col_end = cJSON_GetObjectItem(end_pos, "character")->valueint;
diag->severity = sev ? (DiagSeverity)sev->valueint : DIAG_ERROR;
strncpy(diag->message, msg ? msg->valuestring : "", 255);
}
cJSON_Delete(root);
}
+333 -440
View File
@@ -1,440 +1,333 @@
/** /**
* @file output.c * @file output.c
* @brief Screen rendering and output module for the Beluga text editor * @brief Screen rendering and output module for the Beluga text editor
* @details Handles all screen updates, cursor positioning, status bar * @details Handles all screen updates, cursor positioning, status bar
* rendering, and display synchronization using ANSI escape sequences * rendering, and display synchronization using ANSI escape sequences
*/ */
#include "../include/output.h" #include "../include/output.h"
#include "../include/append_buffer.h" #include "../include/append_buffer.h"
#include "../include/buffer.h" #include "../include/buffer.h"
#include "../include/editor_op.h" #include "../include/data.h"
#include "../include/data.h" #include "../include/define.h"
#include "../include/define.h" #include "../include/row_op.h"
#include "../include/row_op.h" #include "../include/split_screen.h"
#include "../include/split_screen.h" #include "../include/syntax_highlighter.h"
#include "../include/syntax_highlighter.h" #include <stdio.h>
#include "../include/terminal.h" #include <stdlib.h>
#include "../include/lsp_ui.h" #include <string.h>
#include <stdio.h> #include <time.h>
#include <stdlib.h>
#include <string.h> extern struct editorConfig E;
#include <time.h>
/**
#include "include/completion.h" * @brief Renders a single pane with its buffer content
#include "include/utils.h" */
static void editorDrawPane(struct abuf *ab, EditorPane *pane) {
extern struct editorConfig E; if (pane == NULL || pane->buffer_id < 0)
return;
/**
* @brief Renders a single pane with its buffer content struct buffer_t *buf = bufferFindById(pane->buffer_id);
*/ if (buf == NULL)
static void editorDrawPane(struct abuf* ab, EditorPane* pane) return;
{
int file_row; // Draw content for this pane
char pos_buf[32]; for (int y = 0; y < pane->height; y++) {
int pos_len; int file_row = y + pane->row_offset;
int byte_len_to_print;
int bytes_to_print; // Position cursor at start of pane row
char* highlighted; char pos_buf[32];
int pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH",
if (pane == NULL || pane->buffer_id < 0) pane->start_row + y + 1, pane->start_col + 1);
return; abAppend(ab, pos_buf, pos_len);
const struct buffer_t* buf = bufferFindById(pane->buffer_id); // Apply background color (6 bytes for RGB format)
if (buf == NULL) abAppend(ab, E.theme.BACKGROUND_COLOR, strlen(E.theme.BACKGROUND_COLOR));
return;
int chars_printed = 0;
// Draw all pane
for (int y = 0; y < pane->height; y++) if (file_row >= buf->numrows) {
{ // Empty line - show tilde
file_row = y + pane->y_offset; if (buf->numrows == 0 && y == pane->height / 3) {
char welcome[80];
// Set cursor at start of pane row int welcome_len;
pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH",
pane->origin_y + y + 1, pane->origin_x + 1); // Different welcome message for terminal buffers
abAppend(ab, pos_buf, pos_len); if (buf->type == TERMINAL_BUFF) {
welcome_len = snprintf(welcome, sizeof(welcome), "Terminal %d", pane->buffer_id);
// Apply background color (6 bytes for RGB format) } else {
abAppend(ab, E.theme.BACKGROUND_COLOR, (int)strlen(E.theme.BACKGROUND_COLOR)); welcome_len = snprintf(welcome, sizeof(welcome), "Buffer %d", pane->buffer_id);
}
// pane line is out of buffer if (welcome_len > pane->width)
if (file_row >= buf->numrows) welcome_len = pane->width;
{
// Empty line - show tilde int padding = (pane->width - welcome_len) / 2;
if (padding) {
abAppend(ab, "~", 1); abAppend(ab, "~", 1);
for (int i = 0; chars_printed++;
i < pane->width - 1; padding--;
++i) }
{ while (padding-- && chars_printed < pane->width) {
abAppend(ab, " ", 1); abAppend(ab, " ", 1);
} chars_printed++;
} }
else abAppend(ab, welcome, welcome_len);
{ chars_printed += welcome_len;
lspUiDrawGutter(ab, &E.lsp_diagnostics, pane->buffer_id, file_row); } else {
abAppend(ab, "~", 1);
if (buf->filename[strlen(buf->filename) - 1] == 'c' || buf->filename[strlen(buf->filename) - 1] == 'h') chars_printed++;
{ }
// Render line with syntax highlighting, constrain to pane width } else {
highlighted = highlight_line( // Render line with syntax highlighting, constrain to pane width
&buf->row[file_row].chars[pane->x_offset], &byte_len_to_print); int start_offset = pane->col_offset;
int visible_len = buf->row[file_row].rsize - start_offset;
// Print only up to pane width if (visible_len < 0)
abAppend(ab, highlighted, byte_len_to_print); visible_len = 0;
bFree(highlighted); if (visible_len > pane->width)
} visible_len = pane->width;
else
{ if (buf->type == TERMINAL_BUFF) {
// Render basic line // Terminal buffer - show prompt for input line
bytes_to_print = if (file_row == buf->numrows - 1) {
buf->row[file_row].size - pane->x_offset < pane->width char prompt[16];
? buf->row[file_row].size - pane->x_offset int prompt_len = snprintf(prompt, sizeof(prompt), "$ ");
: pane->width; abAppend(ab, prompt, prompt_len);
abAppend(ab, &buf->row[file_row].chars[pane->x_offset], bytes_to_print); chars_printed += prompt_len;
}
// Fill remaining space with background color to pane width // Show the command being typed (if any)
for (int i = 0; if (buf->terminal_command != NULL) {
i < pane->width - editorRowCharCount(&buf->row[file_row], pane->width) + pane->x_offset; int cmd_len = strlen(buf->terminal_command);
++i) if (cmd_len > pane->width - prompt_len) {
{ cmd_len = pane->width - prompt_len;
abAppend(ab, " ", 1); }
} abAppend(ab, buf->terminal_command, cmd_len);
} chars_printed += cmd_len;
} }
} } else {
// Show terminal output
/** abAppend(ab, &buf->row[file_row].render[start_offset], visible_len);
* @brief Renders all panes based on current split configuration chars_printed = visible_len;
*/ }
static void editorDrawAllPanes(struct abuf* ab) } else if (buf->filename && strlen(buf->filename) > 0 && buf->filename[strlen(buf->filename) - 1] == 'c') {
{ int byte_len_to_print;
const ScreenLayout* layout = splitScreenGetLayout();
char *highlighted = highlight_line(
if (layout->num_panes == 1) &buf->row[file_row].render[start_offset], &byte_len_to_print);
{
// Single pane fullscreen // Print only up to pane width
editorDrawPane(ab, &layout->panes[0]);
} abAppend(ab, highlighted, byte_len_to_print);
else if (layout->num_panes == 2) free(highlighted);
{ } else {
// Draw both panes abAppend(ab, &buf->row[file_row].render[start_offset], buf->row[file_row].rsize);
for (int i = 0; i < 2; i++) }
{
editorDrawPane(ab, &layout->panes[i]); chars_printed = visible_len;
}
// Draw pane border/divider if not the last pane
if (layout->mode == SPLIT_VERTICAL && i == 0) // Fill remaining space with background color to pane width
{ abAppend(ab, E.theme.BACKGROUND_COLOR, strlen(E.theme.BACKGROUND_COLOR));
// Draw vertical divider while (chars_printed < pane->width) {
int divider_col = layout->panes[0].width; abAppend(ab, " ", 1);
for (int y = 0; y < layout->panes[0].height; y++) chars_printed++;
{ }
char pos_buf[32];
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", y + 1, divider_col); // Restore background color at end of line
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) * @brief Renders all panes based on current split configuration
{ */
// Draw horizontal divider static void editorDrawAllPanes(struct abuf *ab) {
int divider_row = layout->panes[0].height; ScreenLayout *layout = splitScreenGetLayout();
char pos_buf[32];
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", divider_row + 1, 1); if (layout->num_panes == 1) {
abAppend(ab, pos_buf, (int)strlen(pos_buf)); // Single pane fullscreen
for (int x = 0; x < E.screencols; x++) editorDrawPane(ab, &layout->panes[0]);
{ } else if (layout->num_panes == 2) {
abAppend(ab, "\x1b[1m-\x1b[0m", 9); // Bold dash divider // 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;
* @brief Updates scroll offsets to keep cursor visible on screen for (int y = 0; y < layout->panes[0].height; y++) {
* @details Adjusts E.row_offset and E.col_offset to ensure the cursor remains char pos_buf[32];
* within the visible viewport. Also updates E.rx (rendered x-coordinate). snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", y + 1, divider_col);
* @note Updates global editor state E abAppend(ab, pos_buf, strlen(pos_buf));
* @see editorRowCxToRx() abAppend(ab, "\x1b[1m|\x1b[0m", 9); // Bold pipe divider
*/ }
void editorScroll() } else if (layout->mode == SPLIT_HORIZONTAL && i == 0) {
{ // Draw horizontal divider
EditorPane* active = splitScreenGetActivePane(); int divider_row = layout->panes[0].height;
struct buffer_t* buf = bufferFindById(active->buffer_id); char pos_buf[32];
int rel_x, rel_y; snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", divider_row + 1, 1);
abAppend(ab, pos_buf, strlen(pos_buf));
// compute relative coordinates for (int x = 0; x < E.screencols; x++) {
rel_x = editorRowCharCount(&buf->row[buf->y], buf->x); abAppend(ab, "\x1b[1m-\x1b[0m", 9); // Bold dash divider
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) * @brief Updates scroll offsets to keep cursor visible on screen
{ * @details Adjusts E.row_offset and E.col_offset to ensure the cursor remains
if (rel_x < active->cursor_x + active->x_offset) * within the visible viewport. Also updates E.rx (rendered x-coordinate).
{ * @note Updates global editor state E
// LEFT * @see editorRowCxToRx()
if (active->cursor_x == 0 && active->x_offset) */
{ void editorScroll() {
active->x_offset--; EditorPane *active = splitScreenGetActivePane();
} struct buffer_t *buf = bufferFindById(active->buffer_id);
else active->rx = active->cursor_x;
{
active->cursor_x--; fprintf(stderr, "%d %d / %d %d\n", active->cursor_x, active->col_offset,
} active->cursor_y, active->row_offset);
}
else if (active->cursor_x < 0) {
{ active->cursor_x = 0;
// RIGHT active->col_offset = active->col_offset == 0 ? 0 : --active->col_offset;
if (rel_x > active->cursor_x + active->x_offset) }
{
if (active->cursor_x == active->width - 1) if (active->cursor_y < 0) {
{ active->cursor_y = 0;
active->x_offset++; active->row_offset = active->row_offset == 0 ? 0 : --active->row_offset;
} }
else
{ if (active->cursor_x == active->width) {
active->cursor_x++;
} active->cursor_x--;
} active->col_offset++;
} }
if (rel_y < active->cursor_y + active->y_offset) if (active->cursor_y == active->height) {
{
if (active->cursor_y == 0 && active->y_offset) active->cursor_y--;
{ active->row_offset++;
active->y_offset--; }
} }
else
{ /**
active->cursor_y--; * @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.
if (rel_y > active->cursor_y + active->y_offset) * @param ab Pointer to append buffer structure for accumulating output
{ * @note Uses ANSI escape codes for color inversion
if (active->cursor_y == active->height - 1) */
{ void editorDrawStatusBar(struct abuf *ab) {
active->y_offset++; int len, render_len;
} char status[80], render_status[80];
else EditorPane *active = splitScreenGetActivePane();
{ struct buffer_t *buf = bufferFindById(active->buffer_id);
active->cursor_y++;
} abAppend(ab, "\x1b[7m", 4); // inverting colors
}
} const char *mode_str = "";
} ScreenLayout *layout = splitScreenGetLayout();
if (layout->mode == SPLIT_VERTICAL)
char* basename(char* path) mode_str = " [V-SPLIT]";
{ else if (layout->mode == SPLIT_HORIZONTAL)
int len = (int)strlen(path); mode_str = " [H-SPLIT]";
// Build buffer status showing all buffers with dirty indicators
for (int i = len - 1; i > 0; i--) char buf_status[200] = "";
{ int offset = 0;
if (path[i] == '/') for (int i = 0; i < E.number_of_buffer; i++) {
{ struct buffer_t *b = &E.buffers[i];
path = path + i + 1; char marker = (b->buffer_id == active->buffer_id) ? '>' : ' ';
break; char dirty_marker = b->dirty ? '*' : ' ';
} offset += snprintf(&buf_status[offset], sizeof(buf_status) - offset,
} "%c%d:%s%c ", marker, b->buffer_id,
return path; b->filename ? b->filename : "[No Name]", dirty_marker);
} }
/** len = snprintf(status, sizeof(status), "%s%s", buf_status, mode_str);
* @brief Renders the status bar at the bottom of the screen
* @details Displays filename, line count, dirty flag, and current cursor render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
* position in an inverted color bar. Right-aligns the cursor position active->cursor_y + 1, buf->numrows);
* indicator.
* @param ab Pointer to append buffer structure for accumulating output if (len > E.screencols) {
* @note Uses ANSI escape codes for color inversion len = E.screencols;
*/ }
void editorDrawStatusBar(struct abuf* ab)
{ abAppend(ab, status, len);
// TO MODIFY while (len < E.screencols) {
int len, render_len; if (E.screencols - len == render_len) {
char status[E.screencols], render_status[E.screencols * 4]; abAppend(ab, render_status, render_len);
EditorPane* active = splitScreenGetActivePane(); break;
struct buffer_t* buf = bufferFindById(active->buffer_id); } else {
abAppend(ab, " ", 1);
abAppend(ab, "\x1b[7m", 4); // inverting colors ++len;
}
const char* mode_str = ""; }
ScreenLayout* layout = splitScreenGetLayout();
if (layout->mode == SPLIT_VERTICAL) abAppend(ab, "\x1b[m", 3); // normal text mode
mode_str = " [V-SPLIT]"; abAppend(ab, "\r\n", 2);
else if (layout->mode == SPLIT_HORIZONTAL) }
mode_str = " [H-SPLIT]";
/**
// Build buffer status showing all buffers with dirty indicators * @brief Renders the message bar below the status bar
char buf_status[1024] = ""; * @details Displays temporary status messages for a limited time (5 seconds).
int offset = 0; * Only displays message if within time window and within screen width.
for (int i = 0; i < E.number_of_buffer; i++) * @param ab Pointer to append buffer structure for accumulating output
{ * @note Messages are set by editorSetStatusMessage()
struct buffer_t* b = &E.buffers[i]; */
char marker = (b->buffer_id == active->buffer_id) ? '>' : ' '; void editorDrawMessageBar(struct abuf *ab) {
char dirty_marker = b->dirty ? '*' : ' '; int msg_len = strlen(E.status_msg);
offset += snprintf(&buf_status[offset], sizeof(buf_status) - offset, abAppend(ab, ERASE_END_LINE, 3);
"%c%d:%s%c ", marker, b->buffer_id, if (msg_len > E.screencols) {
b->filename ? basename(b->filename) : "[No Name]", dirty_marker); msg_len = E.screencols;
} }
if (msg_len && time(NULL) - E.status_msg_time < 5) {
len = snprintf(status, sizeof(status), "%s%s", buf_status, mode_str); abAppend(ab, E.status_msg, msg_len);
}
render_len = snprintf(render_status, sizeof(render_status), "%d/%d", }
active->cursor_y + 1, buf->numrows);
/**
if (len > E.screencols) * @brief Performs complete screen refresh and buffer synchronization
{ * @details Clears screen, redraws all visible content (rows, status bar,
len = E.screencols; * 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())
abAppend(ab, status, len); * @see editorDrawRows()
while (len < E.screencols) * @see editorDrawStatusBar()
{ * @see editorDrawMessageBar()
if (E.screencols - len == render_len + 1) */
{
abAppend(ab, render_status, render_len); void editorRefreshScreen() {
break; struct abuf ab = ABUF_INIT;
} char buf[32];
else
{ abAppend(&ab, HIDE_CURSOR, 6);
abAppend(ab, " ", 1); abAppend(&ab, CURSOR_TOP_LEFT, 3);
++len; abAppend(&ab, E.theme.BACKGROUND_COLOR,
} strlen(E.theme.BACKGROUND_COLOR)); // RGB background is 12 bytes
}
// Draw all panes
abAppend(ab, "\x1b[m", 3); // normal text mode editorScroll();
abAppend(ab, "\r\n", 2); editorDrawAllPanes(&ab);
}
// Draw status bar and message bar
/** editorDrawStatusBar(&ab);
* @brief Renders the message bar below the status bar editorDrawMessageBar(&ab);
* @details Displays temporary status messages for a limited time (5 seconds).
* Only displays message if within time window and within screen width. // Position cursor in active pane
* @param ab Pointer to append buffer structure for accumulating output EditorPane *active = splitScreenGetActivePane();
* @note Messages are set by editorSetStatusMessage() if (active != NULL) {
*/ snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
void editorDrawMessageBar(struct abuf* ab) active->cursor_y + active->start_row + 1,
{ active->cursor_x + active->start_col + 1);
int msg_len = (int)strlen(E.status_msg); abAppend(&ab, buf, strlen(buf));
abAppend(ab, ERASE_END_LINE, 3); }
if (msg_len > E.screencols)
{ abAppend(&ab, SHOW_CURSOR, 6);
msg_len = E.screencols; write(STDOUT_FILENO, ab.b, ab.len);
} abFree(&ab);
if (msg_len && time(NULL) - E.status_msg_time < 5) }
{
abAppend(ab, E.status_msg, msg_len);
}
}
void editorDrawContextBuffer(struct abuf* ab)
{
int pos_len;
char pos_buf[1024];
int i, j;
if (!E.context_buffers)
return;
appDebug("Printing context");
for (i = 0; i < E.context_buffers->height; ++i)
{
if (E.context_buffers->editor_y + i + 1 > 0)
{
pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH",
E.context_buffers->editor_y + i + 1, E.context_buffers->editor_x - 2);
abAppend(ab, pos_buf, pos_len);
// Apply background color (6 bytes for RGB format)
abAppend(ab, E.theme.BACKGROUND_COLOR, (int)strlen(E.theme.BACKGROUND_COLOR));
abAppend(ab, "|", 1);
abAppend(ab, E.context_buffers->rows->chars, E.context_buffers->rows->size);
abAppend(ab, "|", 1);
}
}
bFree(E.context_buffers);
E.context_buffers = 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();
editorDrawAllPanes(&ab);
editorDrawStatusBar(&ab);
editorDrawMessageBar(&ab);
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
// ── LSP: sync buffer changes to clangd ────────────────────────────────────
if (buffer->b_has_changed) {
if (E.lsp_client && E.lsp_client->state == LSP_READY) {
lspDidChange(E.lsp_client, buffer);
E.lsp_client->completion_just_arrived = 0; // consume the flag
}
buffer->b_has_changed = 0;
}
// ── LSP: draw completion popup every frame while visible ──────────────────
fprintf(stderr, "[REFRESH] lsp_completion.visible=%d\n",
E.lsp_completion.visible);
while (E.lsp_client->completion_requested && !E.lsp_client->completion_just_arrived)
;
// reset flags
E.lsp_client->completion_just_arrived = 0;
E.lsp_client->completion_requested = 0;
if (E.lsp_client && E.lsp_client->state == LSP_READY)
{
lspUiDrawCompletion(&ab, &E.lsp_completion);
appDebug("ready\n");
}
// ── LSP: diagnostic for current line in status bar ────────────────────────
const char *diag = lspUiDiagnosticAtCursor(
&E.lsp_diagnostics,
active->buffer_id,
active->cursor_y + active->y_offset);
if (diag)
editorSetStatusMessage("● %s", diag);
// ── Position cursor (account for gutter width) ────────────────────────────
snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
active->cursor_y + active->origin_y + 1,
active->cursor_x + active->origin_x + 1 + GUTTER_WIDTH);
abAppend(&ab, buf, (int)strlen(buf));
abAppend(&ab, SHOW_CURSOR, 6);
write(STDOUT_FILENO, ab.b, ab.len);
abFree(&ab);
}
+152 -12
View File
@@ -1,12 +1,152 @@
#include "../include/row_op.h" #include "../include/row_op.h"
#include "../include/data.h" #include <stdio.h>
#include "../include/utf8.h" #include <stdlib.h>
#include <stdio.h> #include <string.h>
#include <stdlib.h> #include <time.h>
#include <string.h>
#include <time.h> extern struct editorConfig E;
extern struct editorConfig E; int bufferRowCxToRx(frow *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 bufferRowRxToCx(frow *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 bufferUpdatfrow(frow *row)
* \brief Copy content of \p row in \p row->render.
* */
void bufferUpdatfrow(frow *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 bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) {
if (at < 0 || at > buffer->numrows) {
return;
}
frow *tmp = (frow *)realloc(buffer->row, sizeof(frow) * (buffer->numrows + 1));
if (!tmp) {
return;
}
buffer->row = tmp;
memmove(&buffer->row[at + 1], &buffer->row[at], sizeof(frow) * (buffer->numrows - at));
buffer->row[at].size = len;
buffer->row[at].chars = malloc(len + 1);
memcpy(buffer->row[at].chars, s, len);
buffer->row[at].chars[len] = '\0';
buffer->row[at].rsize = 0;
buffer->row[at].render = NULL;
bufferUpdatfrow(&buffer->row[at]);
++buffer->numrows;
++buffer->dirty;
}
void bufferFrefrow(frow *row) {
free(row->render);
free(row->chars);
}
void bufferDelRow(struct buffer_t *buffer, int at) {
if (at < 0 || at >= buffer->numrows) {
return;
}
bufferFrefrow(&buffer->row[at]);
memmove(&buffer->row[at], &buffer->row[at + 1], sizeof(frow) * (buffer->numrows - at - 1));
--buffer->numrows;
++buffer->dirty;
}
/**
* \fn bufferRowInsertChar(frow *row, int at, int c)
* \param at Index of where we want to insert the char */
void bufferRowInsertChar(struct buffer_t *buffer, frow *row, int at, int c) {
if (buffer->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;
bufferUpdatfrow(row);
++buffer->dirty;
}
void bufferRowAppendString(struct buffer_t *buffer, frow *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';
bufferUpdatfrow(row);
++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 bufferRowDelchar(struct buffer_t *buffer, frow *row, int at) {
if (at < 0 || at >= row->size) {
return;
}
memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
--row->size;
bufferUpdatfrow(row);
++buffer->dirty;
}
+218 -220
View File
@@ -1,220 +1,218 @@
/** /**
* @file split_screen.c * @file split_screen.c
* @brief Split screen implementation * @brief Split screen implementation
*/ */
#include "../include/split_screen.h" #include "../include/split_screen.h"
#include "../include/buffer.h" #include "../include/buffer.h"
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "include/utils.h" extern struct editorConfig E;
extern struct editorConfig E; /**
* @brief Initializes split screen system with single fullscreen pane
/** */
* @brief Initializes split screen system with single fullscreen pane void splitScreenInit(void) {
*/ E.layout.mode = SPLIT_NONE;
void splitScreenInit(void) { E.layout.num_panes = 1;
E.layout.mode = SPLIT_NONE; E.layout.active_pane = 0;
E.layout.num_panes = 1;
E.layout.active_pane = 0; E.layout.panes = malloc(sizeof(EditorPane) * 2);
E.layout.panes = bAlloc(sizeof(EditorPane) * 2); // Initialize single fullscreen pane
E.layout.panes[0].buffer_id = -1; // No buffer for now
// Initialize single fullscreen pane E.layout.panes[0].start_row = 0;
E.layout.panes[0].buffer_id = -1; // No buffer for now E.layout.panes[0].start_col = 0;
E.layout.panes[0].origin_y = 0; E.layout.panes[0].height = E.screenrows - 2; // Leave room for status bar
E.layout.panes[0].origin_x = 0; E.layout.panes[0].width = E.screencols;
E.layout.panes[0].height = E.screenrows - 2; // Leave room for status bar E.layout.panes[0].cursor_x = 0;
E.layout.panes[0].width = E.screencols; E.layout.panes[0].cursor_y = 0;
E.layout.panes[0].cursor_x = 0; E.layout.panes[0].row_offset = 0;
E.layout.panes[0].cursor_y = 0; E.layout.panes[0].col_offset = 0;
E.layout.panes[0].y_offset = 0; E.layout.panes[0].is_active = 1;
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
* @brief Splits screen vertically (left-right) * @param buffer_id_right Buffer ID for right pane
* @param buffer_id_left Buffer ID for left pane * @return 0 on success, -1 on failure
* @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
int splitScreenVertical(int buffer_id_left, int buffer_id_right) { if (bufferFindById(buffer_id_left) == NULL ||
// Verify both buffers exist bufferFindById(buffer_id_right) == NULL) {
if (bufferFindById(buffer_id_left) == NULL || editorSetStatusMessage("Error: invalid buffer IDs");
bufferFindById(buffer_id_right) == NULL) { return -1;
editorSetStatusMessage("Error: invalid buffer IDs"); }
return -1;
} // Reallocate panes array
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
// bReallocate panes array E.layout.mode = SPLIT_VERTICAL;
E.layout.panes = bRealloc(E.layout.panes, sizeof(EditorPane) * 2); E.layout.num_panes = 2;
E.layout.mode = SPLIT_VERTICAL; E.layout.active_pane = 0;
E.layout.num_panes = 2;
E.layout.active_pane = 0; int half_width = E.screencols / 2;
int pane_height = E.screenrows - 2; // Leave room for status bar
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;
// Left pane E.layout.panes[0].start_row = 0;
E.layout.panes[0].buffer_id = buffer_id_left; E.layout.panes[0].start_col = 0;
E.layout.panes[0].origin_y = 0; E.layout.panes[0].height = pane_height;
E.layout.panes[0].origin_x = 0; E.layout.panes[0].width = half_width;
E.layout.panes[0].height = pane_height; E.layout.panes[0].cursor_x = 0;
E.layout.panes[0].width = half_width; E.layout.panes[0].cursor_y = 0;
E.layout.panes[0].cursor_x = 0; E.layout.panes[0].row_offset = 0;
E.layout.panes[0].cursor_y = 0; E.layout.panes[0].col_offset = 0;
E.layout.panes[0].y_offset = 0; E.layout.panes[0].is_active = 1;
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;
// Right pane E.layout.panes[1].start_row = 0;
E.layout.panes[1].buffer_id = buffer_id_right; E.layout.panes[1].start_col = half_width;
E.layout.panes[1].origin_y = 0; E.layout.panes[1].height = pane_height;
E.layout.panes[1].origin_x = half_width; E.layout.panes[1].width = E.screencols - half_width;
E.layout.panes[1].height = pane_height; E.layout.panes[1].cursor_x = 0;
E.layout.panes[1].width = E.screencols - half_width; E.layout.panes[1].cursor_y = 0;
E.layout.panes[1].cursor_x = 0; E.layout.panes[1].row_offset = 0;
E.layout.panes[1].cursor_y = 0; E.layout.panes[1].col_offset = 0;
E.layout.panes[1].y_offset = 0; E.layout.panes[1].is_active = 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;
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
* @brief Splits screen horizontally (top-bottom) * @param buffer_id_bottom Buffer ID for bottom pane
* @param buffer_id_top Buffer ID for top pane * @return 0 on success, -1 on failure
* @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
int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom) { if (bufferFindById(buffer_id_top) == NULL ||
// Verify both buffers exist bufferFindById(buffer_id_bottom) == NULL) {
if (bufferFindById(buffer_id_top) == NULL || editorSetStatusMessage("Error: invalid buffer IDs");
bufferFindById(buffer_id_bottom) == NULL) { return -1;
editorSetStatusMessage("Error: invalid buffer IDs"); }
return -1;
} // Reallocate panes array
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
// bReallocate panes array E.layout.mode = SPLIT_HORIZONTAL;
E.layout.panes = bRealloc(E.layout.panes, sizeof(EditorPane) * 2); E.layout.num_panes = 2;
E.layout.mode = SPLIT_HORIZONTAL; E.layout.active_pane = 0;
E.layout.num_panes = 2;
E.layout.active_pane = 0; int half_height = (E.screenrows - 2) / 2; // Account for status bar
int half_height = (E.screenrows - 2) / 2; // Account for status bar // Top pane
E.layout.panes[0].buffer_id = buffer_id_top;
// Top pane E.layout.panes[0].start_row = 0;
E.layout.panes[0].buffer_id = buffer_id_top; E.layout.panes[0].start_col = 0;
E.layout.panes[0].origin_y = 0; E.layout.panes[0].height = half_height;
E.layout.panes[0].origin_x = 0; E.layout.panes[0].width = E.screencols;
E.layout.panes[0].height = half_height; E.layout.panes[0].cursor_x = 0;
E.layout.panes[0].width = E.screencols; E.layout.panes[0].cursor_y = 0;
E.layout.panes[0].cursor_x = 0; E.layout.panes[0].row_offset = 0;
E.layout.panes[0].cursor_y = 0; E.layout.panes[0].col_offset = 0;
E.layout.panes[0].y_offset = 0; E.layout.panes[0].is_active = 1;
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;
// Bottom pane E.layout.panes[1].start_row = half_height;
E.layout.panes[1].buffer_id = buffer_id_bottom; E.layout.panes[1].start_col = 0;
E.layout.panes[1].origin_y = half_height; E.layout.panes[1].height = E.screenrows - 2 - half_height;
E.layout.panes[1].origin_x = 0; E.layout.panes[1].width = E.screencols;
E.layout.panes[1].height = E.screenrows - 2 - half_height; E.layout.panes[1].cursor_x = 0;
E.layout.panes[1].width = E.screencols; E.layout.panes[1].cursor_y = 0;
E.layout.panes[1].cursor_x = 0; E.layout.panes[1].row_offset = 0;
E.layout.panes[1].cursor_y = 0; E.layout.panes[1].col_offset = 0;
E.layout.panes[1].x_offset = 0; E.layout.panes[1].is_active = 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;
editorSetStatusMessage("Horizontal split: %d / %d", buffer_id_top, buffer_id_bottom); }
return 0;
} /**
* @brief Returns to single buffer fullscreen
/** */
* @brief Returns to single buffer fullscreen void splitScreenUnify(void) {
*/ E.layout.mode = SPLIT_NONE;
void splitScreenUnify(void) { E.layout.num_panes = 1;
E.layout.mode = SPLIT_NONE; E.layout.active_pane = 0;
E.layout.num_panes = 1;
E.layout.active_pane = 0; E.layout.panes[0].start_row = 0;
E.layout.panes[0].start_col = 0;
E.layout.panes[0].origin_x = 0; E.layout.panes[0].height = E.screenrows - 2;
E.layout.panes[0].origin_y = 0; E.layout.panes[0].width = E.screencols;
E.layout.panes[0].height = E.screenrows; E.layout.panes[0].is_active = 1;
E.layout.panes[0].width = E.screencols;
E.layout.panes[0].is_active = 1; editorSetStatusMessage("Unified view");
}
editorSetStatusMessage("Unified view");
} /**
* @brief Switches active pane (focus moves between splits)
/** * @return 0 on success, -1 on failure
* @brief Switches active pane (focus moves between splits) */
* @return 0 on success, -1 on failure int splitScreenSwitchPane(void) {
*/ if (E.layout.num_panes < 2) {
int splitScreenSwitchPane(void) { editorSetStatusMessage("No split to switch");
if (E.layout.num_panes < 2) { return -1;
editorSetStatusMessage("No split to switch"); }
return -1;
} // Deactivate current pane
E.layout.panes[E.layout.active_pane].is_active = 0;
// 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;
// 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;
// Activate new pane
E.layout.panes[E.layout.active_pane].is_active = 1; editorSetStatusMessage("Switched to pane %d (buffer %d)",
E.layout.active_pane,
editorSetStatusMessage("Switched to pane %d (buffer %d)", E.layout.panes[E.layout.active_pane].buffer_id);
E.layout.active_pane, return 0;
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
* @brief Updates the active pane's buffer * @return 0 on success, -1 on failure
* @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) {
int splitScreenSetPaneBuffer(int buffer_id) { editorSetStatusMessage("Error: invalid buffer ID");
if (bufferFindById(buffer_id) == NULL) { return -1;
editorSetStatusMessage("Error: invalid buffer ID"); }
return -1;
} EditorPane *active = &E.layout.panes[E.layout.active_pane];
active->buffer_id = buffer_id;
EditorPane *active = &E.layout.panes[E.layout.active_pane]; active->cursor_x = 0;
active->buffer_id = buffer_id; active->cursor_y = 0;
active->cursor_x = 0; active->row_offset = 0;
active->cursor_y = 0; active->col_offset = 0;
active->origin_x = 0;
active->origin_y = 0; editorSetStatusMessage("Pane %d now showing buffer %d",
E.layout.active_pane, buffer_id);
editorSetStatusMessage("Pane %d now showing buffer %d", return 0;
E.layout.active_pane, buffer_id); }
return 0;
} /**
* @brief Gets current screen layout
/** * @return Pointer to current ScreenLayout
* @brief Gets current screen layout */
* @return Pointer to current ScreenLayout ScreenLayout *splitScreenGetLayout(void) {
*/ return &E.layout;
ScreenLayout *splitScreenGetLayout(void) { }
return &E.layout;
} /**
* @brief Gets active pane
/** * @return Pointer to active EditorPane
* @brief Gets active pane */
* @return Pointer to active EditorPane EditorPane *splitScreenGetActivePane(void) {
*/ if (E.layout.num_panes == 0) return NULL;
EditorPane *splitScreenGetActivePane(void) { return &E.layout.panes[E.layout.active_pane];
if (E.layout.num_panes == 0) return NULL; }
return &E.layout.panes[E.layout.active_pane];
}
+197 -250
View File
@@ -1,250 +1,197 @@
#include "../include/syntax_highlighter.h" #include "../include/syntax_highlighter.h"
#include "../include/data.h" #include "../include/data.h"
#include "../include/utf8.h" #include <stdio.h>
#include <stdio.h> #include <stdlib.h>
#include <stdlib.h> #include <string.h>
#include <string.h>
extern struct editorConfig E;
#include "include/utils.h"
const char *c_keywords[] = {
extern struct editorConfig E; "if", "else", "while", "for", "do", "switch",
"case", "break", "#include", "#define", "continue", "return",
const char *c_keywords[] = { "goto", "struct", "union", "enum", "typedef", "static",
"if", "else", "while", "for", "do", "switch", "extern", "const", "volatile", "sizeof", "auto", "register",
"case", "break", "#include", "#define", "#if", "#endif", "#ifndef", "continue", "return", "inline", "restrict", NULL};
"goto", "struct", "union", "enum", "typedef", "static",
"extern", "const", "volatile", "sizeof", "auto", "register", // C types
"inline", "restrict", NULL}; const char *c_types[] = {"int", "char", "float", "double",
"void", "long", "short", "unsigned",
// C types "signed", "bool", NULL};
const char *c_types[] = {"int", "char", "float", "double",
"void", "long", "short", "unsigned", // Check if character is alphanumeric or underscore
"signed", "bool", NULL}; int is_word_char(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
// Returns the byte length of the UTF-8 character starting at s. (c >= '0' && c <= '9') || c == '_' || c == '#';
// Never returns 0 for a non-NUL byte, so callers won't infinite-loop. }
static int utf8_char_len(const char *s)
{ // Check if string is a keyword
unsigned char c = (unsigned char)*s; int is_keyword(const char *word) {
if (c == 0) return 0; for (int i = 0; c_keywords[i] != NULL; i++) {
if (c < 0x80) return 1; if (strcmp(word, c_keywords[i]) == 0)
if ((c & 0xE0) == 0xC0) return 2; return 1;
if ((c & 0xF0) == 0xE0) return 3; }
if ((c & 0xF8) == 0xF0) return 4; return 0;
return 1; // continuation byte or invalid — advance 1 to avoid infinite loop }
}
// Check if string is a type
// Copy one full UTF-8 character from src+i into dst+pos, advance both indices. int is_type(const char *word) {
static void copy_utf8_char(char *dst, int *dst_pos, const char *src, int *src_pos) for (int i = 0; c_types[i] != NULL; i++) {
{ if (strcmp(word, c_types[i]) == 0)
int len = utf8_char_len(&src[*src_pos]); return 1;
for (int b = 0; b < len; b++) }
dst[(*dst_pos)++] = src[(*src_pos)++]; return 0;
} }
// Check if character is alphanumeric or underscore // Get color code for token type
int is_word_char(const char *s) const char *get_color(SyntaxTokenType type) {
{ switch (type) {
uint32_t cp = utf8Decode(&s); case TOKEN_KEYWORD:
return E.theme.COLOR_KEYWORD;
if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') || case TOKEN_DATATYPE:
(cp >= '0' && cp <= '9') || cp == '_' || cp == '#') return E.theme.COLOR_TYPE;
return 1; case TOKEN_STRING:
return E.theme.COLOR_STRING;
if (cp == 0xFFFD) return 0; case TOKEN_COMMENT:
return E.theme.COLOR_COMMENT;
if (cp >= 0x00C0 && cp <= 0x017F) return 1; case TOKEN_NUMBER:
if (cp >= 0x0370 && cp <= 0x03FF) return 1; return E.theme.COLOR_NUMBER;
if (cp >= 0x0400 && cp <= 0x04FF) return 1; default:
if (cp >= 0x0600 && cp <= 0x06FF) return 1; return E.theme.COLOR_DEFAULT;
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) || int comment_section = 0;
(cp >= 0x30A0 && cp <= 0x30FF)) return 1;
if (cp >= 0xAC00 && cp <= 0xD7A3) return 1; // Highlight a line of C code and return the highlighted string
if ((cp >= 0x0660 && cp <= 0x0669) || // Returns a newly allocated string that must be freed by the caller
(cp >= 0x06F0 && cp <= 0x06F9)) return 1; char *highlight_line(const char *line, int *length) {
char *result = malloc(1024); // Allocate space for result
return 0; int result_pos = 0;
} int i = 0;
// Check if string is a keyword while (line[i] != '\0' && line[i] != '\n') {
int is_keyword(const char *word) { // Skip whitespace
for (int i = 0; c_keywords[i] != NULL; i++) { if (line[i] == ' ' || line[i] == '\t') {
if (strcmp(word, c_keywords[i]) == 0) result[result_pos++] = line[i++];
return 1; continue;
} }
return 0;
} if (comment_section) {
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
// Check if string is a type while (line[i] != '\0' && line[i] != '\n') {
int is_type(const char *word) {
for (int i = 0; c_types[i] != NULL; i++) { if (line[i] == '*' && line[i + 1] == '/') {
if (strcmp(word, c_types[i]) == 0) comment_section = 0;
return 1; }
}
return 0; result[result_pos++] = line[i++];
} }
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
// Get color code for token type continue;
const char *get_color(TokenType type) { }
switch (type) {
case TOKEN_KEYWORD: // Handle line comments
return E.theme.COLOR_KEYWORD; if (line[i] == '/' && line[i + 1] == '/') {
case TOKEN_TYPE: result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
return E.theme.COLOR_TYPE; while (line[i] != '\0' && line[i] != '\n') {
case TOKEN_STRING: result[result_pos++] = line[i++];
return E.theme.COLOR_STRING; }
case TOKEN_COMMENT: result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
return E.theme.COLOR_COMMENT; continue;
case TOKEN_NUMBER: }
return E.theme.COLOR_NUMBER;
default: // Handle block comments
return E.theme.COLOR_DEFAULT; if ((line[i] == '/' && line[i + 1] == '*')) {
} comment_section = 1;
} result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
result[result_pos++] = line[i++];
int comment_section = 0; result[result_pos++] = line[i++];
while (line[i] != '\0') {
// Highlight a line of C code and return the highlighted string if (line[i] == '*' && line[i + 1] == '/') {
// Returns a newly allocated string that must be freed by the caller result[result_pos++] = line[i++];
char *highlight_line(const char *line, int *length) { result[result_pos++] = line[i++];
// Each byte can expand to at most (color_prefix + 4 bytes + color_reset). break;
// Allocate generously based on line length to avoid overflow. }
int line_len = strlen(line); result[result_pos++] = line[i++];
int buf_size = line_len * 32 + 256; }
char *result = bAlloc(buf_size); result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
int result_pos = 0; continue;
int i = 0; }
while (line[i] != '\0' && line[i] != '\n') { // Handle strings
// Skip whitespace — copy full UTF-8 char (whitespace is always ASCII, if (line[i] == '"') {
// but using copy_utf8_char keeps the pattern consistent) result_pos += sprintf(&result[result_pos], "%s\"", E.theme.COLOR_STRING);
if (line[i] == ' ' || line[i] == '\t') { i++;
copy_utf8_char(result, &result_pos, line, &i); while (line[i] != '\0' && line[i] != '"') {
continue; if (line[i] == '\\') {
} result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
if (comment_section) { } else {
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT); result[result_pos++] = line[i++];
while (line[i] != '\0' && line[i] != '\n') { }
if (line[i] == '*' && line[i + 1] == '/') { }
comment_section = 0; if (line[i] == '"')
} result[result_pos++] = line[i++];
copy_utf8_char(result, &result_pos, line, &i); result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
} continue;
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); }
continue;
} // Handle character literals
if (line[i] == '\'') {
// Handle line comments result_pos += sprintf(&result[result_pos], "%s'", E.theme.COLOR_STRING);
if (line[i] == '/' && line[i + 1] == '/') { i++;
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT); while (line[i] != '\0' && line[i] != '\'') {
while (line[i] != '\0' && line[i] != '\n') { if (line[i] == '\\') {
copy_utf8_char(result, &result_pos, line, &i); result[result_pos++] = line[i++];
} result[result_pos++] = line[i++];
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); } else {
continue; result[result_pos++] = line[i++];
} }
}
// Handle block comments if (line[i] == '\'')
if (line[i] == '/' && line[i + 1] == '*') { result[result_pos++] = line[i++];
comment_section = 1; result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT); continue;
result[result_pos++] = line[i++]; }
result[result_pos++] = line[i++];
while (line[i] != '\0') { // Handle numbers
if (line[i] == '*' && line[i + 1] == '/') { if (line[i] >= '0' && line[i] <= '9') {
result[result_pos++] = line[i++]; result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_NUMBER);
result[result_pos++] = line[i++]; while (is_word_char(line[i]) || line[i] == '.') {
comment_section = 0; result[result_pos++] = line[i++];
break; }
} result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
copy_utf8_char(result, &result_pos, line, &i); continue;
} }
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
continue; // Handle identifiers and keywords
} if (is_word_char(line[i])) {
int start = i;
// Handle strings while (is_word_char(line[i]))
if (line[i] == '"') { i++;
result_pos += sprintf(&result[result_pos], "%s\"", E.theme.COLOR_STRING);
i++; char word[256];
while (line[i] != '\0' && line[i] != '"') { strncpy(word, &line[start], i - start);
if (line[i] == '\\') { word[i - start] = '\0';
result[result_pos++] = line[i++];
copy_utf8_char(result, &result_pos, line, &i); SyntaxTokenType type = TOKEN_DEFAULT;
} else { if (is_keyword(word))
copy_utf8_char(result, &result_pos, line, &i); type = TOKEN_KEYWORD;
} else if (is_type(word))
} type = TOKEN_DATATYPE;
if (line[i] == '"')
result[result_pos++] = line[i++]; result_pos += sprintf(&result[result_pos], "%s%s%s", get_color(type),
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET); word, COLOR_RESET);
continue; continue;
} }
// Handle character literals // Handle operators and other characters
if (line[i] == '\'') { result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_DEFAULT);
result_pos += sprintf(&result[result_pos], "%s'", E.theme.COLOR_STRING); result[result_pos++] = line[i++];
i++; result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
while (line[i] != '\0' && line[i] != '\'') { }
if (line[i] == '\\') {
result[result_pos++] = line[i++]; result[result_pos] = '\0';
copy_utf8_char(result, &result_pos, line, &i); *length = result_pos + 1;
} else { return result;
copy_utf8_char(result, &result_pos, line, &i); }
}
}
if (line[i] == '\'')
result[result_pos++] = line[i++];
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
continue;
}
// Handle numbers
if (line[i] >= '0' && line[i] <= '9') {
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_NUMBER);
while (is_word_char(&line[i]) || line[i] == '.') {
copy_utf8_char(result, &result_pos, line, &i);
}
result_pos += sprintf(&result[result_pos], "%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 += sprintf(&result[result_pos], "%s%s%s", get_color(type),
word, COLOR_RESET);
continue;
}
// Handle operators and other characters (including non-ASCII multi-byte)
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_DEFAULT);
copy_utf8_char(result, &result_pos, line, &i);
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
}
result[result_pos] = '\0';
*length = result_pos + 1;
return result;
}
+556
View File
@@ -0,0 +1,556 @@
/* termiWin.c
*
* Copyright (C) 2017 Christian Visintin - christian.visintin1997@gmail.com
*
* This file is part of "termiWin: a termios porting for Windows"
*
* termiWin is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* termiWin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with termiWin. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "../include/termiWin.h"
#ifdef _WIN32
#ifdef __cplusplus > 201711L
#define TERMIWIN_MAYBE_UNUSED [[maybe_unused]]
#else
#ifdef __GNUC__
#define TERMIWIN_MAYBE_UNUSED __attribute__((unused))
#else
#define TERMIWIN_MAYBE_UNUSED
#endif
#endif
#include <fcntl.h>
#include <stdlib.h>
typedef struct COM {
HANDLE hComm;
int fd; //Actually it's completely useless
char port[128];
} COM;
DCB SerialParams = { 0 }; //Initializing DCB structure
struct COM com;
COMMTIMEOUTS timeouts = { 0 }; //Initializing COMMTIMEOUTS structure
//LOCAL functions
//nbyte 0->7
int getByte(tcflag_t flag, int nbyte, int nibble) {
int byte;
if (nibble == 1)
byte = (flag >> (8 * (nbyte)) & 0x0f);
else
byte = (flag >> (8 * (nbyte)) & 0xf0);
return byte;
}
//INPUT FUNCTIONS
enum{
i_IXOFF = 0x01,
i_IXON = 0x02,
i_IXOFF_IXON = 0x03,
i_PARMRK = 0x04,
i_PARMRK_IXOFF = 0x05,
i_PARMRK_IXON = 0x06,
i_PARMRK_IXON_IXOFF = 0x07
};
int getIXOptions(tcflag_t flag) {
int byte = getByte(flag, 1, 1);
return byte;
}
//LOCALOPT FUNCTIONS
enum{
l_NOECHO = 0x00,
l_ECHO = 0x01,
l_ECHO_ECHOE = 0x03,
l_ECHO_ECHOK = 0x05,
l_ECHO_ECHONL = 0x09,
l_ECHO_ECHOE_ECHOK = 0x07,
l_ECHO_ECHOE_ECHONL = 0x0b,
l_ECHO_ECHOE_ECHOK_ECHONL = 0x0f,
l_ECHO_ECHOK_ECHONL = 0x0d,
l_ECHOE = 0x02,
l_ECHOE_ECHOK = 0x06,
l_ECHOE_ECHONL = 0x0a,
l_ECHOE_ECHOK_ECHONL = 0x0e,
l_ECHOK = 0x04,
l_ECHOK_ECHONL = 0x0c,
l_ECHONL = 0x08
};
int getEchoOptions(tcflag_t flag) {
int byte = getByte(flag, 1, 1);
return byte;
}
enum{
l_ICANON = 0x10,
l_ICANON_ISIG = 0x50,
l_ICANON_IEXTEN = 0x30,
l_ICANON_NOFLSH = 0x90,
l_ICANON_ISIG_IEXTEN = 0x70,
l_ICANON_ISIG_NOFLSH = 0xd0,
l_ICANON_IEXTEN_NOFLSH = 0xb0,
l_ICANON_ISIG_IEXTEN_NOFLSH = 0xf0,
l_ISIG = 0x40,
l_ISIG_IEXTEN = 0x60,
l_ISIG_NOFLSH = 0xc0,
l_ISIG_IEXTEN_NOFLSH = 0xe0,
l_IEXTEN = 0x20,
l_IEXTEN_NOFLSH = 0xa0,
l_NOFLSH = 0x80,
};
int getLocalOptions(tcflag_t flag) {
int byte = getByte(flag, 1, 0);
return byte;
}
enum{
l_TOSTOP = 0x01
};
int getToStop(tcflag_t flag) {
int byte = getByte(flag, 1, 1);
return byte;
}
//CONTROLOPT FUNCTIONS
int getCharSet(tcflag_t flag) {
//FLAG IS MADE UP OF 8 BYTES, A FLAG IS MADE UP OF A NIBBLE -> 4 BITS, WE NEED TO EXTRACT THE SECOND NIBBLE (1st) FROM THE FIFTH BYTE (6th).
int byte = getByte(flag, 1, 1);
switch (byte) {
case 0X0:
return CS5;
break;
case 0X4:
return CS6;
break;
case 0X8:
return CS7;
break;
case 0Xc:
return CS8;
break;
default:
return CS8;
break;
}
}
enum{
c_ALL_ENABLED = 0xd0,
c_PAREVEN_CSTOPB = 0x50,
c_PAREVEN_NOCSTOPB = 0x40,
c_PARODD_NOCSTOPB = 0xc0,
c_NOPARENB_CSTOPB = 0x10,
c_ALL_DISABLED = 0x00,
};
int getControlOptions(tcflag_t flag) {
int byte = getByte(flag, 1, 0);
return byte;
}
//LIBFUNCTIONS
int tcgetattr(int fd, struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {
if (fd != com.fd) return -1;
int TERMIWIN_MAYBE_UNUSED ret = 0;
ret = GetCommState(com.hComm, &SerialParams);
return 0;
}
int tcsetattr(int fd, int TERMIWIN_MAYBE_UNUSED optional_actions, const struct termios* termios_p) {
if (fd != com.fd) return -1;
int ret = 0;
//Store flags into local variables
tcflag_t iflag = termios_p->c_iflag;
tcflag_t lflag = termios_p->c_lflag;
tcflag_t cflag = termios_p->c_cflag;
tcflag_t TERMIWIN_MAYBE_UNUSED oflag = termios_p->c_oflag;
//iflag
int IX = getIXOptions(iflag);
if ((IX == i_IXOFF_IXON) || (IX == i_PARMRK_IXON_IXOFF)) {
SerialParams.fOutX = TRUE;
SerialParams.fInX = TRUE;
SerialParams.fTXContinueOnXoff = TRUE;
}
//lflag
int TERMIWIN_MAYBE_UNUSED EchoOpt = getEchoOptions(lflag);
int TERMIWIN_MAYBE_UNUSED l_opt = getLocalOptions(lflag);
int TERMIWIN_MAYBE_UNUSED tostop = getToStop(lflag);
//Missing parameters...
//cflags
int CharSet = getCharSet(cflag);
int c_opt = getControlOptions(cflag);
switch (CharSet) {
case CS5:
SerialParams.ByteSize = 5;
break;
case CS6:
SerialParams.ByteSize = 6;
break;
case CS7:
SerialParams.ByteSize = 7;
break;
case CS8:
SerialParams.ByteSize = 8;
break;
}
switch (c_opt) {
case c_ALL_ENABLED:
SerialParams.Parity = ODDPARITY;
SerialParams.StopBits = TWOSTOPBITS;
break;
case c_ALL_DISABLED:
SerialParams.Parity = NOPARITY;
SerialParams.StopBits = ONESTOPBIT;
break;
case c_PAREVEN_CSTOPB:
SerialParams.Parity = EVENPARITY;
SerialParams.StopBits = TWOSTOPBITS;
break;
case c_PAREVEN_NOCSTOPB:
SerialParams.Parity = EVENPARITY;
SerialParams.StopBits = ONESTOPBIT;
break;
case c_PARODD_NOCSTOPB:
SerialParams.Parity = ODDPARITY;
SerialParams.StopBits = ONESTOPBIT;
break;
case c_NOPARENB_CSTOPB:
SerialParams.Parity = NOPARITY;
SerialParams.StopBits = TWOSTOPBITS;
break;
}
//aflags
/*
int OP;
if(oflag == OPOST)
else ...
*/
//Missing parameters...
//special characters
if (termios_p->c_cc[VEOF] != 0) SerialParams.EofChar = (char)termios_p->c_cc[VEOF];
if (termios_p->c_cc[VINTR] != 0) SerialParams.EvtChar = (char)termios_p->c_cc[VINTR];
if (termios_p->c_cc[VMIN] == 1) { //Blocking
timeouts.ReadIntervalTimeout = 0; // in milliseconds
timeouts.ReadTotalTimeoutConstant = 0; // in milliseconds
timeouts.ReadTotalTimeoutMultiplier = 0; // in milliseconds
timeouts.WriteTotalTimeoutConstant = 0; // in milliseconds
timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds
} else { //Non blocking
timeouts.ReadIntervalTimeout = termios_p->c_cc[VTIME] * 100; // in milliseconds
timeouts.ReadTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100; // in milliseconds
timeouts.ReadTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100; // in milliseconds
timeouts.WriteTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100; // in milliseconds
timeouts.WriteTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100; // in milliseconds
}
SetCommTimeouts(com.hComm, &timeouts);
//EOF
ret = SetCommState(com.hComm, &SerialParams);
if (ret != 0)
return 0;
else
return -1;
}
int tcsendbreak(int fd, int TERMIWIN_MAYBE_UNUSED duration) {
if (fd != com.fd) return -1;
int ret = 0;
ret = TransmitCommChar(com.hComm, '\x00');
if (ret != 0)
return 0;
else
return -1;
}
int tcdrain(int fd) {
if (fd != com.fd) return -1;
return FlushFileBuffers(com.hComm);
}
int tcflush(int fd, int queue_selector) {
if (fd != com.fd) return -1;
int rc = 0;
switch (queue_selector) {
case TCIFLUSH:
rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
break;
case TCOFLUSH:
rc = PurgeComm(com.hComm, PURGE_TXCLEAR);
break;
case TCIOFLUSH:
rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
rc *= PurgeComm(com.hComm, PURGE_TXCLEAR);
break;
default:
rc = 0;
break;
}
if (rc != 0)
return 0;
else
return -1;
}
int tcflow(int fd, int action) {
if (fd != com.fd) return -1;
int rc = 0;
switch (action) {
case TCOOFF:
rc = PurgeComm(com.hComm, PURGE_TXABORT);
break;
case TCOON:
rc = ClearCommBreak(com.hComm);
break;
case TCIOFF:
rc = PurgeComm(com.hComm, PURGE_RXABORT);
break;
case TCION:
rc = ClearCommBreak(com.hComm);
break;
default:
rc = 0;
break;
}
if (rc != 0)
return 0;
else
return -1;
}
void cfmakeraw(struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {
SerialParams.ByteSize = 8;
SerialParams.StopBits = ONESTOPBIT;
SerialParams.Parity = NOPARITY;
}
speed_t cfgetispeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {
return SerialParams.BaudRate;
}
speed_t cfgetospeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {
return SerialParams.BaudRate;
}
int cfsetispeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {
SerialParams.BaudRate = speed;
return 0;
}
int cfsetospeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {
SerialParams.BaudRate = speed;
return 0;
}
int cfsetspeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {
SerialParams.BaudRate = speed;
return 0;
}
ssize_t read_serial(int fd, void* buffer, size_t count) {
if (fd != com.fd) return -1;
int rc = 0;
int ret;
ret = ReadFile(com.hComm, buffer, count, &rc, NULL);
if (ret == 0)
return -1;
else
return rc;
}
ssize_t write_serial(int fd, const void* buffer, size_t count) {
if (fd != com.fd) return -1;
int rc = 0;
int ret;
ret = WriteFile(com.hComm, buffer, count, &rc, NULL);
if (ret == 0)
return -1;
else
return rc;
}
int open_serial(const char* portname, int opt) {
if (strlen(portname) < 4) return -1;
// Set to zero
memset(com.port, 0x00, 128);
//COMxx
size_t portSize = 0;
if (strlen(portname) > 4) {
portSize = sizeof(char) * strlen("\\\\.\\COM10") + 1;
#ifdef _MSC_VER
strncat_s(com.port, portSize, "\\\\.\\", strlen("\\\\.\\"));
#else
strncat(com.port, "\\\\.\\", strlen("\\\\.\\"));
#endif
}
//COMx
else {
portSize = sizeof(char) * 5;
}
#ifdef _MSC_VER
strncat_s(com.port, portSize, portname, 4);
#else
strncat(com.port, portname, 4);
#endif
com.port[portSize] = 0x00;
switch (opt) {
case O_RDWR:
com.hComm = CreateFile(com.port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
break;
case O_RDONLY:
com.hComm = CreateFile(com.port, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
break;
case O_WRONLY:
com.hComm = CreateFile(com.port, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
break;
}
if (com.hComm == INVALID_HANDLE_VALUE) {
return -1;
}
com.fd = atoi(portname + 3); // COMx and COMxx
SerialParams.DCBlength = sizeof(SerialParams);
return com.fd;
}
int close_serial(int TERMIWIN_MAYBE_UNUSED fd) {
int ret = CloseHandle(com.hComm);
if (ret != 0)
return 0;
else
return -1;
}
int select_serial(int TERMIWIN_MAYBE_UNUSED nfds, fd_set* readfds, fd_set* TERMIWIN_MAYBE_UNUSED writefds, fd_set* TERMIWIN_MAYBE_UNUSED exceptfds, struct timeval* TERMIWIN_MAYBE_UNUSED timeout) {
SetCommMask(com.hComm, EV_RXCHAR);
DWORD dwEventMask;
if (WaitCommEvent(com.hComm, &dwEventMask, NULL) == 0) {
return -1; // Return -1 if failed
}
if (dwEventMask == EV_RXCHAR) {
return com.fd;
} else {
if (readfds) {
// Clear file descriptor if event is not RXCHAR
FD_CLR(com.fd, readfds);
}
}
// NOTE: write event not detectable!
// NOTE: no timeout
return 0; // No data
}
//Returns hComm from the COM structure
HANDLE getHandle() {
return com.hComm;
}
#endif
+163 -238
View File
@@ -1,238 +1,163 @@
#include "../include/terminal.h" #include "../include/terminal.h"
#include "../include/data.h"
#include <ctype.h>
#include <stdio.h>
#include "../include/data.h"
#include "../include/define.h" #ifdef _WIN32
#include <windows.h>
#include <stdarg.h> #endif
#include <stdio.h>
#include <string.h> void die(const char *s) {
#include <unistd.h> write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
#include "include/utf8.h" lisp_shutdown(E.ctx);
perror(s);
void die(const char *s) { exit(1);
write(STDOUT_FILENO, "\x1b[2J", 4); }
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
lisp_shutdown(E.ctx); void disableRawMode() {
perror(s); if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) {
exit(1); die("tcsetattr");
} }
}
void disableRawMode() {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) { void enableRawMode() {
die("tcsetattr"); if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) {
} die("tcgetattr");
} }
void enableRawMode() { struct termios raw = E.orig_termios;
if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) { raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
die("tcgetattr"); raw.c_oflag &= ~(OPOST);
} raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
struct termios raw = E.orig_termios; raw.c_cc[VMIN] = 0;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); raw.c_cc[VTIME] = 1;
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8); if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); die("tcgetattr");
raw.c_cc[VMIN] = 0; }
raw.c_cc[VTIME] = 1; }
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { int editorReadKey() {
die("tcgetattr"); int nread;
} char c;
} char seq[3];
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
#include <ctype.h> /* isprint */ if (nread == -1 && errno != EAGAIN) {
die("read");
char *keyToString(int key) { }
static char key_str[32]; }
if (key == '\r') { if (c == '\x1b') {
strcpy(key_str, "ENTER"); if (read(STDIN_FILENO, &seq[0], 1) != 1 ||
} else if (key == 0x09) { read(STDIN_FILENO, &seq[1], 1) != 1) {
strcpy(key_str, "TAB"); return '\x1b';
} else if (key >= 1 && key <= 26) { }
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1); if (seq[0] == '[') {
} else { if (seq[1] >= '0' && seq[1] <= '9') {
switch (key) { if (read(STDIN_FILENO, &seq[2], 1) != 1) {
case ARROW_UP: return '\x1b';
strcpy(key_str, "ARROW-UP"); }
break; if (seq[2] == '~') {
case ARROW_DOWN: switch (seq[1]) {
strcpy(key_str, "ARROW-DOWN"); case '1':
break; return BEG_LINE;
case ARROW_LEFT: case '3':
strcpy(key_str, "ARROW-LEFT"); return DEL_KEY;
break; case '4':
case ARROW_RIGHT: return END_LINE;
strcpy(key_str, "ARROW-RIGHT"); case '5':
break; return PAGE_UP;
case PAGE_UP: case '6':
strcpy(key_str, "PAGE-UP"); return PAGE_DOWN;
break; case '7':
case PAGE_DOWN: return BEG_LINE;
strcpy(key_str, "PAGE-DOWN"); case '8':
break; return END_LINE;
case DEL_KEY: }
strcpy(key_str, "DEL"); }
break; } else {
case BACKSPACE:
strcpy(key_str, "BACKSPACE"); switch (seq[1]) {
break; case 'A':
case BEG_LINE: return ARROW_UP;
strcpy(key_str, "HOME"); case 'B':
break; return ARROW_DOWN;
case END_LINE: case 'C':
strcpy(key_str, "END"); return ARROW_RIGHT;
break; case 'D':
case '\x1b': return ARROW_LEFT;
strcpy(key_str, "ESCAPE"); case 'H':
break; return BEG_LINE;
default: case 'F':
if (key > 127) { return END_LINE;
/* UTF-8 code point — re-encode into the buffer */ }
char buf[5] = {0}; }
int n = utf8Encode((uint32_t)key, buf); } else if (seq[0] == 'O') {
snprintf(key_str, sizeof(key_str), "%.*s", n, buf); switch (seq[1]) {
} else if (isprint(key)) { case 'H':
snprintf(key_str, sizeof(key_str), "%c", key); return BEG_LINE;
} else { case 'F':
snprintf(key_str, sizeof(key_str), "KEY-%d", key); return END_LINE;
} }
} }
} return '\x1b';
return key_str; } else {
} return c;
}
int editorReadKey() { }
char c;
/* read first byte — may be start of UTF-8 or escape */ int getCursorPosition(int *rows, int *cols) {
while (read(STDIN_FILENO, &c, 1) != 1) char buf[32];
; unsigned int i = 0;
appDebug("f : %hhu %ld\r\n", c, 0x200);
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) {
if (c == '\x1b') { return -1;
char seq[6]; }
/* try to read escape sequence */
if (read(STDIN_FILENO, &seq[0], 1) != 1) while (i < sizeof(buf) - 1) {
return '\x1b'; if (read(STDIN_FILENO, &buf[i], 1) != 1) {
if (read(STDIN_FILENO, &seq[1], 1) != 1) break;
return '\x1b'; }
appDebug("f2 : %s\r\n", seq); if (buf[i] == 'R') {
break;
if (seq[0] == '[') { }
if (seq[1] >= '0' && seq[1] <= '9') { ++i;
if (read(STDIN_FILENO, &seq[2], 1) != 1) }
return '\x1b'; buf[i] = '\0';
if (seq[2] == '~') {
switch (seq[1]) { if (buf[0] != '\x1b' || buf[1] != '[') {
case '1': return -1;
return BEG_LINE; }
case '3': if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) {
return DEL_KEY; return -1;
case '4': }
return END_LINE;
case '5': return 0;
return PAGE_UP; }
case '6':
return PAGE_DOWN; int getWindowSize(int *rows, int *cols) {
case '7': #ifdef _WIN32
return BEG_LINE; // Windows implementation
case '8': CONSOLE_SCREEN_BUFFER_INFO csbi;
return END_LINE; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
} *cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
} *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
} else { return 0;
switch (seq[1]) { #else
case 'A': // Unix implementation
return ARROW_UP; struct winsize ws;
case 'B':
return ARROW_DOWN; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
case 'C': if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) {
return ARROW_RIGHT; return -1;
case 'D': }
return ARROW_LEFT; return getCursorPosition(rows, cols);
case 'H': } else {
return BEG_LINE; *cols = ws.ws_col;
case 'F': *rows = ws.ws_row;
return END_LINE; return 0;
} }
} #endif
} }
return '\x1b';
}
/* multi-byte UTF-8: read remaining bytes */
int seqlen = utf8Seqlen((unsigned char)c);
if (seqlen > 1) {
/* pack into a pseudo-codepoint just to pass bytes through;
we handle encoding/decoding at the row level */
char buf[4] = {c, 0, 0, 0};
for (int i = 1; i < seqlen; i++)
if (read(STDIN_FILENO, &buf[i], 1) != 1)
break;
/* decode and return as uint32, but we need int — use high range */
const char *p = buf;
uint32_t cp = utf8Decode(&p);
return (int)cp; /* caller re-encodes when inserting */
}
return (unsigned char)c;
}
int getCursorPosition(int *rows, int *cols) {
char buf[32];
unsigned int i = 0;
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);
} else {
*cols = ws.ws_col;
*rows = ws.ws_row;
return 0;
}
}
void appDebug(const char *fmt, ...) {
#ifdef APP_DEBUG
va_list ap;
char message[256];
va_start(ap, fmt);
vsnprintf(message, 256, fmt, ap);
va_end(ap);
fprintf(stderr, "%s\n", message);
#endif
}
-147
View File
@@ -1,147 +0,0 @@
/**
* @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;
}
-38
View File
@@ -1,38 +0,0 @@
//
// Created by Giorgio on 28/05/2026.
//
#include "../include/utils.h"
#include <stdlib.h>
int beluga_alloc_counter = 0;
void * bAlloc(size_t size)
{
void * result = malloc(size);
if (!result)
return NULL;
beluga_alloc_counter++;
return result;
}
void * bRealloc(void * ptr, size_t size)
{
void * result = realloc(ptr, size);
if (!result)
return NULL;
beluga_alloc_counter++;
return result;
}
void * bFree(void * ptr)
{
if (ptr)
{
free(ptr);
beluga_alloc_counter--;
}
return NULL;
}
-3
View File
@@ -1,3 +0,0 @@
int main() {
printf("hello");
}