54 Commits

Author SHA1 Message Date
Arthur Barraux fa7f8d39d8 update splash screen
Build project / build (push) Successful in 23s
2026-01-14 20:38:33 +01:00
Arthur Barraux 23036cf3d3 warranty 0 memoey leak
Build project / build (push) Successful in 58s
2026-01-14 20:24:37 +01:00
Arthur Barraux d0afd4f304 patch file_completion crash
Build project / build (push) Successful in 1m0s
2026-01-13 15:20:18 +01:00
Arthur Barraux de4fa78221 Merge remote-tracking branch 'gitea/main'
Build project / build (push) Successful in 48s
2026-01-11 19:41:57 +01:00
Arthur Barraux fc93832130 Syntax highlighting and comment 2026-01-11 19:41:30 +01:00
arthur 39e07902f4 Update README.md
Build project / build (push) Successful in 24s
2026-01-09 15:03:08 +01:00
Arthur Barraux 815114923d Add prefix feature
Build project / build (push) Successful in 56s
2026-01-09 14:54:07 +01:00
arthur 410f382592 Add go to end of word function
Build project / build (push) Successful in 1m22s
2025-12-24 14:50:14 +01:00
arthur 80c0bf73e0 Update README.md
Build project / build (push) Successful in 1m1s
2025-12-08 00:25:05 +01:00
arthur d4ee0d86c2 Update .gitea/workflows/build.yml
Build project / build (push) Failing after 2m18s
2025-12-04 01:34:33 +01:00
arthur c06c820dfb Patch path complete and read char lisp function
Build project / build (push) Has been cancelled
2025-11-07 16:23:56 +01:00
arthur 5588b0a8d7 add path autocomplete
Build project / build (push) Has been cancelled
2025-11-05 15:49:01 +01:00
arthur 419e924650 Add search function
Build project / build (push) Has been cancelled
2025-11-03 16:45:23 +01:00
arthur 6a201b3ebc delete line macro
Build project / build (push) Has been cancelled
2025-11-03 16:05:25 +01:00
arthur 1d253e51ef Update README.md
Build project / build (push) Failing after 3m27s
2025-11-01 13:22:10 +01:00
Arthur Barraux 42f82e2e0d add packages
Build project / build (push) Has been cancelled
2025-11-01 13:03:07 +01:00
arthur fa32f4b177 Update src/init.c
Build project / build (push) Has been cancelled
2025-10-31 09:10:57 +01:00
arthur 65f997e964 Update install.sh
Build project / build (push) Has been cancelled
2025-10-31 09:09:20 +01:00
Arthur Barraux 7eaf6913cb Merge remote-tracking branch 'gitea/main'
Build project / build (push) Has been cancelled
2025-10-30 18:18:12 +01:00
Arthur Barraux 8f7dcf3534 Adding installer script 2025-10-30 18:17:19 +01:00
arthur d8c6b9ace3 Update README.md
Build project / build (push) Has been cancelled
2025-10-28 09:16:36 +01:00
arthur d0173d7308 Update README.md
Build project / build (push) Has been cancelled
2025-10-28 09:02:29 +01:00
arthur 40fc234eeb Update .gitea/workflows/build.yml
Build project / build (push) Failing after 39s
2025-10-18 15:35:57 +02:00
Arthur Barraux 756deba83e remove .cache repo for cleaning
Build project / build (push) Failing after 33s
2025-10-18 15:12:06 +02:00
Arthur Barraux 85e8067e41 clean up the repo
Build project / build (push) Failing after 1m22s
2025-10-18 15:06:23 +02:00
Arthur Barraux 09d78f48e9 Merge remote-tracking branch 'gitea/lisp'
Build project / build (push) Successful in 1m3s
2025-10-06 11:37:39 +02:00
Arthur Barraux ce94e9fb87 permission files
Build and Deploy Docs / build (push) Failing after 43s
2025-10-06 11:28:49 +02:00
Arthur Barraux 29a92ce904 Merge Lisp branch
Build and Deploy Docs / build (push) Failing after 1m24s
2025-10-05 22:05:21 +02:00
Arthur Barraux 9348ae668a Add open file key-bind
Build and Deploy Docs / build (push) Failing after 40s
2025-10-05 21:44:58 +02:00
Arthur Barraux 9157b94398 Adding usefull keybinds
Build and Deploy Docs / build (push) Successful in 50s
2025-10-02 14:59:28 +02:00
Arthur Barraux 3b6c60a49e functional lisp config files
Build and Deploy Docs / build (push) Successful in 47s
2025-10-02 11:26:18 +02:00
arthur 53d6572c8c Revert doxygen deployement
Build and Deploy Docs / build (push) Successful in 37s
2025-09-25 12:10:44 +02:00
arthur d083948dfe Update .gitea/workflows/build.yml
Build and Deploy Docs / build (push) Failing after 30s
2025-09-25 11:35:41 +02:00
arthur 09ef5c0f3b Update .gitea/workflows/build.yml
Build and Deploy Docs / build (push) Failing after 1m26s
2025-09-25 11:25:52 +02:00
arthur e4691669b8 Update .gitea/workflows/build.yml
Build and Deploy Docs / build (push) Failing after 1m54s
2025-09-25 11:22:11 +02:00
arthur 27ae0a684f Update .gitea/workflows/build.yml
Build and Deploy Docs / build (push) Failing after 1m33s
2025-09-25 11:06:53 +02:00
Arthur Barraux 3505084527 second try
Meson Build and Deploy / build (push) Failing after 1m23s
2025-09-25 11:00:53 +02:00
Arthur Barraux 02d7f27ec3 Merge remote-tracking branch 'gitea/lisp' into lisp
Meson Build and Deploy / build (push) Failing after 53s
2025-09-25 10:48:09 +02:00
Arthur Barraux 7dded62db9 add doxygen deployement 2025-09-25 10:47:07 +02:00
arthur 6cd79b5c76 downgrade version of runner upload
Meson Build and Deploy / build (push) Successful in 25s
2025-09-24 11:14:22 +02:00
arthur d9aab06c1c Downgrade upload version to 3
CMake Build and Deploy / build (push) Failing after 2m34s
2025-09-24 11:07:22 +02:00
Arthur Barraux 8844d2f064 meson bulid changes
Meson Build and Deploy / build (push) Failing after 29s
2025-09-24 11:04:16 +02:00
Arthur Barraux d8fc7d2d67 adding lisp-interpreter
Meson Build and Deploy / build (push) Failing after 29s
2025-09-24 10:58:09 +02:00
Arthur Barraux ab482df604 get submodules
Meson Build and Deploy / build (push) Failing after 1m31s
2025-09-24 10:48:45 +02:00
Arthur Barraux be31e83fb9 update build.yml
Meson Build and Deploy / build (push) Failing after 1m21s
2025-09-24 10:45:11 +02:00
Arthur Barraux 54db6321ad add CI/CD config
Meson Build and Deploy / build (push) Failing after 1m54s
2025-09-24 10:29:31 +02:00
arthur ec0eba849a Update .gitea/workflows/build.yml
CMake Build and Deploy / build (push) Failing after 1m58s
2025-09-24 08:33:08 +02:00
arthur 2ca64fae41 add build.yml
CMake Build and Deploy / build (push) Has been cancelled
2025-09-24 08:32:22 +02:00
Arthur Barraux 8ce621dfde Made Editor Config global 2025-09-19 14:31:12 +02:00
Arthur Barraux 91e247d1de adding meson build 2025-09-19 11:02:44 +02:00
Arthur Barraux 72178adebb submodeule update 2025-06-12 16:56:10 +02:00
Arthur Barraux 7a98f89531 readme update 2025-06-12 16:53:21 +02:00
Arthur Barraux 3e562c1071 add blisp submodule 2025-06-02 10:36:22 +02:00
Arthur Barraux bff3f84ecd Configurable settings with blisp 2025-06-02 10:31:31 +02:00
32 changed files with 7188 additions and 373 deletions
+35
View File
@@ -0,0 +1,35 @@
name: Build project
on:
push:
branches: ["main"]
jobs:
build:
runs-on: giorgio-runner
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
tokens: ${{ secrets.GITEA_TOKEN }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y meson ninja-build gcc
- name: Configure Meson
run: meson setup build
- name: Build project
run: meson compile -C build
- name: Run tests
run: ctest --test-dir build || true
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: beluga
path: build/beluga
+2 -2
View File
@@ -1,3 +1,3 @@
tmp/* build/*
bin/*
doc/* doc/*
beluga.wiki/*
-32
View File
@@ -1,32 +0,0 @@
##
# TEST
#
# @file
# @version 0.1
BELUGA_OUTPUT=bin
BUILD_FLAGS=-Wall -Wextra -pedantic
build: main.c src/*
if [ ! -d $(BELUGA_OUTPUT) ]; then mkdir $(BELUGA_OUTPUT); fi
$(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* $(BUILD_FLAGS)
DEBUG_FLAGS=-Wall -Wextra -pedantic -Werror -fsanitize=address -g
debug: main.c src/*
if [ ! -d $(BELUGA_OUTPUT) ]; then mkdir $(BELUGA_OUTPUT); fi
$(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* $(DEBUG_FLAGS)
doc:
if [ ! -d doc/ ]; then mkdir doc; fi
doxygen
clean:
rm -r $(BELUGA_OUTPUT)
rm -rf doc/
rm -rf tmp/
all: build doc
# end
+16 -10
View File
@@ -1,26 +1,32 @@
# Beluga # Beluga
Beluga is a project of CLI text editor that will fit perfectly with your azerty keyboard. Beluga is a project of CLI text editor that uses lisp as configuration language.
## Requirements ## Requirements
You will only need **make** or **gcc** to compile the editor. You will only need **meson** and a **C compiler** to compile the editor.
## Installation ## Installation
### From source
Here is the installation line for development version:
Here is the installation line : ```git clone https://git.giorgio-nas.fr/arthur/beluga.git && cd beluga && meson setup build && meson compile -C build```
```git clone --branch V1.0 --single-branch https://github.com/le-cocotier/beluga.git ~/.beluga && cd ~/.beluga && make build``` The executable file will be `build/beluga`. Feel free to add it to your path.
The executable file will be in `bin/beluga`. Feel free to add it to your path. ### From installation script ( prefered )
You can either run `make all` if you're interested by the doxygen documentation. 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 :
```beluga path_to_my_file``` ```./build/beluga path_to_my_file```
The only keybinds that you will need will be : Here is some few command that you will need :
- Ctrl-Q : leave the editor
- Ctrl-S : Save a file | keybind | command |
|---------------|------------------|
| Ctrl-x Ctrl-c | leave the editor |
| Ctrl-x Ctrl-s | Save a file |
| Ctrl-x f | open file |
+43
View File
@@ -0,0 +1,43 @@
**#%#*****###%**
*##+--------------------=##*
#*=----------------------------=*#*
#*------------------------------------*#
%+----------------------------------------=#*
#+---------------------------------------------##
*#-------------------------------------------------=##
*#----------------------------------------------------:-##
#----------------------------------------------------------##
#=--------------------------------------------------------------##
+--------------------------+@#-%*-----------------------------------#*
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.2.1
*-=-------------------------#@@*---------------------------------------=%
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
*%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave
%=--------------------------------------------------------------------------#* CTRL-x CTRL-s save
%-----------------------------------------------------------------------------** CTRL-x f open-file
*+--=---===----=---------------=*-----------------------------------------------**
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
*%**=-----------==== ==---------------------------------=+#+-----------------=#
*%=----------------------------------------------------------=#*---------------#
##=----------------------------------=------------------------+%=------------+#
#%+---------------------------------=*------------------------+%------------#
*#%*=-------------=-----------------#-------------------------#+----------#
**#%#*******+=======-------------#=------------------------#----------#
#===#*=======------------------#*----=-----------=--=##*-----------#
-====##=------------------------*%+------------=*#+=====----------#
--=====+#*=----------------------=-=+*#####***+=======-----------=*
%------=====*%*=-------------------------========-----------------+*
*-=--------====%%###+=--------------------------=-----------------#
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
%=-------#* #%*=-----------------+#
*%+--=## ##=-----------------=#*
** #+----=-------------------#*
%+----------------------------#*
*%-------------==----------------+#
##--------------==------------------#
*#--------------===%-----------------=%
##---------------=-##*-----------------+#
*#---------------==#+=#%-----------------%
*%---------------+# %*---------------#*
*#------------=+#* #%*=-----------#*
#****##****** *#%%##+=----%
+84
View File
@@ -0,0 +1,84 @@
;; MACROS
(define TAB-LENGTH 4)
(define QUIT-TIMES 1)
;; PACKAGES
;; First git clone it
;; (add-package "smart_delimiters")
;; FUNCTIONS
(define editor-delete-next-char (lambda () (
(move-cursor "right")
(editor-delete-previous-char)
)
)
)
(define (char-between ch lo hi)
(if (char>=? ch lo)
(char<=? ch hi)
#f))
(define (alphanumericp ch)
(if ch
(if (char-between ch #\a #\z)
#t
(if (char-between ch #\A #\Z)
#t
(if (char-between ch #\0 #\9)
#t
#f)))
#f))
(define (word-char-p ch)
(if (alphanumericp ch)
#t
#f))
(define editor-move-to-end-of-word (lambda () (
(if (word-char-p (editor-read-char))
((move-cursor "right")
(editor-move-to-end-of-word))
))
))
(define enter-and-tab
(lambda ()
(editor-insert-new-line)
(let ((is-in (move-cursor "up")))
(do ((ch (editor-read-char) (editor-read-char)))
((and (not (char=? ch #\space)) is-in) #f)
(move-cursor "down")
(editor-insert-char " ")
(set! is-in (move-cursor "up")))
(move-cursor "down"))))
(add-prefix "user")
(map-key "CTRL-x" '(editor-set-prefix "user") "no-prefix")
(map-key "CTRL-g" '(editor-set-prefix "no-prefix") "user")
(map-key "CTRL-c" editor-quit "user")
(map-key "CTRL-s" editor-save "user")
(map-key "ARROW-UP" '(move-cursor "up") "no-prefix")
(map-key "ARROW-DOWN" '(move-cursor "down") "no-prefix")
(map-key "ARROW-RIGHT" '(move-cursor "right") "no-prefix")
(map-key "ARROW-LEFT" '(move-cursor "left") "no-prefix")
(map-key "ENTER" enter-and-tab "no-prefix")
(map-key "CTRL-a" move-cursor-beg-line "no-prefix")
(map-key "CTRL-e" move-cursor-end-line "no-prefix")
(map-key "BACKSPACE" editor-delete-previous-char "no-prefix")
(map-key "DEL" editor-delete-next-char "no-prefix")
(map-key "PAGE-UP" move-cursor-page-up "no-prefix")
(map-key "PAGE-DOWN" move-cursor-page-down "no-prefix")
(map-key "f" editor-open-file "user")
(map-key "TAB" editor-insert-tab "no-prefix")
(map-key "CTRL-k" editor-del-row "no-prefix")
(map-key "CTRL-s" editor-find "no-prefix")
(map-key "CTRL-r" editor-move-to-end-of-word "no-prefix")
+48
View File
@@ -0,0 +1,48 @@
#ifndef BUILTINS_H_
#define BUILTINS_H_
#include "lisp.h"
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx);
Lisp mapKey(Lisp args, LispError *e, LispContext ctx);
void registerBuiltin(char * key_sequence, LispCFunc f);
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx);
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx);
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx);
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx);
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx);
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx);
Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx);
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx);
void free_structs(void);
#endif
+48
View File
@@ -1,9 +1,12 @@
#ifndef DATA_H_ #ifndef DATA_H_
#define DATA_H_ #define DATA_H_
#include <stdio.h>
#include <termios.h> #include <termios.h>
#include <time.h> #include <time.h>
#include "lisp.h"
/** /**
* \struct erow * \struct erow
* \brief Store one editor row * \brief Store one editor row
@@ -17,6 +20,29 @@ typedef struct erow {
char *render; /**< The actual line we will print */ char *render; /**< The actual line we will print */
} erow; } erow;
enum editorStatus_e {
IDLE,
READ_ONLY,
READ_AND_WRITE,
};
struct const_t {
int TAB_LENGTH;
int QUIT_TIMES;
};
struct prefix_t {
char prefix_name[64];
int prefix_id;
};
struct keyBind_t {
char *key_sequence;
int prefix_id;
Lisp command;
};
/** /**
* \struct editorConfig * \struct editorConfig
* \brief Containing our editor state. * \brief Containing our editor state.
@@ -32,9 +58,28 @@ struct editorConfig {
erow *row; /**< Store all the rows printed */ erow *row; /**< Store all the rows printed */
int dirty; int dirty;
char *filename; char *filename;
enum editorStatus_e state;
int prefix_state;
char status_msg[80]; char status_msg[80];
time_t status_msg_time; time_t status_msg_time;
struct termios orig_termios; /**< Terminal communication interface */ 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;
}; };
/** /**
@@ -47,4 +92,7 @@ struct abuf {
int len; /**< Length of the text */ int len; /**< Length of the text */
}; };
extern struct editorConfig E;
#endif #endif
+1 -3
View File
@@ -24,8 +24,6 @@ enum editorKey {
#define ABUF_INIT {NULL, 0} #define ABUF_INIT {NULL, 0}
#define BELUGA_VERSION "1.0" #define BELUGA_VERSION "1.1"
#define TAB_LENGTH 4
#define QUIT_TIMES 1
#endif // DEFINE_H_ #endif // DEFINE_H_
+3 -3
View File
@@ -2,10 +2,10 @@
#define EDITOR_OP_H_ #define EDITOR_OP_H_
#include "data.h" #include "data.h"
void editorInsertChar(struct editorConfig *E, int c); void editorInsertChar(int c);
void editorInsertNewLine(struct editorConfig *E); void editorInsertNewLine();
void editorDelChar(struct editorConfig *E); void editorDelChar();
#endif // EDITOR_OP_H_ #endif // EDITOR_OP_H_
+8 -3
View File
@@ -8,10 +8,15 @@
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
char *editorRowsToString(struct editorConfig *E, int *buffer_len); char *editorRowsToString(int *buffer_len);
void editorOpen(struct editorConfig *E, char *filename);
void editorSave(struct editorConfig *E); void editorCloseFile(void);
void editorOpen(char *filename);
void editorSave();
void editorFind();
#endif // FILE_IO_H_ #endif // FILE_IO_H_
+4 -1
View File
@@ -1,6 +1,7 @@
#ifndef INIT_H_ #ifndef INIT_H_
#define INIT_H_ #define INIT_H_
#include "builtins.h"
#include "data.h" #include "data.h"
#include "terminal.h" #include "terminal.h"
#include <stdio.h> #include <stdio.h>
@@ -10,6 +11,8 @@
* \brief Job's function is to initialize all the fields of editorConfig. * \brief Job's function is to initialize all the fields of editorConfig.
* */ * */
void initEditor(struct editorConfig *E); void initBuiltins();
void initEditor();
#endif // INIT_H_ #endif // INIT_H_
+8 -3
View File
@@ -5,6 +5,7 @@
#include "define.h" #include "define.h"
#include "output.h" #include "output.h"
#include "terminal.h" #include "terminal.h"
#include "builtins.h"
#include <unistd.h> #include <unistd.h>
// KEYS keycode // KEYS keycode
@@ -19,15 +20,19 @@
// 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(struct editorConfig *E, char *prompt); char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
void editorMoveCursor(struct editorConfig *E, int key); char *key_to_string(int key);
int editorMoveCursor(int key);
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(struct editorConfig *E); void editorProcessKeypress();
#endif // INPUT_H_ #endif // INPUT_H_
+3218
View File
File diff suppressed because it is too large Load Diff
+2177
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -13,16 +13,16 @@
* \brief Draws left rows of the editor. * \brief Draws left rows of the editor.
*/ */
void editorDrawRows(struct editorConfig *E, struct abuf *ab); void editorDrawRows(struct abuf *ab);
void editorRefreshScreen(struct editorConfig *E); void editorRefreshScreen();
void editorScroll(struct editorConfig *E); void editorScroll();
void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab); void editorDrawStatusBar(struct abuf *ab);
void editorDrawMessageBar(struct editorConfig *E, struct abuf *ab); void editorDrawMessageBar(struct abuf *ab);
void editorSetStatusMessage(struct editorConfig *E, const char *fmt, ...); void editorSetStatusMessage(const char *fmt, ...);
#endif // OUTPUT_H_ #endif // OUTPUT_H_
+7 -6
View File
@@ -10,19 +10,20 @@
int editorRowCxToRx(erow *row, int cursor_x); int editorRowCxToRx(erow *row, int cursor_x);
int editorRowRxToCx(erow *row, int rx);
void editorUpdateRow(erow *row); void editorUpdateRow(erow *row);
void editorInsertRow(struct editorConfig *E, int at, char *s, size_t len); void editorInsertRow(int at, char *s, size_t len);
void editorFreeRow(erow *row); void editorFreeRow(erow *row);
void editorDelRow(struct editorConfig *E, int at); void editorDelRow(int at);
void editorRowInsertChar(struct editorConfig *E, erow *row, int at, int c); void editorRowInsertChar(erow *row, int at, int c);
void editorRowAppendString(struct editorConfig *E, erow *row, char *s, void editorRowAppendString(erow *row, char *s, size_t len);
size_t len);
void editorRowDelchar(struct editorConfig *E, erow *row, int at); void editorRowDelchar(erow *row, int at);
#endif // ROW_OP_H_ #endif // ROW_OP_H_
+20
View File
@@ -0,0 +1,20 @@
#define COLOR_RESET "\033[0m"
#define COLOR_KEYWORD "\033[1;35m" // Bold magenta
#define COLOR_TYPE "\033[1;34m" // Bold blue
#define COLOR_STRING "\033[1;32m" // Bold green
#define COLOR_COMMENT "\033[0;36m" // Cyan
#define COLOR_NUMBER "\033[1;33m" // Bold yellow
#define COLOR_DEFAULT "\033[0;37m" // White
// Token types
typedef enum {
TOKEN_KEYWORD,
TOKEN_TYPE,
TOKEN_STRING,
TOKEN_COMMENT,
TOKEN_NUMBER,
TOKEN_OPERATOR,
TOKEN_DEFAULT
} TokenType;
char *highlight_line(const char * line, int *length);
+2 -2
View File
@@ -21,9 +21,9 @@
void die(const char *s); void die(const char *s);
void disableRawMode(struct editorConfig *E); void disableRawMode();
void enableRawMode(struct editorConfig *E); void enableRawMode();
int editorReadKey(); int editorReadKey();
Executable
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
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
# Check dependencies
if ! command -v "meson" &>/dev/null; then
echo "❌ Error: meson not found. Please install it first."
exit 1
fi
# Create config files
echo "Create config files ..."
mkdir -pv ~/.beluga/
cp -rv ./assets/ ~/.beluga/
mkdir -pv ~/.beluga/packages/
mkdir -pv ~/.beluga/config/
read -p "Do you want to replace your config file or keep it (init.lisp.bak) / (init.lisp.new) ? (Y/n)" confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
mv ~/.beluga/config/init.lisp ~/.beluga/config/init.lisp.bak
cp -rv ./config/init.lisp ~/.beluga/config/
else
cp -rv ./config/init.lisp ~/.beluga/config/init.lisp.new
fi
# Compile the project
echo "Start compilation ..."
meson setup build/
meson compile -C build/
# Add to path
echo "Adding beluga to the path"
sudo cp -f ./build/beluga /usr/local/bin/
echo "Installation finish"
echo "Check ~/.beluga/config/init.lisp for customization"
+23 -8
View File
@@ -5,6 +5,11 @@
* interactions. \version 0.1 \date 21 septembre 2024 * interactions. \version 0.1 \date 21 septembre 2024
*/ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define _DEFAULT_SOURCE #define _DEFAULT_SOURCE
#define _BSD_SOURCE #define _BSD_SOURCE
#define _GNU_SOURCE #define _GNU_SOURCE
@@ -16,21 +21,31 @@
#include "include/output.h" #include "include/output.h"
#include "include/terminal.h" #include "include/terminal.h"
struct editorConfig E;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
struct editorConfig E; char * splash_screen = (char *) calloc(256, sizeof(char));
enableRawMode(&E); enableRawMode();
initEditor(&E); initEditor();
if (argc >= 2) { if (argc >= 2) {
editorOpen(&E, argv[1]); E.state = READ_AND_WRITE;
editorOpen(argv[1]);
} else {
strcat(splash_screen, getenv("HOME"));
strcat(splash_screen, "/.beluga/assets/beluga.txt");
fprintf(stderr, "%s\n", splash_screen);
editorOpen(splash_screen);
} }
free(splash_screen);
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-q = quit");
editorSetStatusMessage(&E, "HELP: Ctrl-S = save | Ctrl-Q = quit");
while (1) { while (1) {
editorRefreshScreen(&E); editorRefreshScreen();
editorProcessKeypress(&E); editorProcessKeypress();
} }
return 0; return 0;
} }
+30
View File
@@ -0,0 +1,30 @@
project('beluga', 'c',
version : '1.1',
default_options : [
'c_std=none',
]
)
cc = meson.get_compiler('c')
m = cc.find_library('m', required: false)
# Source files
src_files = files(
'main.c',
'src/append_buffer.c',
'src/editor_op.c',
'src/syntax_highlighter.c',
'src/file_io.c',
'src/init.c',
'src/input.c',
'src/output.c',
'src/row_op.c',
'src/terminal.c',
'src/builtins.c',
)
# Executable
executable('beluga',
src_files,
dependencies: [m]
)
+2
View File
@@ -1,5 +1,7 @@
#include "../include/append_buffer.h" #include "../include/append_buffer.h"
extern struct editorConfig E;
void abAppend(struct abuf *ab, const char *s, int len) { void abAppend(struct abuf *ab, const char *s, int len) {
char *new = realloc(ab->b, ab->len + len); char *new = realloc(ab->b, ab->len + len);
+459
View File
@@ -0,0 +1,459 @@
/**
* @file builtins.c
* @brief Built-in Lisp functions for editor operations
* @details Provides Lisp bindings for core editor functionality including
* cursor movement, file operations, keybinding management, and text manipulation
*/
#include "../include/builtins.h"
#include "../include/data.h"
#include "../include/define.h"
#include "../include/editor_op.h"
#include "../include/file_io.h"
#include "../include/input.h"
#include "../include/row_op.h"
#include "include/output.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* @brief Finds a prefix configuration by name
* @details Searches the prefix array for a prefix matching the given name.
* Returns the first prefix (default) if no match is found.
* @param prefix_name Name of the prefix to search for (max 64 chars)
* @return Matching prefix_t structure, or E.prefix[0] if not found
* @note Updates global editor state E
*/
struct prefix_t find_prefix(const char prefix_name[64]) {
int i = E.number_of_prefix + 1;
while (i--) {
if (!strcmp(prefix_name, E.prefix[i].prefix_name)) {
return E.prefix[i];
}
}
return E.prefix[0];
}
/**
* @brief Lisp function to bind a command to a key sequence
* @details Registers a keybinding with an associated Lisp command and optional
* prefix modifier. Dynamically extends the keybind array.
* @param args Lisp list of 3 arguments: (key-sequence command prefix-name)
* - key_sequence (string): The key or key combination to bind
* - command (Lisp): The Lisp command/function to execute
* - prefix_name (string): Prefix modifier name
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E (key_binds array)
*/
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
/*
* 3 arguments keybind command prefix
*/
const char *key_sequence = lisp_string(lisp_car(args));
args = lisp_cdr(args);
// second argument
Lisp func = lisp_car(args);
E.key_binds = (struct keyBind_t *)realloc(
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
E.key_binds[E.number_of_keybinds - 1].key_sequence =
(char *)malloc(50 * sizeof(char));
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
E.key_binds[E.number_of_keybinds - 1].command = func;
// Third argument
args = lisp_cdr(args);
const char *prefix_name = lisp_string(lisp_car(args));
struct prefix_t prefix = find_prefix(prefix_name);
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
return lisp_null();
}
/**
* @brief Lisp function to move cursor in a specified direction
* @details Moves the editor cursor up, down, left, or right based on direction string.
* @param args Lisp list with one argument: direction string
* - "u": up, "d": down, "r": right, "l": left
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return Lisp boolean indicating whether movement was valid
* @note Updates global editor state E
* @see editorMoveCursor()
*/
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
const char *direction = lisp_string(lisp_car(args));
int is_in = 0;
switch (direction[0]) {
case 'u':
is_in = editorMoveCursor(ARROW_UP);
break;
case 'd':
is_in = editorMoveCursor(ARROW_DOWN);
break;
case 'r':
is_in = editorMoveCursor(ARROW_RIGHT);
break;
case 'l':
is_in = editorMoveCursor(ARROW_LEFT);
break;
}
fprintf(stderr, "move lisp %d\n", is_in);
return lisp_make_bool(is_in);
}
/**
* @brief Frees all dynamically allocated editor structures
* @details Releases memory for prefix table, keybinds, filename, and all rows.
* Called during shutdown to prevent memory leaks.
* @note Updates global editor state E
*/
void free_structs(void) {
int i;
free(E.prefix);
for (i = 0; i < E.number_of_keybinds; ++i) {
free(E.key_binds[i].key_sequence);
}
free(E.key_binds);
free(E.filename);
for (i = 0; i < E.numrows; ++i) {
free(E.row[i].render);
free(E.row[i].chars);
}
free(E.row);
free(E.init_file_path);
fclose(E.fd_init_file);
}
/**
* @brief Lisp function to quit the editor
* @details Closes editor with unsaved changes protection. Prompts user to confirm
* quit after multiple attempts if file is dirty. Cleans up resources and restores terminal.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null() (never returns on successful exit)
* @note Calls exit(0) to terminate program
* @note Updates quit_times_buffer counter
* @see free_structs()
*/
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
if (E.dirty && E.quit_times_buffer > 0) {
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
"another time to quit.");
--E.quit_times_buffer;
return lisp_null();
}
free_structs();
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode();
lisp_shutdown(E.ctx);
exit(0);
return lisp_null();
}
/**
* @brief Lisp function to save the current file
* @details Wrapper around editorSave() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @see editorSave()
*/
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
editorSave();
return lisp_null();
}
/**
* @brief Lisp function to insert a new line at cursor
* @details Wrapper around editorInsertNewLine() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @see editorInsertNewLine()
*/
Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
editorInsertNewLine();
return lisp_null();
}
/**
* @brief Lisp function to insert a tab (spaces) at cursor
* @details Inserts TAB_LENGTH spaces at the current cursor position.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Uses E.constantes.TAB_LENGTH for indentation width
*/
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
for (int i = 0; i<E.constantes.TAB_LENGTH; ++i) {
editorInsertChar(' ');
}
return lisp_null();
}
/**
* @brief Lisp function to move cursor to beginning of line
* @details Moves cursor to column 0 of the current line.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
*/
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
E.cursor_x = 0;
return lisp_null();
}
/**
* @brief Lisp function to move cursor to end of line
* @details Moves cursor to the end of the current line content.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
*/
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
if (E.cursor_y < E.numrows) {
E.cursor_x = E.row[E.cursor_y].size;
}
return lisp_null();
}
/**
* @brief Lisp function to delete character before cursor
* @details Wrapper around editorDelChar() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @see editorDelChar()
*/
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
editorDelChar();
return lisp_null();
}
/**
* @brief Lisp function to move cursor up by one screen
* @details Scrolls up one full screen height, moving cursor to top of visible area.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
* @see editorMoveCursor()
*/
Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
E.cursor_y = E.row_offset;
int times = E.screenrows;
while (--times) {
editorMoveCursor(ARROW_UP);
}
return lisp_null();
}
/**
* @brief Lisp function to move cursor down by one screen
* @details Scrolls down one full screen height, moving cursor to bottom of visible area.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
* @see editorMoveCursor()
*/
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
E.cursor_y = E.row_offset + E.screenrows - 1;
if (E.cursor_y > E.numrows) {
E.cursor_y = E.numrows;
}
int times = E.screenrows;
while (--times) {
editorMoveCursor(ARROW_DOWN);
}
return lisp_null();
}
/**
* @brief Lisp function to open a file
* @details Prompts user for filename and opens the file for editing.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
* @see editorOpen()
* @see editorPrompt()
*/
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename){
editorOpen(filename);
}
free(filename);
return lisp_null();
}
/**
* @brief Lisp function to insert a character
* @details Extracts a character from Lisp string argument and inserts it at cursor.
* @param args Lisp list with one string argument
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Uses first character of the string argument
*/
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
char c = lisp_string(lisp_car(args))[0];
editorInsertChar(c);
return lisp_null();
}
/**
* @brief Lisp function to load and execute a package
* @details Loads a Lisp package from the user's packages directory
* (~/.beluga/packages/<package_name>/init.lisp) and evaluates it.
* @param args Lisp list with one argument: package name (string)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Package files must be valid Lisp code
* @note Package directory defaults to ~/.beluga/packages/
*/
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
const char *package_name = lisp_string(lisp_car(args));
fprintf(stderr, "%s\n", package_name);
char *package_dir = (char *)calloc(256, sizeof(char));
FILE *fd_package = NULL;
strcat(package_dir, getenv("HOME"));
strcat(package_dir, "/.beluga/packages/");
strcat(package_dir, package_name);
strcat(package_dir, "/init.lisp");
fprintf(stderr, "%s\n", package_dir);
fd_package = fopen(package_dir, "r");
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx);
fclose(fd_package);
free(package_dir);
return lisp_null();
}
/**
* @brief Lisp function to delete the current row
* @details Removes the line at the current cursor position.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
* @see editorDelRow()
*/
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) {
editorDelRow(E.cursor_y);
return lisp_null();
}
/**
* @brief Lisp function to search for text
* @details Wrapper around editorFind() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @see editorFind()
*/
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) {
fprintf(stderr, "LispFind\n");
editorFind();
return lisp_null();
}
/**
* @brief Lisp function to read character at cursor
* @details Returns the character at the current cursor position as a Lisp character.
* Returns 'a' if at end of line.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return Lisp character object representing the character at cursor
*/
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
Lisp returned_char;
if (E.row[E.cursor_y].render[E.cursor_x] == 0) {
returned_char = lisp_make_char('a');
} else {
returned_char = lisp_make_char(E.row[E.cursor_y].render[E.cursor_x]);
}
return returned_char;
}
/**
* @brief Lisp function to set the current prefix mode
* @details Changes the editor's prefix state to a named prefix, affecting which
* keybindings are active. Updates status message to show active prefix.
* @param args Lisp list with one argument: prefix name (string)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E (prefix_state)
* @see find_prefix()
*/
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
/*
* Set the prefix state of editor to the prefix in argument
*/
const char *prefix_name = lisp_string(lisp_car(args));
struct prefix_t prefix = find_prefix(prefix_name);
E.prefix_state = prefix.prefix_id;
editorSetStatusMessage("prefix %s", prefix.prefix_name);
fprintf(stderr, "%s set\n", prefix_name);
return lisp_null();
}
/**
* @brief Lisp function to define a new prefix modifier
* @details Registers a named prefix modifier that can be used with keybindings
* to create context-aware command sequences (e.g., Ctrl-X as a prefix).
* @param args Lisp list with one argument: prefix name (string, max 64 chars)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E (prefix array)
*/
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx) {
E.prefix = (struct prefix_t *)realloc(E.prefix, (++(E.number_of_prefix) + 1) *
sizeof(struct prefix_t));
E.prefix[E.number_of_prefix].prefix_id = E.number_of_prefix;
strncpy(E.prefix[E.number_of_prefix].prefix_name, lisp_string(lisp_car(args)),
64);
return lisp_null();
}
+34 -26
View File
@@ -1,44 +1,52 @@
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/row_op.h" #include "../include/row_op.h"
void editorInsertChar(struct editorConfig *E, int c) {
if (E->cursor_y == E->numrows) { extern struct editorConfig E;
editorInsertRow(E, E->numrows, "", 0);
} void editorInsertChar(int c) {
editorRowInsertChar(E, &E->row[E->cursor_y], E->cursor_x, c); if (E.cursor_y == E.numrows) {
E->cursor_x++; editorInsertRow(E.numrows, "", 0);
}
editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, c);
E.cursor_x++;
} }
void editorInsertNewLine(struct editorConfig *E) { void editorInsertNewLine() {
/*
* Add new line and place the cursor at the beginning of it
*/
fprintf(stderr, "Inserting new line\n");
erow *row; erow *row;
if (!E->cursor_x) { if (!E.cursor_x) {
editorInsertRow(E, E->cursor_y, "", 0); editorInsertRow(E.cursor_y, "", 0);
} else { } else {
row = &E->row[E->cursor_y]; row = &E.row[E.cursor_y];
editorInsertRow(E, E->cursor_y + 1, &row->chars[E->cursor_x], editorInsertRow(E.cursor_y + 1, &row->chars[E.cursor_x],
row->size - E->cursor_x); row->size - E.cursor_x);
row = &E->row[E->cursor_y]; row = &E.row[E.cursor_y];
row->size = E->cursor_x; row->size = E.cursor_x;
row->chars[row->size] = '\0'; row->chars[row->size] = '\0';
editorUpdateRow(row); editorUpdateRow(row);
} }
++E->cursor_y; ++E.cursor_y;
E->cursor_x = 0; E.cursor_x = 0;
fprintf(stderr, "Insert new line done\n");
} }
void editorDelChar(struct editorConfig *E) { void editorDelChar() {
erow *row; erow *row;
if (E->cursor_y == E->numrows || !(E->cursor_x || E->cursor_y)) { if (E.cursor_y == E.numrows || !(E.cursor_x || E.cursor_y)) {
return; return;
} }
row = &E->row[E->cursor_y]; row = &E.row[E.cursor_y];
if (E->cursor_x > 0) { if (E.cursor_x > 0) {
editorRowDelchar(E, row, E->cursor_x - 1); editorRowDelchar(row, E.cursor_x - 1);
--E->cursor_x; --E.cursor_x;
} else { } else {
E->cursor_x = E->row[E->cursor_y - 1].size; E.cursor_x = E.row[E.cursor_y - 1].size;
editorRowAppendString(E, &E->row[E->cursor_y - 1], row->chars, row->size); editorRowAppendString(&E.row[E.cursor_y - 1], row->chars, row->size);
editorDelRow(E, E->cursor_y); editorDelRow(E.cursor_y);
--E->cursor_y; --E.cursor_y;
} }
} }
+170 -65
View File
@@ -1,3 +1,11 @@
/**
* @file file_io.c
* @brief File I/O operations module for the Beluga text editor
* @details Handles file loading, saving, searching, and buffer management.
* Provides functionality for opening/closing files, persisting changes to disk,
* and searching for text patterns within the document.
*/
#include "../include/file_io.h" #include "../include/file_io.h"
#include "../include/input.h" #include "../include/input.h"
#include "../include/output.h" #include "../include/output.h"
@@ -8,79 +16,176 @@
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
char *editorRowsToString(struct editorConfig *E, int *buffer_len) { extern char *strdup(const char *);
int tot_len = 0; extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
int j; FILE *restrict stream);
char *buf; extern int ftruncate(int fd, off_t length);
char *p; extern struct editorConfig E;
for (j = 0; j < E->numrows; ++j) { /**
tot_len += E->row[j].size + 1; * @brief Converts all editor rows to a single string buffer
} * @details Concatenates all row content into a single allocated buffer with
*buffer_len = tot_len; * newlines between rows. Useful for file saving and buffer operations.
buf = malloc(tot_len); * @param buffer_len Pointer to integer where total buffer length will be stored
p = buf; * @return Pointer to dynamically allocated buffer containing all row data.
for (j = 0; j < E->numrows; ++j) { * Rows are separated by newline characters.
memcpy(p, E->row[j].chars, E->row[j].size); * @note Caller is responsible for freeing the returned buffer
p += E->row[j].size; */
*p = '\n'; char *editorRowsToString(int *buffer_len) {
p++; int tot_len = 0;
} int j;
char *buf;
char *p;
return buf; for (j = 0; j < E.numrows; ++j) {
tot_len += E.row[j].size + 1;
}
*buffer_len = tot_len;
buf = malloc(tot_len);
p = buf;
for (j = 0; j < E.numrows; ++j) {
memcpy(p, E.row[j].chars, E.row[j].size);
p += E.row[j].size;
*p = '\n';
p++;
}
return buf;
} }
void editorOpen(struct editorConfig *E, char *filename) { /**
FILE *fp; * @brief Closes the current file and resets editor state
* @details Clears all rows, resets cursor position, scroll offsets, and file metadata.
free(E->filename); * Does not prompt to save unsaved changes.
E->filename = strdup(filename); * @note Updates global editor state E
*/
fp = fopen(filename, "r"); void editorCloseFile(void) {
if (!fp) E.cursor_x = 0;
die("fopen"); E.cursor_y = 0;
E.rx = 0;
char *line = NULL; E.row_offset = 0;
size_t line_cap = 0; E.col_offset = 0;
ssize_t line_len; for (int i = 0; i < E.numrows; ++i) {
free(E.row[i].chars);
while ((line_len = getline(&line, &line_cap, fp)) != -1) { free(E.row[i].render);
while (line_len > 0 &&
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
--line_len;
} }
editorInsertRow(E, E->numrows, line, line_len); E.numrows = 0;
} free(E.row);
free(line); E.row = NULL;
fclose(fp); E.dirty = 0;
E->dirty = 0; free(E.filename);
E.filename = NULL;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
} }
void editorSave(struct editorConfig *E) { /**
int len; * @brief Opens a file for editing
char *buf; * @details Loads file content into editor rows, one line per row. If another file
int fd; * is already open, it is closed first (without saving). File is opened in a+
if (E->filename == NULL) { * (read/append) mode to allow both reading and modification.
E->filename = editorPrompt(E, "Save as: %s (ESC to cancel)"); * @param filename Path to the file to open (relative or absolute)
if (E->filename == NULL) { * @note Updates global editor state E
editorSetStatusMessage(E, "Save aborted"); * @note Calls die() on file open failure
return; * @note Newline characters are stripped from loaded lines
* @see editorInsertRow()
*/
void editorOpen(char *filename) {
FILE *fp;
// Test if a file is already open
if (E.filename != NULL) {
editorCloseFile();
E.state = READ_AND_WRITE;
} }
}
buf = editorRowsToString(E, &len); E.filename = strdup(filename);
fd = open(E->filename, O_RDWR | O_CREAT, 0644);
if (fd != -1) { fp = fopen(filename, "a+");
if (ftruncate(fd, len) != -1) { if (!fp)
if (write(fd, buf, len) == len) { die("fopen");
char *line = NULL;
size_t line_cap = 0;
ssize_t line_len;
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
while (line_len > 0 &&
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
--line_len;
}
editorInsertRow(E.numrows, line, line_len);
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() {
int len;
char *buf;
int fd;
if (E.filename == NULL) {
E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (E.filename == NULL) {
editorSetStatusMessage("Save aborted");
return;
}
}
buf = editorRowsToString(&len);
fd = open(E.filename, O_RDWR | O_CREAT, 0644);
if (fd != -1) {
if (ftruncate(fd, len) != -1) {
if (write(fd, buf, len) == len) {
close(fd);
free(buf);
E.dirty = 0;
editorSetStatusMessage("%d bytes written to disk", len);
return;
}
}
close(fd); close(fd);
free(buf);
E->dirty = 0;
editorSetStatusMessage(E, "%d bytes written to disk", len);
return;
}
} }
close(fd); free(buf);
} editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
free(buf); }
editorSetStatusMessage(E, "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 editorFind() {
fprintf(stderr, "searching\n");
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
if (query == NULL) return;
int i;
for (i = E.cursor_y + 1; i < E.numrows; i++) {
erow *row = &E.row[i];
char *match = strstr(row->render, query);
if (match) {
E.cursor_y = i;
E.cursor_x = editorRowRxToCx(row, match - row->render);
E.row_offset = E.numrows;
break;
}
}
free(query);
} }
+94 -14
View File
@@ -1,19 +1,99 @@
#include "../include/init.h" #include "../include/init.h"
#include "../include/builtins.h"
#include "../include/data.h"
#include "../include/terminal.h"
#include <stdio.h>
#include <stdlib.h>
void initEditor(struct editorConfig *E) { #define LISP_IMPLEMENTATION
E->cursor_x = 0; #include "../include/lisp.h"
E->cursor_y = 0; #include "../include/lisp_lib.h"
E->rx = 0;
E->row_offset = 0; struct editorConfig;
E->col_offset = 0;
E->numrows = 0; void registerBuiltin(char *key_sequence, LispCFunc f) {
E->row = NULL; lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
E->dirty = 0; lisp_make_func(f), E.ctx);
E->filename = NULL; }
E->status_msg[0] = '\0';
E->status_msg_time = 0; void initBuiltins() {
if (getWindowSize(&E->screenrows, &E->screencols) == -1) { // move cursor
registerBuiltin("move-cursor", moveCursor);
registerBuiltin("map-key", mapKey);
registerBuiltin("editor-quit", editorQuit);
registerBuiltin("editor-save", l_editorSave);
registerBuiltin("editor-insert-new-line", l_editorInsertNewLine);
registerBuiltin("move-cursor-beg-line", moveCursorBeginLine);
registerBuiltin("move-cursor-end-line", moveCursorEndLine);
registerBuiltin("editor-delete-previous-char", deletePreviousChar);
registerBuiltin("move-cursor-page-up", editorMoveCursorPageUp);
registerBuiltin("move-cursor-page-down", editorMoveCursorPageDown);
registerBuiltin("editor-open-file", editorOpenFile);
registerBuiltin("editor-insert-char", editorPrintC);
registerBuiltin("add-package", addPackage);
registerBuiltin("editor-del-row", editorDelRow_L);
registerBuiltin("editor-find", editorFind_L);
registerBuiltin("editor-read-char", editorReadChar_L);
registerBuiltin("add-prefix", editorPrefix);
registerBuiltin("editor-set-prefix", editorSetPrefix);
registerBuiltin("editor-insert-tab", l_editorInserTab);
}
void initEditor() {
E.init_file_path = (char *)calloc(256, sizeof(char));
E.cursor_x = 0;
E.cursor_y = 0;
E.rx = 0;
E.row_offset = 0;
E.col_offset = 0;
E.numrows = 0;
E.row = NULL;
E.dirty = 0;
E.filename = NULL;
E.state = READ_ONLY;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
die("getWindowSize"); die("getWindowSize");
} }
E->screenrows -= 2; E.screenrows -= 2;
E.number_of_keybinds = 0;
E.number_of_prefix = 0;
// General prefix is 0 (no prefix)
E.prefix = (struct prefix_t *)malloc(sizeof(struct prefix_t));
E.prefix[0].prefix_id = 0;
strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
E.prefix_state = 0;
strcat(E.init_file_path, getenv("HOME"));
strcat(E.init_file_path, "/.beluga/config/init.lisp");
// printf("%s\n", init_file_path);
E.fd_init_file = fopen(E.init_file_path, "r");
E.ctx = lisp_init();
E.env = lisp_env(E.ctx);
lisp_lib_load(E.ctx);
// Init builtins lisp functions
initBuiltins();
// Read config file
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
if (E.ctx_error != LISP_ERROR_NONE) {
die("init failed");
}
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
// 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;
} }
+268 -104
View File
@@ -1,42 +1,150 @@
#include "../include/input.h" #include "../include/input.h"
#include "../include/define.h"
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/file_io.h"
#include "../include/output.h" #include "../include/output.h"
#include <ctype.h> #include <ctype.h>
#include <dirent.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <string.h>
#include <sys/stat.h>
extern struct editorConfig E;
/** /**
* \fn char * editorPrompt(struct editorConfig *E, char *prompt) * @file input.c
* \brief Return user input in a prompt when enter is hit. */ * @brief Input handling module for the Beluga text editor
* @details Manages user input processing, key bindings, cursor movement, and file path completion
*/
char *editorPrompt(struct editorConfig *E, char *prompt) { /**
* @brief Returns the first file completion match for the given path
* @details Searches the directory containing the given path prefix and returns
* the first file or directory entry that matches the filename prefix.
* Appends a trailing slash for directory entries.
* @param path The file path to complete (can be relative or absolute)
* @return Pointer to the completed file path (dynamically allocated), or NULL if:
* - path ends with '/' (already a directory)
* - no matching entries found
* - directory cannot be opened
* @note Caller is responsible for freeing the returned string
* @note Uses static buffer internally; may return stale pointers across calls
*/
const char *file_completion(const char *path) {
DIR *dir;
struct dirent *entry;
char directory[128];
char predict[128];
const char *last_slash;
int predict_len = 0;
size_t dir_len;
// path is a directory
if (path[strlen(path) - 1] == '/') {
return path;
}
// Find dir name
last_slash = strrchr(path, '/');
if (last_slash) {
dir_len = last_slash - path + 1; // length of dir_path
strncpy(directory, path, dir_len);
predict_len = strlen(path) - dir_len;
strncpy(predict, last_slash + 1, predict_len);
directory[dir_len] = '\0';
predict[predict_len] = '\0';
fprintf(stderr, "%s %s\n", directory, predict);
} else {
return NULL;
}
dir = opendir(directory);
if (!dir)
return NULL;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, predict, predict_len) == 0) {
static char full_path[512];
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)) {
strcat(full_path, "/"); // add slash for directories
}
closedir(dir);
return strdup(full_path);
}
}
// Cleanup when no more entries
closedir(dir);
dir = NULL;
free(entry);
return NULL;
}
/**
* @brief Displays an interactive prompt and returns user input
* @details Allows the user to enter text in a prompt with optional path completion
* via Tab key. Supports backspace, delete, and escape key handling. Dynamically
* allocates memory for the input buffer.
* @param prompt The prompt message format string (printf-style)
* @param placeHolder Initial text to display in the input buffer
* @param bPathMode If non-zero, enables Tab key file path completion
* @return Pointer to the user-entered text (dynamically allocated), or NULL if:
* - User pressed ESC to cancel
* - Input buffer is empty when Enter is pressed
* @note Caller is responsible for freeing the returned string
* @note Uses editorReadKey() for input and editorRefreshScreen() for display
*/
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
size_t buf_size = 128; size_t buf_size = 128;
char *buf = malloc(buf_size); char *buf = malloc(buf_size);
size_t buf_len = 0; size_t buf_len = 0;
int c = 0; int c = 0;
buf[0] = '\0'; buf[0] = '\0';
strcpy(buf, placeHolder);
buf_len = strlen(placeHolder);
while (1) { while (1) {
editorSetStatusMessage(E, prompt, buf); editorSetStatusMessage(prompt, buf);
editorRefreshScreen(E); editorRefreshScreen();
c = editorReadKey(); c = editorReadKey();
if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
if (buf_len != 0) { if (buf_len != 0) {
buf[--buf_len] = '\0'; buf[--buf_len] = '\0';
} }
} else if (c == ESCAPE) { } else if (c == ESCAPE) {
fprintf(stderr, "escape"); editorSetStatusMessage("");
editorSetStatusMessage(E, "");
free(buf); free(buf);
return NULL; return NULL;
} else if (c == '\r') { } else if (c == '\r') {
if (buf_len != 0) { if (buf_len != 0) {
editorSetStatusMessage(E, ""); editorSetStatusMessage("");
return buf; return buf;
} }
} else if (bPathMode && c == '\t') {
char path[128];
char *pwd;
if (buf[0] != '/') {
pwd = getenv("PWD");
fprintf(stderr, "%s\n", pwd);
memcpy(path, pwd, strlen(pwd));
path[strlen(pwd)] = '/';
strncat(path, buf, buf_len);
} else {
strcpy(path, buf);
}
memset(buf, 0, 128);
buf_len = 0;
char * buf_complete = (char *) file_completion(path);
strcpy(buf, buf_complete);
free(buf_complete);
buf_len = strlen(buf);
buf[buf_len] = '\0';
} else if (!iscntrl(c) && c < 128) { } else if (!iscntrl(c) && c < 128) {
if (buf_len == buf_size - 1) { if (buf_len == buf_size - 1) {
buf_size *= 2; buf_size *= 2;
@@ -48,121 +156,177 @@ char *editorPrompt(struct editorConfig *E, char *prompt) {
} }
} }
void editorMoveCursor(struct editorConfig *E, int key) { /**
erow *row = (E->cursor_y >= E->numrows) ? NULL : &E->row[E->cursor_y]; * @brief Converts a key code to its string representation
* @details Translates raw key codes (including special keys, control keys,
* and regular characters) into human-readable string formats suitable for
* display and keybinding configuration.
* @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];
char tmp[10];
sprintf(tmp, "%d", key);
// First test enter key
if (key == '\r') {
strcpy(key_str, "ENTER");
} else if (key == '\t') {
strcpy(key_str, "TAB");
} else if (key >= 1 && key <= 26) { // CTRL keys
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
} else {
switch (key) {
case ARROW_UP:
strcpy(key_str, "ARROW-UP");
break;
case ARROW_DOWN:
strcpy(key_str, "ARROW-DOWN");
break;
case ARROW_LEFT:
strcpy(key_str, "ARROW-LEFT");
break;
case ARROW_RIGHT:
strcpy(key_str, "ARROW-RIGHT");
break;
case PAGE_UP:
strcpy(key_str, "PAGE-UP");
fprintf(stderr, "pagr up\n");
break;
case PAGE_DOWN:
strcpy(key_str, "PAGE-DOWN");
break;
case DEL_KEY:
strcpy(key_str, "DEL");
break;
case BACKSPACE:
strcpy(key_str, "BACKSPACE");
break;
case '\r':
strcpy(key_str, "ENTER");
break;
case '\x1b':
strcpy(key_str, "ESCAPE");
break;
case BEG_LINE:
strcpy(key_str, "HOME");
break;
case END_LINE:
strcpy(key_str, "END");
break;
default:
// For regular characters
if (isprint(key)) {
snprintf(key_str, sizeof(key_str), "%c", key);
} else {
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
}
}
}
return key_str;
}
/**
* @brief Moves the cursor based on arrow key input
* @details Updates cursor position (E.cursor_x, E.cursor_y) based on the given
* key direction. Handles line wrapping and boundary conditions. Prevents cursor
* from exceeding line lengths.
* @param key The arrow key code (ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT)
* @return 1 if cursor movement was valid, 0 if cursor was constrained to line boundary
* @note Updates global editor state E
*/
int editorMoveCursor(int key) {
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
int row_len; int row_len;
switch (key) { switch (key) {
case ARROW_RIGHT: case ARROW_RIGHT:
if (row && E->cursor_x < row->size) { if (row && E.cursor_x < row->size) {
++E->cursor_x; ++E.cursor_x;
} else if (row && E->cursor_x == row->size) { } else if (row && E.cursor_x == row->size) {
E->cursor_y++; E.cursor_y++;
E->cursor_x = 0; E.cursor_x = 0;
} }
break; break;
case ARROW_DOWN: case ARROW_DOWN:
if (E->cursor_y < E->numrows) { if (E.cursor_y < E.numrows) {
++E->cursor_y; ++E.cursor_y;
} }
break; break;
case ARROW_UP: case ARROW_UP:
if (E->cursor_y != 0) { if (E.cursor_y != 0) {
--E->cursor_y; --E.cursor_y;
} }
break; break;
case ARROW_LEFT: case ARROW_LEFT:
if (E->cursor_x != 0) { if (E.cursor_x != 0) {
--E->cursor_x; --E.cursor_x;
} else if (E->cursor_y > 0) { } else if (E.cursor_y > 0) {
--E->cursor_y; --E.cursor_y;
E->cursor_x = E->row[E->cursor_y].size; E.cursor_x = E.row[E.cursor_y].size;
} }
break; break;
} }
row = (E->cursor_y >= E->numrows) ? NULL : &E->row[E->cursor_y]; row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
row_len = row ? row->size : 0; row_len = row ? row->size : 0;
if (E->cursor_x > row_len) { if (E.cursor_x > row_len) {
E->cursor_x = row_len; E.cursor_x = row_len;
return 0;
} }
return 1;
} }
void editorProcessKeypress(struct editorConfig *E) { /**
static int quit_times = QUIT_TIMES; * @brief Executes the command bound to a key sequence
int c = editorReadKey(); * @details Searches the keybinding table for a matching key sequence and
int times; * prefix state, then evaluates the associated Lisp command.
* @param key_sequence The string representation of the key sequence
switch (c) { * @return 1 if a matching keybinding was found and executed, 0 otherwise
* @note Updates global editor state E (prefix_state)
case '\r': * @note Uses Lisp interpreter to evaluate bound commands
editorInsertNewLine(E); */
break; int executeKeyBind(char *key_sequence) {
case CTRL_KEY('q'): int i;
if (E->dirty && quit_times > 0) { int previous_state = 0;
editorSetStatusMessage(E, fprintf(stderr, "pressed %s\n", key_sequence);
"WARNING! Changes hasn't been saved. Press Ctrl-Q " for (i = 0; i < E.number_of_keybinds; ++i) {
"another time to quit."); if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
--quit_times; if (E.prefix_state != E.key_binds[i].prefix_id) {
return; continue;
}
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode(E);
exit(0);
break;
case CTRL_KEY('s'):
editorSave(E);
break;
case BEG_LINE:
E->cursor_x = 0;
break;
case END_LINE:
if (E->cursor_y < E->numrows) {
E->cursor_x = E->row[E->cursor_y].size;
}
break;
case BACKSPACE:
case CTRL_KEY('h'):
case DEL_KEY:
if (c == DEL_KEY) {
editorMoveCursor(E, ARROW_RIGHT);
}
editorDelChar(E);
break;
case PAGE_UP:
case PAGE_DOWN: {
if (c == PAGE_UP) {
E->cursor_y = E->row_offset;
} else if (c == PAGE_DOWN) {
E->cursor_y = E->row_offset + E->screenrows - 1;
if (E->cursor_y > E->numrows) {
E->cursor_y = E->numrows;
} }
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;
} }
times = E->screenrows;
while (--times) {
editorMoveCursor(E, c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
}
} break;
case ARROW_UP:
case ARROW_DOWN:
case ARROW_LEFT:
case ARROW_RIGHT:
editorMoveCursor(E, c);
break;
case CTRL_KEY('l'):
case '\x1b':
break;
default:
editorInsertChar(E, c);
break;
} }
quit_times = QUIT_TIMES; 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();
if (executeKeyBind(key_to_string(c))) {
return;
}
editorInsertChar(c);
E.quit_times_buffer = E.constantes.QUIT_TIMES;
} }
+112 -49
View File
@@ -1,27 +1,48 @@
/**
* @file output.c
* @brief Screen rendering and output module for the Beluga text editor
* @details Handles all screen updates, cursor positioning, status bar rendering,
* and display synchronization using ANSI escape sequences
*/
#include "../include/output.h" #include "../include/output.h"
#include "../include/syntax_highlighter.h"
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
void editorDrawRows(struct editorConfig *E, struct abuf *ab) { extern struct editorConfig E;
/**
* @brief Renders all visible rows to the screen buffer
* @details Draws file content with syntax highlighting, handles line wrapping,
* and displays tilde characters (~) for empty lines. Shows welcome message
* when no file is open.
* @param ab Pointer to append buffer structure for accumulating output
* @note Respects E.row_offset and E.col_offset for scrolling
* @note Clears to end of each line after content
* @see editorRefreshScreen()
*/
void editorDrawRows(struct abuf *ab) {
int y; int y;
char welcome[80]; char welcome[80];
int welcome_len; int welcome_len;
int padding; int padding;
int len; int len;
int file_row; int file_row;
for (y = 0; y < E->screenrows; ++y) { for (y = 0; y < E.screenrows; ++y) {
file_row = y + E->row_offset; file_row = y + E.row_offset;
if (file_row >= E->numrows) { if (file_row >= E.numrows) {
if (E->numrows == 0 && y == E->screenrows / 3) { if (E.numrows == 0 && y == E.screenrows / 3) {
welcome_len = welcome_len =
snprintf(welcome, sizeof(welcome), snprintf(welcome, sizeof(welcome),
"Beluga text editor -- version %s", BELUGA_VERSION); "Beluga text editor -- version %s", BELUGA_VERSION);
if (welcome_len > E->screencols) { if (welcome_len > E.screencols) {
welcome_len = E->screencols; welcome_len = E.screencols;
} }
padding = (E->screencols - welcome_len) / 2; padding = (E.screencols - welcome_len) / 2;
if (padding) { if (padding) {
abAppend(ab, "~", 1); abAppend(ab, "~", 1);
--padding; --padding;
@@ -34,54 +55,70 @@ void editorDrawRows(struct editorConfig *E, struct abuf *ab) {
abAppend(ab, "~", 1); abAppend(ab, "~", 1);
} }
} else { } else {
len = E->row[file_row].rsize - E->col_offset; len = E.row[file_row].rsize - E.col_offset;
if (len < 0) if (len < 0)
len = 0; len = 0;
if (len > E->screencols) if (len > E.screencols)
len = E->screencols; len = E.screencols;
abAppend(ab, &E->row[file_row].render[E->col_offset], len); char * highlighted = highlight_line(&E.row[file_row].render[E.col_offset], &E.row[file_row].rsize);
abAppend(ab, highlighted, E.row[file_row].rsize);
free(highlighted);
} }
abAppend(ab, ERASE_END_LINE, 3); abAppend(ab, ERASE_END_LINE, 3);
abAppend(ab, "\r\n", 2); abAppend(ab, "\r\n", 2);
} }
} }
void editorScroll(struct editorConfig *E) { /**
E->rx = E->cursor_x; * @brief Updates scroll offsets to keep cursor visible on screen
if (E->cursor_y < E->numrows) { * @details Adjusts E.row_offset and E.col_offset to ensure the cursor remains
E->rx = editorRowCxToRx(&E->row[E->cursor_y], E->cursor_x); * within the visible viewport. Also updates E.rx (rendered x-coordinate).
* @note Updates global editor state E
* @see editorRowCxToRx()
*/
void editorScroll() {
E.rx = E.cursor_x;
if (E.cursor_y < E.numrows) {
E.rx = editorRowCxToRx(&E.row[E.cursor_y], E.cursor_x);
} }
if (E->cursor_y < E->row_offset) { if (E.cursor_y < E.row_offset) {
E->row_offset = E->cursor_y; E.row_offset = E.cursor_y;
} }
if (E->cursor_y >= E->row_offset + E->screenrows) { if (E.cursor_y >= E.row_offset + E.screenrows) {
E->row_offset = E->cursor_y - E->screenrows + 1; E.row_offset = E.cursor_y - E.screenrows + 1;
} }
if (E->rx < E->col_offset) { if (E.rx < E.col_offset) {
E->col_offset = E->rx; E.col_offset = E.rx;
} }
if (E->rx >= E->col_offset + E->screencols) { if (E.rx >= E.col_offset + E.screencols) {
E->col_offset = E->rx - E->screencols + 1; E.col_offset = E.rx - E.screencols + 1;
} }
} }
void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab) { /**
* @brief Renders the status bar at the bottom of the screen
* @details Displays filename, line count, dirty flag, and current cursor position
* in an inverted color bar. Right-aligns the cursor position indicator.
* @param ab Pointer to append buffer structure for accumulating output
* @note Uses ANSI escape codes for color inversion
*/
void editorDrawStatusBar(struct abuf *ab) {
int len, render_len; int len, render_len;
char status[80], render_status[80]; char status[80], render_status[80];
abAppend(ab, "\x1b[7m", 4); // inverting colors abAppend(ab, "\x1b[7m", 4); // inverting colors
len = snprintf(status, sizeof(status), "%.20s - %d lines%s", len = snprintf(status, sizeof(status), "%.20s - %d lines%s",
E->filename ? E->filename : "[No Name]", E->numrows, E.filename ? E.filename : "[No Name]", E.numrows,
E->dirty ? "*" : ""); E.dirty ? "*" : "");
render_len = snprintf(render_status, sizeof(render_status), "%d/%d", render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
E->cursor_y + 1, E->numrows); E.cursor_y + 1, E.numrows);
if (len > E->screencols) { if (len > E.screencols) {
len = E->screencols; len = E.screencols;
} }
abAppend(ab, status, len); abAppend(ab, status, len);
while (len < E->screencols) { while (len < E.screencols) {
if (E->screencols - len == render_len) { if (E.screencols - len == render_len) {
abAppend(ab, render_status, render_len); abAppend(ab, render_status, render_len);
break; break;
} else { } else {
@@ -93,43 +130,69 @@ void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab) {
abAppend(ab, "\r\n", 2); abAppend(ab, "\r\n", 2);
} }
void editorDrawMessageBar(struct editorConfig *E, struct abuf *ab) { /**
int msg_len = strlen(E->status_msg); * @brief Renders the message bar below the status bar
* @details Displays temporary status messages for a limited time (5 seconds).
* Only displays message if within time window and within screen width.
* @param ab Pointer to append buffer structure for accumulating output
* @note Messages are set by editorSetStatusMessage()
*/
void editorDrawMessageBar(struct abuf *ab) {
int msg_len = strlen(E.status_msg);
abAppend(ab, ERASE_END_LINE, 3); abAppend(ab, ERASE_END_LINE, 3);
if (msg_len > E->screencols) { if (msg_len > E.screencols) {
msg_len = E->screencols; msg_len = E.screencols;
} }
if (msg_len && time(NULL) - E->status_msg_time < 5) { if (msg_len && time(NULL) - E.status_msg_time < 5) {
abAppend(ab, E->status_msg, msg_len); abAppend(ab, E.status_msg, msg_len);
} }
} }
void editorRefreshScreen(struct editorConfig *E) { /**
editorScroll(E); * @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() {
editorScroll();
struct abuf ab = ABUF_INIT; struct abuf ab = ABUF_INIT;
char buf[32]; char buf[32];
abAppend(&ab, HIDE_CURSOR, 6); abAppend(&ab, HIDE_CURSOR, 6);
abAppend(&ab, CURSOR_TOP_LEFT, 3); abAppend(&ab, CURSOR_TOP_LEFT, 3);
editorDrawRows(E, &ab); editorDrawRows(&ab);
editorDrawStatusBar(E, &ab); editorDrawStatusBar(&ab);
editorDrawMessageBar(E, &ab); editorDrawMessageBar(&ab);
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E->cursor_y - E->row_offset) + 1, snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cursor_y - E.row_offset) + 1,
(E->rx - E->col_offset) + 1); (E.rx - E.col_offset) + 1);
abAppend(&ab, buf, strlen(buf)); abAppend(&ab, buf, strlen(buf));
abAppend(&ab, SHOW_CURSOR, 6); abAppend(&ab, SHOW_CURSOR, 6);
write(STDOUT_FILENO, ab.b, ab.len); write(STDOUT_FILENO, ab.b, ab.len);
abFree(&ab); abFree(&ab);
} }
void editorSetStatusMessage(struct editorConfig *E, const char *fmt, ...) {
/**
* @brief Sets a temporary status message for display
* @details Formats and stores a message that will be displayed in the message bar
* for 5 seconds. Uses printf-style variable argument formatting.
* @param fmt Printf-style format string
* @param ... Variable arguments for format string
* @note Updates global editor state E (status_msg, status_msg_time)
* @see editorDrawMessageBar()
*/
void editorSetStatusMessage(const char *fmt, ...) {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
vsnprintf(E->status_msg, sizeof(E->status_msg), fmt, ap); vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
va_end(ap); va_end(ap);
E->status_msg_time = time(NULL); E.status_msg_time = time(NULL);
} }
+51 -31
View File
@@ -1,20 +1,35 @@
#include "../include/row_op.h" #include "../include/row_op.h"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
extern struct editorConfig E;
int editorRowCxToRx(erow *row, int cursor_x) { int editorRowCxToRx(erow *row, int cursor_x) {
int render_x = 0; int render_x = 0;
int i; int i;
for (i = 0; i < cursor_x; ++i) { for (i = 0; i < cursor_x; ++i) {
if (row->chars[i] == '\t') { if (row->chars[i] == '\t') {
render_x += (TAB_LENGTH - 1) - (render_x % TAB_LENGTH); render_x += (E.constantes.TAB_LENGTH - 1) - (render_x % E.constantes.TAB_LENGTH);
} }
render_x++; render_x++;
} }
return render_x; return render_x;
} }
int editorRowRxToCx(erow *row, int rx) {
int cur_rx = 0;
int cx;
for (cx = 0; cx < row->size; cx++) {
if (row->chars[cx] == '\t')
cur_rx += (E.constantes.TAB_LENGTH - 1) - (cur_rx % E.constantes.TAB_LENGTH);
cur_rx++;
if (cur_rx > rx) return cx;
}
return cx;
}
/** /**
* \fn editorUpdateRow(erow *row) * \fn editorUpdateRow(erow *row)
* \brief Copy content of \p row in \p row->render. * \brief Copy content of \p row in \p row->render.
@@ -32,8 +47,8 @@ void editorUpdateRow(erow *row) {
} }
free(row->render); free(row->render);
row->render = malloc(row->size + tabs * (TAB_LENGTH - 1) + row->render = malloc(row->size + tabs * (E.constantes.TAB_LENGTH - 1) +
1); /**< Tabs needs TAB_LENGTH chars so TAB_LENGTH - 1 1); /**< Tabs needs E.constantes.TAB_LENGTH chars so E.constantes.TAB_LENGTH - 1
more than the first already counted. */ more than the first already counted. */
// end of counting // end of counting
@@ -41,7 +56,7 @@ void editorUpdateRow(erow *row) {
for (i = 0; i < row->size; ++i) { for (i = 0; i < row->size; ++i) {
if (row->chars[i] == '\t') { if (row->chars[i] == '\t') {
row->render[i_render++] = ' '; row->render[i_render++] = ' ';
while (i_render % TAB_LENGTH) { while (i_render % E.constantes.TAB_LENGTH) {
row->render[i_render++] = row->render[i_render++] =
' '; /**< Addind the right amount of spaces for tabs */ ' '; /**< Addind the right amount of spaces for tabs */
} }
@@ -53,25 +68,29 @@ void editorUpdateRow(erow *row) {
row->rsize = i_render; row->rsize = i_render;
} }
void editorInsertRow(struct editorConfig *E, int at, char *s, size_t len) { void editorInsertRow(int at, char *s, size_t len) {
if (at < 0 || at > E->numrows) { if (at < 0 || at > E.numrows) {
return; return;
} }
erow *tmp = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1));
if (!tmp) {
return;
}
E.row = tmp;
memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));
E->row = realloc(E->row, sizeof(erow) * (E->numrows + 1)); E.row[at].size = len;
memmove(&E->row[at + 1], &E->row[at], sizeof(erow) * (E->numrows - at)); E.row[at].chars = malloc(len + 1);
memcpy(E.row[at].chars, s, len);
E.row[at].chars[len] = '\0';
E->row[at].size = len; E.row[at].rsize = 0;
E->row[at].chars = malloc(len + 1); E.row[at].render = NULL;
memcpy(E->row[at].chars, s, len); editorUpdateRow(&E.row[at]);
E->row[at].chars[len] = '\0';
E->row[at].rsize = 0; ++E.numrows;
E->row[at].render = NULL; ++E.dirty;
editorUpdateRow(&E->row[at]);
++E->numrows;
++E->dirty;
} }
void editorFreeRow(erow *row) { void editorFreeRow(erow *row) {
@@ -79,21 +98,23 @@ void editorFreeRow(erow *row) {
free(row->chars); free(row->chars);
} }
void editorDelRow(struct editorConfig *E, int at) { void editorDelRow(int at) {
if (at < 0 || at >= E->numrows) { if (at < 0 || at >= E.numrows) {
return; return;
} }
editorFreeRow(&E->row[at]); editorFreeRow(&E.row[at]);
memmove(&E->row[at], &E->row[at + 1], sizeof(erow) * (E->numrows - at - 1)); memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1));
--E->numrows; --E.numrows;
++E->dirty; ++E.dirty;
} }
/** /**
* \fn editorRowInsertChar(erow *row, int at, int c) * \fn editorRowInsertChar(erow *row, int at, int c)
* \param at Index of where we want to insert the char */ * \param at Index of where we want to insert the char */
void editorRowInsertChar(struct editorConfig *E, erow *row, int at, int c) { void editorRowInsertChar(erow *row, int at, int c) {
if (E.state == READ_ONLY)
return;
if (at < 0 || at > row->size) { if (at < 0 || at > row->size) {
at = row->size; at = row->size;
} }
@@ -102,17 +123,16 @@ void editorRowInsertChar(struct editorConfig *E, erow *row, int at, int c) {
++row->size; ++row->size;
row->chars[at] = c; row->chars[at] = c;
editorUpdateRow(row); editorUpdateRow(row);
++E->dirty; ++E.dirty;
} }
void editorRowAppendString(struct editorConfig *E, erow *row, char *s, void editorRowAppendString(erow *row, char *s, size_t len) {
size_t len) {
row->chars = realloc(row->chars, row->size + len + 1); row->chars = realloc(row->chars, row->size + len + 1);
memcpy(&row->chars[row->size], s, len); memcpy(&row->chars[row->size], s, len);
row->size += len; row->size += len;
row->chars[row->size] = '\0'; row->chars[row->size] = '\0';
editorUpdateRow(row); editorUpdateRow(row);
++E->dirty; ++E.dirty;
} }
/** /**
@@ -120,12 +140,12 @@ void editorRowAppendString(struct editorConfig *E, erow *row, char *s,
* \brief Delete the a char at the chosen position on the given row * \brief Delete the a char at the chosen position on the given row
* \param at Index of the char to delete * \param at Index of the char to delete
* \param row Row on operation is made */ * \param row Row on operation is made */
void editorRowDelchar(struct editorConfig *E, erow *row, int at) { void editorRowDelchar(erow *row, int at) {
if (at < 0 || at >= row->size) { if (at < 0 || at >= row->size) {
return; return;
} }
memmove(&row->chars[at], &row->chars[at + 1], row->size - at); memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
--row->size; --row->size;
editorUpdateRow(row); editorUpdateRow(row);
++E->dirty; ++E.dirty;
} }
+163
View File
@@ -0,0 +1,163 @@
#include "../include/syntax_highlighter.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char *c_keywords[] = {
"if", "else", "while", "for", "do", "switch", "case", "break",
"continue", "return", "goto", "struct", "union", "enum",
"typedef", "static", "extern", "const", "volatile", "sizeof",
"auto", "register", "inline", "restrict", NULL
};
// C types
const char *c_types[] = {
"int", "char", "float", "double", "void", "long", "short",
"unsigned", "signed", "bool", NULL
};
// Check if character is alphanumeric or underscore
int is_word_char(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_';
}
// Check if string is a keyword
int is_keyword(const char *word) {
for (int i = 0; c_keywords[i] != NULL; i++) {
if (strcmp(word, c_keywords[i]) == 0) return 1;
}
return 0;
}
// Check if string is a type
int is_type(const char *word) {
for (int i = 0; c_types[i] != NULL; i++) {
if (strcmp(word, c_types[i]) == 0) return 1;
}
return 0;
}
// Get color code for token type
const char *get_color(TokenType type) {
switch (type) {
case TOKEN_KEYWORD: return COLOR_KEYWORD;
case TOKEN_TYPE: return COLOR_TYPE;
case TOKEN_STRING: return COLOR_STRING;
case TOKEN_COMMENT: return COLOR_COMMENT;
case TOKEN_NUMBER: return COLOR_NUMBER;
default: return COLOR_DEFAULT;
}
}
// Highlight a line of C code and return the highlighted string
// Returns a newly allocated string that must be freed by the caller
char *highlight_line(const char *line, int *length) {
char *result = malloc(1024); // Allocate space for result
int result_pos = 0;
int i = 0;
while (line[i] != '\0' && line[i] != '\n') {
// Skip whitespace
if (line[i] == ' ' || line[i] == '\t') {
result[result_pos++] = line[i++];
continue;
}
// Handle line comments
if (line[i] == '/' && line[i + 1] == '/') {
result_pos += sprintf(&result[result_pos], "%s", COLOR_COMMENT);
while (line[i] != '\0' && line[i] != '\n') {
result[result_pos++] = line[i++];
}
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
continue;
}
// Handle block comments
if (line[i] == '/' && line[i + 1] == '*') {
result_pos += sprintf(&result[result_pos], "%s", COLOR_COMMENT);
result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
while (line[i] != '\0') {
if (line[i] == '*' && line[i + 1] == '/') {
result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
break;
}
result[result_pos++] = line[i++];
}
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
continue;
}
// Handle strings
if (line[i] == '"') {
result_pos += sprintf(&result[result_pos], "%s\"", COLOR_STRING);
i++;
while (line[i] != '\0' && line[i] != '"') {
if (line[i] == '\\') {
result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
} else {
result[result_pos++] = line[i++];
}
}
if (line[i] == '"') result[result_pos++] = line[i++];
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
continue;
}
// Handle character literals
if (line[i] == '\'') {
result_pos += sprintf(&result[result_pos], "%s'", COLOR_STRING);
i++;
while (line[i] != '\0' && line[i] != '\'') {
if (line[i] == '\\') {
result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
} else {
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", COLOR_NUMBER);
while (is_word_char(line[i]) || line[i] == '.') {
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;
while (is_word_char(line[i])) i++;
char word[256];
strncpy(word, &line[start], i - start);
word[i - start] = '\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
result[result_pos++] = line[i++];
}
result[result_pos] = '\0';
*length = result_pos + 1;
return result;
}
+9 -5
View File
@@ -1,24 +1,28 @@
#include "../include/terminal.h" #include "../include/terminal.h"
#include "../include/data.h"
#include <stdio.h>
void die(const char *s) { void die(const char *s) {
write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
lisp_shutdown(E.ctx);
perror(s); perror(s);
exit(1); exit(1);
} }
void disableRawMode(struct editorConfig *E) { void disableRawMode() {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E->orig_termios) == -1) { if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) {
die("tcsetattr"); die("tcsetattr");
} }
} }
void enableRawMode(struct editorConfig *E) { void enableRawMode() {
if (tcgetattr(STDIN_FILENO, &E->orig_termios) == -1) { if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) {
die("tcgetattr"); die("tcgetattr");
} }
struct termios raw = E->orig_termios; struct termios raw = E.orig_termios;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST); raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8); raw.c_cflag |= (CS8);