14 Commits

Author SHA1 Message Date
Arthur Barraux af1835d75b config correct
Build project / build (push) Failing after 1m0s
2026-01-22 14:03:59 +01:00
Arthur Barraux f3bd5dd1c9 Color theming 2026-01-22 14:00:47 +01:00
Arthur Barraux fa7f8d39d8 update splash screen
Build project / build (push) Successful in 23s
2026-01-14 20:38:33 +01:00
Arthur Barraux 23036cf3d3 warranty 0 memoey leak
Build project / build (push) Successful in 58s
2026-01-14 20:24:37 +01:00
Arthur Barraux d0afd4f304 patch file_completion crash
Build project / build (push) Successful in 1m0s
2026-01-13 15:20:18 +01:00
Arthur Barraux de4fa78221 Merge remote-tracking branch 'gitea/main'
Build project / build (push) Successful in 48s
2026-01-11 19:41:57 +01:00
Arthur Barraux fc93832130 Syntax highlighting and comment 2026-01-11 19:41:30 +01:00
arthur 39e07902f4 Update README.md
Build project / build (push) Successful in 24s
2026-01-09 15:03:08 +01:00
Arthur Barraux 815114923d Add prefix feature
Build project / build (push) Successful in 56s
2026-01-09 14:54:07 +01:00
arthur 410f382592 Add go to end of word function
Build project / build (push) Successful in 1m22s
2025-12-24 14:50:14 +01:00
arthur 80c0bf73e0 Update README.md
Build project / build (push) Successful in 1m1s
2025-12-08 00:25:05 +01:00
arthur d4ee0d86c2 Update .gitea/workflows/build.yml
Build project / build (push) Failing after 2m18s
2025-12-04 01:34:33 +01:00
arthur 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
25 changed files with 1471 additions and 375 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: giorgio-runner
steps: steps:
- name: Checkout code - name: Checkout code
+5 -6
View File
@@ -1,7 +1,6 @@
# 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.
It's abviously only working for **Linux**.
## Requirements ## Requirements
@@ -11,7 +10,7 @@ You will only need **meson** and a **C compiler** to compile the editor.
### From source ### From source
Here is the installation line for development version: Here is the installation line for development version:
```git clone https://homelinuxserver.ddns.net/git/arthur/beluga.git ~/.beluga && cd ~/.beluga && meson setup build && meson compile -C build``` ```git clone https://git.giorgio-nas.fr/arthur/beluga.git && cd beluga && meson setup build && meson compile -C build```
The executable file will be `build/beluga`. Feel free to add it to your path. The executable file will be `build/beluga`. Feel free to add it to your path.
@@ -27,7 +26,7 @@ To open an existing file just type :
Here is some few command that you will need : Here is some few command that you will need :
| keybind | command | | keybind | command |
|--------|------------------| |---------------|------------------|
| Ctrl-Q | leave the editor | | Ctrl-x Ctrl-c | leave the editor |
| Ctrl-S | Save a file | | Ctrl-x Ctrl-s | Save a file |
| Ctrl-O | open file | | Ctrl-x f | open file |
+4 -4
View File
@@ -9,12 +9,12 @@
#----------------------------------------------------------## #----------------------------------------------------------##
#=--------------------------------------------------------------## #=--------------------------------------------------------------##
+--------------------------+@#-%*-----------------------------------#* +--------------------------+@#-%*-----------------------------------#*
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 1.1 +--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.2.1
*-=-------------------------#@@*---------------------------------------=% *-=-------------------------#@@*---------------------------------------=%
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS ----- %*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
*%%=-=--------------------------------------------------------------------=# CTRL-q leave *%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave
%=--------------------------------------------------------------------------#* CTRL-s save %=--------------------------------------------------------------------------#* CTRL-x CTRL-s save
%-----------------------------------------------------------------------------** CTRL-o open-file %-----------------------------------------------------------------------------** CTRL-x f open-file
*+--=---===----=---------------=*-----------------------------------------------** *+--=---===----=---------------=*-----------------------------------------------**
#--=## *#%#*+==----==+**+----------------------= ***=---------------------% #--=## *#%#*+==----==+**+----------------------= ***=---------------------%
*%**=-----------==== ==---------------------------------=+#+-----------------=# *%**=-----------==== ==---------------------------------=+#+-----------------=#
+68 -17
View File
@@ -2,6 +2,12 @@
(define TAB-LENGTH 4) (define TAB-LENGTH 4)
(define QUIT-TIMES 1) (define QUIT-TIMES 1)
(define THEME "dark")
;; PACKAGES
;; First git clone it
;; (add-package "smart_delimiters")
;; FUNCTIONS ;; FUNCTIONS
@@ -13,22 +19,67 @@
) )
;; KEY MAPPING (define (char-between ch lo hi)
(if (char>=? ch lo)
(char<=? ch hi)
#f))
(map-key "CTRL-q" editor-quit) (define (alphanumericp ch)
(map-key "CTRL-d" editor-save) (if ch
(map-key "ARROW-UP" '(move-cursor "up")) (if (char-between ch #\a #\z)
(map-key "ARROW-DOWN" '(move-cursor "down")) #t
(map-key "ARROW-RIGHT" '(move-cursor "right")) (if (char-between ch #\A #\Z)
(map-key "ARROW-LEFT" '(move-cursor "left")) #t
(map-key "ENTER" editor-insert-new-line) (if (char-between ch #\0 #\9)
(map-key "CTRL-a" move-cursor-beg-line) #t
(map-key "CTRL-e" move-cursor-end-line) #f)))
(map-key "BACKSPACE" editor-delete-previous-char) #f))
(map-key "DEL" editor-delete-next-char)
(map-key "PAGE-UP" move-cursor-page-up)
(map-key "PAGE-DOWN" move-cursor-page-down)
(map-key "CTRL-o" editor-open-file)
(map-key "CTRL-k" editor-del-row)
(map-key "CTRL-s" editor-find)
(define (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")
+8
View File
@@ -0,0 +1,8 @@
#ifndef BUFFER_H_
#define BUFFER_H_
#include "data.h"
int new_buffer(enum buffer_type type, char * filename, int width, int height);
#endif
+10
View File
@@ -19,6 +19,8 @@ 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 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 editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx); Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
@@ -35,4 +37,12 @@ Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorFind_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 #endif
+123
View File
@@ -0,0 +1,123 @@
#ifndef COLOR_H_
#define COLOR_H_
// ============================================================================
// TEXT STYLES / ATTRIBUTES
// ============================================================================
#define ANSI_RESET "\x1b[0m" // Reset all attributes
#define ANSI_BOLD "\x1b[1m" // Bold text
#define ANSI_DIM "\x1b[2m" // Dim/faint text
#define ANSI_ITALIC "\x1b[3m" // Italic text
#define ANSI_UNDERLINE "\x1b[4m" // Underline text
#define ANSI_BLINK "\x1b[5m" // Blinking text
#define ANSI_REVERSE "\x1b[7m" // Reverse video (invert colors)
#define ANSI_HIDDEN "\x1b[8m" // Hidden/invisible text
#define ANSI_STRIKETHROUGH "\x1b[9m" // Strikethrough text
// ============================================================================
// FOREGROUND COLORS (30-37 standard, 90-97 bright)
// ============================================================================
#define ANSI_BLACK "\x1b[30m"
#define ANSI_RED "\x1b[31m"
#define ANSI_GREEN "\x1b[32m"
#define ANSI_YELLOW "\x1b[33m"
#define ANSI_BLUE "\x1b[34m"
#define ANSI_MAGENTA "\x1b[35m"
#define ANSI_CYAN "\x1b[36m"
#define ANSI_WHITE "\x1b[37m"
// Bright/Light foreground colors (90-97)
#define ANSI_BRIGHT_BLACK "\x1b[90m"
#define ANSI_BRIGHT_RED "\x1b[91m"
#define ANSI_BRIGHT_GREEN "\x1b[92m"
#define ANSI_BRIGHT_YELLOW "\x1b[93m"
#define ANSI_BRIGHT_BLUE "\x1b[94m"
#define ANSI_BRIGHT_MAGENTA "\x1b[95m"
#define ANSI_BRIGHT_CYAN "\x1b[96m"
#define ANSI_BRIGHT_WHITE "\x1b[97m"
// ============================================================================
// BACKGROUND COLORS (40-47 standard, 100-107 bright)
// ============================================================================
#define ANSI_BG_BLACK "\x1b[40m"
#define ANSI_BG_RED "\x1b[41m"
#define ANSI_BG_GREEN "\x1b[42m"
#define ANSI_BG_YELLOW "\x1b[43m"
#define ANSI_BG_BLUE "\x1b[44m"
#define ANSI_BG_MAGENTA "\x1b[45m"
#define ANSI_BG_CYAN "\x1b[46m"
#define ANSI_BG_WHITE "\x1b[47m"
// Bright/Light background colors (100-107)
#define ANSI_BG_BRIGHT_BLACK "\x1b[100m"
#define ANSI_BG_BRIGHT_RED "\x1b[101m"
#define ANSI_BG_BRIGHT_GREEN "\x1b[102m"
#define ANSI_BG_BRIGHT_YELLOW "\x1b[103m"
#define ANSI_BG_BRIGHT_BLUE "\x1b[104m"
#define ANSI_BG_BRIGHT_MAGENTA "\x1b[105m"
#define ANSI_BG_BRIGHT_CYAN "\x1b[106m"
#define ANSI_BG_BRIGHT_WHITE "\x1b[107m"
// ============================================================================
// 256-COLOR MODE (for more colors)
// ============================================================================
// Foreground: \x1b[38;5;Nm where N is 0-255
// Background: \x1b[48;5;Nm where N is 0-255
// Example macros:
#define ANSI_FG_256(N) "\x1b[38;5;" #N "m"
#define ANSI_BG_256(N) "\x1b[48;5;" #N "m"
// ============================================================================
// TRUE COLOR / 24-BIT COLOR (RGB)
// ============================================================================
// Foreground: \x1b[38;2;R;G;Bm
// Background: \x1b[48;2;R;G;Bm
// Example macros:
#define ANSI_FG_RGB(R,G,B) "\x1b[38;2;" #R ";" #G ";" #B "m"
#define ANSI_BG_RGB(R,G,B) "\x1b[48;2;" #R ";" #G ";" #B "m"
// ============================================================================
// CURSOR MOVEMENT
// ============================================================================
#define ANSI_CURSOR_UP(N) "\x1b[" #N "A" // Move up N lines
#define ANSI_CURSOR_DOWN(N) "\x1b[" #N "B" // Move down N lines
#define ANSI_CURSOR_RIGHT(N) "\x1b[" #N "C" // Move right N columns
#define ANSI_CURSOR_LEFT(N) "\x1b[" #N "D" // Move left N columns
#define ANSI_CURSOR_HOME "\x1b[H" // Move to home (0,0)
#define ANSI_CURSOR_HIDE "\x1b[?25l" // Hide cursor
#define ANSI_CURSOR_SHOW "\x1b[?25h" // Show cursor
// ============================================================================
// SCREEN CONTROL
// ============================================================================
#define ANSI_CLEAR_SCREEN "\x1b[2J" // Clear entire screen
#define ANSI_CLEAR_LINE "\x1b[K" // Clear from cursor to end of line
#define ANSI_CLEAR_UP "\x1b[1J" // Clear from cursor to start
#define ANSI_CLEAR_DOWN "\x1b[0J" // Clear from cursor to end
// ============================================================================
// EXAMPLE USAGE
// ============================================================================
/*
#include <stdio.h>
int main(void) {
// Simple color example
printf("%sHello in Red%s\n", ANSI_RED, ANSI_RESET);
// Bold and colored
printf("%s%sBold Green%s\n", ANSI_BOLD, ANSI_GREEN, ANSI_RESET);
// Background color
printf("%s%sYellow on Blue%s\n", ANSI_YELLOW, ANSI_BG_BLUE, ANSI_RESET);
// Combine multiple styles
printf("%s%s%sUnderlined Bold Cyan%s\n",
ANSI_UNDERLINE, ANSI_BOLD, ANSI_CYAN, ANSI_RESET);
return 0;
}
*/
#endif
+37 -1
View File
@@ -20,6 +20,17 @@ typedef struct erow {
char *render; /**< The actual line we will print */ char *render; /**< The actual line we will print */
} erow; } erow;
typedef struct theme {
char *BACKGROUND_COLOR;
char *COLOR_KEYWORD;
char *COLOR_TYPE;
char *COLOR_STRING;
char *COLOR_COMMENT;
char *COLOR_NUMBER;
char *COLOR_DEFAULT;
} theme_t;
enum editorStatus_e { enum editorStatus_e {
IDLE, IDLE,
READ_ONLY, READ_ONLY,
@@ -29,12 +40,28 @@ enum editorStatus_e {
struct const_t { struct const_t {
int TAB_LENGTH; int TAB_LENGTH;
int QUIT_TIMES; int QUIT_TIMES;
char *THEME;
};
struct prefix_t {
char prefix_name[64];
int prefix_id;
}; };
struct keyBind_t { struct keyBind_t {
char *key_sequence; char *key_sequence;
int prefix_id;
Lisp command; Lisp command;
};
enum buffer_type { FILE_BUFF, TERMINAL_BUFF };
struct buffer_t {
enum buffer_type type;
int buffer_id;
int width, height;
int x, y; /**< Position of the buffer*/
char *filename;
}; };
/** /**
@@ -53,6 +80,7 @@ struct editorConfig {
int dirty; int dirty;
char *filename; char *filename;
enum editorStatus_e state; 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 */
@@ -60,6 +88,7 @@ struct editorConfig {
struct const_t constantes; struct const_t constantes;
int quit_times_buffer; int quit_times_buffer;
char *init_file_path;
FILE *fd_init_file; FILE *fd_init_file;
Lisp env; Lisp env;
LispContext ctx; /** Lisp context */ LispContext ctx; /** Lisp context */
@@ -69,6 +98,14 @@ struct editorConfig {
struct keyBind_t *key_binds; struct keyBind_t *key_binds;
int number_of_keybinds; int number_of_keybinds;
struct prefix_t *prefix;
int number_of_prefix;
struct buffer_t buffers[64];
struct buffer_t ***screen_layout; /**< Which buffer is the current cell*/
int number_of_buffer;
theme_t theme;
}; };
/** /**
@@ -83,5 +120,4 @@ struct abuf {
extern struct editorConfig E; extern struct editorConfig E;
#endif #endif
+2
View File
@@ -8,4 +8,6 @@ void editorInsertNewLine();
void editorDelChar(); void editorDelChar();
void editorSetStatusMessage(const char *fmt, ...);
#endif // EDITOR_OP_H_ #endif // EDITOR_OP_H_
+2 -2
View File
@@ -20,11 +20,11 @@
// 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 *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
char *key_to_string(int key); char *key_to_string(int key);
void editorMoveCursor(int key); int editorMoveCursor(int key);
int executeKeyBind(char *key_sequence); int executeKeyBind(char *key_sequence);
+16 -13
View File
@@ -1861,8 +1861,8 @@ static Lisp parse_symbol_(Lexer* lex, LispContext ctx)
char scratch[LISP_IDENTIFIER_MAX]; char scratch[LISP_IDENTIFIER_MAX];
size_t length = lexer_copy_token(lex, 0, LISP_IDENTIFIER_MAX, scratch); size_t length = lexer_copy_token(lex, 0, LISP_IDENTIFIER_MAX, scratch);
// always convert symbols to uppercase // always convert symbols to uppercase
for (int i = 0; i < length; ++i) // for (int i = 0; i < length; ++i)
scratch[i] = toupper(scratch[i]); // scratch[i] = toupper(scratch[i]);
return symbol_intern_(ctx.p->symbols, scratch, length, ctx); return symbol_intern_(ctx.p->symbols, scratch, length, ctx);
} }
@@ -2075,8 +2075,10 @@ static Lisp parse(Lexer* lex, LispError* out_error, LispContext ctx)
result = lisp_cons(get_sym(SYM_BEGIN, ctx), lisp_list_reverse(result), ctx); result = lisp_cons(get_sym(SYM_BEGIN, ctx), lisp_list_reverse(result), ctx);
} }
if (out_error) *out_error = error; if (out_error)
*out_error = error;
return result; return result;
} }
Lisp lisp_read(const char *program, LispError* out_error, LispContext ctx) Lisp lisp_read(const char *program, LispError* out_error, LispContext ctx)
@@ -2122,6 +2124,7 @@ void* fread_all_(FILE* file, size_t* out_size) {
return NULL; return NULL;
} }
data = new_data; data = new_data;
memset(data + *out_size, 0, cap - *out_size);
} }
size_t read = fread(data + *out_size, 1, BLOCK_SIZE, file); size_t read = fread(data + *out_size, 1, BLOCK_SIZE, file);
@@ -3183,17 +3186,17 @@ LispContext lisp_init(void)
ctx.p->macros = lisp_make_table(ctx); ctx.p->macros = lisp_make_table(ctx);
Lisp* c = ctx.p->symbol_cache; Lisp* c = ctx.p->symbol_cache;
c[SYM_IF] = lisp_make_symbol("IF", ctx); c[SYM_IF] = lisp_make_symbol("if", ctx);
c[SYM_BEGIN] = lisp_make_symbol("BEGIN", ctx); c[SYM_BEGIN] = lisp_make_symbol("begin", ctx);
c[SYM_QUOTE] = lisp_make_symbol("QUOTE", ctx); c[SYM_QUOTE] = lisp_make_symbol("quote", ctx);
c[SYM_QUASI_QUOTE] = lisp_make_symbol("QUASIQUOTE", ctx); c[SYM_QUASI_QUOTE] = lisp_make_symbol("quasiquote", ctx);
c[SYM_UNQUOTE] = lisp_make_symbol("UNQUOTE", ctx); c[SYM_UNQUOTE] = lisp_make_symbol("unquote", ctx);
c[SYM_UNQUOTE_SPLICE] = lisp_make_symbol("UNQUOTESPLICE", ctx); c[SYM_UNQUOTE_SPLICE] = lisp_make_symbol("unquotesplice", ctx);
c[SYM_DEFINE] = lisp_make_symbol("_DEF", ctx); c[SYM_DEFINE] = lisp_make_symbol("_def", ctx);
c[SYM_DEFINE_MACRO] = lisp_make_symbol("DEFINE-MACRO", ctx); c[SYM_DEFINE_MACRO] = lisp_make_symbol("define-macro", ctx);
c[SYM_SET] = lisp_make_symbol("_SET!", ctx); c[SYM_SET] = lisp_make_symbol("_set!", ctx);
c[SYM_LAMBDA] = lisp_make_symbol("/\\_", ctx); c[SYM_LAMBDA] = lisp_make_symbol("/\\_", ctx);
c[SYM_CONS] = lisp_make_symbol("CONS", ctx); c[SYM_CONS] = lisp_make_symbol("cons", ctx);
return ctx; return ctx;
} }
+194 -197
View File
@@ -36,7 +36,7 @@ static const char* lib_0_sequences_src_ =
(if (pair? args) \n\ (if (pair? args) \n\
(if (pair? (cdr args)) \n\ (if (pair? (cdr args)) \n\
(if (pair? (cdr (cdr args))) \n\ (if (pair? (cdr (cdr args))) \n\
`(/\\_ ,(car args) ,(cons 'BEGIN (cdr args))) \n\ `(/\\_ ,(car args) ,(cons 'begin (cdr args))) \n\
`(/\\_ ,(car args) ,(car (cdr args)))) \n\ `(/\\_ ,(car args) ,(car (cdr args)))) \n\
(syntax-error \"lambda missing body expressions: (lambda (args) body)\")) \n\ (syntax-error \"lambda missing body expressions: (lambda (args) body)\")) \n\
(syntax-error \"lambda missing argument: (lambda (args) body)\")))) \n\ (syntax-error \"lambda missing argument: (lambda (args) body)\")))) \n\
@@ -44,18 +44,18 @@ static const char* lib_0_sequences_src_ =
(define-macro set! (lambda (var x) \n\ (define-macro set! (lambda (var x) \n\
(begin \n\ (begin \n\
(if (not (symbol? var)) (syntax-error \"set! not a variable\")) \n\ (if (not (symbol? var)) (syntax-error \"set! not a variable\")) \n\
`(_SET! ,var ,x)))) \n\ `(_set! ,var ,x)))) \n\
\n\ \n\
(define-macro define \n\ (define-macro define \n\
(lambda (var . exprs) \n\ (lambda (var . exprs) \n\
(if (symbol? var) \n\ (if (symbol? var) \n\
(if (pair? (cdr exprs)) \n\ (if (pair? (cdr exprs)) \n\
(syntax-error \"define: (define var x)\") \n\ (syntax-error \"define: (define var x)\") \n\
`(_DEF ,var ,(car exprs))) \n\ `(_def ,var ,(car exprs))) \n\
(if (pair? var) \n\ (if (pair? var) \n\
`(_DEF ,(car var) \n\ `(_def ,(car var) \n\
(LAMBDA ,(cdr var) \n\ (lambda ,(cdr var) \n\
,(if (null? (cdr exprs)) (car exprs) (cons 'BEGIN exprs)))) \n\ ,(if (null? (cdr exprs)) (car exprs) (cons 'begin exprs)))) \n\
(syntax-error \"define: not a symbol\") )))) \n\ (syntax-error \"define: not a symbol\") )))) \n\
\n\ \n\
(define (first x) (car x)) \n\ (define (first x) (car x)) \n\
@@ -98,13 +98,13 @@ static const char* lib_0_sequences_src_ =
(define (_expand-shorthand-body path) \n\ (define (_expand-shorthand-body path) \n\
(if (null? path) (cons 'pair '()) \n\ (if (null? path) (cons 'pair '()) \n\
(list (if (char=? (car path) #\\A) \n\ (list (if (char=? (car path) #\\A) \n\
(cons 'CAR (_expand-shorthand-body (cdr path))))))) \n\ (cons 'car (_expand-shorthand-body (cdr path))))))) \n\
\n\ \n\
(define (_expand-shorthand text) \n\ (define (_expand-shorthand text) \n\
(cons 'DEFINE (cons (list (string->symbol (string-append \"C\" text \"R\")) 'pair) \n\ (cons 'define (cons (list (string->symbol (string-append \"C\" text \"R\")) 'pair) \n\
(_expand-shorthand-body (string->list text))))) \n\ (_expand-shorthand-body (string->list text))))) \n\
\n\ \n\
(define-macro _shorthand-accessors (lambda args (cons 'BEGIN (map1 _expand-shorthand args)))) \n\ (define-macro _shorthand-accessors (lambda args (cons 'begin (map1 _expand-shorthand args)))) \n\
\n\ \n\
(define (vector . args) (list->vector args)) \n\ (define (vector . args) (list->vector args)) \n\
\n\ \n\
@@ -124,14 +124,14 @@ static const char* lib_0_sequences_src_ =
static const char* lib_1_forms_src_ = static const char* lib_1_forms_src_ =
"(define (_make-lambda args body) \n\ "(define (_make-lambda args body) \n\
(list 'LAMBDA args (if (null? (cdr body)) (car body) (cons 'BEGIN body)))) \n\ (list 'lambda args (if (null? (cdr body)) (car body) (cons 'begin body)))) \n\
\n\ \n\
\n\ \n\
; (LET <name> ((<var0> <expr0>) ... (<varN> <expr1>)) <body0> ... <bodyN>) \n\ ; (let <name> ((<var0> <expr0>) ... (<varN> <expr1>)) <body0> ... <bodyN>) \n\
; => ((LAMBDA (<var0> ... <varN>) (BEGIN <body0> ... <bodyN>)) <expr0> ... <expr1>) \n\ ; => ((lambda (<var0> ... <varN>) (begin <body0> ... <bodyN>)) <expr0> ... <expr1>) \n\
; => named \n\ ; => named \n\
; ((lambda () \n\ ; ((lambda () \n\
; (define <name> (LAMBDA (<var0> ... <varN>) (BEGIN <body0> ... <bodyN>))) \n\ ; (define <name> (lambda (<var0> ... <varN>) (begin <body0> ... <bodyN>))) \n\
; (<name> <expr0> ... <exprN>))) \n\ ; (<name> <expr0> ... <exprN>))) \n\
\n\ \n\
(define (_check-binding-list bindings) \n\ (define (_check-binding-list bindings) \n\
@@ -145,7 +145,7 @@ static const char* lib_1_forms_src_ =
(define initial-args (map1 (lambda (entry) (second entry)) bindings)) \n\ (define initial-args (map1 (lambda (entry) (second entry)) bindings)) \n\
(if (null? var) \n\ (if (null? var) \n\
(cons body-func initial-args) \n\ (cons body-func initial-args) \n\
(list (_make-lambda '() (list (list 'DEFINE var body-func) (cons var initial-args)))))) \n\ (list (_make-lambda '() (list (list 'define var body-func) (cons var initial-args)))))) \n\
\n\ \n\
(define-macro let (lambda args \n\ (define-macro let (lambda args \n\
(if (pair? (first args)) \n\ (if (pair? (first args)) \n\
@@ -153,8 +153,8 @@ static const char* lib_1_forms_src_ =
(_let->combination (first args) (second args) (cdr (cdr args)))))) \n\ (_let->combination (first args) (second args) (cdr (cdr args)))))) \n\
\n\ \n\
(define (_let*-helper bindings body) \n\ (define (_let*-helper bindings body) \n\
(if (null? bindings) (if (null? (cdr body)) (car body) (cons 'BEGIN body)) \n\ (if (null? bindings) (if (null? (cdr body)) (car body) (cons 'begin body)) \n\
(list 'LET (list (car bindings)) (_let*-helper (cdr bindings) body)))) \n\ (list 'let (list (car bindings)) (_let*-helper (cdr bindings) body)))) \n\
\n\ \n\
(define-macro let* (lambda (bindings . body) \n\ (define-macro let* (lambda (bindings . body) \n\
(_check-binding-list bindings) \n\ (_check-binding-list bindings) \n\
@@ -163,17 +163,17 @@ static const char* lib_1_forms_src_ =
(define-macro letrec (lambda (bindings . body) \n\ (define-macro letrec (lambda (bindings . body) \n\
(_check-binding-list bindings) \n\ (_check-binding-list bindings) \n\
(cons (_make-lambda (map1 (lambda (entry) (first entry)) bindings) \n\ (cons (_make-lambda (map1 (lambda (entry) (first entry)) bindings) \n\
(append (map1 (lambda (entry) (list 'SET! (first entry) (second entry))) \n\ (append (map1 (lambda (entry) (list 'set! (first entry) (second entry))) \n\
bindings) body)) \n\ bindings) body)) \n\
(map1 (lambda (entry) '()) bindings)))) \n\ (map1 (lambda (entry) '()) bindings)))) \n\
\n\ \n\
\n\ \n\
; (COND (<pred0> <expr0>) \n\ ; (cond (<pred0> <expr0>) \n\
; (<pred1> <expr1>) \n\ ; (<pred1> <expr1>) \n\
; ... \n\ ; ... \n\
; (else <expr-1>)) \n\ ; (else <expr-1>)) \n\
; => \n\ ; => \n\
; (IF <pred0> <expr0> \n\ ; (if <pred0> <expr0> \n\
; (if <pred1> <expr1> \n\ ; (if <pred1> <expr1> \n\
; .... \n\ ; .... \n\
; (if <predN> <exprN> <expr-1>)) ... ) \n\ ; (if <predN> <exprN> <expr-1>)) ... ) \n\
@@ -187,11 +187,11 @@ static const char* lib_1_forms_src_ =
\n\ \n\
(define (_cond-helper clauses) \n\ (define (_cond-helper clauses) \n\
(if (null? clauses) '() \n\ (if (null? clauses) '() \n\
(if (eq? (car (car clauses)) 'ELSE) \n\ (if (eq? (car (car clauses)) 'else) \n\
(cons 'BEGIN (cdr (car clauses))) \n\ (cons 'begin (cdr (car clauses))) \n\
(list 'IF \n\ (list 'if \n\
(car (car clauses)) \n\ (car (car clauses)) \n\
(cons 'BEGIN (cdr (car clauses))) \n\ (cons 'begin (cdr (car clauses))) \n\
(_cond-helper (cdr clauses)))))) \n\ (_cond-helper (cdr clauses)))))) \n\
\n\ \n\
(define-macro cond (lambda clauses \n\ (define-macro cond (lambda clauses \n\
@@ -206,26 +206,26 @@ static const char* lib_2_forms_src_ =
(cond ((null? preds) #t) \n\ (cond ((null? preds) #t) \n\
((null? (cdr preds)) (car preds)) \n\ ((null? (cdr preds)) (car preds)) \n\
(else \n\ (else \n\
`(IF ,(car preds) ,(_and-helper (cdr preds)) #f)))) \n\ `(if ,(car preds) ,(_and-helper (cdr preds)) #f)))) \n\
(define-macro and (lambda preds (_and-helper preds))) \n\ (define-macro and (lambda preds (_and-helper preds))) \n\
\n\ \n\
(define (_or-helper preds var) \n\ (define (_or-helper preds var) \n\
(cond ((null? preds) #f) \n\ (cond ((null? preds) #f) \n\
((null? (cdr preds)) (car preds)) \n\ ((null? (cdr preds)) (car preds)) \n\
(else \n\ (else \n\
`(BEGIN (SET! ,var ,(car preds)) \n\ `(begin (set! ,var ,(car preds)) \n\
(IF ,var ,var ,(_or-helper (cdr preds) var)))))) \n\ (if ,var ,var ,(_or-helper (cdr preds) var)))))) \n\
\n\ \n\
(define-macro or (lambda preds \n\ (define-macro or (lambda preds \n\
(let ((var (gensym))) \n\ (let ((var (gensym))) \n\
`(LET ((,var '())) ,(_or-helper preds var))))) \n\ `(let ((,var '())) ,(_or-helper preds var))))) \n\
\n\ \n\
(define-macro case (lambda (key . clauses) \n\ (define-macro case (lambda (key . clauses) \n\
(let ((expr (gensym))) \n\ (let ((expr (gensym))) \n\
`(LET ((,expr ,key)) \n\ `(let ((,expr ,key)) \n\
,(cons 'COND (map1 (lambda (entry) \n\ ,(cons 'cond (map1 (lambda (entry) \n\
(cons (if (pair? (car entry)) \n\ (cons (if (pair? (car entry)) \n\
`(MEMV ,expr (quote ,(car entry))) \n\ `(memv ,expr (quote ,(car entry))) \n\
(car entry)) \n\ (car entry)) \n\
(cdr entry))) clauses)))))) \n\ (cdr entry))) clauses)))))) \n\
\n\ \n\
@@ -234,20 +234,21 @@ static const char* lib_2_forms_src_ =
`(begin (set! ,l (cons ,v ,l)) ,l))) \n\ `(begin (set! ,l (cons ,v ,l)) ,l))) \n\
\n\ \n\
; (DO ((<var0> <init0> <step0>) ...) (<test> <result>) <body>) \n\ ; (DO ((<var0> <init0> <step0>) ...) (<test> <result>) <body>) \n\
\n\
(define-macro do \n\ (define-macro do \n\
(lambda (vars loop-check . loops) \n\ (lambda (vars loop-check . loops) \n\
(let ( (names '()) (inits '()) (steps '()) (f (gensym)) ) \n\ (let ( (names '()) (inits '()) (steps '()) (f (gensym)) ) \n\
(for-each1 (lambda (var) \n\ (for-each1 (lambda (var-spec) \n\
(push (car var) names) \n\ (push (car var-spec) names) \n\
(set! var (cdr var)) \n\ (push (car (cdr var-spec)) inits) \n\
(push (car var) inits) \n\ (if (pair? (cdr (cdr var-spec))) \n\
(set! var (cdr var)) \n\ (push (car (cdr (cdr var-spec))) steps) \n\
(push (car var) steps)) vars) \n\ (push (car var-spec) steps))) vars) \n\
`((LAMBDA () \n\ `((lambda () \n\
(DEFINE ,f (LAMBDA ,names \n\ (define ,f (lambda ,names \n\
(IF ,(car loop-check) \n\ (if ,(car loop-check) \n\
,(if (pair? (cdr loop-check)) (car (cdr loop-check)) '()) \n\ ,(if (pair? (cdr loop-check)) (car (cdr loop-check)) '()) \n\
,(cons 'BEGIN (append loops (list (cons f steps)))) ))) \n\ ,(cons 'begin (append loops (list (cons f steps)))) ))) \n\
,(cons f inits) \n\ ,(cons f inits) \n\
)) ))) \n\ )) ))) \n\
\n\ \n\
@@ -262,18 +263,18 @@ static const char* lib_2_forms_src_ =
(define-macro swap! \n\ (define-macro swap! \n\
(lambda (x y) \n\ (lambda (x y) \n\
(let ((temp (gensym))) \n\ (let ((temp (gensym))) \n\
`(LET ((,temp ,x)) \n\ `(let ((,temp ,x)) \n\
(SET! ,temp ,x) \n\ (set! ,temp ,x) \n\
(SET! ,x ,y) \n\ (set! ,x ,y) \n\
(SET! ,y ,temp))))) \n\ (set! ,y ,temp))))) \n\
\n\ \n\
(define-macro inc! ; CL incf \n\ (define-macro inc! ; CL incf \n\
(lambda (x) \n\ (lambda (x) \n\
`(SET! ,x (+ ,x 1)))) \n\ `(set! ,x (+ ,x 1)))) \n\
\n\ \n\
(define-macro dec! ; CL decf \n\ (define-macro dec! ; CL decf \n\
(lambda (x) \n\ (lambda (x) \n\
`(SET! ,x (- ,x 1))))"; `(set! ,x (- ,x 1))))";
static const char* lib_3_math_src_ = static const char* lib_3_math_src_ =
"(define (number? x) (real? x)) \n\ "(define (number? x) (real? x)) \n\
@@ -445,7 +446,7 @@ static const char* lib_4_sequences_src_ =
static const char* lib_5_streams_src_ = static const char* lib_5_streams_src_ =
"(define-macro delay (lambda (expr) \n\ "(define-macro delay (lambda (expr) \n\
`(make-promise ,(cons 'LAMBDA \n\ `(make-promise ,(cons 'lambda \n\
(cons '() \n\ (cons '() \n\
(cons expr '())))))) \n\ (cons expr '())))))) \n\
\n\ \n\
@@ -1926,182 +1927,178 @@ static Lisp sch_is_cont(Lisp args, LispError* e, LispContext ctx)
static const LispFuncDef lib_cfunc_defs[] = { static const LispFuncDef lib_cfunc_defs[] = {
{ "ERROR", sch_error }, { "error", sch_error },
{ "SYNTAX-ERROR", sch_syntax_error }, { "syntax-error", sch_syntax_error },
// Output Procedures https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Output-Procedures.html // Output Procedures
{ "_WRITE", sch_write }, { "_write", sch_write },
{ "_DISPLAY", sch_display }, { "_display", sch_display },
{ "_WRITE-CHAR", sch_write_char }, { "_write-char", sch_write_char },
{ "_FLUSH-OUTPUT-PORT", sch_flush }, { "_flush-output-port", sch_flush },
{ "_READ", sch_read }, { "_read", sch_read },
{ "INPUT-PORT?", sch_is_port_in }, { "input-port?", sch_is_port_in },
{ "OUTPUT-PORT?", sch_is_port_out }, { "output-port?", sch_is_port_out },
{ "OPEN-INPUT-FILE", sch_open_input }, { "open-input-file", sch_open_input },
{ "OPEN-OUTPUT-FILE", sch_open_output }, { "open-output-file", sch_open_output },
{ "CLOSE-INPUT-PORT", sch_port_close }, { "close-input-port", sch_port_close },
{ "CLOSE-OUTPUT-PORT", sch_port_close }, { "close-output-port", sch_port_close },
{ "EOF-OBJECT?", sch_is_eof }, { "eof-object?", sch_is_eof },
// Universal Time https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Universal-Time.html // Universal Time
{ "GET-UNIVERSAL-TIME", sch_univeral_time }, { "get-universal-time", sch_univeral_time },
{ "PRINT-GC-STATISTICS", sch_print_gc_stats }, { "print-gc-statistics", sch_print_gc_stats },
{ "MACROEXPAND", sch_macroexpand }, { "macroexpand", sch_macroexpand },
// Equivalence Predicates https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Equivalence-Predicates.html // Equivalence Predicates
{ "EQ?", sch_exact_eq }, { "eq?", sch_exact_eq },
{ "EQV?", sch_equal }, { "eqv?", sch_equal },
{ "EQUAL?", sch_equal_r }, { "equal?", sch_equal_r },
// Booleans https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Booleans.html // Booleans
{ "BOOLEAN?", sch_is_boolean }, { "boolean?", sch_is_boolean },
{ "NOT", sch_not }, { "not", sch_not },
// PAIRS // PAIRS
{ "CONS", sch_cons }, { "cons", sch_cons },
{ "CAR", sch_car }, { "car", sch_car },
{ "CDR", sch_cdr }, { "cdr", sch_cdr },
{ "SET-CAR!", sch_set_car }, { "set-car!", sch_set_car },
{ "SET-CDR!", sch_set_cdr }, { "set-cdr!", sch_set_cdr },
{ "NULL?", sch_is_null }, { "null?", sch_is_null },
{ "PAIR?", sch_is_pair }, { "pair?", sch_is_pair },
// Lists https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_8.html // Lists
{ "LIST", sch_list }, { "list", sch_list },
{ "LIST?", sch_is_list }, { "list?", sch_is_list },
{ "MAKE-LIST", sch_make_list }, { "make-list", sch_make_list },
{ "LIST-COPY", sch_list_copy }, { "list-copy", sch_list_copy },
{ "LENGTH", sch_length }, { "length", sch_length },
{ "APPEND", sch_append }, { "append", sch_append },
{ "APPEND-REVERSE!", sch_append_reverse }, { "append-reverse!", sch_append_reverse },
{ "LIST-REF", sch_list_ref }, { "list-ref", sch_list_ref },
{ "NTHCDR", sch_list_advance }, { "nthcdr", sch_list_advance },
// Vectors https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_9.html#SEC82 // Vectors
{ "VECTOR?", sch_is_vector }, { "vector?", sch_is_vector },
{ "MAKE-VECTOR", sch_make_vector }, { "make-vector", sch_make_vector },
{ "VECTOR-GROW", sch_vector_grow }, { "vector-grow", sch_vector_grow },
{ "VECTOR-LENGTH", sch_vector_length }, { "vector-length", sch_vector_length },
{ "VECTOR-SET!", sch_vector_set }, { "vector-set!", sch_vector_set },
{ "VECTOR-SWAP!", sch_vector_swap }, { "vector-swap!", sch_vector_swap },
{ "VECTOR-REF", sch_vector_ref }, { "vector-ref", sch_vector_ref },
{ "VECTOR-FILL!", sch_vector_fill }, { "vector-fill!", sch_vector_fill },
{ "VECTOR-ASSQ", sch_vector_assq }, { "vector-assq", sch_vector_assq },
{ "SUBVECTOR", sch_subvector }, { "subvector", sch_subvector },
{ "LIST->VECTOR", sch_list_to_vector }, { "list->vector", sch_list_to_vector },
{ "VECTOR->LIST", sch_vector_to_list }, { "vector->list", sch_vector_to_list },
// Strings https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_7.html#SEC61 // Strings
{ "STRING?", sch_is_string }, { "string?", sch_is_string },
{ "MAKE-STRING", sch_make_string }, { "make-string", sch_make_string },
{ "STRING=?", sch_equal_r }, { "string=?", sch_equal_r },
{ "STRING<?", sch_string_less }, { "string<?", sch_string_less },
{ "SUBSTRING", sch_substring }, { "substring", sch_substring },
{ "STRING-NULL?", sch_string_is_null }, { "string-null?", sch_string_is_null },
{ "STRING-LENGTH", sch_string_length }, { "string-length", sch_string_length },
{ "STRING-REF", sch_string_ref }, { "string-ref", sch_string_ref },
{ "STRING-SET!", sch_string_set }, { "string-set!", sch_string_set },
{ "STRING-UPCASE", sch_string_upcase }, { "string-upcase", sch_string_upcase },
{ "STRING-DOWNCASE", sch_string_downcase }, { "string-downcase", sch_string_downcase },
{ "STRING-APPEND", sch_string_append }, { "string-append", sch_string_append },
{ "STRING->LIST", sch_string_to_list }, { "string->list", sch_string_to_list },
{ "LIST->STRING", sch_list_to_string }, { "list->string", sch_list_to_string },
{ "STRING->NUMBER", sch_string_to_number }, { "string->number", sch_string_to_number },
{ "NUMBER->STRING", sch_number_to_string }, { "number->string", sch_number_to_string },
// Characters https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Characters.html#Characters // Characters
{ "CHAR?", sch_is_char }, { "char?", sch_is_char },
{ "CHAR=?", sch_equals }, { "char=?", sch_equals },
{ "CHAR<?", sch_char_less }, { "char<?", sch_char_less },
{ "CHAR-UPCASE", sch_char_upcase }, { "char-upcase", sch_char_upcase },
{ "CHAR-DOWNCASE", sch_char_downcase }, { "char-downcase", sch_char_downcase },
{ "CHAR-WHITESPACE?", sch_char_is_white }, { "char-whitespace?", sch_char_is_white },
{ "CHAR-ALPHANUMERIC?", sch_char_is_alphanum }, { "char-alphanumeric?", sch_char_is_alphanum },
{ "CHAR-ALPHABETIC?", sch_char_is_alpha }, { "char-alphabetic?", sch_char_is_alpha },
{ "CHAR-NUMERIC?", sch_char_is_number }, { "char-numeric?", sch_char_is_number },
{ "CHAR->INTEGER", sch_char_to_int }, { "char->integer", sch_char_to_int },
// Association Lists https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Association-Lists.html // Numerical operations
// Numerical operations https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Numerical-operations.html
{ "=", sch_equals }, { "=", sch_equals },
{ "+", sch_add }, { "+", sch_add },
{ "-", sch_sub }, { "-", sch_sub },
{ "*", sch_mult }, { "*", sch_mult },
{ "/", sch_divide }, { "/", sch_divide },
{ "<", sch_less }, { "<", sch_less },
{ "INTEGER?", sch_is_int }, { "integer?", sch_is_int },
{ "EVEN?", sch_is_even }, { "even?", sch_is_even },
{ "REAL?", sch_is_real }, { "real?", sch_is_real },
{ "EXP", sch_exp }, { "exp", sch_exp },
{ "EXPT", sch_power }, { "expt", sch_power },
{ "LOG", sch_log }, { "log", sch_log },
{ "SIN", sch_sin }, { "sin", sch_sin },
{ "COS", sch_cos }, { "cos", sch_cos },
{ "TAN", sch_tan }, { "tan", sch_tan },
{ "ATAN", sch_atan }, { "atan", sch_atan },
{ "SQRT", sch_sqrt }, { "sqrt", sch_sqrt },
{ "ROUND", sch_round }, { "round", sch_round },
{ "FLOOR", sch_floor }, { "floor", sch_floor },
{ "CEILING", sch_ceiling }, { "ceiling", sch_ceiling },
{ "QUOTIENT", sch_quotient }, { "quotient", sch_quotient },
{ "REMAINDER", sch_remainder }, { "remainder", sch_remainder },
{ "MODULO", sch_modulo }, { "modulo", sch_modulo },
{ "ABS", sch_abs }, { "abs", sch_abs },
{ "MAGNITUDE", sch_abs }, { "magnitude", sch_abs },
{ "EXACT?", sch_is_int }, { "exact?", sch_is_int },
{ "EXACT->INEXACT", sch_to_inexact }, { "exact->inexact", sch_to_inexact },
{ "INEXACT->EXACT", sch_to_exact }, { "inexact->exact", sch_to_exact },
// Symbols https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Symbols.html // Symbols
{ "SYMBOL?", sch_is_symbol }, { "symbol?", sch_is_symbol },
{ "SYMBOL<?", sch_symbol_less }, { "symbol<?", sch_symbol_less },
{ "STRING->SYMBOL", sch_string_to_symbol }, { "string->symbol", sch_string_to_symbol },
{ "SYMBOL->STRING", sch_symbol_to_string }, { "symbol->string", sch_symbol_to_string },
{ "GENERATE-UNINTERNED-SYMBOL", sch_gensym }, { "generate-uninterned-symbol", sch_gensym },
{ "GENSYM", sch_gensym }, { "gensym", sch_gensym },
// Environments https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_14.html // Environments
{ "EVAL", sch_eval }, { "eval", sch_eval },
{ "SCHEME-REPORT-ENVIRONMENT", sch_system_env }, { "scheme-report-environment", sch_system_env },
{ "INTERACTION-ENVIRONMENT", sch_user_env }, { "interaction-environment", sch_user_env },
// { "THE-ENVIRONMENT", sch_current_env },
// Hash Tables https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Basic-Hash-Table-Operations.html#Basic-Hash-Table-Operations // Hash Tables
{ "HASH-TABLE?", sch_is_table }, { "hash-table?", sch_is_table },
{ "MAKE-HASH-TABLE", sch_table_make }, { "make-hash-table", sch_table_make },
{ "HASH-TABLE-SET!", sch_table_set }, { "hash-table-set!", sch_table_set },
{ "HASH-TABLE-REF", sch_table_get }, { "hash-table-ref", sch_table_get },
{ "HASH-TABLE-SIZE", sch_table_size }, { "hash-table-size", sch_table_size },
{ "HASH-TABLE->ALIST", sch_table_to_alist }, { "hash-table->alist", sch_table_to_alist },
{ "PROMISE?", sch_is_promise }, { "promise?", sch_is_promise },
{ "MAKE-PROMISE", sch_make_promise }, { "make-promise", sch_make_promise },
{ "_PROMISE-PROCEDURE", sch_promise_proc }, { "_promise-procedure", sch_promise_proc },
{ "_PROMISE-STORE!", sch_promise_store }, { "_promise-store!", sch_promise_store },
{ "PROMISE-VALUE", sch_promise_val }, { "promise-value", sch_promise_val },
{ "PROMISE-FORCED?", sch_promise_forced }, { "promise-forced?", sch_promise_forced },
// Procedures https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Procedure-Operations.html#Procedure-Operations // Procedures
{ "APPLY", sch_apply }, { "apply", sch_apply },
{ "COMPILED-PROCEDURE?", sch_is_func }, { "compiled-procedure?", sch_is_func },
{ "COMPOUND-PROCEDURE?", sch_is_lambda }, { "compound-procedure?", sch_is_lambda },
{ "PROCEDURE-ENVIRONMENT", sch_lambda_env }, { "procedure-environment", sch_lambda_env },
// TOOD: Almost standard { "procedure-body", sch_lambda_body },
{ "PROCEDURE-BODY", sch_lambda_body }, { "call/cc", sch_call_cc },
{ "CALL/CC", sch_call_cc }, { "continuation?", sch_is_cont },
{ "CONTINUATION?", sch_is_cont },
// Random Numbers https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Random-Numbers.html // Random Numbers
{ "RANDOM", sch_pseudo_rand }, { "random", sch_pseudo_rand },
{ "RANDOM-SEED!", sch_pseudo_seed }, { "random-seed!", sch_pseudo_seed },
// Garbage Collection https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-user/Garbage-Collection.html // Garbage Collection
{ "GC-FLIP", sch_gc_flip }, { "gc-flip", sch_gc_flip },
{ NULL, NULL } { NULL, NULL }
}; };
void lisp_lib_load(LispContext ctx) void lisp_lib_load(LispContext ctx)
@@ -2111,14 +2108,14 @@ void lisp_lib_load(LispContext ctx)
lisp_table_set( lisp_table_set(
table, table,
lisp_make_symbol("_CURRENT-OUTPUT-PORT", ctx), lisp_make_symbol("_current-output-port", ctx),
lisp_make_port(stdout, 0), lisp_make_port(stdout, 0),
ctx ctx
); );
lisp_table_set( lisp_table_set(
table, table,
lisp_make_symbol("_CURRENT-INPUT-PORT", ctx), lisp_make_symbol("_current-input-port", ctx),
lisp_make_port(stdin, 1), lisp_make_port(stdin, 1),
ctx ctx
); );
-2
View File
@@ -23,6 +23,4 @@ void editorDrawStatusBar(struct abuf *ab);
void editorDrawMessageBar(struct abuf *ab); void editorDrawMessageBar(struct abuf *ab);
void editorSetStatusMessage(const char *fmt, ...);
#endif // OUTPUT_H_ #endif // OUTPUT_H_
+23
View File
@@ -0,0 +1,23 @@
#ifndef 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)
// Token types
typedef enum {
TOKEN_KEYWORD,
TOKEN_TYPE,
TOKEN_STRING,
TOKEN_COMMENT,
TOKEN_NUMBER,
TOKEN_OPERATOR,
TOKEN_DEFAULT
} TokenType;
char *highlight_line(const char * line, int *length);
#endif
+1
View File
@@ -18,6 +18,7 @@ echo "Create config files ..."
mkdir -pv ~/.beluga/ mkdir -pv ~/.beluga/
cp -rv ./assets/ ~/.beluga/ cp -rv ./assets/ ~/.beluga/
mkdir -pv ~/.beluga/packages/ 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 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
+2 -2
View File
@@ -18,7 +18,7 @@
#include "include/file_io.h" #include "include/file_io.h"
#include "include/init.h" #include "include/init.h"
#include "include/input.h" #include "include/input.h"
#include "include/output.h" #include "include/editor_op.h"
#include "include/terminal.h" #include "include/terminal.h"
struct editorConfig E; struct editorConfig E;
@@ -40,7 +40,7 @@ int main(int argc, char *argv[]) {
} }
free(splash_screen); free(splash_screen);
editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit"); editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-q = quit");
while (1) { while (1) {
+1
View File
@@ -13,6 +13,7 @@ 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/file_io.c', 'src/file_io.c',
'src/init.c', 'src/init.c',
'src/input.c', 'src/input.c',
+20
View File
@@ -0,0 +1,20 @@
#include "../include/data.h"
#include <string.h>
int new_buffer(enum buffer_type type, char *filename, int x, int y, int width,
int height) {
// Create a new buffer
struct buffer_t *buffer = &E.buffers[E.number_of_buffer];
buffer->type = type;
if (type == FILE_BUFF) {
strcpy(buffer->filename, filename);
}
buffer->width = width, buffer->height = height;
buffer->x = x, buffer->y = y;
return 1;
}
+317 -27
View File
@@ -1,53 +1,149 @@
/**
* @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/builtins.h"
#include "../include/define.h"
#include "../include/input.h"
#include "../include/file_io.h"
#include "../include/editor_op.h"
#include "../include/row_op.h"
#include "../include/data.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 <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.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) { Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
/*
* 3 arguments keybind command prefix
*/
const char *key_sequence = lisp_string(lisp_car(args)); const char *key_sequence = lisp_string(lisp_car(args));
args = lisp_cdr(args); args = lisp_cdr(args);
// second argument // second argument
Lisp func = lisp_car(args); Lisp func = lisp_car(args);
E.key_binds = E.key_binds = (struct keyBind_t *)realloc(
(struct keyBind_t *)realloc(E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t)); 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)); E.key_binds[E.number_of_keybinds - 1].key_sequence =
(char *)malloc(50 * sizeof(char));
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50); strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
E.key_binds[E.number_of_keybinds - 1].command = func; E.key_binds[E.number_of_keybinds - 1].command = func;
// Third argument
args = lisp_cdr(args);
const char *prefix_name = lisp_string(lisp_car(args));
struct prefix_t prefix = find_prefix(prefix_name);
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
return lisp_null(); 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) { Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
const char *direction = lisp_string(lisp_car(args)); const char *direction = lisp_string(lisp_car(args));
int is_in = 0;
switch (direction[0]) { switch (direction[0]) {
case 'u': case 'u':
editorMoveCursor(ARROW_UP); is_in = editorMoveCursor(ARROW_UP);
break; break;
case 'd': case 'd':
editorMoveCursor(ARROW_DOWN); is_in = editorMoveCursor(ARROW_DOWN);
break; break;
case 'r': case 'r':
editorMoveCursor(ARROW_RIGHT); is_in = editorMoveCursor(ARROW_RIGHT);
break; break;
case 'l': case 'l':
editorMoveCursor(ARROW_LEFT); is_in = editorMoveCursor(ARROW_LEFT);
break; break;
} }
fprintf(stderr, "move lisp %d\n", is_in);
return lisp_make_bool(is_in);
return lisp_null();
} }
/**
* @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) { Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
if (E.dirty && E.quit_times_buffer > 0) { if (E.dirty && E.quit_times_buffer > 0) {
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q " editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
@@ -55,40 +151,89 @@ Lisp editorQuit(Lisp args, LispError* e, LispContext ctx) {
--E.quit_times_buffer; --E.quit_times_buffer;
return lisp_null(); return lisp_null();
} }
free_structs();
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);
disableRawMode(); disableRawMode();
lisp_shutdown(E.ctx);
exit(0); exit(0);
return lisp_null(); 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) { Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
editorSave(); editorSave();
return lisp_null(); 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) { Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
editorInsertNewLine(); editorInsertNewLine();
return lisp_null(); 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) { Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
E.cursor_x = 0; E.cursor_x = 0;
return lisp_null(); 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) { Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
if (E.cursor_y < E.numrows) { if (E.cursor_y < E.numrows) {
E.cursor_x = E.row[E.cursor_y].size; E.cursor_x = E.row[E.cursor_y].size;
@@ -96,12 +241,30 @@ Lisp moveCursorEndLine(Lisp args, LispError* e, LispContext ctx) {
return lisp_null(); 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) { Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
editorDelChar(); editorDelChar();
return lisp_null(); 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) { Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
E.cursor_y = E.row_offset; E.cursor_y = E.row_offset;
int times = E.screenrows; int times = E.screenrows;
@@ -111,6 +274,16 @@ Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) {
return lisp_null(); 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) { Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
E.cursor_y = E.row_offset + E.screenrows - 1; E.cursor_y = E.row_offset + E.screenrows - 1;
if (E.cursor_y > E.numrows) { if (E.cursor_y > E.numrows) {
@@ -124,29 +297,64 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx) {
return lisp_null(); 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) { Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
char *filename = editorPrompt("Path : %s"); char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename) if (filename){
editorOpen(filename); editorOpen(filename);
}
free(filename);
return lisp_null(); 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) { Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
char c = lisp_string(lisp_car(args))[0]; char c = lisp_string(lisp_car(args))[0];
editorInsertChar(c); editorInsertChar(c);
return lisp_null(); 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) { Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
const char *package_name = lisp_string(lisp_car(args)); const char *package_name = lisp_string(lisp_car(args));
fprintf(stderr, "%s\n", package_name);
char *package_dir = (char *)calloc(256, sizeof(char)); char *package_dir = (char *)calloc(256, sizeof(char));
FILE *fd_package = NULL; FILE *fd_package = NULL;
strcat(package_dir, getenv("HOME")); strcat(package_dir, getenv("HOME"));
strcat(package_dir, "/.beluga/packages/"); strcat(package_dir, "/.beluga/packages/");
strcat(package_dir, package_name); strcat(package_dir, package_name);
strcat(package_dir, "/init.lisp"); strcat(package_dir, "/init.lisp");
fprintf(stderr, "%s\n", package_dir);
fd_package = fopen(package_dir, "r"); fd_package = fopen(package_dir, "r");
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error, lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx); E.ctx);
@@ -154,15 +362,97 @@ Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
free(package_dir); free(package_dir);
return lisp_null(); 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) { Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) {
editorDelRow(E.cursor_y); editorDelRow(E.cursor_y);
return lisp_null(); 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) { Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) {
fprintf(stderr, "LispFind\n");
editorFind(); editorFind();
return lisp_null(); 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();
}
+26
View File
@@ -1,9 +1,30 @@
#include <stdarg.h>
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/row_op.h" #include "../include/row_op.h"
extern struct editorConfig E; 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
* @param ... Variable arguments for format string
* @note Updates global editor state E (status_msg, status_msg_time)
* @see editorDrawMessageBar()
*/
void editorSetStatusMessage(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
va_end(ap);
E.status_msg_time = time(NULL);
}
void editorInsertChar(int c) { void editorInsertChar(int c) {
if (E.cursor_y == E.numrows) { if (E.cursor_y == E.numrows) {
editorInsertRow(E.numrows, "", 0); editorInsertRow(E.numrows, "", 0);
@@ -13,6 +34,10 @@ void editorInsertChar(int c) {
} }
void editorInsertNewLine() { 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.cursor_y, "", 0); editorInsertRow(E.cursor_y, "", 0);
@@ -27,6 +52,7 @@ void editorInsertNewLine() {
} }
++E.cursor_y; ++E.cursor_y;
E.cursor_x = 0; E.cursor_x = 0;
fprintf(stderr, "Insert new line done\n");
} }
void editorDelChar() { void editorDelChar() {
+67 -6
View File
@@ -1,6 +1,14 @@
/**
* @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/editor_op.h"
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -14,6 +22,15 @@ extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
extern int ftruncate(int fd, off_t length); extern int ftruncate(int fd, off_t length);
extern struct editorConfig E; extern struct editorConfig E;
/**
* @brief Converts all editor rows to a single string buffer
* @details Concatenates all row content into a single allocated buffer with
* newlines between rows. Useful for file saving and buffer operations.
* @param buffer_len Pointer to integer where total buffer length will be stored
* @return Pointer to dynamically allocated buffer containing all row data.
* Rows are separated by newline characters.
* @note Caller is responsible for freeing the returned buffer
*/
char *editorRowsToString(int *buffer_len) { char *editorRowsToString(int *buffer_len) {
int tot_len = 0; int tot_len = 0;
int j; int j;
@@ -36,20 +53,43 @@ char *editorRowsToString(int *buffer_len) {
return buf; return buf;
} }
/**
* @brief Closes the current file and resets editor state
* @details Clears all rows, resets cursor position, scroll offsets, and file metadata.
* Does not prompt to save unsaved changes.
* @note Updates global editor state E
*/
void editorCloseFile(void) { void editorCloseFile(void) {
E.cursor_x = 0; E.cursor_x = 0;
E.cursor_y = 0; E.cursor_y = 0;
E.rx = 0; E.rx = 0;
E.row_offset = 0; E.row_offset = 0;
E.col_offset = 0; E.col_offset = 0;
for (int i = 0; i < E.numrows; ++i) {
free(E.row[i].chars);
free(E.row[i].render);
}
E.numrows = 0; E.numrows = 0;
free(E.row);
E.row = NULL; E.row = NULL;
E.dirty = 0; E.dirty = 0;
free(E.filename);
E.filename = NULL; E.filename = NULL;
E.status_msg[0] = '\0'; E.status_msg[0] = '\0';
E.status_msg_time = 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(char *filename) { void editorOpen(char *filename) {
FILE *fp; FILE *fp;
@@ -59,7 +99,6 @@ void editorOpen(char *filename) {
E.state = READ_AND_WRITE; E.state = READ_AND_WRITE;
} }
free(E.filename);
E.filename = strdup(filename); E.filename = strdup(filename);
fp = fopen(filename, "a+"); fp = fopen(filename, "a+");
@@ -76,18 +115,30 @@ void editorOpen(char *filename) {
--line_len; --line_len;
} }
editorInsertRow(E.numrows, line, line_len); editorInsertRow(E.numrows, line, line_len);
free(line);
line = NULL;
} }
free(line); free(line);
fclose(fp); fclose(fp);
E.dirty = 0; 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() { void editorSave() {
int len; int len;
char *buf; char *buf;
int fd; int fd;
if (E.filename == NULL) { if (E.filename == NULL) {
E.filename = editorPrompt("Save as: %s (ESC to cancel)"); E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (E.filename == NULL) { if (E.filename == NULL) {
editorSetStatusMessage("Save aborted"); editorSetStatusMessage("Save aborted");
return; return;
@@ -111,11 +162,22 @@ void editorSave() {
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); 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 editorFind() { void editorFind() {
char *query = editorPrompt("Search: %s (ESC to cancel)"); fprintf(stderr, "searching\n");
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
if (query == NULL) return; if (query == NULL) return;
int i; int i;
for (i = 0; i < E.numrows; i++) { for (i = E.cursor_y + 1; i < E.numrows; i++) {
erow *row = &E.row[i]; erow *row = &E.row[i];
char *match = strstr(row->render, query); char *match = strstr(row->render, query);
if (match) { if (match) {
@@ -127,4 +189,3 @@ void editorFind() {
} }
free(query); free(query);
} }
+95 -45
View File
@@ -1,66 +1,48 @@
#include "../include/init.h" #include "../include/init.h"
#include "../include/builtins.h"
#include "../include/color.h"
#include "../include/data.h" #include "../include/data.h"
#include "../include/terminal.h" #include "../include/terminal.h"
#include "../include/builtins.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.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"
extern 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() { void initBuiltins() {
// move cursor // Registering C functions as lisp macro
registerBuiltin("MOVE-CURSOR", moveCursor); registerBuiltin("move-cursor", moveCursor);
registerBuiltin("MAP-KEY", mapKey); registerBuiltin("map-key", mapKey);
registerBuiltin("EDITOR-QUIT", editorQuit); registerBuiltin("editor-quit", editorQuit);
registerBuiltin("EDITOR-SAVE", l_editorSave); registerBuiltin("editor-save", l_editorSave);
registerBuiltin("EDITOR-INSERT-NEW-LINE", l_editorInsertNewLine); registerBuiltin("editor-insert-new-line", l_editorInsertNewLine);
registerBuiltin("MOVE-CURSOR-BEG-LINE", moveCursorBeginLine); registerBuiltin("move-cursor-beg-line", moveCursorBeginLine);
registerBuiltin("MOVE-CURSOR-END-LINE", moveCursorEndLine); registerBuiltin("move-cursor-end-line", moveCursorEndLine);
registerBuiltin("EDITOR-DELETE-PREVIOUS-CHAR", deletePreviousChar); registerBuiltin("editor-delete-previous-char", deletePreviousChar);
registerBuiltin("MOVE-CURSOR-PAGE-UP", editorMoveCursorPageUp); registerBuiltin("move-cursor-page-up", editorMoveCursorPageUp);
registerBuiltin("MOVE-CURSOR-PAGE-DOWN", editorMoveCursorPageDown); registerBuiltin("move-cursor-page-down", editorMoveCursorPageDown);
registerBuiltin("EDITOR-OPEN-FILE", editorOpenFile); registerBuiltin("editor-open-file", editorOpenFile);
registerBuiltin("EDITOR-INSERT-CHAR", editorPrintC); registerBuiltin("editor-insert-char", editorPrintC);
registerBuiltin("ADD-PACKAGE", addPackage); registerBuiltin("add-package", addPackage);
registerBuiltin("EDITOR-DEL-ROW", editorDelRow_L); registerBuiltin("editor-del-row", editorDelRow_L);
registerBuiltin("EDITOR-FIND", editorFind_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() { void initConfig() {
char * init_file_path = (char *) calloc(256, sizeof(char));
E.cursor_x = 0;
E.cursor_y = 0;
E.rx = 0;
E.row_offset = 0;
E.col_offset = 0;
E.numrows = 0;
E.row = NULL;
E.dirty = 0;
E.filename = NULL;
E.state = READ_ONLY;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
die("getWindowSize");
}
E.screenrows -= 2;
E.number_of_keybinds = 0;
strcat(init_file_path, getenv("HOME"));
strcat(init_file_path, "/.beluga/config/init.lisp");
printf("%s\n", init_file_path);
E.fd_init_file = fopen(init_file_path, "r");
E.ctx = lisp_init(); E.ctx = lisp_init();
E.env = lisp_env(E.ctx); E.env = lisp_env(E.ctx);
lisp_lib_load(E.ctx); lisp_lib_load(E.ctx);
@@ -69,11 +51,79 @@ void initEditor() {
// Read config file // Read config file
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx); E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
free(init_file_path);
if (E.ctx_error != LISP_ERROR_NONE) { if (E.ctx_error != LISP_ERROR_NONE) {
die("init failed"); die("init failed");
} }
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx); lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
}
void init_theme() {
E.constantes.THEME = (char *)lisp_string(
lisp_eval(lisp_read("THEME", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx));
if (strcmp(E.constantes.THEME, "dark") == 0) {
E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(40, 44, 52);
E.theme.COLOR_KEYWORD = ANSI_FG_RGB(198, 120, 221);
E.theme.COLOR_TYPE = ANSI_FG_RGB(97, 175, 239);
E.theme.COLOR_STRING = ANSI_FG_RGB(152, 195, 121);
E.theme.COLOR_COMMENT = ANSI_FG_RGB(92, 99, 112);
E.theme.COLOR_NUMBER = ANSI_FG_RGB(209, 154, 102);
E.theme.COLOR_DEFAULT = ANSI_FG_RGB(171, 178, 191);
} else {
E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(250, 251, 252);
E.theme.COLOR_KEYWORD = ANSI_FG_RGB(166, 38, 164);
E.theme.COLOR_TYPE = ANSI_FG_RGB(4, 90, 180);
E.theme.COLOR_STRING = ANSI_FG_RGB(80, 161, 79);
E.theme.COLOR_COMMENT = ANSI_FG_RGB(152, 152, 152);
E.theme.COLOR_NUMBER = ANSI_FG_RGB(206, 102, 54);
E.theme.COLOR_DEFAULT = ANSI_FG_RGB(86, 89, 90);
}
}
void initEditor() {
// Init graphics variables
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;
// File relative variables
E.dirty = 0;
E.filename = NULL;
E.init_file_path = (char *)calloc(256, sizeof(char));
strcat(E.init_file_path, getenv("HOME"));
strcat(E.init_file_path, "/.beluga/config/init.lisp");
E.fd_init_file = fopen(E.init_file_path, "r");
E.state = READ_ONLY;
// Status bar
E.status_msg[0] = '\0';
E.status_msg_time = 0;
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
die("getWindowSize");
}
E.screenrows -= 2;
E.screen_layout =
(struct buffer_t ***)malloc(E.screenrows * sizeof(struct buffer_t **));
for (int i = 0; i < E.screenrows; ++i) {
E.screen_layout[i] =
(struct buffer_t **)malloc(E.screencols * sizeof(struct buffer_t *));
}
// Key binds
E.number_of_keybinds = 0;
E.number_of_prefix = 0;
// General prefix is 0 (no prefix)
E.prefix = (struct prefix_t *)malloc(sizeof(struct prefix_t));
E.prefix[0].prefix_id = 0;
strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
E.prefix_state = 0;
initConfig();
init_theme();
// To modify // To modify
+161 -12
View File
@@ -1,26 +1,112 @@
#include "../include/input.h" #include "../include/input.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/define.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 <string.h> #include <string.h>
#include <unistd.h> #include <sys/stat.h>
extern struct editorConfig E; 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(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(prompt, buf); editorSetStatusMessage(prompt, buf);
@@ -39,6 +125,26 @@ char *editorPrompt(char *prompt) {
editorSetStatusMessage(""); 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;
@@ -50,17 +156,29 @@ char *editorPrompt(char *prompt) {
} }
} }
/**
* @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) { char *key_to_string(int key) {
static char key_str[32]; static char key_str[32];
char tmp[10]; char tmp[10];
sprintf(tmp, "%d", key); sprintf(tmp, "%d", key);
// First test enter key // First test enter key
if (key == '\r') { if (key == '\r') {
strcpy(key_str, "ENTER"); strcpy(key_str, "ENTER");
} else if (key == '\t') {
strcpy(key_str, "TAB");
} else if (key >= 1 && key <= 26) { // CTRL keys } else if (key >= 1 && key <= 26) { // CTRL keys
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1); snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
} else { } else {
@@ -85,7 +203,6 @@ char *key_to_string(int key) {
strcpy(key_str, "PAGE-DOWN"); strcpy(key_str, "PAGE-DOWN");
break; break;
case DEL_KEY: case DEL_KEY:
fprintf(stderr, "delete key\n");
strcpy(key_str, "DEL"); strcpy(key_str, "DEL");
break; break;
@@ -116,8 +233,16 @@ char *key_to_string(int key) {
return key_str; return key_str;
} }
/**
void editorMoveCursor(int key) { * @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]; erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
int row_len; int row_len;
switch (key) { switch (key) {
@@ -153,24 +278,49 @@ void editorMoveCursor(int key) {
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;
} }
/**
* @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 executeKeyBind(char *key_sequence) {
int i; int i;
int previous_state = 0;
fprintf(stderr, "pressed %s\n", key_sequence);
for (i = 0; i < E.number_of_keybinds; ++i) { for (i = 0; i < E.number_of_keybinds; ++i) {
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) { if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
if (E.prefix_state != E.key_binds[i].prefix_id) {
fprintf(stderr, "lisp function %s\n", key_sequence); continue;
}
previous_state = E.prefix_state;
// It's a symbol, create a function call // It's a symbol, create a function call
lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx), lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx),
&E.ctx_error, E.ctx); &E.ctx_error, E.ctx);
if (E.prefix_state == previous_state)
E.prefix_state = 0;
return 1; return 1;
} }
} }
return 0; 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() { void editorProcessKeypress() {
int c = editorReadKey(); int c = editorReadKey();
@@ -179,5 +329,4 @@ void editorProcessKeypress() {
} }
editorInsertChar(c); editorInsertChar(c);
E.quit_times_buffer = E.constantes.QUIT_TIMES; E.quit_times_buffer = E.constantes.QUIT_TIMES;
} }
+61 -11
View File
@@ -1,11 +1,30 @@
/**
* @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 <stdarg.h> #include "../include/append_buffer.h"
#include "../include/syntax_highlighter.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
extern struct editorConfig E; 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) { void editorDrawRows(struct abuf *ab) {
int y; int y;
char welcome[80]; char welcome[80];
@@ -15,6 +34,7 @@ void editorDrawRows(struct abuf *ab) {
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;
abAppend(ab, E.theme.BACKGROUND_COLOR, strlen(E.theme.BACKGROUND_COLOR));
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 =
@@ -41,13 +61,25 @@ void editorDrawRows(struct abuf *ab) {
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, E.theme.BACKGROUND_COLOR, strlen(E.theme.BACKGROUND_COLOR));
abAppend(ab, ERASE_END_LINE, 3); abAppend(ab, ERASE_END_LINE, 3);
abAppend(ab, "\r\n", 2); abAppend(ab, "\r\n", 2);
} }
} }
/**
* @brief Updates scroll offsets to keep cursor visible on screen
* @details Adjusts E.row_offset and E.col_offset to ensure the cursor remains
* within the visible viewport. Also updates E.rx (rendered x-coordinate).
* @note Updates global editor state E
* @see editorRowCxToRx()
*/
void editorScroll() { void editorScroll() {
E.rx = E.cursor_x; E.rx = E.cursor_x;
if (E.cursor_y < E.numrows) { if (E.cursor_y < E.numrows) {
@@ -68,6 +100,14 @@ void editorScroll() {
} }
} }
/**
* @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) { 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];
@@ -95,6 +135,13 @@ void editorDrawStatusBar(struct abuf *ab) {
abAppend(ab, "\r\n", 2); abAppend(ab, "\r\n", 2);
} }
/**
* @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) { void editorDrawMessageBar(struct abuf *ab) {
int msg_len = strlen(E.status_msg); int msg_len = strlen(E.status_msg);
abAppend(ab, ERASE_END_LINE, 3); abAppend(ab, ERASE_END_LINE, 3);
@@ -106,14 +153,26 @@ void editorDrawMessageBar(struct abuf *ab) {
} }
} }
/**
* @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() { void editorRefreshScreen() {
editorScroll(); 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(&ab); editorDrawRows(&ab);
editorDrawStatusBar(&ab); editorDrawStatusBar(&ab);
editorDrawMessageBar(&ab); editorDrawMessageBar(&ab);
@@ -123,15 +182,6 @@ void editorRefreshScreen() {
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(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
va_end(ap);
E.status_msg_time = time(NULL);
}
+198
View File
@@ -0,0 +1,198 @@
#include "../include/syntax_highlighter.h"
#include "../include/data.h"
#include "../include/theme.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern struct editorConfig E;
const char *c_keywords[] = {
"if", "else", "while", "for", "do", "switch",
"case", "break", "#include", "#define", "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 == '_' || 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 E.theme.COLOR_KEYWORD;
case TOKEN_TYPE:
return E.theme.COLOR_TYPE;
case TOKEN_STRING:
return E.theme.COLOR_STRING;
case TOKEN_COMMENT:
return E.theme.COLOR_COMMENT;
case TOKEN_NUMBER:
return E.theme.COLOR_NUMBER;
default:
return E.theme.COLOR_DEFAULT;
}
}
int comment_section = 0;
// Highlight a line of C code and return the highlighted string
// Returns a newly allocated string that must be freed by the caller
char *highlight_line(const char *line, int *length) {
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;
}
if (comment_section) {
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
while (line[i] != '\0' && line[i] != '\n') {
if (line[i] == '*' && line[i + 1] == '/') {
comment_section = 0;
}
result[result_pos++] = line[i++];
}
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
continue;
}
// Handle line comments
if (line[i] == '/' && line[i + 1] == '/') {
result_pos += sprintf(&result[result_pos], "%s", E.theme.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] == '*')) {
comment_section = 1;
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
while (line[i] != '\0') {
if (line[i] == '*' && line[i + 1] == '/') {
result[result_pos++] = line[i++];
result[result_pos++] = line[i++];
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\"", E.theme.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'", E.theme.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", E.theme.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_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_DEFAULT);
result[result_pos++] = line[i++];
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
}
result[result_pos] = '\0';
*length = result_pos + 1;
return result;
}