Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eae85c32ca |
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: giorgio-runner
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Beluga
|
||||
|
||||
Beluga is a project of CLI text editor that uses lisp as configuration language.
|
||||
It's abviously only working for **Linux**.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -10,7 +11,7 @@ You will only need **meson** and a **C compiler** to compile the editor.
|
||||
### From source
|
||||
Here is the installation line for development version:
|
||||
|
||||
```git clone https://git.giorgio-nas.fr/arthur/beluga.git && cd beluga && meson setup build && meson compile -C build```
|
||||
```git clone https://homelinuxserver.ddns.net/git/arthur/beluga.git ~/.beluga && cd ~/.beluga && meson setup build && meson compile -C build```
|
||||
|
||||
The executable file will be `build/beluga`. Feel free to add it to your path.
|
||||
|
||||
@@ -25,8 +26,8 @@ To open an existing file just type :
|
||||
|
||||
Here is some few command that you will need :
|
||||
|
||||
| keybind | command |
|
||||
|---------------|------------------|
|
||||
| Ctrl-x Ctrl-c | leave the editor |
|
||||
| Ctrl-x Ctrl-s | Save a file |
|
||||
| Ctrl-x f | open file |
|
||||
| keybind| command |
|
||||
|--------|------------------|
|
||||
| Ctrl-Q | leave the editor |
|
||||
| Ctrl-S | Save a file |
|
||||
| Ctrl-O | open file |
|
||||
|
||||
+4
-4
@@ -9,12 +9,12 @@
|
||||
#----------------------------------------------------------##
|
||||
#=--------------------------------------------------------------##
|
||||
+--------------------------+@#-%*-----------------------------------#*
|
||||
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.2.1
|
||||
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 1.1
|
||||
*-=-------------------------#@@*---------------------------------------=%
|
||||
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
|
||||
*%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave
|
||||
%=--------------------------------------------------------------------------#* CTRL-x CTRL-s save
|
||||
%-----------------------------------------------------------------------------** CTRL-x f open-file
|
||||
*%%=-=--------------------------------------------------------------------=# CTRL-q leave
|
||||
%=--------------------------------------------------------------------------#* CTRL-s save
|
||||
%-----------------------------------------------------------------------------** CTRL-o open-file
|
||||
*+--=---===----=---------------=*-----------------------------------------------**
|
||||
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
|
||||
*%**=-----------==== ==---------------------------------=+#+-----------------=#
|
||||
|
||||
+17
-67
@@ -3,11 +3,6 @@
|
||||
(define TAB-LENGTH 4)
|
||||
(define QUIT-TIMES 1)
|
||||
|
||||
;; PACKAGES
|
||||
|
||||
;; First git clone it
|
||||
;; (add-package "smart_delimiters")
|
||||
|
||||
;; FUNCTIONS
|
||||
|
||||
(define editor-delete-next-char (lambda () (
|
||||
@@ -18,67 +13,22 @@
|
||||
)
|
||||
|
||||
|
||||
(define (char-between ch lo hi)
|
||||
(if (char>=? ch lo)
|
||||
(char<=? ch hi)
|
||||
#f))
|
||||
;; KEY MAPPING
|
||||
|
||||
(define (alphanumericp ch)
|
||||
(if ch
|
||||
(if (char-between ch #\a #\z)
|
||||
#t
|
||||
(if (char-between ch #\A #\Z)
|
||||
#t
|
||||
(if (char-between ch #\0 #\9)
|
||||
#t
|
||||
#f)))
|
||||
#f))
|
||||
(map-key "CTRL-q" editor-quit)
|
||||
(map-key "CTRL-d" editor-save)
|
||||
(map-key "ARROW-UP" '(move-cursor "up"))
|
||||
(map-key "ARROW-DOWN" '(move-cursor "down"))
|
||||
(map-key "ARROW-RIGHT" '(move-cursor "right"))
|
||||
(map-key "ARROW-LEFT" '(move-cursor "left"))
|
||||
(map-key "ENTER" editor-insert-new-line)
|
||||
(map-key "CTRL-a" move-cursor-beg-line)
|
||||
(map-key "CTRL-e" move-cursor-end-line)
|
||||
(map-key "BACKSPACE" editor-delete-previous-char)
|
||||
(map-key "DEL" editor-delete-next-char)
|
||||
(map-key "PAGE-UP" move-cursor-page-up)
|
||||
(map-key "PAGE-DOWN" move-cursor-page-down)
|
||||
(map-key "CTRL-o" editor-open-file)
|
||||
(map-key "CTRL-k" editor-del-row)
|
||||
(map-key "CTRL-s" editor-find)
|
||||
|
||||
|
||||
(define (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")
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void abAppend(struct abuf *ab, const char *s, int len);
|
||||
void abAppend(struct abuf *ab, const unsigned char *s, int len);
|
||||
|
||||
void abFree(struct abuf *ab);
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
|
||||
@@ -39,10 +37,4 @@ 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
|
||||
|
||||
+45
-15
@@ -7,6 +7,12 @@
|
||||
|
||||
#include "lisp.h"
|
||||
|
||||
|
||||
typedef struct{
|
||||
unsigned char c[4];
|
||||
char len;
|
||||
} utf_8_char_t;
|
||||
|
||||
/**
|
||||
* \struct erow
|
||||
* \brief Store one editor row
|
||||
@@ -16,8 +22,8 @@
|
||||
typedef struct erow {
|
||||
int size; /**< Size of the line */
|
||||
int rsize; /**< Size of the render line */
|
||||
char *chars; /**< Characters of the line */
|
||||
char *render; /**< The actual line we will print */
|
||||
utf_8_char_t *chars; /**< Characters of the line */
|
||||
utf_8_char_t *render; /**< The actual line we will print */
|
||||
} erow;
|
||||
|
||||
enum editorStatus_e {
|
||||
@@ -31,16 +37,45 @@ struct const_t {
|
||||
int QUIT_TIMES;
|
||||
};
|
||||
|
||||
struct prefix_t {
|
||||
char prefix_name[64];
|
||||
int prefix_id;
|
||||
};
|
||||
// Key types
|
||||
typedef enum {
|
||||
KEY_CHAR, // Regular character or UTF-8
|
||||
KEY_CTRL, // Ctrl+letter
|
||||
KEY_ALT, // Alt+letter
|
||||
KEY_ARROW, // Arrow keys
|
||||
KEY_FUNCTION, // Function keys
|
||||
KEY_SPECIAL, // Tab, Enter, ESC, Backspace, etc.
|
||||
KEY_NAVIGATION, // Home, End, PgUp, PgDn, Insert, Delete
|
||||
KEY_UNKNOWN
|
||||
} KeyType;
|
||||
|
||||
// Modifiers
|
||||
typedef enum {
|
||||
MOD_NONE = 0,
|
||||
MOD_SHIFT = 1,
|
||||
MOD_ALT = 2,
|
||||
MOD_CTRL = 4
|
||||
} KeyModifier;
|
||||
|
||||
// Key information structure
|
||||
typedef struct {
|
||||
KeyType type;
|
||||
int modifiers; // Bitmask of KeyModifier
|
||||
union {
|
||||
unsigned int codepoint; // For KEY_CHAR
|
||||
char ctrl_char; // For KEY_CTRL (A-Z)
|
||||
char alt_char; // For KEY_ALT
|
||||
char arrow; // For KEY_ARROW (U/D/L/R)
|
||||
int function_num; // For KEY_FUNCTION (1-12)
|
||||
char special; // For KEY_SPECIAL and KEY_NAVIGATION
|
||||
} data;
|
||||
utf_8_char_t c; // Raw bytes
|
||||
} KeyInfo;
|
||||
|
||||
|
||||
struct keyBind_t {
|
||||
char *key_sequence;
|
||||
int prefix_id;
|
||||
KeyInfo *key_sequence;
|
||||
Lisp command;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -59,7 +94,6 @@ struct editorConfig {
|
||||
int dirty;
|
||||
char *filename;
|
||||
enum editorStatus_e state;
|
||||
int prefix_state;
|
||||
char status_msg[80];
|
||||
time_t status_msg_time;
|
||||
struct termios orig_termios; /**< Terminal communication interface */
|
||||
@@ -67,7 +101,6 @@ struct editorConfig {
|
||||
struct const_t constantes;
|
||||
int quit_times_buffer;
|
||||
|
||||
char *init_file_path;
|
||||
FILE *fd_init_file;
|
||||
Lisp env;
|
||||
LispContext ctx; /** Lisp context */
|
||||
@@ -76,9 +109,6 @@ struct editorConfig {
|
||||
|
||||
struct keyBind_t* key_binds;
|
||||
int number_of_keybinds;
|
||||
|
||||
struct prefix_t* prefix;
|
||||
int number_of_prefix;
|
||||
|
||||
};
|
||||
|
||||
@@ -88,7 +118,7 @@ struct editorConfig {
|
||||
* */
|
||||
|
||||
struct abuf {
|
||||
char *b; /**< Text that will be printed */
|
||||
unsigned char *b; /**< Text that will be printed */
|
||||
int len; /**< Length of the text */
|
||||
};
|
||||
|
||||
|
||||
+3
-12
@@ -8,19 +8,10 @@
|
||||
#define HIDE_CURSOR "\x1b[?25l"
|
||||
#define SHOW_CURSOR "\x1b[?25h"
|
||||
#define ERASE_END_LINE "\x1b[K"
|
||||
#define TAB "\x09"
|
||||
#define SPACE "\x20"
|
||||
|
||||
|
||||
enum editorKey {
|
||||
BACKSPACE = 127,
|
||||
ARROW_LEFT = 1000,
|
||||
ARROW_RIGHT,
|
||||
ARROW_UP,
|
||||
ARROW_DOWN,
|
||||
DEL_KEY,
|
||||
BEG_LINE,
|
||||
END_LINE,
|
||||
PAGE_UP,
|
||||
PAGE_DOWN,
|
||||
};
|
||||
|
||||
#define ABUF_INIT {NULL, 0}
|
||||
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
#define EDITOR_OP_H_
|
||||
|
||||
#include "data.h"
|
||||
void editorInsertChar(int c);
|
||||
void editorInsertChar(utf_8_char_t *c);
|
||||
|
||||
void editorInsertNewLine();
|
||||
void editorInsertNewLine(void);
|
||||
|
||||
void editorDelChar();
|
||||
|
||||
|
||||
+2
-2
@@ -24,9 +24,9 @@ char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
|
||||
|
||||
char *key_to_string(int key);
|
||||
|
||||
int editorMoveCursor(int key);
|
||||
void editorMoveCursor(KeyInfo * key);
|
||||
|
||||
int executeKeyBind(char *key_sequence);
|
||||
int executeKeyBind(KeyInfo *key_sequence);
|
||||
|
||||
/**
|
||||
* \fn void editorProcessKeypress()
|
||||
|
||||
+13
-16
@@ -1861,8 +1861,8 @@ static Lisp parse_symbol_(Lexer* lex, LispContext ctx)
|
||||
char scratch[LISP_IDENTIFIER_MAX];
|
||||
size_t length = lexer_copy_token(lex, 0, LISP_IDENTIFIER_MAX, scratch);
|
||||
// always convert symbols to uppercase
|
||||
// for (int i = 0; i < length; ++i)
|
||||
// scratch[i] = toupper(scratch[i]);
|
||||
for (int i = 0; i < length; ++i)
|
||||
scratch[i] = toupper(scratch[i]);
|
||||
return symbol_intern_(ctx.p->symbols, scratch, length, ctx);
|
||||
}
|
||||
|
||||
@@ -2075,10 +2075,8 @@ static Lisp parse(Lexer* lex, LispError* out_error, LispContext ctx)
|
||||
result = lisp_cons(get_sym(SYM_BEGIN, ctx), lisp_list_reverse(result), ctx);
|
||||
}
|
||||
|
||||
if (out_error)
|
||||
*out_error = error;
|
||||
if (out_error) *out_error = error;
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
Lisp lisp_read(const char *program, LispError* out_error, LispContext ctx)
|
||||
@@ -2124,7 +2122,6 @@ void* fread_all_(FILE* file, size_t* out_size) {
|
||||
return NULL;
|
||||
}
|
||||
data = new_data;
|
||||
memset(data + *out_size, 0, cap - *out_size);
|
||||
}
|
||||
|
||||
size_t read = fread(data + *out_size, 1, BLOCK_SIZE, file);
|
||||
@@ -3186,17 +3183,17 @@ LispContext lisp_init(void)
|
||||
ctx.p->macros = lisp_make_table(ctx);
|
||||
|
||||
Lisp* c = ctx.p->symbol_cache;
|
||||
c[SYM_IF] = lisp_make_symbol("if", ctx);
|
||||
c[SYM_BEGIN] = lisp_make_symbol("begin", ctx);
|
||||
c[SYM_QUOTE] = lisp_make_symbol("quote", ctx);
|
||||
c[SYM_QUASI_QUOTE] = lisp_make_symbol("quasiquote", ctx);
|
||||
c[SYM_UNQUOTE] = lisp_make_symbol("unquote", ctx);
|
||||
c[SYM_UNQUOTE_SPLICE] = lisp_make_symbol("unquotesplice", ctx);
|
||||
c[SYM_DEFINE] = lisp_make_symbol("_def", ctx);
|
||||
c[SYM_DEFINE_MACRO] = lisp_make_symbol("define-macro", ctx);
|
||||
c[SYM_SET] = lisp_make_symbol("_set!", ctx);
|
||||
c[SYM_IF] = lisp_make_symbol("IF", ctx);
|
||||
c[SYM_BEGIN] = lisp_make_symbol("BEGIN", ctx);
|
||||
c[SYM_QUOTE] = lisp_make_symbol("QUOTE", ctx);
|
||||
c[SYM_QUASI_QUOTE] = lisp_make_symbol("QUASIQUOTE", ctx);
|
||||
c[SYM_UNQUOTE] = lisp_make_symbol("UNQUOTE", ctx);
|
||||
c[SYM_UNQUOTE_SPLICE] = lisp_make_symbol("UNQUOTESPLICE", ctx);
|
||||
c[SYM_DEFINE] = lisp_make_symbol("_DEF", ctx);
|
||||
c[SYM_DEFINE_MACRO] = lisp_make_symbol("DEFINE-MACRO", ctx);
|
||||
c[SYM_SET] = lisp_make_symbol("_SET!", ctx);
|
||||
c[SYM_LAMBDA] = lisp_make_symbol("/\\_", ctx);
|
||||
c[SYM_CONS] = lisp_make_symbol("cons", ctx);
|
||||
c[SYM_CONS] = lisp_make_symbol("CONS", ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
|
||||
+203
-200
@@ -36,7 +36,7 @@ static const char* lib_0_sequences_src_ =
|
||||
(if (pair? args) \n\
|
||||
(if (pair? (cdr args)) \n\
|
||||
(if (pair? (cdr (cdr args))) \n\
|
||||
`(/\\_ ,(car args) ,(cons 'begin (cdr args))) \n\
|
||||
`(/\\_ ,(car args) ,(cons 'BEGIN (cdr args))) \n\
|
||||
`(/\\_ ,(car args) ,(car (cdr args)))) \n\
|
||||
(syntax-error \"lambda missing body expressions: (lambda (args) body)\")) \n\
|
||||
(syntax-error \"lambda missing argument: (lambda (args) body)\")))) \n\
|
||||
@@ -44,18 +44,18 @@ static const char* lib_0_sequences_src_ =
|
||||
(define-macro set! (lambda (var x) \n\
|
||||
(begin \n\
|
||||
(if (not (symbol? var)) (syntax-error \"set! not a variable\")) \n\
|
||||
`(_set! ,var ,x)))) \n\
|
||||
`(_SET! ,var ,x)))) \n\
|
||||
\n\
|
||||
(define-macro define \n\
|
||||
(lambda (var . exprs) \n\
|
||||
(if (symbol? var) \n\
|
||||
(if (pair? (cdr exprs)) \n\
|
||||
(syntax-error \"define: (define var x)\") \n\
|
||||
`(_def ,var ,(car exprs))) \n\
|
||||
`(_DEF ,var ,(car exprs))) \n\
|
||||
(if (pair? var) \n\
|
||||
`(_def ,(car var) \n\
|
||||
(lambda ,(cdr var) \n\
|
||||
,(if (null? (cdr exprs)) (car exprs) (cons 'begin exprs)))) \n\
|
||||
`(_DEF ,(car var) \n\
|
||||
(LAMBDA ,(cdr var) \n\
|
||||
,(if (null? (cdr exprs)) (car exprs) (cons 'BEGIN exprs)))) \n\
|
||||
(syntax-error \"define: not a symbol\") )))) \n\
|
||||
\n\
|
||||
(define (first x) (car x)) \n\
|
||||
@@ -98,13 +98,13 @@ static const char* lib_0_sequences_src_ =
|
||||
(define (_expand-shorthand-body path) \n\
|
||||
(if (null? path) (cons 'pair '()) \n\
|
||||
(list (if (char=? (car path) #\\A) \n\
|
||||
(cons 'car (_expand-shorthand-body (cdr path))))))) \n\
|
||||
(cons 'CAR (_expand-shorthand-body (cdr path))))))) \n\
|
||||
\n\
|
||||
(define (_expand-shorthand text) \n\
|
||||
(cons 'define (cons (list (string->symbol (string-append \"C\" text \"R\")) 'pair) \n\
|
||||
(cons 'DEFINE (cons (list (string->symbol (string-append \"C\" text \"R\")) 'pair) \n\
|
||||
(_expand-shorthand-body (string->list text))))) \n\
|
||||
\n\
|
||||
(define-macro _shorthand-accessors (lambda args (cons 'begin (map1 _expand-shorthand args)))) \n\
|
||||
(define-macro _shorthand-accessors (lambda args (cons 'BEGIN (map1 _expand-shorthand args)))) \n\
|
||||
\n\
|
||||
(define (vector . args) (list->vector args)) \n\
|
||||
\n\
|
||||
@@ -124,14 +124,14 @@ static const char* lib_0_sequences_src_ =
|
||||
|
||||
static const char* lib_1_forms_src_ =
|
||||
"(define (_make-lambda args body) \n\
|
||||
(list 'lambda args (if (null? (cdr body)) (car body) (cons 'begin body)))) \n\
|
||||
(list 'LAMBDA args (if (null? (cdr body)) (car body) (cons 'BEGIN body)))) \n\
|
||||
\n\
|
||||
\n\
|
||||
; (let <name> ((<var0> <expr0>) ... (<varN> <expr1>)) <body0> ... <bodyN>) \n\
|
||||
; => ((lambda (<var0> ... <varN>) (begin <body0> ... <bodyN>)) <expr0> ... <expr1>) \n\
|
||||
; (LET <name> ((<var0> <expr0>) ... (<varN> <expr1>)) <body0> ... <bodyN>) \n\
|
||||
; => ((LAMBDA (<var0> ... <varN>) (BEGIN <body0> ... <bodyN>)) <expr0> ... <expr1>) \n\
|
||||
; => named \n\
|
||||
; ((lambda () \n\
|
||||
; (define <name> (lambda (<var0> ... <varN>) (begin <body0> ... <bodyN>))) \n\
|
||||
; (define <name> (LAMBDA (<var0> ... <varN>) (BEGIN <body0> ... <bodyN>))) \n\
|
||||
; (<name> <expr0> ... <exprN>))) \n\
|
||||
\n\
|
||||
(define (_check-binding-list bindings) \n\
|
||||
@@ -145,7 +145,7 @@ static const char* lib_1_forms_src_ =
|
||||
(define initial-args (map1 (lambda (entry) (second entry)) bindings)) \n\
|
||||
(if (null? var) \n\
|
||||
(cons body-func initial-args) \n\
|
||||
(list (_make-lambda '() (list (list 'define var body-func) (cons var initial-args)))))) \n\
|
||||
(list (_make-lambda '() (list (list 'DEFINE var body-func) (cons var initial-args)))))) \n\
|
||||
\n\
|
||||
(define-macro let (lambda args \n\
|
||||
(if (pair? (first args)) \n\
|
||||
@@ -153,8 +153,8 @@ static const char* lib_1_forms_src_ =
|
||||
(_let->combination (first args) (second args) (cdr (cdr args)))))) \n\
|
||||
\n\
|
||||
(define (_let*-helper bindings body) \n\
|
||||
(if (null? bindings) (if (null? (cdr body)) (car body) (cons 'begin body)) \n\
|
||||
(list 'let (list (car bindings)) (_let*-helper (cdr bindings) body)))) \n\
|
||||
(if (null? bindings) (if (null? (cdr body)) (car body) (cons 'BEGIN body)) \n\
|
||||
(list 'LET (list (car bindings)) (_let*-helper (cdr bindings) body)))) \n\
|
||||
\n\
|
||||
(define-macro let* (lambda (bindings . body) \n\
|
||||
(_check-binding-list bindings) \n\
|
||||
@@ -163,17 +163,17 @@ static const char* lib_1_forms_src_ =
|
||||
(define-macro letrec (lambda (bindings . body) \n\
|
||||
(_check-binding-list bindings) \n\
|
||||
(cons (_make-lambda (map1 (lambda (entry) (first entry)) bindings) \n\
|
||||
(append (map1 (lambda (entry) (list 'set! (first entry) (second entry))) \n\
|
||||
(append (map1 (lambda (entry) (list 'SET! (first entry) (second entry))) \n\
|
||||
bindings) body)) \n\
|
||||
(map1 (lambda (entry) '()) bindings)))) \n\
|
||||
\n\
|
||||
\n\
|
||||
; (cond (<pred0> <expr0>) \n\
|
||||
; (COND (<pred0> <expr0>) \n\
|
||||
; (<pred1> <expr1>) \n\
|
||||
; ... \n\
|
||||
; (else <expr-1>)) \n\
|
||||
; => \n\
|
||||
; (if <pred0> <expr0> \n\
|
||||
; (IF <pred0> <expr0> \n\
|
||||
; (if <pred1> <expr1> \n\
|
||||
; .... \n\
|
||||
; (if <predN> <exprN> <expr-1>)) ... ) \n\
|
||||
@@ -187,11 +187,11 @@ static const char* lib_1_forms_src_ =
|
||||
\n\
|
||||
(define (_cond-helper clauses) \n\
|
||||
(if (null? clauses) '() \n\
|
||||
(if (eq? (car (car clauses)) 'else) \n\
|
||||
(cons 'begin (cdr (car clauses))) \n\
|
||||
(list 'if \n\
|
||||
(if (eq? (car (car clauses)) 'ELSE) \n\
|
||||
(cons 'BEGIN (cdr (car clauses))) \n\
|
||||
(list 'IF \n\
|
||||
(car (car clauses)) \n\
|
||||
(cons 'begin (cdr (car clauses))) \n\
|
||||
(cons 'BEGIN (cdr (car clauses))) \n\
|
||||
(_cond-helper (cdr clauses)))))) \n\
|
||||
\n\
|
||||
(define-macro cond (lambda clauses \n\
|
||||
@@ -206,26 +206,26 @@ static const char* lib_2_forms_src_ =
|
||||
(cond ((null? preds) #t) \n\
|
||||
((null? (cdr preds)) (car preds)) \n\
|
||||
(else \n\
|
||||
`(if ,(car preds) ,(_and-helper (cdr preds)) #f)))) \n\
|
||||
`(IF ,(car preds) ,(_and-helper (cdr preds)) #f)))) \n\
|
||||
(define-macro and (lambda preds (_and-helper preds))) \n\
|
||||
\n\
|
||||
(define (_or-helper preds var) \n\
|
||||
(cond ((null? preds) #f) \n\
|
||||
((null? (cdr preds)) (car preds)) \n\
|
||||
(else \n\
|
||||
`(begin (set! ,var ,(car preds)) \n\
|
||||
(if ,var ,var ,(_or-helper (cdr preds) var)))))) \n\
|
||||
`(BEGIN (SET! ,var ,(car preds)) \n\
|
||||
(IF ,var ,var ,(_or-helper (cdr preds) var)))))) \n\
|
||||
\n\
|
||||
(define-macro or (lambda preds \n\
|
||||
(let ((var (gensym))) \n\
|
||||
`(let ((,var '())) ,(_or-helper preds var))))) \n\
|
||||
`(LET ((,var '())) ,(_or-helper preds var))))) \n\
|
||||
\n\
|
||||
(define-macro case (lambda (key . clauses) \n\
|
||||
(let ((expr (gensym))) \n\
|
||||
`(let ((,expr ,key)) \n\
|
||||
,(cons 'cond (map1 (lambda (entry) \n\
|
||||
`(LET ((,expr ,key)) \n\
|
||||
,(cons 'COND (map1 (lambda (entry) \n\
|
||||
(cons (if (pair? (car entry)) \n\
|
||||
`(memv ,expr (quote ,(car entry))) \n\
|
||||
`(MEMV ,expr (quote ,(car entry))) \n\
|
||||
(car entry)) \n\
|
||||
(cdr entry))) clauses)))))) \n\
|
||||
\n\
|
||||
@@ -234,24 +234,23 @@ static const char* lib_2_forms_src_ =
|
||||
`(begin (set! ,l (cons ,v ,l)) ,l))) \n\
|
||||
\n\
|
||||
; (DO ((<var0> <init0> <step0>) ...) (<test> <result>) <body>) \n\
|
||||
(define-macro do \n\
|
||||
(lambda (vars loop-check . loops) \n\
|
||||
(let ( (names '()) (inits '()) (steps '()) (f (gensym)) ) \n\
|
||||
(for-each1 (lambda (var) \n\
|
||||
(push (car var) names) \n\
|
||||
(set! var (cdr var)) \n\
|
||||
(push (car var) inits) \n\
|
||||
(set! var (cdr var)) \n\
|
||||
(push (car var) steps)) vars) \n\
|
||||
`((LAMBDA () \n\
|
||||
(DEFINE ,f (LAMBDA ,names \n\
|
||||
(IF ,(car loop-check) \n\
|
||||
,(if (pair? (cdr loop-check)) (car (cdr loop-check)) '()) \n\
|
||||
,(cons 'BEGIN (append loops (list (cons f steps)))) ))) \n\
|
||||
,(cons f inits) \n\
|
||||
)) ))) \n\
|
||||
\n\
|
||||
(define-macro do \n\
|
||||
(lambda (vars loop-check . loops) \n\
|
||||
(let ( (names '()) (inits '()) (steps '()) (f (gensym)) ) \n\
|
||||
(for-each1 (lambda (var-spec) \n\
|
||||
(push (car var-spec) names) \n\
|
||||
(push (car (cdr var-spec)) inits) \n\
|
||||
(if (pair? (cdr (cdr var-spec))) \n\
|
||||
(push (car (cdr (cdr var-spec))) steps) \n\
|
||||
(push (car var-spec) steps))) vars) \n\
|
||||
`((lambda () \n\
|
||||
(define ,f (lambda ,names \n\
|
||||
(if ,(car loop-check) \n\
|
||||
,(if (pair? (cdr loop-check)) (car (cdr loop-check)) '()) \n\
|
||||
,(cons 'begin (append loops (list (cons f steps)))) ))) \n\
|
||||
,(cons f inits) \n\
|
||||
)) ))) \n\
|
||||
\n\
|
||||
(define-macro dotimes \n\
|
||||
(lambda (form body) \n\
|
||||
(apply (lambda (i n . result) \n\
|
||||
@@ -263,18 +262,18 @@ static const char* lib_2_forms_src_ =
|
||||
(define-macro swap! \n\
|
||||
(lambda (x y) \n\
|
||||
(let ((temp (gensym))) \n\
|
||||
`(let ((,temp ,x)) \n\
|
||||
(set! ,temp ,x) \n\
|
||||
(set! ,x ,y) \n\
|
||||
(set! ,y ,temp))))) \n\
|
||||
`(LET ((,temp ,x)) \n\
|
||||
(SET! ,temp ,x) \n\
|
||||
(SET! ,x ,y) \n\
|
||||
(SET! ,y ,temp))))) \n\
|
||||
\n\
|
||||
(define-macro inc! ; CL incf \n\
|
||||
(lambda (x) \n\
|
||||
`(set! ,x (+ ,x 1)))) \n\
|
||||
`(SET! ,x (+ ,x 1)))) \n\
|
||||
\n\
|
||||
(define-macro dec! ; CL decf \n\
|
||||
(lambda (x) \n\
|
||||
`(set! ,x (- ,x 1))))";
|
||||
`(SET! ,x (- ,x 1))))";
|
||||
|
||||
static const char* lib_3_math_src_ =
|
||||
"(define (number? x) (real? x)) \n\
|
||||
@@ -446,7 +445,7 @@ static const char* lib_4_sequences_src_ =
|
||||
|
||||
static const char* lib_5_streams_src_ =
|
||||
"(define-macro delay (lambda (expr) \n\
|
||||
`(make-promise ,(cons 'lambda \n\
|
||||
`(make-promise ,(cons 'LAMBDA \n\
|
||||
(cons '() \n\
|
||||
(cons expr '())))))) \n\
|
||||
\n\
|
||||
@@ -1927,178 +1926,182 @@ static Lisp sch_is_cont(Lisp args, LispError* e, LispContext ctx)
|
||||
|
||||
static const LispFuncDef lib_cfunc_defs[] = {
|
||||
|
||||
{ "error", sch_error },
|
||||
{ "syntax-error", sch_syntax_error },
|
||||
{ "ERROR", sch_error },
|
||||
{ "SYNTAX-ERROR", sch_syntax_error },
|
||||
|
||||
// Output Procedures
|
||||
{ "_write", sch_write },
|
||||
{ "_display", sch_display },
|
||||
{ "_write-char", sch_write_char },
|
||||
{ "_flush-output-port", sch_flush },
|
||||
{ "_read", sch_read },
|
||||
{ "input-port?", sch_is_port_in },
|
||||
{ "output-port?", sch_is_port_out },
|
||||
{ "open-input-file", sch_open_input },
|
||||
{ "open-output-file", sch_open_output },
|
||||
{ "close-input-port", sch_port_close },
|
||||
{ "close-output-port", sch_port_close },
|
||||
{ "eof-object?", sch_is_eof },
|
||||
// Output Procedures https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Output-Procedures.html
|
||||
{ "_WRITE", sch_write },
|
||||
{ "_DISPLAY", sch_display },
|
||||
{ "_WRITE-CHAR", sch_write_char },
|
||||
{ "_FLUSH-OUTPUT-PORT", sch_flush },
|
||||
{ "_READ", sch_read },
|
||||
{ "INPUT-PORT?", sch_is_port_in },
|
||||
{ "OUTPUT-PORT?", sch_is_port_out },
|
||||
{ "OPEN-INPUT-FILE", sch_open_input },
|
||||
{ "OPEN-OUTPUT-FILE", sch_open_output },
|
||||
{ "CLOSE-INPUT-PORT", sch_port_close },
|
||||
{ "CLOSE-OUTPUT-PORT", sch_port_close },
|
||||
{ "EOF-OBJECT?", sch_is_eof },
|
||||
|
||||
// Universal Time
|
||||
{ "get-universal-time", sch_univeral_time },
|
||||
{ "print-gc-statistics", sch_print_gc_stats },
|
||||
// Universal Time https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Universal-Time.html
|
||||
{ "GET-UNIVERSAL-TIME", sch_univeral_time },
|
||||
{ "PRINT-GC-STATISTICS", sch_print_gc_stats },
|
||||
|
||||
{ "macroexpand", sch_macroexpand },
|
||||
{ "MACROEXPAND", sch_macroexpand },
|
||||
|
||||
// Equivalence Predicates
|
||||
{ "eq?", sch_exact_eq },
|
||||
{ "eqv?", sch_equal },
|
||||
{ "equal?", sch_equal_r },
|
||||
// Equivalence Predicates https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Equivalence-Predicates.html
|
||||
{ "EQ?", sch_exact_eq },
|
||||
{ "EQV?", sch_equal },
|
||||
{ "EQUAL?", sch_equal_r },
|
||||
|
||||
// Booleans
|
||||
{ "boolean?", sch_is_boolean },
|
||||
{ "not", sch_not },
|
||||
// Booleans https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Booleans.html
|
||||
{ "BOOLEAN?", sch_is_boolean },
|
||||
{ "NOT", sch_not },
|
||||
|
||||
// PAIRS
|
||||
{ "cons", sch_cons },
|
||||
{ "car", sch_car },
|
||||
{ "cdr", sch_cdr },
|
||||
{ "set-car!", sch_set_car },
|
||||
{ "set-cdr!", sch_set_cdr },
|
||||
{ "null?", sch_is_null },
|
||||
{ "pair?", sch_is_pair },
|
||||
{ "CONS", sch_cons },
|
||||
{ "CAR", sch_car },
|
||||
{ "CDR", sch_cdr },
|
||||
{ "SET-CAR!", sch_set_car },
|
||||
{ "SET-CDR!", sch_set_cdr },
|
||||
{ "NULL?", sch_is_null },
|
||||
{ "PAIR?", sch_is_pair },
|
||||
|
||||
// Lists
|
||||
{ "list", sch_list },
|
||||
{ "list?", sch_is_list },
|
||||
{ "make-list", sch_make_list },
|
||||
{ "list-copy", sch_list_copy },
|
||||
{ "length", sch_length },
|
||||
{ "append", sch_append },
|
||||
{ "append-reverse!", sch_append_reverse },
|
||||
{ "list-ref", sch_list_ref },
|
||||
{ "nthcdr", sch_list_advance },
|
||||
// Lists https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_8.html
|
||||
{ "LIST", sch_list },
|
||||
{ "LIST?", sch_is_list },
|
||||
{ "MAKE-LIST", sch_make_list },
|
||||
{ "LIST-COPY", sch_list_copy },
|
||||
{ "LENGTH", sch_length },
|
||||
{ "APPEND", sch_append },
|
||||
{ "APPEND-REVERSE!", sch_append_reverse },
|
||||
{ "LIST-REF", sch_list_ref },
|
||||
{ "NTHCDR", sch_list_advance },
|
||||
|
||||
// Vectors
|
||||
{ "vector?", sch_is_vector },
|
||||
{ "make-vector", sch_make_vector },
|
||||
{ "vector-grow", sch_vector_grow },
|
||||
{ "vector-length", sch_vector_length },
|
||||
{ "vector-set!", sch_vector_set },
|
||||
{ "vector-swap!", sch_vector_swap },
|
||||
{ "vector-ref", sch_vector_ref },
|
||||
{ "vector-fill!", sch_vector_fill },
|
||||
{ "vector-assq", sch_vector_assq },
|
||||
{ "subvector", sch_subvector },
|
||||
{ "list->vector", sch_list_to_vector },
|
||||
{ "vector->list", sch_vector_to_list },
|
||||
// Vectors https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_9.html#SEC82
|
||||
{ "VECTOR?", sch_is_vector },
|
||||
{ "MAKE-VECTOR", sch_make_vector },
|
||||
{ "VECTOR-GROW", sch_vector_grow },
|
||||
{ "VECTOR-LENGTH", sch_vector_length },
|
||||
{ "VECTOR-SET!", sch_vector_set },
|
||||
{ "VECTOR-SWAP!", sch_vector_swap },
|
||||
{ "VECTOR-REF", sch_vector_ref },
|
||||
{ "VECTOR-FILL!", sch_vector_fill },
|
||||
{ "VECTOR-ASSQ", sch_vector_assq },
|
||||
{ "SUBVECTOR", sch_subvector },
|
||||
{ "LIST->VECTOR", sch_list_to_vector },
|
||||
{ "VECTOR->LIST", sch_vector_to_list },
|
||||
|
||||
// Strings
|
||||
{ "string?", sch_is_string },
|
||||
{ "make-string", sch_make_string },
|
||||
{ "string=?", sch_equal_r },
|
||||
{ "string<?", sch_string_less },
|
||||
{ "substring", sch_substring },
|
||||
{ "string-null?", sch_string_is_null },
|
||||
{ "string-length", sch_string_length },
|
||||
{ "string-ref", sch_string_ref },
|
||||
{ "string-set!", sch_string_set },
|
||||
{ "string-upcase", sch_string_upcase },
|
||||
{ "string-downcase", sch_string_downcase },
|
||||
{ "string-append", sch_string_append },
|
||||
{ "string->list", sch_string_to_list },
|
||||
{ "list->string", sch_list_to_string },
|
||||
{ "string->number", sch_string_to_number },
|
||||
{ "number->string", sch_number_to_string },
|
||||
// Strings https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_7.html#SEC61
|
||||
{ "STRING?", sch_is_string },
|
||||
{ "MAKE-STRING", sch_make_string },
|
||||
{ "STRING=?", sch_equal_r },
|
||||
{ "STRING<?", sch_string_less },
|
||||
{ "SUBSTRING", sch_substring },
|
||||
{ "STRING-NULL?", sch_string_is_null },
|
||||
{ "STRING-LENGTH", sch_string_length },
|
||||
{ "STRING-REF", sch_string_ref },
|
||||
{ "STRING-SET!", sch_string_set },
|
||||
{ "STRING-UPCASE", sch_string_upcase },
|
||||
{ "STRING-DOWNCASE", sch_string_downcase },
|
||||
{ "STRING-APPEND", sch_string_append },
|
||||
{ "STRING->LIST", sch_string_to_list },
|
||||
{ "LIST->STRING", sch_list_to_string },
|
||||
{ "STRING->NUMBER", sch_string_to_number },
|
||||
{ "NUMBER->STRING", sch_number_to_string },
|
||||
|
||||
// Characters
|
||||
{ "char?", sch_is_char },
|
||||
{ "char=?", sch_equals },
|
||||
{ "char<?", sch_char_less },
|
||||
// Characters https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Characters.html#Characters
|
||||
{ "CHAR?", sch_is_char },
|
||||
{ "CHAR=?", sch_equals },
|
||||
{ "CHAR<?", sch_char_less },
|
||||
|
||||
{ "char-upcase", sch_char_upcase },
|
||||
{ "char-downcase", sch_char_downcase },
|
||||
{ "char-whitespace?", sch_char_is_white },
|
||||
{ "char-alphanumeric?", sch_char_is_alphanum },
|
||||
{ "char-alphabetic?", sch_char_is_alpha },
|
||||
{ "char-numeric?", sch_char_is_number },
|
||||
{ "char->integer", sch_char_to_int },
|
||||
{ "CHAR-UPCASE", sch_char_upcase },
|
||||
{ "CHAR-DOWNCASE", sch_char_downcase },
|
||||
{ "CHAR-WHITESPACE?", sch_char_is_white },
|
||||
{ "CHAR-ALPHANUMERIC?", sch_char_is_alphanum },
|
||||
{ "CHAR-ALPHABETIC?", sch_char_is_alpha },
|
||||
{ "CHAR-NUMERIC?", sch_char_is_number },
|
||||
{ "CHAR->INTEGER", sch_char_to_int },
|
||||
|
||||
// Numerical operations
|
||||
// Association Lists https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Association-Lists.html
|
||||
// Numerical operations https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Numerical-operations.html
|
||||
{ "=", sch_equals },
|
||||
{ "+", sch_add },
|
||||
{ "-", sch_sub },
|
||||
{ "*", sch_mult },
|
||||
{ "/", sch_divide },
|
||||
{ "<", sch_less },
|
||||
{ "integer?", sch_is_int },
|
||||
{ "even?", sch_is_even },
|
||||
{ "real?", sch_is_real },
|
||||
{ "exp", sch_exp },
|
||||
{ "expt", sch_power },
|
||||
{ "log", sch_log },
|
||||
{ "sin", sch_sin },
|
||||
{ "cos", sch_cos },
|
||||
{ "tan", sch_tan },
|
||||
{ "atan", sch_atan },
|
||||
{ "sqrt", sch_sqrt },
|
||||
{ "round", sch_round },
|
||||
{ "floor", sch_floor },
|
||||
{ "ceiling", sch_ceiling },
|
||||
{ "quotient", sch_quotient },
|
||||
{ "remainder", sch_remainder },
|
||||
{ "modulo", sch_modulo },
|
||||
{ "abs", sch_abs },
|
||||
{ "magnitude", sch_abs },
|
||||
{ "exact?", sch_is_int },
|
||||
{ "exact->inexact", sch_to_inexact },
|
||||
{ "inexact->exact", sch_to_exact },
|
||||
{ "INTEGER?", sch_is_int },
|
||||
{ "EVEN?", sch_is_even },
|
||||
{ "REAL?", sch_is_real },
|
||||
{ "EXP", sch_exp },
|
||||
{ "EXPT", sch_power },
|
||||
{ "LOG", sch_log },
|
||||
{ "SIN", sch_sin },
|
||||
{ "COS", sch_cos },
|
||||
{ "TAN", sch_tan },
|
||||
{ "ATAN", sch_atan },
|
||||
{ "SQRT", sch_sqrt },
|
||||
{ "ROUND", sch_round },
|
||||
{ "FLOOR", sch_floor },
|
||||
{ "CEILING", sch_ceiling },
|
||||
{ "QUOTIENT", sch_quotient },
|
||||
{ "REMAINDER", sch_remainder },
|
||||
{ "MODULO", sch_modulo },
|
||||
{ "ABS", sch_abs },
|
||||
{ "MAGNITUDE", sch_abs },
|
||||
{ "EXACT?", sch_is_int },
|
||||
{ "EXACT->INEXACT", sch_to_inexact },
|
||||
{ "INEXACT->EXACT", sch_to_exact },
|
||||
|
||||
// Symbols
|
||||
{ "symbol?", sch_is_symbol },
|
||||
{ "symbol<?", sch_symbol_less },
|
||||
{ "string->symbol", sch_string_to_symbol },
|
||||
{ "symbol->string", sch_symbol_to_string },
|
||||
{ "generate-uninterned-symbol", sch_gensym },
|
||||
{ "gensym", sch_gensym },
|
||||
// Symbols https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Symbols.html
|
||||
{ "SYMBOL?", sch_is_symbol },
|
||||
{ "SYMBOL<?", sch_symbol_less },
|
||||
{ "STRING->SYMBOL", sch_string_to_symbol },
|
||||
{ "SYMBOL->STRING", sch_symbol_to_string },
|
||||
{ "GENERATE-UNINTERNED-SYMBOL", sch_gensym },
|
||||
{ "GENSYM", sch_gensym },
|
||||
|
||||
// Environments
|
||||
{ "eval", sch_eval },
|
||||
{ "scheme-report-environment", sch_system_env },
|
||||
{ "interaction-environment", sch_user_env },
|
||||
// Environments https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_14.html
|
||||
{ "EVAL", sch_eval },
|
||||
{ "SCHEME-REPORT-ENVIRONMENT", sch_system_env },
|
||||
{ "INTERACTION-ENVIRONMENT", sch_user_env },
|
||||
// { "THE-ENVIRONMENT", sch_current_env },
|
||||
|
||||
// Hash Tables
|
||||
{ "hash-table?", sch_is_table },
|
||||
{ "make-hash-table", sch_table_make },
|
||||
{ "hash-table-set!", sch_table_set },
|
||||
{ "hash-table-ref", sch_table_get },
|
||||
{ "hash-table-size", sch_table_size },
|
||||
{ "hash-table->alist", sch_table_to_alist },
|
||||
// Hash Tables https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Basic-Hash-Table-Operations.html#Basic-Hash-Table-Operations
|
||||
{ "HASH-TABLE?", sch_is_table },
|
||||
{ "MAKE-HASH-TABLE", sch_table_make },
|
||||
{ "HASH-TABLE-SET!", sch_table_set },
|
||||
{ "HASH-TABLE-REF", sch_table_get },
|
||||
{ "HASH-TABLE-SIZE", sch_table_size },
|
||||
{ "HASH-TABLE->ALIST", sch_table_to_alist },
|
||||
|
||||
{ "promise?", sch_is_promise },
|
||||
{ "make-promise", sch_make_promise },
|
||||
{ "_promise-procedure", sch_promise_proc },
|
||||
{ "_promise-store!", sch_promise_store },
|
||||
{ "promise-value", sch_promise_val },
|
||||
{ "promise-forced?", sch_promise_forced },
|
||||
{ "PROMISE?", sch_is_promise },
|
||||
{ "MAKE-PROMISE", sch_make_promise },
|
||||
{ "_PROMISE-PROCEDURE", sch_promise_proc },
|
||||
{ "_PROMISE-STORE!", sch_promise_store },
|
||||
{ "PROMISE-VALUE", sch_promise_val },
|
||||
{ "PROMISE-FORCED?", sch_promise_forced },
|
||||
|
||||
// Procedures
|
||||
{ "apply", sch_apply },
|
||||
{ "compiled-procedure?", sch_is_func },
|
||||
{ "compound-procedure?", sch_is_lambda },
|
||||
{ "procedure-environment", sch_lambda_env },
|
||||
{ "procedure-body", sch_lambda_body },
|
||||
{ "call/cc", sch_call_cc },
|
||||
{ "continuation?", sch_is_cont },
|
||||
// Procedures https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Procedure-Operations.html#Procedure-Operations
|
||||
{ "APPLY", sch_apply },
|
||||
{ "COMPILED-PROCEDURE?", sch_is_func },
|
||||
{ "COMPOUND-PROCEDURE?", sch_is_lambda },
|
||||
{ "PROCEDURE-ENVIRONMENT", sch_lambda_env },
|
||||
// TOOD: Almost standard
|
||||
{ "PROCEDURE-BODY", sch_lambda_body },
|
||||
{ "CALL/CC", sch_call_cc },
|
||||
{ "CONTINUATION?", sch_is_cont },
|
||||
|
||||
// Random Numbers
|
||||
{ "random", sch_pseudo_rand },
|
||||
{ "random-seed!", sch_pseudo_seed },
|
||||
// Random Numbers https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Random-Numbers.html
|
||||
{ "RANDOM", sch_pseudo_rand },
|
||||
{ "RANDOM-SEED!", sch_pseudo_seed },
|
||||
|
||||
// Garbage Collection
|
||||
{ "gc-flip", sch_gc_flip },
|
||||
// Garbage Collection https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-user/Garbage-Collection.html
|
||||
{ "GC-FLIP", sch_gc_flip },
|
||||
|
||||
{ NULL, NULL }
|
||||
|
||||
};
|
||||
|
||||
void lisp_lib_load(LispContext ctx)
|
||||
@@ -2108,14 +2111,14 @@ void lisp_lib_load(LispContext ctx)
|
||||
|
||||
lisp_table_set(
|
||||
table,
|
||||
lisp_make_symbol("_current-output-port", ctx),
|
||||
lisp_make_symbol("_CURRENT-OUTPUT-PORT", ctx),
|
||||
lisp_make_port(stdout, 0),
|
||||
ctx
|
||||
);
|
||||
|
||||
lisp_table_set(
|
||||
table,
|
||||
lisp_make_symbol("_current-input-port", ctx),
|
||||
lisp_make_symbol("_CURRENT-INPUT-PORT", ctx),
|
||||
lisp_make_port(stdin, 1),
|
||||
ctx
|
||||
);
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ void editorFreeRow(erow *row);
|
||||
|
||||
void editorDelRow(int at);
|
||||
|
||||
void editorRowInsertChar(erow *row, int at, int c);
|
||||
void editorRowInsertChar(erow *row, int at, utf_8_char_t c);
|
||||
|
||||
void editorRowAppendString(erow *row, char *s, size_t len);
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#define COLOR_RESET "\033[0m"
|
||||
#define COLOR_KEYWORD "\033[1;35m" // Bold magenta
|
||||
#define COLOR_TYPE "\033[1;34m" // Bold blue
|
||||
#define COLOR_STRING "\033[1;32m" // Bold green
|
||||
#define COLOR_COMMENT "\033[0;36m" // Cyan
|
||||
#define COLOR_NUMBER "\033[1;33m" // Bold yellow
|
||||
#define COLOR_DEFAULT "\033[0;37m" // White
|
||||
|
||||
// Token types
|
||||
typedef enum {
|
||||
TOKEN_KEYWORD,
|
||||
TOKEN_TYPE,
|
||||
TOKEN_STRING,
|
||||
TOKEN_COMMENT,
|
||||
TOKEN_NUMBER,
|
||||
TOKEN_OPERATOR,
|
||||
TOKEN_DEFAULT
|
||||
} TokenType;
|
||||
|
||||
char *highlight_line(const char * line, int *length);
|
||||
+3
-1
@@ -25,10 +25,12 @@ void disableRawMode();
|
||||
|
||||
void enableRawMode();
|
||||
|
||||
int editorReadKey();
|
||||
KeyInfo * editorReadKey();
|
||||
|
||||
int getCursorPosition(int *rows, int *cols);
|
||||
|
||||
KeyInfo *stringToCodepoint(const char *string);
|
||||
|
||||
int getWindowSize(int *rows, int *cols);
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -17,8 +17,8 @@ fi
|
||||
echo "Create config files ..."
|
||||
mkdir -pv ~/.beluga/
|
||||
cp -rv ./assets/ ~/.beluga/
|
||||
mkdir -pv ~/.beluga/packages/
|
||||
mkdir -pv ~/.beluga/config/
|
||||
mkdir -pv ~/.beluga/packages/
|
||||
|
||||
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
|
||||
|
||||
@@ -21,11 +21,19 @@
|
||||
#include "include/output.h"
|
||||
#include "include/terminal.h"
|
||||
|
||||
#include <locale.h>
|
||||
#include <wchar.h>
|
||||
|
||||
struct editorConfig E;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
char * splash_screen = (char *) calloc(256, sizeof(char));
|
||||
|
||||
// Set support for utf-8
|
||||
setlocale(LC_ALL, "");
|
||||
|
||||
// INIT
|
||||
|
||||
enableRawMode();
|
||||
initEditor();
|
||||
@@ -40,7 +48,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
free(splash_screen);
|
||||
|
||||
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-q = quit");
|
||||
editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit");
|
||||
|
||||
|
||||
while (1) {
|
||||
|
||||
@@ -13,7 +13,6 @@ src_files = files(
|
||||
'main.c',
|
||||
'src/append_buffer.c',
|
||||
'src/editor_op.c',
|
||||
'src/syntax_highlighter.c',
|
||||
'src/file_io.c',
|
||||
'src/init.c',
|
||||
'src/input.c',
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
void abAppend(struct abuf *ab, const char *s, int len) {
|
||||
char *new = realloc(ab->b, ab->len + len);
|
||||
void abAppend(struct abuf *ab, const unsigned char *s, int len) {
|
||||
unsigned char *new = realloc(ab->b, ab->len + len);
|
||||
|
||||
if (new == NULL) {
|
||||
return;
|
||||
|
||||
+64
-328
@@ -1,459 +1,195 @@
|
||||
/**
|
||||
* @file builtins.c
|
||||
* @brief Built-in Lisp functions for editor operations
|
||||
* @details Provides Lisp bindings for core editor functionality including
|
||||
* cursor movement, file operations, keybinding management, and text manipulation
|
||||
*/
|
||||
|
||||
#include "../include/builtins.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/define.h"
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/file_io.h"
|
||||
#include "../include/input.h"
|
||||
#include "../include/file_io.h"
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/row_op.h"
|
||||
#include "include/output.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/terminal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* @brief Finds a prefix configuration by name
|
||||
* @details Searches the prefix array for a prefix matching the given name.
|
||||
* Returns the first prefix (default) if no match is found.
|
||||
* @param prefix_name Name of the prefix to search for (max 64 chars)
|
||||
* @return Matching prefix_t structure, or E.prefix[0] if not found
|
||||
* @note Updates global editor state E
|
||||
*/
|
||||
struct prefix_t find_prefix(const char prefix_name[64]) {
|
||||
int i = E.number_of_prefix + 1;
|
||||
while (i--) {
|
||||
if (!strcmp(prefix_name, E.prefix[i].prefix_name)) {
|
||||
return E.prefix[i];
|
||||
}
|
||||
}
|
||||
return E.prefix[0];
|
||||
utf_8_char_t make_utf8_char(const char *bytes, int len) {
|
||||
utf_8_char_t ch;
|
||||
ch.len = len;
|
||||
memcpy(ch.c, bytes, len);
|
||||
return ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to bind a command to a key sequence
|
||||
* @details Registers a keybinding with an associated Lisp command and optional
|
||||
* prefix modifier. Dynamically extends the keybind array.
|
||||
* @param args Lisp list of 3 arguments: (key-sequence command prefix-name)
|
||||
* - key_sequence (string): The key or key combination to bind
|
||||
* - command (Lisp): The Lisp command/function to execute
|
||||
* - prefix_name (string): Prefix modifier name
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Updates global editor state E (key_binds array)
|
||||
*/
|
||||
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
||||
/*
|
||||
* 3 arguments keybind command prefix
|
||||
*/
|
||||
const char *key_sequence = lisp_string(lisp_car(args));
|
||||
const char *key_string = lisp_string(lisp_car(args));
|
||||
KeyInfo *key = stringToCodepoint(key_string);
|
||||
args = lisp_cdr(args);
|
||||
// second argument
|
||||
Lisp func = lisp_car(args);
|
||||
|
||||
E.key_binds = (struct keyBind_t *)realloc(
|
||||
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
|
||||
E.key_binds[E.number_of_keybinds - 1].key_sequence =
|
||||
(char *)malloc(50 * sizeof(char));
|
||||
E.key_binds =
|
||||
(struct keyBind_t *)realloc(E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
|
||||
E.key_binds[E.number_of_keybinds - 1].key_sequence = (KeyInfo *) malloc(sizeof(KeyInfo));
|
||||
|
||||
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
|
||||
memcpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key, sizeof(KeyInfo));
|
||||
|
||||
E.key_binds[E.number_of_keybinds - 1].command = func;
|
||||
|
||||
// Third argument
|
||||
args = lisp_cdr(args);
|
||||
const char *prefix_name = lisp_string(lisp_car(args));
|
||||
struct prefix_t prefix = find_prefix(prefix_name);
|
||||
|
||||
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
|
||||
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to move cursor in a specified direction
|
||||
* @details Moves the editor cursor up, down, left, or right based on direction string.
|
||||
* @param args Lisp list with one argument: direction string
|
||||
* - "u": up, "d": down, "r": right, "l": left
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return Lisp boolean indicating whether movement was valid
|
||||
* @note Updates global editor state E
|
||||
* @see editorMoveCursor()
|
||||
*/
|
||||
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
|
||||
const char *direction = lisp_string(lisp_car(args));
|
||||
int is_in = 0;
|
||||
KeyInfo key;
|
||||
key.type = KEY_ARROW;
|
||||
switch (direction[0]) {
|
||||
case 'u':
|
||||
is_in = editorMoveCursor(ARROW_UP);
|
||||
key.data.arrow = 'A';
|
||||
break;
|
||||
case 'd':
|
||||
is_in = editorMoveCursor(ARROW_DOWN);
|
||||
key.data.arrow = 'B';
|
||||
break;
|
||||
case 'r':
|
||||
is_in = editorMoveCursor(ARROW_RIGHT);
|
||||
key.data.arrow = 'C';
|
||||
break;
|
||||
case 'l':
|
||||
is_in = editorMoveCursor(ARROW_LEFT);
|
||||
key.data.arrow = 'D';
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "move lisp %d\n", is_in);
|
||||
return lisp_make_bool(is_in);
|
||||
editorMoveCursor(&key);
|
||||
|
||||
|
||||
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) {
|
||||
fprintf(stderr, "quit\n");
|
||||
if (E.dirty && E.quit_times_buffer > 0) {
|
||||
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
|
||||
"another time to quit.");
|
||||
--E.quit_times_buffer;
|
||||
return lisp_null();
|
||||
}
|
||||
free_structs();
|
||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
||||
disableRawMode();
|
||||
lisp_shutdown(E.ctx);
|
||||
exit(0);
|
||||
|
||||
return lisp_null();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to save the current file
|
||||
* @details Wrapper around editorSave() for use in Lisp keybindings.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @see editorSave()
|
||||
*/
|
||||
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
|
||||
|
||||
Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx) {
|
||||
|
||||
editorSave();
|
||||
|
||||
|
||||
return lisp_null();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to insert a new line at cursor
|
||||
* @details Wrapper around editorInsertNewLine() for use in Lisp keybindings.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @see editorInsertNewLine()
|
||||
*/
|
||||
Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
|
||||
editorInsertNewLine();
|
||||
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx) {
|
||||
|
||||
// editorInsertNewLine();
|
||||
|
||||
return lisp_null();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to insert a tab (spaces) at cursor
|
||||
* @details Inserts TAB_LENGTH spaces at the current cursor position.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Uses E.constantes.TAB_LENGTH for indentation width
|
||||
*/
|
||||
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
|
||||
|
||||
for (int i = 0; i<E.constantes.TAB_LENGTH; ++i) {
|
||||
editorInsertChar(' ');
|
||||
}
|
||||
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to move cursor to beginning of line
|
||||
* @details Moves cursor to column 0 of the current line.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Updates global editor state E
|
||||
*/
|
||||
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
E.cursor_x = 0;
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to move cursor to end of line
|
||||
* @details Moves cursor to the end of the current line content.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Updates global editor state E
|
||||
*/
|
||||
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
Lisp moveCursorEndLine(Lisp args, LispError* e, LispContext ctx) {
|
||||
if (E.cursor_y < E.numrows) {
|
||||
E.cursor_x = E.row[E.cursor_y].size;
|
||||
}
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to delete character before cursor
|
||||
* @details Wrapper around editorDelChar() for use in Lisp keybindings.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @see editorDelChar()
|
||||
*/
|
||||
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
|
||||
|
||||
Lisp deletePreviousChar(Lisp args, LispError* e, LispContext ctx) {
|
||||
editorDelChar();
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to move cursor up by one screen
|
||||
* @details Scrolls up one full screen height, moving cursor to top of visible area.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Updates global editor state E
|
||||
* @see editorMoveCursor()
|
||||
*/
|
||||
Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
||||
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) {
|
||||
E.cursor_y = E.row_offset;
|
||||
int times = E.screenrows;
|
||||
KeyInfo key;
|
||||
key.type = KEY_ARROW;
|
||||
key.data.arrow = 'D';
|
||||
while (--times) {
|
||||
editorMoveCursor(ARROW_UP);
|
||||
editorMoveCursor(&key);
|
||||
}
|
||||
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;
|
||||
if (E.cursor_y > E.numrows) {
|
||||
E.cursor_y = E.numrows;
|
||||
}
|
||||
int times = E.screenrows;
|
||||
KeyInfo key;
|
||||
key.type = KEY_ARROW;
|
||||
key.data.arrow = 'D';
|
||||
while (--times) {
|
||||
editorMoveCursor(ARROW_DOWN);
|
||||
editorMoveCursor(&key);
|
||||
}
|
||||
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to open a file
|
||||
* @details Prompts user for filename and opens the file for editing.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Updates global editor state E
|
||||
* @see editorOpen()
|
||||
* @see editorPrompt()
|
||||
*/
|
||||
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
||||
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
|
||||
if (filename){
|
||||
if (filename)
|
||||
editorOpen(filename);
|
||||
}
|
||||
free(filename);
|
||||
|
||||
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to insert a character
|
||||
* @details Extracts a character from Lisp string argument and inserts it at cursor.
|
||||
* @param args Lisp list with one string argument
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Uses first character of the string argument
|
||||
*/
|
||||
|
||||
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
|
||||
char c = lisp_string(lisp_car(args))[0];
|
||||
editorInsertChar(c);
|
||||
char *c = lisp_string(lisp_car(args));
|
||||
utf_8_char_t ch = make_utf8_char(c, 1);
|
||||
editorInsertChar(&ch);
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to load and execute a package
|
||||
* @details Loads a Lisp package from the user's packages directory
|
||||
* (~/.beluga/packages/<package_name>/init.lisp) and evaluates it.
|
||||
* @param args Lisp list with one argument: package name (string)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Package files must be valid Lisp code
|
||||
* @note Package directory defaults to ~/.beluga/packages/
|
||||
*/
|
||||
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
|
||||
const char *package_name = lisp_string(lisp_car(args));
|
||||
fprintf(stderr, "%s\n", package_name);
|
||||
char *package_dir = (char *)calloc(256, sizeof(char));
|
||||
char *package_dir = (char *) calloc(256, sizeof(char));
|
||||
FILE *fd_package = NULL;
|
||||
strcat(package_dir, getenv("HOME"));
|
||||
strcat(package_dir, "/.beluga/packages/");
|
||||
strcat(package_dir, package_name);
|
||||
strcat(package_dir, "/init.lisp");
|
||||
fprintf(stderr, "%s\n", package_dir);
|
||||
fd_package = fopen(package_dir, "r");
|
||||
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
|
||||
E.ctx);
|
||||
fclose(fd_package);
|
||||
free(package_dir);
|
||||
|
||||
|
||||
return lisp_null();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to delete the current row
|
||||
* @details Removes the line at the current cursor position.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Updates global editor state E
|
||||
* @see editorDelRow()
|
||||
*/
|
||||
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) {
|
||||
editorDelRow(E.cursor_y);
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to search for text
|
||||
* @details Wrapper around editorFind() for use in Lisp keybindings.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @see editorFind()
|
||||
*/
|
||||
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) {
|
||||
fprintf(stderr, "LispFind\n");
|
||||
editorFind();
|
||||
return lisp_null();
|
||||
editorFind();
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to read character at cursor
|
||||
* @details Returns the character at the current cursor position as a Lisp character.
|
||||
* Returns 'a' if at end of line.
|
||||
* @param args Lisp arguments (unused)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return Lisp character object representing the character at cursor
|
||||
*/
|
||||
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
|
||||
Lisp returned_char;
|
||||
if (E.row[E.cursor_y].render[E.cursor_x] == 0) {
|
||||
returned_char = lisp_make_char('a');
|
||||
} else {
|
||||
|
||||
returned_char = lisp_make_char(E.row[E.cursor_y].render[E.cursor_x]);
|
||||
}
|
||||
return returned_char;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lisp function to set the current prefix mode
|
||||
* @details Changes the editor's prefix state to a named prefix, affecting which
|
||||
* keybindings are active. Updates status message to show active prefix.
|
||||
* @param args Lisp list with one argument: prefix name (string)
|
||||
* @param e Error pointer for Lisp error handling
|
||||
* @param ctx Lisp context
|
||||
* @return lisp_null()
|
||||
* @note Updates global editor state E (prefix_state)
|
||||
* @see find_prefix()
|
||||
*/
|
||||
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
|
||||
/*
|
||||
* Set the prefix state of editor to the prefix in argument
|
||||
*/
|
||||
const char *prefix_name = lisp_string(lisp_car(args));
|
||||
struct prefix_t prefix = find_prefix(prefix_name);
|
||||
E.prefix_state = prefix.prefix_id;
|
||||
editorSetStatusMessage("prefix %s", prefix.prefix_name);
|
||||
fprintf(stderr, "%s set\n", prefix_name);
|
||||
|
||||
// fprintf(stderr, "char read : %c\n", E.row[E.cursor_y].render[E.cursor_x]);
|
||||
// return lisp_make_char(E.row[E.cursor_y].render[E.cursor_x]);
|
||||
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();
|
||||
}
|
||||
|
||||
+71
-29
@@ -1,52 +1,94 @@
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/row_op.h"
|
||||
#include "include/data.h"
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
void editorInsertChar(int c) {
|
||||
if (E.cursor_y == E.numrows) {
|
||||
editorInsertRow(E.numrows, "", 0);
|
||||
}
|
||||
editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, c);
|
||||
E.cursor_x++;
|
||||
void editorInsertChar(utf_8_char_t *c) {
|
||||
if (E.state == READ_ONLY) return;
|
||||
fprintf(stderr, "Insert char %s %d\n", c->c, c->len);
|
||||
// If cursor is past end of file, add empty rows
|
||||
if (E.cursor_y == E.numrows) {
|
||||
editorInsertRow(E.numrows, "", 0);
|
||||
}
|
||||
|
||||
// Insert character at cursor position
|
||||
editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, *c);
|
||||
E.cursor_x++;
|
||||
}
|
||||
|
||||
void editorInsertNewLine() {
|
||||
/*
|
||||
* Add new line and place the cursor at the beginning of it
|
||||
*/
|
||||
fprintf(stderr, "Inserting new line\n");
|
||||
erow *row;
|
||||
if (!E.cursor_x) {
|
||||
void editorInsertNewline(void) {
|
||||
if (E.state == READ_ONLY) return;
|
||||
|
||||
if (E.cursor_x == 0) {
|
||||
// Insert blank line before current line
|
||||
editorInsertRow(E.cursor_y, "", 0);
|
||||
} else {
|
||||
row = &E.row[E.cursor_y];
|
||||
editorInsertRow(E.cursor_y + 1, &row->chars[E.cursor_x],
|
||||
row->size - E.cursor_x);
|
||||
row = &E.row[E.cursor_y];
|
||||
// Split current line at cursor
|
||||
erow *row = &E.row[E.cursor_y];
|
||||
|
||||
// Calculate byte length of remaining part
|
||||
int remaining_chars = row->size - E.cursor_x;
|
||||
|
||||
// Allocate buffer for remaining characters
|
||||
char *buf = malloc(remaining_chars * 4); // Max 4 bytes per UTF-8 char
|
||||
int buf_len = 0;
|
||||
|
||||
// Convert utf_8_char_t to bytes
|
||||
for (int i = E.cursor_x; i < row->size; i++) {
|
||||
for (int j = 0; j < row->chars[i].len; j++) {
|
||||
buf[buf_len++] = row->chars[i].c[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new row with remaining text
|
||||
editorInsertRow(E.cursor_y + 1, buf, buf_len);
|
||||
free(buf);
|
||||
|
||||
// Truncate current row at cursor
|
||||
row = &E.row[E.cursor_y]; // Refresh pointer after realloc
|
||||
row->size = E.cursor_x;
|
||||
row->chars[row->size] = '\0';
|
||||
editorUpdateRow(row);
|
||||
}
|
||||
++E.cursor_y;
|
||||
|
||||
E.cursor_y++;
|
||||
E.cursor_x = 0;
|
||||
fprintf(stderr, "Insert new line done\n");
|
||||
}
|
||||
|
||||
void editorDelChar() {
|
||||
erow *row;
|
||||
if (E.cursor_y == E.numrows || !(E.cursor_x || E.cursor_y)) {
|
||||
return;
|
||||
}
|
||||
row = &E.row[E.cursor_y];
|
||||
void editorRowAppendRow(erow *dest, erow *src) {
|
||||
// Allocate space for combined rows
|
||||
utf_8_char_t *new_chars = realloc(dest->chars,
|
||||
sizeof(utf_8_char_t) * (dest->size + src->size));
|
||||
if (!new_chars) return;
|
||||
|
||||
dest->chars = new_chars;
|
||||
|
||||
// Copy source row characters
|
||||
memcpy(&dest->chars[dest->size], src->chars, sizeof(utf_8_char_t) * src->size);
|
||||
dest->size += src->size;
|
||||
|
||||
editorUpdateRow(dest);
|
||||
++E.dirty;
|
||||
}
|
||||
|
||||
void editorDelChar(void) {
|
||||
if (E.state == READ_ONLY) return;
|
||||
if (E.cursor_y == E.numrows) return;
|
||||
if (E.cursor_x == 0 && E.cursor_y == 0) return;
|
||||
|
||||
erow *row = &E.row[E.cursor_y];
|
||||
|
||||
if (E.cursor_x > 0) {
|
||||
// Delete character before cursor
|
||||
editorRowDelchar(row, E.cursor_x - 1);
|
||||
--E.cursor_x;
|
||||
E.cursor_x--;
|
||||
} else {
|
||||
// At beginning of line - join with previous line
|
||||
E.cursor_x = E.row[E.cursor_y - 1].size;
|
||||
editorRowAppendString(&E.row[E.cursor_y - 1], row->chars, row->size);
|
||||
editorRowAppendRow(&E.row[E.cursor_y - 1], row);
|
||||
editorDelRow(E.cursor_y);
|
||||
--E.cursor_y;
|
||||
E.cursor_y--;
|
||||
}
|
||||
}
|
||||
|
||||
+120
-94
@@ -1,11 +1,3 @@
|
||||
/**
|
||||
* @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/input.h"
|
||||
#include "../include/output.h"
|
||||
@@ -15,6 +7,7 @@
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
extern char *strdup(const char *);
|
||||
extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
|
||||
@@ -22,53 +15,51 @@ extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
|
||||
extern int ftruncate(int fd, off_t length);
|
||||
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
|
||||
*/
|
||||
// Convert utf_8_char_t array to byte string
|
||||
char *editorRowsToString(int *buffer_len) {
|
||||
int tot_len = 0;
|
||||
int j;
|
||||
int j, i;
|
||||
char *buf;
|
||||
char *p;
|
||||
|
||||
// Calculate total byte length (not character count)
|
||||
for (j = 0; j < E.numrows; ++j) {
|
||||
tot_len += E.row[j].size + 1;
|
||||
// Count actual bytes in each character
|
||||
for (i = 0; i < E.row[j].size; i++) {
|
||||
tot_len += E.row[j].chars[i].len;
|
||||
}
|
||||
tot_len++; // For newline
|
||||
}
|
||||
|
||||
*buffer_len = tot_len;
|
||||
buf = malloc(tot_len);
|
||||
if (!buf) return NULL;
|
||||
|
||||
p = buf;
|
||||
for (j = 0; j < E.numrows; ++j) {
|
||||
memcpy(p, E.row[j].chars, E.row[j].size);
|
||||
p += E.row[j].size;
|
||||
*p = '\n';
|
||||
p++;
|
||||
// Copy each character's bytes
|
||||
for (i = 0; i < E.row[j].size; i++) {
|
||||
for (int k = 0; k < E.row[j].chars[i].len; k++) {
|
||||
*p++ = E.row[j].chars[i].c[k];
|
||||
}
|
||||
}
|
||||
*p++ = '\n';
|
||||
}
|
||||
|
||||
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) {
|
||||
// Free all rows
|
||||
for (int i = 0; i < E.numrows; i++) {
|
||||
editorFreeRow(&E.row[i]);
|
||||
}
|
||||
|
||||
E.cursor_x = 0;
|
||||
E.cursor_y = 0;
|
||||
E.rx = 0;
|
||||
E.row_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;
|
||||
free(E.row);
|
||||
E.row = NULL;
|
||||
@@ -79,64 +70,47 @@ void editorCloseFile(void) {
|
||||
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) {
|
||||
FILE *fp;
|
||||
|
||||
// Test if a file is already open
|
||||
if (E.filename != NULL) {
|
||||
editorCloseFile();
|
||||
E.state = READ_AND_WRITE;
|
||||
}
|
||||
E.state = READ_AND_WRITE;
|
||||
|
||||
E.filename = strdup(filename);
|
||||
|
||||
fp = fopen(filename, "a+");
|
||||
if (!fp)
|
||||
die("fopen");
|
||||
fp = fopen(filename, "r");
|
||||
if (!fp) {
|
||||
// File doesn't exist - that's okay, we'll create it on save
|
||||
E.dirty = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char *line = NULL;
|
||||
size_t line_cap = 0;
|
||||
ssize_t line_len;
|
||||
|
||||
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
|
||||
// Strip newline characters
|
||||
while (line_len > 0 &&
|
||||
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
|
||||
--line_len;
|
||||
}
|
||||
// editorInsertRow will convert bytes to utf_8_char_t
|
||||
editorInsertRow(E.numrows, line, line_len);
|
||||
free(line);
|
||||
line = NULL;
|
||||
}
|
||||
free(line);
|
||||
fclose(fp);
|
||||
E.dirty = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Saves the current file to disk
|
||||
* @details Prompts for filename if not set, converts all rows to a buffer,
|
||||
* writes to disk using open/ftruncate/write, and updates dirty flag.
|
||||
* Displays status messages on success or failure.
|
||||
* @note Updates global editor state E (dirty flag)
|
||||
* @note If no filename is set, prompts user via editorPrompt()
|
||||
* @note Uses O_RDWR | O_CREAT with mode 0644
|
||||
* @see editorRowsToString()
|
||||
*/
|
||||
void editorSave() {
|
||||
int len;
|
||||
char *buf;
|
||||
int fd;
|
||||
|
||||
if (E.filename == NULL) {
|
||||
E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
|
||||
if (E.filename == NULL) {
|
||||
@@ -144,48 +118,100 @@ void editorSave() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
buf = editorRowsToString(&len);
|
||||
fd = open(E.filename, O_RDWR | O_CREAT, 0644);
|
||||
if (!buf) {
|
||||
editorSetStatusMessage("Can't save! Memory error");
|
||||
return;
|
||||
}
|
||||
|
||||
fd = open(E.filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd != -1) {
|
||||
if (ftruncate(fd, len) != -1) {
|
||||
if (write(fd, buf, len) == len) {
|
||||
close(fd);
|
||||
free(buf);
|
||||
E.dirty = 0;
|
||||
editorSetStatusMessage("%d bytes written to disk", len);
|
||||
return;
|
||||
}
|
||||
if (write(fd, buf, len) == len) {
|
||||
close(fd);
|
||||
free(buf);
|
||||
E.dirty = 0;
|
||||
editorSetStatusMessage("%d bytes written to disk", len);
|
||||
return;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Searches for a string in the document
|
||||
* @details Prompts user for a search query, then searches forward from current
|
||||
* cursor position. Updates cursor position to the first match found.
|
||||
* @note Updates global editor state E (cursor position, row_offset)
|
||||
* @note Search is case-sensitive and operates on rendered line content
|
||||
* @note Searches begin from the line after current cursor position
|
||||
* @see editorPrompt()
|
||||
* @see editorRowRxToCx()
|
||||
*/
|
||||
void editorFind() {
|
||||
fprintf(stderr, "searching\n");
|
||||
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
|
||||
if (query == NULL) return;
|
||||
int i;
|
||||
for (i = E.cursor_y + 1; i < E.numrows; i++) {
|
||||
erow *row = &E.row[i];
|
||||
char *match = strstr(row->render, query);
|
||||
if (match) {
|
||||
E.cursor_y = i;
|
||||
E.cursor_x = editorRowRxToCx(row, match - row->render);
|
||||
E.row_offset = E.numrows;
|
||||
break;
|
||||
// Helper to convert utf_8_char_t array to byte string for searching
|
||||
static char *row_to_string(erow *row) {
|
||||
// Calculate byte length
|
||||
int byte_len = 0;
|
||||
for (int i = 0; i < row->rsize; i++) {
|
||||
byte_len += row->render[i].len;
|
||||
}
|
||||
}
|
||||
free(query);
|
||||
|
||||
char *str = malloc(byte_len + 1);
|
||||
if (!str) return NULL;
|
||||
|
||||
// Convert to bytes
|
||||
int pos = 0;
|
||||
for (int i = 0; i < row->rsize; i++) {
|
||||
for (int j = 0; j < row->render[i].len; j++) {
|
||||
str[pos++] = row->render[i].c[j];
|
||||
}
|
||||
}
|
||||
str[pos] = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void editorFind() {
|
||||
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
|
||||
if (query == NULL) return;
|
||||
|
||||
int saved_cursor_x = E.cursor_x;
|
||||
int saved_cursor_y = E.cursor_y;
|
||||
int saved_row_offset = E.row_offset;
|
||||
int saved_col_offset = E.col_offset;
|
||||
|
||||
// Search from current position forward
|
||||
for (int i = E.cursor_y; i < E.numrows; i++) {
|
||||
erow *row = &E.row[i];
|
||||
|
||||
// Convert row to byte string for searching
|
||||
char *render_str = row_to_string(row);
|
||||
if (!render_str) continue;
|
||||
|
||||
char *match = strstr(render_str, query);
|
||||
if (match) {
|
||||
E.cursor_y = i;
|
||||
|
||||
// Find the character index from byte position
|
||||
int byte_pos = match - render_str;
|
||||
int char_idx = 0;
|
||||
int current_byte = 0;
|
||||
|
||||
for (char_idx = 0; char_idx < row->rsize; char_idx++) {
|
||||
if (current_byte >= byte_pos) break;
|
||||
current_byte += row->render[char_idx].len;
|
||||
}
|
||||
|
||||
E.cursor_x = editorRowRxToCx(row, char_idx);
|
||||
E.row_offset = E.numrows; // Force scroll
|
||||
|
||||
free(render_str);
|
||||
free(query);
|
||||
return;
|
||||
}
|
||||
|
||||
free(render_str);
|
||||
}
|
||||
|
||||
// Not found - restore cursor position
|
||||
E.cursor_x = saved_cursor_x;
|
||||
E.cursor_y = saved_cursor_y;
|
||||
E.row_offset = saved_row_offset;
|
||||
E.col_offset = saved_col_offset;
|
||||
|
||||
editorSetStatusMessage("Not found: %s", query);
|
||||
free(query);
|
||||
}
|
||||
|
||||
+28
-36
@@ -1,46 +1,44 @@
|
||||
#include "../include/init.h"
|
||||
#include "../include/builtins.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/terminal.h"
|
||||
#include "../include/builtins.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define LISP_IMPLEMENTATION
|
||||
#include "../include/lisp.h"
|
||||
#include "../include/lisp_lib.h"
|
||||
|
||||
struct editorConfig;
|
||||
extern struct editorConfig;
|
||||
|
||||
|
||||
void registerBuiltin(char *key_sequence, LispCFunc f) {
|
||||
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
|
||||
lisp_make_func(f), E.ctx);
|
||||
|
||||
}
|
||||
|
||||
void initBuiltins() {
|
||||
// move cursor
|
||||
registerBuiltin("move-cursor", moveCursor);
|
||||
registerBuiltin("map-key", mapKey);
|
||||
registerBuiltin("editor-quit", editorQuit);
|
||||
registerBuiltin("editor-save", l_editorSave);
|
||||
registerBuiltin("editor-insert-new-line", l_editorInsertNewLine);
|
||||
registerBuiltin("move-cursor-beg-line", moveCursorBeginLine);
|
||||
registerBuiltin("move-cursor-end-line", moveCursorEndLine);
|
||||
registerBuiltin("editor-delete-previous-char", deletePreviousChar);
|
||||
registerBuiltin("move-cursor-page-up", editorMoveCursorPageUp);
|
||||
registerBuiltin("move-cursor-page-down", editorMoveCursorPageDown);
|
||||
registerBuiltin("editor-open-file", editorOpenFile);
|
||||
registerBuiltin("editor-insert-char", editorPrintC);
|
||||
registerBuiltin("add-package", addPackage);
|
||||
registerBuiltin("editor-del-row", editorDelRow_L);
|
||||
registerBuiltin("editor-find", editorFind_L);
|
||||
registerBuiltin("editor-read-char", editorReadChar_L);
|
||||
registerBuiltin("add-prefix", editorPrefix);
|
||||
registerBuiltin("editor-set-prefix", editorSetPrefix);
|
||||
registerBuiltin("editor-insert-tab", l_editorInserTab);
|
||||
registerBuiltin("MOVE-CURSOR", moveCursor);
|
||||
registerBuiltin("MAP-KEY", mapKey);
|
||||
registerBuiltin("EDITOR-QUIT", editorQuit);
|
||||
registerBuiltin("EDITOR-SAVE", l_editorSave);
|
||||
registerBuiltin("EDITOR-INSERT-NEW-LINE", l_editorInsertNewLine);
|
||||
registerBuiltin("MOVE-CURSOR-BEG-LINE", moveCursorBeginLine);
|
||||
registerBuiltin("MOVE-CURSOR-END-LINE", moveCursorEndLine);
|
||||
registerBuiltin("EDITOR-DELETE-PREVIOUS-CHAR", deletePreviousChar);
|
||||
registerBuiltin("MOVE-CURSOR-PAGE-UP", editorMoveCursorPageUp);
|
||||
registerBuiltin("MOVE-CURSOR-PAGE-DOWN", editorMoveCursorPageDown);
|
||||
registerBuiltin("EDITOR-OPEN-FILE", editorOpenFile);
|
||||
registerBuiltin("EDITOR-INSERT-CHAR", editorPrintC);
|
||||
registerBuiltin("ADD-PACKAGE", addPackage);
|
||||
registerBuiltin("EDITOR-DEL-ROW", editorDelRow_L);
|
||||
registerBuiltin("EDITOR-FIND", editorFind_L);
|
||||
registerBuiltin("EDITOR-READ-CHAR", editorReadChar_L);
|
||||
}
|
||||
|
||||
void initEditor() {
|
||||
E.init_file_path = (char *)calloc(256, sizeof(char));
|
||||
char * init_file_path = (char *) calloc(256, sizeof(char));
|
||||
E.cursor_x = 0;
|
||||
E.cursor_y = 0;
|
||||
E.rx = 0;
|
||||
@@ -59,18 +57,11 @@ void initEditor() {
|
||||
E.screenrows -= 2;
|
||||
|
||||
E.number_of_keybinds = 0;
|
||||
E.number_of_prefix = 0;
|
||||
// General prefix is 0 (no prefix)
|
||||
E.prefix = (struct prefix_t *)malloc(sizeof(struct prefix_t));
|
||||
E.prefix[0].prefix_id = 0;
|
||||
strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
|
||||
|
||||
E.prefix_state = 0;
|
||||
|
||||
strcat(E.init_file_path, getenv("HOME"));
|
||||
strcat(E.init_file_path, "/.beluga/config/init.lisp");
|
||||
// printf("%s\n", init_file_path);
|
||||
E.fd_init_file = fopen(E.init_file_path, "r");
|
||||
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.env = lisp_env(E.ctx);
|
||||
lisp_lib_load(E.ctx);
|
||||
@@ -79,11 +70,12 @@ void initEditor() {
|
||||
|
||||
// Read config file
|
||||
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
|
||||
free(init_file_path);
|
||||
if (E.ctx_error != LISP_ERROR_NONE) {
|
||||
die("init failed");
|
||||
}
|
||||
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
|
||||
|
||||
|
||||
// To modify
|
||||
|
||||
E.constantes.TAB_LENGTH =
|
||||
@@ -94,6 +86,6 @@ void initEditor() {
|
||||
(int)lisp_eval(lisp_read("QUIT-TIMES", &E.ctx_error, E.ctx), &E.ctx_error,
|
||||
E.ctx)
|
||||
.val.int_val;
|
||||
|
||||
fprintf(stderr, "Tab %d\n", E.constantes.QUIT_TIMES);
|
||||
E.quit_times_buffer = E.constantes.QUIT_TIMES;
|
||||
}
|
||||
|
||||
+109
-193
@@ -2,6 +2,7 @@
|
||||
#include "../include/define.h"
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/output.h"
|
||||
#include "include/data.h"
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <stdint.h>
|
||||
@@ -9,52 +10,30 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
/**
|
||||
* @file input.c
|
||||
* @brief Input handling module for the Beluga text editor
|
||||
* @details Manages user input processing, key bindings, cursor movement, and file path completion
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
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;
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
// Find dir name
|
||||
last_slash = strrchr(path, '/');
|
||||
char *last_slash = strrchr(path, '/');
|
||||
if (last_slash) {
|
||||
dir_len = last_slash - path + 1; // length of dir_path
|
||||
size_t dir_len = last_slash - path + 1;
|
||||
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;
|
||||
}
|
||||
@@ -65,45 +44,30 @@ const char *file_completion(const char *path) {
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strncmp(entry->d_name, predict, predict_len) == 0) {
|
||||
static char full_path[512];
|
||||
static char full_path[128];
|
||||
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
|
||||
strcat(full_path, "/");
|
||||
}
|
||||
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
|
||||
*/
|
||||
* \fn char * editorPrompt(struct editorConfig *E, char *prompt, char bPathMode)
|
||||
* \brief Return user input in a prompt when enter is hit. */
|
||||
|
||||
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||
size_t buf_size = 128;
|
||||
char *buf = malloc(buf_size);
|
||||
size_t buf_len = 0;
|
||||
int c = 0;
|
||||
buf[0] = '\0';
|
||||
strcpy(buf, placeHolder);
|
||||
buf_len = strlen(placeHolder);
|
||||
@@ -111,142 +75,73 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||
while (1) {
|
||||
editorSetStatusMessage(prompt, buf);
|
||||
editorRefreshScreen();
|
||||
c = editorReadKey();
|
||||
if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
|
||||
|
||||
KeyInfo *key = editorReadKey();
|
||||
|
||||
// Handle backspace/delete
|
||||
if (key->type == KEY_SPECIAL && (key->data.special == 127 || key->data.special == 8)) {
|
||||
if (buf_len != 0) {
|
||||
buf[--buf_len] = '\0';
|
||||
}
|
||||
} else if (c == ESCAPE) {
|
||||
}
|
||||
// Handle Ctrl+H (backspace)
|
||||
else if (key->type == KEY_CTRL && key->data.ctrl_char == 'H') {
|
||||
if (buf_len != 0) {
|
||||
buf[--buf_len] = '\0';
|
||||
}
|
||||
}
|
||||
// Handle ESC
|
||||
else if (key->type == KEY_SPECIAL && key->data.special == 27) {
|
||||
editorSetStatusMessage("");
|
||||
free(buf);
|
||||
return NULL;
|
||||
} else if (c == '\r') {
|
||||
}
|
||||
// Handle Enter
|
||||
else if (key->type == KEY_SPECIAL && (key->data.special == 13 || key->data.special == 10)) {
|
||||
if (buf_len != 0) {
|
||||
editorSetStatusMessage("");
|
||||
return buf;
|
||||
}
|
||||
} else if (bPathMode && c == '\t') {
|
||||
}
|
||||
// Handle Tab for path completion
|
||||
else if (bPathMode && key->type == KEY_SPECIAL && key->data.special == 9) {
|
||||
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);
|
||||
snprintf(path, sizeof(path), "%s/%s", pwd, buf);
|
||||
} 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) {
|
||||
|
||||
char *completion = file_completion(path);
|
||||
if (completion) {
|
||||
memset(buf, 0, buf_size);
|
||||
strcpy(buf, completion);
|
||||
buf_len = strlen(buf);
|
||||
free(completion);
|
||||
}
|
||||
}
|
||||
// Handle regular characters (ASCII only for prompts)
|
||||
else if (key->type == KEY_CHAR && key->data.codepoint < 128) {
|
||||
if (buf_len == buf_size - 1) {
|
||||
buf_size *= 2;
|
||||
buf = realloc(buf, buf_size);
|
||||
}
|
||||
buf[buf_len++] = c;
|
||||
buf[buf_len++] = (char)key->data.codepoint;
|
||||
buf[buf_len] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a key code to its string representation
|
||||
* @details Translates raw key codes (including special keys, control keys,
|
||||
* and regular characters) into human-readable string formats suitable for
|
||||
* display and keybinding configuration.
|
||||
* @param key The key code to convert
|
||||
* @return Pointer to static buffer containing the string representation.
|
||||
* Examples: "ENTER", "ARROW-UP", "CTRL-a", "TAB", "DELETE", etc.
|
||||
* @note Returns pointer to static buffer; string is overwritten on next call
|
||||
* @note Non-printable characters are formatted as "KEY-<number>"
|
||||
*/
|
||||
char *key_to_string(int key) {
|
||||
static char key_str[32];
|
||||
|
||||
char tmp[10];
|
||||
sprintf(tmp, "%d", key);
|
||||
|
||||
// First test enter key
|
||||
|
||||
if (key == '\r') {
|
||||
strcpy(key_str, "ENTER");
|
||||
} else if (key == '\t') {
|
||||
strcpy(key_str, "TAB");
|
||||
} else if (key >= 1 && key <= 26) { // CTRL keys
|
||||
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
|
||||
} else {
|
||||
switch (key) {
|
||||
case ARROW_UP:
|
||||
strcpy(key_str, "ARROW-UP");
|
||||
break;
|
||||
case ARROW_DOWN:
|
||||
strcpy(key_str, "ARROW-DOWN");
|
||||
break;
|
||||
case ARROW_LEFT:
|
||||
strcpy(key_str, "ARROW-LEFT");
|
||||
break;
|
||||
case ARROW_RIGHT:
|
||||
strcpy(key_str, "ARROW-RIGHT");
|
||||
break;
|
||||
case PAGE_UP:
|
||||
strcpy(key_str, "PAGE-UP");
|
||||
fprintf(stderr, "pagr up\n");
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
strcpy(key_str, "PAGE-DOWN");
|
||||
break;
|
||||
case DEL_KEY:
|
||||
strcpy(key_str, "DEL");
|
||||
|
||||
break;
|
||||
case BACKSPACE:
|
||||
strcpy(key_str, "BACKSPACE");
|
||||
break;
|
||||
case '\r':
|
||||
strcpy(key_str, "ENTER");
|
||||
break;
|
||||
case '\x1b':
|
||||
strcpy(key_str, "ESCAPE");
|
||||
break;
|
||||
case BEG_LINE:
|
||||
strcpy(key_str, "HOME");
|
||||
break;
|
||||
case END_LINE:
|
||||
strcpy(key_str, "END");
|
||||
break;
|
||||
default:
|
||||
// For regular characters
|
||||
if (isprint(key)) {
|
||||
snprintf(key_str, sizeof(key_str), "%c", key);
|
||||
} else {
|
||||
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return key_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Moves the cursor based on arrow key input
|
||||
* @details Updates cursor position (E.cursor_x, E.cursor_y) based on the given
|
||||
* key direction. Handles line wrapping and boundary conditions. Prevents cursor
|
||||
* from exceeding line lengths.
|
||||
* @param key The arrow key code (ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT)
|
||||
* @return 1 if cursor movement was valid, 0 if cursor was constrained to line boundary
|
||||
* @note Updates global editor state E
|
||||
*/
|
||||
int editorMoveCursor(int key) {
|
||||
void editorMoveCursor(KeyInfo *key) {
|
||||
if (key->type != KEY_ARROW) return;
|
||||
|
||||
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
|
||||
int row_len;
|
||||
switch (key) {
|
||||
case ARROW_RIGHT:
|
||||
|
||||
switch (key->data.arrow) {
|
||||
case 'C': // Right
|
||||
if (row && E.cursor_x < row->size) {
|
||||
++E.cursor_x;
|
||||
} else if (row && E.cursor_x == row->size) {
|
||||
@@ -254,17 +149,17 @@ int editorMoveCursor(int key) {
|
||||
E.cursor_x = 0;
|
||||
}
|
||||
break;
|
||||
case ARROW_DOWN:
|
||||
case 'B': // Down
|
||||
if (E.cursor_y < E.numrows) {
|
||||
++E.cursor_y;
|
||||
}
|
||||
break;
|
||||
case ARROW_UP:
|
||||
case 'A': // Up
|
||||
if (E.cursor_y != 0) {
|
||||
--E.cursor_y;
|
||||
}
|
||||
break;
|
||||
case ARROW_LEFT:
|
||||
case 'D': // Left
|
||||
if (E.cursor_x != 0) {
|
||||
--E.cursor_x;
|
||||
} else if (E.cursor_y > 0) {
|
||||
@@ -278,55 +173,76 @@ int editorMoveCursor(int key) {
|
||||
row_len = row ? row->size : 0;
|
||||
if (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 i;
|
||||
int previous_state = 0;
|
||||
fprintf(stderr, "pressed %s\n", key_sequence);
|
||||
for (i = 0; i < E.number_of_keybinds; ++i) {
|
||||
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
|
||||
if (E.prefix_state != E.key_binds[i].prefix_id) {
|
||||
continue;
|
||||
}
|
||||
previous_state = E.prefix_state;
|
||||
// It's a symbol, create a function call
|
||||
KeyInfo *stringToCodepoint(const char *string) {
|
||||
KeyInfo *key = (KeyInfo *)malloc(sizeof(KeyInfo));
|
||||
// test control key
|
||||
if (!strncmp("CTRL", string, 4)) {
|
||||
key->type = KEY_CTRL;
|
||||
key->data.ctrl_char = toupper(string[6]) + 64;
|
||||
} else if (!strncmp("ARROW", string, 5)) {
|
||||
key->type = KEY_ARROW;
|
||||
if (!strcmp("UP", string + 7)) {
|
||||
key->data.arrow = 'A';
|
||||
} else if (!strcmp("DOWN", string + 7)) {
|
||||
key->data.arrow = 'B';
|
||||
} else if (!strcmp("RIGHT", string + 7)) {
|
||||
key->data.arrow = 'C';
|
||||
} else if (!strcmp("LEFT", string + 7)) {
|
||||
key->data.arrow = 'D';
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static int key_match(KeyInfo *a, KeyInfo *b) {
|
||||
if (a->type != b->type) return 0;
|
||||
if (a->modifiers != b->modifiers) return 0;
|
||||
|
||||
switch (a->type) {
|
||||
case KEY_CTRL:
|
||||
return toupper(a->data.ctrl_char) == toupper(b->data.ctrl_char);
|
||||
case KEY_ALT:
|
||||
return a->data.alt_char == b->data.alt_char;
|
||||
case KEY_ARROW:
|
||||
return a->data.arrow == b->data.arrow;
|
||||
case KEY_FUNCTION:
|
||||
return a->data.function_num == b->data.function_num;
|
||||
case KEY_CHAR:
|
||||
return a->data.codepoint == b->data.codepoint;
|
||||
case KEY_SPECIAL:
|
||||
case KEY_NAVIGATION:
|
||||
return a->data.special == b->data.special;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int executeKeyBind(KeyInfo *key_sequence) {
|
||||
for (int i = 0; i < E.number_of_keybinds; ++i) {
|
||||
fprintf(stderr, "Keybind found\n");
|
||||
if (key_match(key_sequence, E.key_binds[i].key_sequence)) {
|
||||
// Execute the lisp command
|
||||
lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx),
|
||||
&E.ctx_error, E.ctx);
|
||||
if (E.prefix_state == previous_state)
|
||||
E.prefix_state = 0;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes a single keypress from the user
|
||||
* @details Reads a key, checks if it matches any registered keybinding,
|
||||
* and either executes the bound command or inserts the character. Resets
|
||||
* the quit buffer counter on successful key processing.
|
||||
* @note Updates global editor state E
|
||||
* @note Calls editorReadKey() to get input and editorInsertChar() for unbound keys
|
||||
*/
|
||||
void editorProcessKeypress() {
|
||||
int c = editorReadKey();
|
||||
KeyInfo *key = editorReadKey();
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
if (executeKeyBind(key_to_string(c))) {
|
||||
if (executeKeyBind(key)) {
|
||||
fprintf(stderr, "Keybinds found\n");
|
||||
return;
|
||||
}
|
||||
editorInsertChar(c);
|
||||
editorInsertChar(&key->c);
|
||||
E.quit_times_buffer = E.constantes.QUIT_TIMES;
|
||||
}
|
||||
|
||||
+22
-63
@@ -1,30 +1,22 @@
|
||||
/**
|
||||
* @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/syntax_highlighter.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
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()
|
||||
*/
|
||||
static void utf8_to_bytes(utf_8_char_t *chars, int count, unsigned char *output, int *output_len) {
|
||||
int pos = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int j = 0; j < chars[i].len; j++) {
|
||||
output[pos++] = chars[i].c[j];
|
||||
}
|
||||
fprintf(stderr, "bytes length : %s %d\n", chars[i].c, pos);
|
||||
}
|
||||
*output_len = pos;
|
||||
}
|
||||
|
||||
void editorDrawRows(struct abuf *ab) {
|
||||
int y;
|
||||
char welcome[80];
|
||||
@@ -60,22 +52,22 @@ void editorDrawRows(struct abuf *ab) {
|
||||
len = 0;
|
||||
if (len > E.screencols)
|
||||
len = E.screencols;
|
||||
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);
|
||||
if (len > 0) {
|
||||
unsigned char *display_buf = malloc(len * 4); // Max 4 bytes per char
|
||||
int byte_len;
|
||||
|
||||
utf8_to_bytes(&E.row[file_row].render[E.col_offset], len, display_buf,
|
||||
&byte_len);
|
||||
abAppend(ab, display_buf, byte_len);
|
||||
fprintf(stderr, "display buffer : %s %d\n", display_buf, byte_len);
|
||||
free(display_buf);
|
||||
}
|
||||
}
|
||||
abAppend(ab, ERASE_END_LINE, 3);
|
||||
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() {
|
||||
E.rx = E.cursor_x;
|
||||
if (E.cursor_y < E.numrows) {
|
||||
@@ -96,13 +88,6 @@ 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) {
|
||||
int len, render_len;
|
||||
char status[80], render_status[80];
|
||||
@@ -130,13 +115,6 @@ void editorDrawStatusBar(struct abuf *ab) {
|
||||
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) {
|
||||
int msg_len = strlen(E.status_msg);
|
||||
abAppend(ab, ERASE_END_LINE, 3);
|
||||
@@ -148,16 +126,6 @@ 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() {
|
||||
editorScroll();
|
||||
struct abuf ab = ABUF_INIT;
|
||||
@@ -175,20 +143,11 @@ void editorRefreshScreen() {
|
||||
abAppend(&ab, buf, strlen(buf));
|
||||
|
||||
abAppend(&ab, SHOW_CURSOR, 6);
|
||||
|
||||
write(STDOUT_FILENO, ab.b, ab.len);
|
||||
abFree(&ab);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
+106
-25
@@ -1,4 +1,6 @@
|
||||
#include "../include/row_op.h"
|
||||
#include "include/data.h"
|
||||
#include "include/define.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -6,11 +8,29 @@
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
static int is_tab(utf_8_char_t *ch) {
|
||||
return ch->len == 1 && ch->c[0] == '\t';
|
||||
}
|
||||
|
||||
// Helper function to check if two utf_8_char_t are equal
|
||||
static int utf8_char_equal(utf_8_char_t *a, utf_8_char_t *b) {
|
||||
if (a->len != b->len) return 0;
|
||||
return memcmp(a->c, b->c, a->len) == 0;
|
||||
}
|
||||
|
||||
// Helper function to create a space character
|
||||
static utf_8_char_t make_space() {
|
||||
utf_8_char_t space;
|
||||
space.c[0] = ' ';
|
||||
space.len = 1;
|
||||
return space;
|
||||
}
|
||||
|
||||
int editorRowCxToRx(erow *row, int cursor_x) {
|
||||
int render_x = 0;
|
||||
int i;
|
||||
for (i = 0; i < cursor_x; ++i) {
|
||||
if (row->chars[i] == '\t') {
|
||||
if (is_tab(&row->chars[i])) {
|
||||
render_x += (E.constantes.TAB_LENGTH - 1) - (render_x % E.constantes.TAB_LENGTH);
|
||||
}
|
||||
render_x++;
|
||||
@@ -22,7 +42,7 @@ int editorRowRxToCx(erow *row, int rx) {
|
||||
int cur_rx = 0;
|
||||
int cx;
|
||||
for (cx = 0; cx < row->size; cx++) {
|
||||
if (row->chars[cx] == '\t')
|
||||
if (is_tab(&row->chars[cx]))
|
||||
cur_rx += (E.constantes.TAB_LENGTH - 1) - (cur_rx % E.constantes.TAB_LENGTH);
|
||||
cur_rx++;
|
||||
if (cur_rx > rx) return cx;
|
||||
@@ -39,40 +59,42 @@ void editorUpdateRow(erow *row) {
|
||||
int i, i_render;
|
||||
int tabs = 0;
|
||||
|
||||
// counting number of tabs
|
||||
|
||||
// Count number of tabs
|
||||
for (i = 0; i < row->size; ++i) {
|
||||
tabs +=
|
||||
(row->chars[i] == '\t'); /**< increment tabs of 1 if chars[i] is one. */
|
||||
if (is_tab(&row->chars[i])) {
|
||||
tabs++;
|
||||
}
|
||||
}
|
||||
|
||||
free(row->render);
|
||||
row->render = malloc(row->size + tabs * (E.constantes.TAB_LENGTH - 1) +
|
||||
1); /**< Tabs needs E.constantes.TAB_LENGTH chars so E.constantes.TAB_LENGTH - 1
|
||||
more than the first already counted. */
|
||||
// Allocate space for utf_8_char_t array
|
||||
row->render = malloc(sizeof(utf_8_char_t) * (row->size + tabs * (E.constantes.TAB_LENGTH - 1)));
|
||||
|
||||
if (!row->render) {
|
||||
row->rsize = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// end of counting
|
||||
i_render = 0;
|
||||
for (i = 0; i < row->size; ++i) {
|
||||
if (row->chars[i] == '\t') {
|
||||
row->render[i_render++] = ' ';
|
||||
if (is_tab(&row->chars[i])) {
|
||||
// Replace tab with spaces
|
||||
row->render[i_render++] = make_space();
|
||||
while (i_render % E.constantes.TAB_LENGTH) {
|
||||
row->render[i_render++] =
|
||||
' '; /**< Addind the right amount of spaces for tabs */
|
||||
row->render[i_render++] = make_space();
|
||||
}
|
||||
} else {
|
||||
row->render[i_render++] = row->chars[i];
|
||||
}
|
||||
}
|
||||
row->render[i_render] = '\0'; // Don't forget the end of string character.
|
||||
row->rsize = i_render;
|
||||
}
|
||||
|
||||
void editorInsertRow(int at, char *s, size_t len) {
|
||||
if (at < 0 || at > E.numrows) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
erow *tmp = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1));
|
||||
if (!tmp) {
|
||||
return;
|
||||
@@ -80,19 +102,78 @@ void editorInsertRow(int at, char *s, size_t len) {
|
||||
E.row = tmp;
|
||||
memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));
|
||||
|
||||
E.row[at].size = len;
|
||||
E.row[at].chars = malloc(len + 1);
|
||||
memcpy(E.row[at].chars, s, len);
|
||||
E.row[at].chars[len] = '\0';
|
||||
|
||||
// Initialize the new row
|
||||
E.row[at].size = 0;
|
||||
E.row[at].chars = NULL;
|
||||
E.row[at].rsize = 0;
|
||||
E.row[at].render = NULL;
|
||||
|
||||
// Count UTF-8 characters first
|
||||
int char_count = 0;
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
unsigned char first = (unsigned char)s[i];
|
||||
int char_len;
|
||||
|
||||
if ((first & 0x80) == 0) {
|
||||
char_len = 1;
|
||||
} else if ((first & 0xE0) == 0xC0) {
|
||||
char_len = 2;
|
||||
} else if ((first & 0xF0) == 0xE0) {
|
||||
char_len = 3;
|
||||
} else if ((first & 0xF8) == 0xF0) {
|
||||
char_len = 4;
|
||||
} else {
|
||||
char_len = 1; // Invalid, treat as single byte
|
||||
}
|
||||
|
||||
i += char_len;
|
||||
char_count++;
|
||||
}
|
||||
|
||||
// Allocate for the actual number of characters
|
||||
if (char_count > 0) {
|
||||
E.row[at].chars = malloc(sizeof(utf_8_char_t) * char_count);
|
||||
if (!E.row[at].chars) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now convert to utf_8_char_t array
|
||||
i = 0;
|
||||
E.row[at].size = 0;
|
||||
while (i < len && E.row[at].size < char_count) {
|
||||
utf_8_char_t ch;
|
||||
|
||||
unsigned char first = (unsigned char)s[i];
|
||||
if ((first & 0x80) == 0) {
|
||||
ch.len = 1;
|
||||
} else if ((first & 0xE0) == 0xC0) {
|
||||
ch.len = 2;
|
||||
} else if ((first & 0xF0) == 0xE0) {
|
||||
ch.len = 3;
|
||||
} else if ((first & 0xF8) == 0xF0) {
|
||||
ch.len = 4;
|
||||
} else {
|
||||
ch.len = 1;
|
||||
}
|
||||
|
||||
// Copy bytes
|
||||
for (int j = 0; j < ch.len && i < len; j++) {
|
||||
ch.c[j] = s[i++];
|
||||
}
|
||||
|
||||
E.row[at].chars[E.row[at].size++] = ch;
|
||||
}
|
||||
|
||||
editorUpdateRow(&E.row[at]);
|
||||
|
||||
|
||||
++E.numrows;
|
||||
++E.dirty;
|
||||
}
|
||||
|
||||
|
||||
void editorFreeRow(erow *row) {
|
||||
free(row->render);
|
||||
free(row->chars);
|
||||
@@ -112,16 +193,17 @@ void editorDelRow(int at) {
|
||||
* \fn editorRowInsertChar(erow *row, int at, int c)
|
||||
* \param at Index of where we want to insert the char */
|
||||
|
||||
void editorRowInsertChar(erow *row, int at, int c) {
|
||||
void editorRowInsertChar(erow *row, int at, utf_8_char_t c) {
|
||||
if (E.state == READ_ONLY)
|
||||
return;
|
||||
if (at < 0 || at > row->size) {
|
||||
at = row->size;
|
||||
}
|
||||
row->chars = realloc(row->chars, row->size + 2);
|
||||
row->chars = realloc(row->chars, row->size + 1);
|
||||
memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
|
||||
++row->size;
|
||||
++(row->size);
|
||||
row->chars[at] = c;
|
||||
fprintf(stderr, "Row insert : %s %d\n", c.c, c.len);
|
||||
editorUpdateRow(row);
|
||||
++E.dirty;
|
||||
}
|
||||
@@ -130,7 +212,6 @@ void editorRowAppendString(erow *row, char *s, size_t len) {
|
||||
row->chars = realloc(row->chars, row->size + len + 1);
|
||||
memcpy(&row->chars[row->size], s, len);
|
||||
row->size += len;
|
||||
row->chars[row->size] = '\0';
|
||||
editorUpdateRow(row);
|
||||
++E.dirty;
|
||||
}
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#include "../include/syntax_highlighter.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
const char *c_keywords[] = {
|
||||
"if", "else", "while", "for", "do", "switch", "case", "break",
|
||||
"continue", "return", "goto", "struct", "union", "enum",
|
||||
"typedef", "static", "extern", "const", "volatile", "sizeof",
|
||||
"auto", "register", "inline", "restrict", NULL
|
||||
};
|
||||
|
||||
// C types
|
||||
const char *c_types[] = {
|
||||
"int", "char", "float", "double", "void", "long", "short",
|
||||
"unsigned", "signed", "bool", NULL
|
||||
};
|
||||
|
||||
// Check if character is alphanumeric or underscore
|
||||
int is_word_char(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '_';
|
||||
}
|
||||
|
||||
// Check if string is a keyword
|
||||
int is_keyword(const char *word) {
|
||||
for (int i = 0; c_keywords[i] != NULL; i++) {
|
||||
if (strcmp(word, c_keywords[i]) == 0) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if string is a type
|
||||
int is_type(const char *word) {
|
||||
for (int i = 0; c_types[i] != NULL; i++) {
|
||||
if (strcmp(word, c_types[i]) == 0) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get color code for token type
|
||||
const char *get_color(TokenType type) {
|
||||
switch (type) {
|
||||
case TOKEN_KEYWORD: return COLOR_KEYWORD;
|
||||
case TOKEN_TYPE: return COLOR_TYPE;
|
||||
case TOKEN_STRING: return COLOR_STRING;
|
||||
case TOKEN_COMMENT: return COLOR_COMMENT;
|
||||
case TOKEN_NUMBER: return COLOR_NUMBER;
|
||||
default: return COLOR_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight a line of C code and return the highlighted string
|
||||
// Returns a newly allocated string that must be freed by the caller
|
||||
char *highlight_line(const char *line, int *length) {
|
||||
char *result = malloc(1024); // Allocate space for result
|
||||
int result_pos = 0;
|
||||
int i = 0;
|
||||
|
||||
while (line[i] != '\0' && line[i] != '\n') {
|
||||
// Skip whitespace
|
||||
if (line[i] == ' ' || line[i] == '\t') {
|
||||
result[result_pos++] = line[i++];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle line comments
|
||||
if (line[i] == '/' && line[i + 1] == '/') {
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_COMMENT);
|
||||
while (line[i] != '\0' && line[i] != '\n') {
|
||||
result[result_pos++] = line[i++];
|
||||
}
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle block comments
|
||||
if (line[i] == '/' && line[i + 1] == '*') {
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_COMMENT);
|
||||
result[result_pos++] = line[i++];
|
||||
result[result_pos++] = line[i++];
|
||||
while (line[i] != '\0') {
|
||||
if (line[i] == '*' && line[i + 1] == '/') {
|
||||
result[result_pos++] = line[i++];
|
||||
result[result_pos++] = line[i++];
|
||||
break;
|
||||
}
|
||||
result[result_pos++] = line[i++];
|
||||
}
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle strings
|
||||
if (line[i] == '"') {
|
||||
result_pos += sprintf(&result[result_pos], "%s\"", COLOR_STRING);
|
||||
i++;
|
||||
while (line[i] != '\0' && line[i] != '"') {
|
||||
if (line[i] == '\\') {
|
||||
result[result_pos++] = line[i++];
|
||||
result[result_pos++] = line[i++];
|
||||
} else {
|
||||
result[result_pos++] = line[i++];
|
||||
}
|
||||
}
|
||||
if (line[i] == '"') result[result_pos++] = line[i++];
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle character literals
|
||||
if (line[i] == '\'') {
|
||||
result_pos += sprintf(&result[result_pos], "%s'", COLOR_STRING);
|
||||
i++;
|
||||
while (line[i] != '\0' && line[i] != '\'') {
|
||||
if (line[i] == '\\') {
|
||||
result[result_pos++] = line[i++];
|
||||
result[result_pos++] = line[i++];
|
||||
} else {
|
||||
result[result_pos++] = line[i++];
|
||||
}
|
||||
}
|
||||
if (line[i] == '\'') result[result_pos++] = line[i++];
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle numbers
|
||||
if (line[i] >= '0' && line[i] <= '9') {
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_NUMBER);
|
||||
while (is_word_char(line[i]) || line[i] == '.') {
|
||||
result[result_pos++] = line[i++];
|
||||
}
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle identifiers and keywords
|
||||
if (is_word_char(line[i])) {
|
||||
int start = i;
|
||||
while (is_word_char(line[i])) i++;
|
||||
|
||||
char word[256];
|
||||
strncpy(word, &line[start], i - start);
|
||||
word[i - start] = '\0';
|
||||
|
||||
TokenType type = TOKEN_DEFAULT;
|
||||
if (is_keyword(word)) type = TOKEN_KEYWORD;
|
||||
else if (is_type(word)) type = TOKEN_TYPE;
|
||||
|
||||
result_pos += sprintf(&result[result_pos], "%s%s%s",
|
||||
get_color(type), word, COLOR_RESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle operators and other characters
|
||||
result[result_pos++] = line[i++];
|
||||
}
|
||||
|
||||
result[result_pos] = '\0';
|
||||
*length = result_pos + 1;
|
||||
return result;
|
||||
}
|
||||
+197
-57
@@ -2,6 +2,8 @@
|
||||
#include "../include/data.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
void die(const char *s) {
|
||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||
@@ -35,73 +37,211 @@ void enableRawMode() {
|
||||
}
|
||||
}
|
||||
|
||||
int editorReadKey() {
|
||||
int nread;
|
||||
char c;
|
||||
char seq[3];
|
||||
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
|
||||
if (nread == -1 && errno != EAGAIN) {
|
||||
die("read");
|
||||
int utf8_char_length(unsigned char first_byte) {
|
||||
if ((first_byte & 0x80) == 0)
|
||||
return 1; // 0xxxxxxx - ASCII
|
||||
if ((first_byte & 0xE0) == 0xC0)
|
||||
return 2; // 110xxxxx - 2 bytes
|
||||
if ((first_byte & 0xF0) == 0xE0)
|
||||
return 3; // 1110xxxx - 3 bytes
|
||||
if ((first_byte & 0xF8) == 0xF0)
|
||||
return 4; // 11110xxx - 4 bytes
|
||||
return 1; // Invalid, treat as single byte
|
||||
}
|
||||
|
||||
// Convert UTF-8 to Unicode code point
|
||||
unsigned int utf8_to_codepoint(const unsigned char *bytes, int len) {
|
||||
if (len == 1)
|
||||
return bytes[0];
|
||||
if (len == 2)
|
||||
return ((bytes[0] & 0x1F) << 6) | (bytes[1] & 0x3F);
|
||||
if (len == 3)
|
||||
return ((bytes[0] & 0x0F) << 12) | ((bytes[1] & 0x3F) << 6) |
|
||||
(bytes[2] & 0x3F);
|
||||
if (len == 4)
|
||||
return ((bytes[0] & 0x07) << 18) | ((bytes[1] & 0x3F) << 12) |
|
||||
((bytes[2] & 0x3F) << 6) | (bytes[3] & 0x3F);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void parse_key(unsigned char *seq, int len, KeyInfo *key) {
|
||||
memcpy(key->c.c, seq, len);
|
||||
key->c.len = len;
|
||||
key->modifiers = MOD_NONE;
|
||||
key->type = KEY_UNKNOWN;
|
||||
|
||||
// Control characters (Ctrl+A to Ctrl+Z)
|
||||
if (len == 1 && seq[0] < 32 && seq[0] != 27 && seq[0] != 9 && seq[0] != 10 &&
|
||||
seq[0] != 13) {
|
||||
key->type = KEY_CTRL;
|
||||
key->data.ctrl_char = seq[0] + 64;
|
||||
return;
|
||||
}
|
||||
|
||||
// Special single characters
|
||||
if (len == 1) {
|
||||
switch (seq[0]) {
|
||||
case 9:
|
||||
case 10:
|
||||
case 13:
|
||||
case 27:
|
||||
case 127:
|
||||
key->type = KEY_SPECIAL;
|
||||
key->data.special = seq[0];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '\x1b') {
|
||||
if (read(STDIN_FILENO, &seq[0], 1) != 1 ||
|
||||
read(STDIN_FILENO, &seq[1], 1) != 1) {
|
||||
return '\x1b';
|
||||
// Escape sequences
|
||||
if (len >= 2 && seq[0] == 27) {
|
||||
// Alt+key combinations
|
||||
if (len == 2 && seq[1] >= 32 && seq[1] < 127) {
|
||||
key->type = KEY_ALT;
|
||||
key->data.alt_char = seq[1];
|
||||
return;
|
||||
}
|
||||
if (seq[0] == '[') {
|
||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||
if (read(STDIN_FILENO, &seq[2], 1) != 1) {
|
||||
return '\x1b';
|
||||
|
||||
// CSI sequences (ESC [ ...)
|
||||
if (len >= 3 && seq[1] == '[') {
|
||||
// Arrow keys
|
||||
if (len == 3) {
|
||||
switch (seq[2]) {
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
key->type = KEY_ARROW;
|
||||
key->data.arrow = seq[2];
|
||||
return;
|
||||
case 'H':
|
||||
case 'F':
|
||||
key->type = KEY_NAVIGATION;
|
||||
key->data.special = seq[2];
|
||||
return;
|
||||
}
|
||||
if (seq[2] == '~') {
|
||||
switch (seq[1]) {
|
||||
case '1':
|
||||
return BEG_LINE;
|
||||
case '3':
|
||||
return DEL_KEY;
|
||||
case '4':
|
||||
return END_LINE;
|
||||
case '5':
|
||||
return PAGE_UP;
|
||||
case '6':
|
||||
return PAGE_DOWN;
|
||||
case '7':
|
||||
return BEG_LINE;
|
||||
case '8':
|
||||
return END_LINE;
|
||||
}
|
||||
|
||||
// Modified keys (ESC [ 1 ; modifier letter)
|
||||
if (len >= 6 && seq[2] == '1' && seq[3] == ';') {
|
||||
int modifier = seq[4] - '0';
|
||||
char k = seq[5];
|
||||
|
||||
if (modifier & 1)
|
||||
key->modifiers |= MOD_SHIFT;
|
||||
if (modifier & 2)
|
||||
key->modifiers |= MOD_ALT;
|
||||
if (modifier & 4)
|
||||
key->modifiers |= MOD_CTRL;
|
||||
|
||||
switch (k) {
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
key->type = KEY_ARROW;
|
||||
key->data.arrow = k;
|
||||
return;
|
||||
case 'H':
|
||||
case 'F':
|
||||
key->type = KEY_NAVIGATION;
|
||||
key->data.special = k;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Function keys and navigation
|
||||
if (len == 4 && seq[3] == '~') {
|
||||
int num = seq[2] - '0';
|
||||
if (num >= 1 && num <= 6) {
|
||||
key->type = KEY_NAVIGATION;
|
||||
key->data.special = seq[2];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == 5 && seq[4] == '~') {
|
||||
int num = (seq[2] - '0') * 10 + (seq[3] - '0');
|
||||
if (num >= 15 && num <= 24) {
|
||||
key->type = KEY_FUNCTION;
|
||||
// Map to F5-F12
|
||||
int f_map[] = {15, 17, 18, 19, 20, 21, 23, 24};
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (f_map[i] == num) {
|
||||
key->data.function_num = i + 5;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
switch (seq[1]) {
|
||||
case 'A':
|
||||
return ARROW_UP;
|
||||
case 'B':
|
||||
return ARROW_DOWN;
|
||||
case 'C':
|
||||
return ARROW_RIGHT;
|
||||
case 'D':
|
||||
return ARROW_LEFT;
|
||||
case 'H':
|
||||
return BEG_LINE;
|
||||
case 'F':
|
||||
return END_LINE;
|
||||
}
|
||||
}
|
||||
} else if (seq[0] == 'O') {
|
||||
switch (seq[1]) {
|
||||
case 'H':
|
||||
return BEG_LINE;
|
||||
case 'F':
|
||||
return END_LINE;
|
||||
}
|
||||
}
|
||||
return '\x1b';
|
||||
} else {
|
||||
return c;
|
||||
|
||||
// SS3 sequences (ESC O ...)
|
||||
if (len == 3 && seq[1] == 'O') {
|
||||
switch (seq[2]) {
|
||||
case 'P':
|
||||
case 'Q':
|
||||
case 'R':
|
||||
case 'S':
|
||||
key->type = KEY_FUNCTION;
|
||||
key->data.function_num = seq[2] - 'P' + 1;
|
||||
return;
|
||||
case 'H':
|
||||
case 'F':
|
||||
key->type = KEY_NAVIGATION;
|
||||
key->data.special = seq[2];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UTF-8 character
|
||||
if (seq[0] >= 32 || (seq[0] & 0x80)) {
|
||||
int char_len = utf8_char_length(seq[0]);
|
||||
fprintf(stderr, "char length : %d\n", char_len);
|
||||
if (char_len <= len) {
|
||||
key->type = KEY_CHAR;
|
||||
memcpy(key->c.c, seq, len);
|
||||
key->c.len = len;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeyInfo *editorReadKey() {
|
||||
fd_set fds;
|
||||
int timeout_ms = 10;
|
||||
struct timeval tv;
|
||||
int total = 0;
|
||||
KeyInfo *key = (KeyInfo *)malloc(sizeof(KeyInfo));
|
||||
int len;
|
||||
unsigned char buffer[20];
|
||||
|
||||
if (read(STDIN_FILENO, &buffer[0], 1) <= 0)
|
||||
return 0;
|
||||
|
||||
while (total < 20) {
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = timeout_ms * 1000;
|
||||
|
||||
int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
if (read(STDIN_FILENO, &buffer[total], 1) <= 0)
|
||||
break;
|
||||
total++;
|
||||
}
|
||||
total++;
|
||||
|
||||
parse_key(buffer, total, key);
|
||||
|
||||
// DEBUG
|
||||
|
||||
fprintf(stderr, "%s %d %d %s %d\n", buffer, buffer[0], buffer[1], key->c.c, key->c.len);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
int getCursorPosition(int *rows, int *cols) {
|
||||
|
||||
Reference in New Issue
Block a user