Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b6b3d1766 | |||
| 3d49a0e2eb | |||
| 777dc5cb74 | |||
| c4d772e465 | |||
| 310498d582 | |||
| edb5384f0e | |||
| bc66e673fa | |||
| 8e1b4d2f86 | |||
| 87746f09ce | |||
| 5a7b073041 | |||
| 557fc8894a | |||
| b039cf3ded | |||
| af1835d75b | |||
| f3bd5dd1c9 | |||
| fa7f8d39d8 | |||
| 23036cf3d3 | |||
| d0afd4f304 | |||
| de4fa78221 | |||
| fc93832130 | |||
| 39e07902f4 | |||
| 815114923d | |||
| 410f382592 | |||
| 80c0bf73e0 | |||
| d4ee0d86c2 | |||
| eae85c32ca | |||
| c06c820dfb | |||
| 5588b0a8d7 |
@@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: giorgio-runner
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# Beluga
|
# Beluga
|
||||||
|
|
||||||
Beluga is a project of CLI text editor that uses lisp as configuration language.
|
Beluga is a project of CLI text editor that uses lisp as configuration language.
|
||||||
It's abviously only working for **Linux**.
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ You will only need **meson** and a **C compiler** to compile the editor.
|
|||||||
### From source
|
### From source
|
||||||
Here is the installation line for development version:
|
Here is the installation line for development version:
|
||||||
|
|
||||||
```git clone https://homelinuxserver.ddns.net/git/arthur/beluga.git ~/.beluga && cd ~/.beluga && meson setup build && meson compile -C build```
|
```git clone https://git.giorgio-nas.fr/arthur/beluga.git && cd beluga && meson setup build && meson compile -C build```
|
||||||
|
|
||||||
The executable file will be `build/beluga`. Feel free to add it to your path.
|
The executable file will be `build/beluga`. Feel free to add it to your path.
|
||||||
|
|
||||||
@@ -26,8 +25,8 @@ To open an existing file just type :
|
|||||||
|
|
||||||
Here is some few command that you will need :
|
Here is some few command that you will need :
|
||||||
|
|
||||||
| keybind| command |
|
| keybind | command |
|
||||||
|--------|------------------|
|
|---------------|------------------|
|
||||||
| Ctrl-Q | leave the editor |
|
| Ctrl-x Ctrl-c | leave the editor |
|
||||||
| Ctrl-S | Save a file |
|
| Ctrl-x Ctrl-s | Save a file |
|
||||||
| Ctrl-O | open file |
|
| Ctrl-x f | open file |
|
||||||
|
|||||||
+39
-39
@@ -1,43 +1,43 @@
|
|||||||
**#%#*****###%**
|
**#%#*****###%**
|
||||||
*##+--------------------=##*
|
*##+--------------------=##*
|
||||||
#*=----------------------------=*#*
|
#*=----------------------------=*#*
|
||||||
#*------------------------------------*#
|
#*------------------------------------*#
|
||||||
%+----------------------------------------=#*
|
%+----------------------------------------=#*
|
||||||
#+---------------------------------------------##
|
#+---------------------------------------------##
|
||||||
*#-------------------------------------------------=##
|
*#-------------------------------------------------=##
|
||||||
*#----------------------------------------------------:-##
|
*#----------------------------------------------------:-##
|
||||||
#----------------------------------------------------------##
|
#----------------------------------------------------------##
|
||||||
#=--------------------------------------------------------------##
|
#=--------------------------------------------------------------##
|
||||||
+--------------------------+@#-%*-----------------------------------#*
|
+--------------------------+@#-%*-----------------------------------#*
|
||||||
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 1.1
|
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.4
|
||||||
*-=-------------------------#@@*---------------------------------------=%
|
*-=-------------------------#@@*---------------------------------------=%
|
||||||
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
|
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
|
||||||
*%%=-=--------------------------------------------------------------------=# CTRL-q leave
|
*%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave
|
||||||
%=--------------------------------------------------------------------------#* CTRL-s save
|
%=--------------------------------------------------------------------------#* CTRL-x CTRL-s save
|
||||||
%-----------------------------------------------------------------------------** CTRL-o open-file
|
%-----------------------------------------------------------------------------** CTRL-x f open-file
|
||||||
*+--=---===----=---------------=*-----------------------------------------------**
|
*+--=---===----=---------------=*-----------------------------------------------**
|
||||||
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
|
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
|
||||||
*%**=-----------==== ==---------------------------------=+#+-----------------=#
|
*%**=-----------==== ==---------------------------------=+#+-----------------=#
|
||||||
*%=----------------------------------------------------------=#*---------------#
|
*%=----------------------------------------------------------=#*---------------#
|
||||||
##=----------------------------------=------------------------+%=------------+#
|
##=----------------------------------=------------------------+%=------------+#
|
||||||
#%+---------------------------------=*------------------------+%------------#
|
#%+---------------------------------=*------------------------+%------------#
|
||||||
*#%*=-------------=-----------------#-------------------------#+----------#
|
*#%*=-------------=-----------------#-------------------------#+----------#
|
||||||
**#%#*******+=======-------------#=------------------------#----------#
|
**#%#*******+=======-------------#=------------------------#----------#
|
||||||
#===#*=======------------------#*----=-----------=--=##*-----------#
|
#===#*=======------------------#*----=-----------=--=##*-----------#
|
||||||
-====##=------------------------*%+------------=*#+=====----------#
|
-====##=------------------------*%+------------=*#+=====----------#
|
||||||
--=====+#*=----------------------=-=+*#####***+=======-----------=*
|
--=====+#*=----------------------=-=+*#####***+=======-----------=*
|
||||||
%------=====*%*=-------------------------========-----------------+*
|
%------=====*%*=-------------------------========-----------------+*
|
||||||
*-=--------====%%###+=--------------------------=-----------------#
|
*-=--------====%%###+=--------------------------=-----------------#
|
||||||
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
|
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
|
||||||
%=-------#* #%*=-----------------+#
|
%=-------#* #%*=-----------------+#
|
||||||
*%+--=## ##=-----------------=#*
|
*%+--=## ##=-----------------=#*
|
||||||
** #+----=-------------------#*
|
** #+----=-------------------#*
|
||||||
%+----------------------------#*
|
%+----------------------------#*
|
||||||
*%-------------==----------------+#
|
*%-------------==----------------+#
|
||||||
##--------------==------------------#
|
##--------------==------------------#
|
||||||
*#--------------===%-----------------=%
|
*#--------------===%-----------------=%
|
||||||
##---------------=-##*-----------------+#
|
##---------------=-##*-----------------+#
|
||||||
*#---------------==#+=#%-----------------%
|
*#---------------==#+=#%-----------------%
|
||||||
*%---------------+# %*---------------#*
|
*%---------------+# %*---------------#*
|
||||||
*#------------=+#* #%*=-----------#*
|
*#------------=+#* #%*=-----------#*
|
||||||
#****##****** *#%%##+=----%
|
#****##****** *#%%##+=----%
|
||||||
|
|||||||
+75
-17
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
(define TAB-LENGTH 4)
|
(define TAB-LENGTH 4)
|
||||||
(define QUIT-TIMES 1)
|
(define QUIT-TIMES 1)
|
||||||
|
(define THEME "dark")
|
||||||
|
|
||||||
|
;; PACKAGES
|
||||||
|
|
||||||
|
;; First git clone it
|
||||||
|
;; (add-package "smart_delimiters")
|
||||||
|
|
||||||
;; FUNCTIONS
|
;; FUNCTIONS
|
||||||
|
|
||||||
@@ -13,22 +19,74 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
;; KEY MAPPING
|
(define (char-between ch lo hi)
|
||||||
|
(if (char>=? ch lo)
|
||||||
|
(char<=? ch hi)
|
||||||
|
#f))
|
||||||
|
|
||||||
(map-key "CTRL-q" editor-quit)
|
(define (alphanumericp ch)
|
||||||
(map-key "CTRL-d" editor-save)
|
(if ch
|
||||||
(map-key "ARROW-UP" '(move-cursor "up"))
|
(if (char-between ch #\a #\z)
|
||||||
(map-key "ARROW-DOWN" '(move-cursor "down"))
|
#t
|
||||||
(map-key "ARROW-RIGHT" '(move-cursor "right"))
|
(if (char-between ch #\A #\Z)
|
||||||
(map-key "ARROW-LEFT" '(move-cursor "left"))
|
#t
|
||||||
(map-key "ENTER" editor-insert-new-line)
|
(if (char-between ch #\0 #\9)
|
||||||
(map-key "CTRL-a" move-cursor-beg-line)
|
#t
|
||||||
(map-key "CTRL-e" move-cursor-end-line)
|
#f)))
|
||||||
(map-key "BACKSPACE" editor-delete-previous-char)
|
#f))
|
||||||
(map-key "DEL" editor-delete-next-char)
|
|
||||||
(map-key "PAGE-UP" move-cursor-page-up)
|
|
||||||
(map-key "PAGE-DOWN" move-cursor-page-down)
|
|
||||||
(map-key "CTRL-o" editor-open-file)
|
|
||||||
(map-key "CTRL-k" editor-del-row)
|
|
||||||
(map-key "CTRL-s" editor-find)
|
|
||||||
|
|
||||||
|
|
||||||
|
(define (word-char-p ch)
|
||||||
|
(if (alphanumericp ch)
|
||||||
|
#t
|
||||||
|
#f))
|
||||||
|
|
||||||
|
(define editor-move-to-end-of-word (lambda () (
|
||||||
|
(if (word-char-p (editor-read-char))
|
||||||
|
((move-cursor "right")
|
||||||
|
(editor-move-to-end-of-word))
|
||||||
|
))
|
||||||
|
))
|
||||||
|
|
||||||
|
(define enter-and-tab
|
||||||
|
(lambda ()
|
||||||
|
(editor-insert-new-line)
|
||||||
|
(let ((is-in (move-cursor "up")))
|
||||||
|
(do ((ch (editor-read-char) (editor-read-char)))
|
||||||
|
((and (not (char=? ch #\space)) is-in) #f)
|
||||||
|
(move-cursor "down")
|
||||||
|
(editor-insert-char " ")
|
||||||
|
(set! is-in (move-cursor "up")))
|
||||||
|
(move-cursor "down"))))
|
||||||
|
|
||||||
|
(add-prefix "user")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(map-key "CTRL-x" '(editor-set-prefix "user") "no-prefix")
|
||||||
|
(map-key "CTRL-g" '(editor-set-prefix "no-prefix") "user")
|
||||||
|
(map-key "CTRL-c" editor-quit "user")
|
||||||
|
(map-key "CTRL-s" editor-save "user")
|
||||||
|
(map-key "ARROW-UP" '(move-cursor "up") "no-prefix")
|
||||||
|
(map-key "ARROW-DOWN" '(move-cursor "down") "no-prefix")
|
||||||
|
(map-key "ARROW-RIGHT" '(move-cursor "right") "no-prefix")
|
||||||
|
(map-key "ARROW-LEFT" '(move-cursor "left") "no-prefix")
|
||||||
|
(map-key "ENTER" enter-and-tab "no-prefix")
|
||||||
|
(map-key "CTRL-a" move-cursor-beg-line "no-prefix")
|
||||||
|
(map-key "CTRL-e" move-cursor-end-line "no-prefix")
|
||||||
|
(map-key "BACKSPACE" editor-delete-previous-char "no-prefix")
|
||||||
|
(map-key "DEL" editor-delete-next-char "no-prefix")
|
||||||
|
(map-key "PAGE-UP" move-cursor-page-up "no-prefix")
|
||||||
|
(map-key "PAGE-DOWN" move-cursor-page-down "no-prefix")
|
||||||
|
(map-key "f" editor-open-file "user")
|
||||||
|
(map-key "TAB" editor-insert-tab "no-prefix")
|
||||||
|
(map-key "CTRL-s" buffer-find "no-prefix")
|
||||||
|
(map-key "CTRL-r" buffer-find-reverse "no-prefix")
|
||||||
|
(map-key "ARROW-RIGHT" editor-switch-next-buffer "user")
|
||||||
|
(map-key "\"" editor-split-screen-vertical "user")
|
||||||
|
(map-key "o" editor-switch-next-pane "user")
|
||||||
|
(map-key "*" editor-unify-panes "user")
|
||||||
|
(map-key "CTRL-y" editor-paste "no-prefix")
|
||||||
|
(map-key "CTRL-k" editor-cut-end-line "no-prefix")
|
||||||
|
(map-key "a" editor-move-cursor-beg-buffer "user")
|
||||||
|
(map-key "z" editor-move-cursor-end-buffer "user")
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* @file buffer.h
|
||||||
|
* @brief Buffer management for handling multiple open files
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BUFFER_H_
|
||||||
|
#define BUFFER_H_
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a new buffer for a file
|
||||||
|
* @param filename Path to the file
|
||||||
|
* @return Buffer ID on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferCreate(const char *filename, enum bufferStatus_e state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switches to a specific buffer by ID
|
||||||
|
* @param buffer_id The buffer ID to switch to
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferSwitch(int buffer_id);
|
||||||
|
|
||||||
|
struct buffer_t *bufferFindById(int buffer_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Closes a buffer by ID
|
||||||
|
* @param buffer_id The buffer ID to close
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferClose(int buffer_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current active buffer
|
||||||
|
* @return Pointer to current buffer_t, NULL if none
|
||||||
|
*/
|
||||||
|
struct buffer_t *bufferGetCurrent(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lists all open buffers
|
||||||
|
* @return Number of open buffers
|
||||||
|
*/
|
||||||
|
int bufferListAll(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Saves current buffer to disk
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferSave(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Saves all buffers to disk
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferSaveAll(void);
|
||||||
|
|
||||||
|
void bufferFind(struct buffer_t *buf);
|
||||||
|
void bufferFindReverse(struct buffer_t* buf);
|
||||||
|
void bufferInsertNewLine();
|
||||||
|
void bufferInsertBytes(const char *src, int n);
|
||||||
|
void bufferDelBytes();
|
||||||
|
void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at, const char *src, int n);
|
||||||
|
void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n);
|
||||||
|
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len);
|
||||||
|
void bufferFreeRow(row_t *row);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
+41
-19
@@ -3,36 +3,58 @@
|
|||||||
|
|
||||||
#include "lisp.h"
|
#include "lisp.h"
|
||||||
|
|
||||||
|
// Mouvement
|
||||||
|
|
||||||
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx);
|
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
Lisp mapKey(Lisp args, LispError *e, LispContext ctx);
|
|
||||||
|
|
||||||
void registerBuiltin(char * key_sequence, LispCFunc f);
|
|
||||||
|
|
||||||
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx);
|
|
||||||
|
|
||||||
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx);
|
|
||||||
|
|
||||||
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx);
|
|
||||||
|
|
||||||
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx);
|
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx);
|
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
|
|
||||||
|
|
||||||
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
|
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
|
||||||
|
|
||||||
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx);
|
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx);
|
// Text editing
|
||||||
|
|
||||||
|
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx);
|
||||||
|
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
|
||||||
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx);
|
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
|
||||||
|
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp mapKey(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
void registerBuiltin(char * key_sequence, LispCFunc f);
|
||||||
|
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx);
|
||||||
Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
|
Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx);
|
|
||||||
|
|
||||||
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx);
|
// Buffer
|
||||||
|
|
||||||
|
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
|
|
||||||
|
// Pane
|
||||||
|
|
||||||
|
Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
|
|
||||||
|
Lisp editorPaste(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp editorCutEndLine(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
|
Lisp editorMoveBegBuffer(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
Lisp editorMoveEndBuffer(Lisp args, LispError *e, LispContext ctx);
|
||||||
|
|
||||||
|
// Other
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void free_structs(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
#ifndef COLOR_H_
|
||||||
|
#define COLOR_H_
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TEXT STYLES / ATTRIBUTES
|
||||||
|
// ============================================================================
|
||||||
|
#define ANSI_RESET "\x1b[0m" // Reset all attributes
|
||||||
|
#define ANSI_BOLD "\x1b[1m" // Bold text
|
||||||
|
#define ANSI_DIM "\x1b[2m" // Dim/faint text
|
||||||
|
#define ANSI_ITALIC "\x1b[3m" // Italic text
|
||||||
|
#define ANSI_UNDERLINE "\x1b[4m" // Underline text
|
||||||
|
#define ANSI_BLINK "\x1b[5m" // Blinking text
|
||||||
|
#define ANSI_REVERSE "\x1b[7m" // Reverse video (invert colors)
|
||||||
|
#define ANSI_HIDDEN "\x1b[8m" // Hidden/invisible text
|
||||||
|
#define ANSI_STRIKETHROUGH "\x1b[9m" // Strikethrough text
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FOREGROUND COLORS (30-37 standard, 90-97 bright)
|
||||||
|
// ============================================================================
|
||||||
|
#define ANSI_BLACK "\x1b[30m"
|
||||||
|
#define ANSI_RED "\x1b[31m"
|
||||||
|
#define ANSI_GREEN "\x1b[32m"
|
||||||
|
#define ANSI_YELLOW "\x1b[33m"
|
||||||
|
#define ANSI_BLUE "\x1b[34m"
|
||||||
|
#define ANSI_MAGENTA "\x1b[35m"
|
||||||
|
#define ANSI_CYAN "\x1b[36m"
|
||||||
|
#define ANSI_WHITE "\x1b[37m"
|
||||||
|
|
||||||
|
// Bright/Light foreground colors (90-97)
|
||||||
|
#define ANSI_BRIGHT_BLACK "\x1b[90m"
|
||||||
|
#define ANSI_BRIGHT_RED "\x1b[91m"
|
||||||
|
#define ANSI_BRIGHT_GREEN "\x1b[92m"
|
||||||
|
#define ANSI_BRIGHT_YELLOW "\x1b[93m"
|
||||||
|
#define ANSI_BRIGHT_BLUE "\x1b[94m"
|
||||||
|
#define ANSI_BRIGHT_MAGENTA "\x1b[95m"
|
||||||
|
#define ANSI_BRIGHT_CYAN "\x1b[96m"
|
||||||
|
#define ANSI_BRIGHT_WHITE "\x1b[97m"
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BACKGROUND COLORS (40-47 standard, 100-107 bright)
|
||||||
|
// ============================================================================
|
||||||
|
#define ANSI_BG_BLACK "\x1b[40m"
|
||||||
|
#define ANSI_BG_RED "\x1b[41m"
|
||||||
|
#define ANSI_BG_GREEN "\x1b[42m"
|
||||||
|
#define ANSI_BG_YELLOW "\x1b[43m"
|
||||||
|
#define ANSI_BG_BLUE "\x1b[44m"
|
||||||
|
#define ANSI_BG_MAGENTA "\x1b[45m"
|
||||||
|
#define ANSI_BG_CYAN "\x1b[46m"
|
||||||
|
#define ANSI_BG_WHITE "\x1b[47m"
|
||||||
|
|
||||||
|
// Bright/Light background colors (100-107)
|
||||||
|
#define ANSI_BG_BRIGHT_BLACK "\x1b[100m"
|
||||||
|
#define ANSI_BG_BRIGHT_RED "\x1b[101m"
|
||||||
|
#define ANSI_BG_BRIGHT_GREEN "\x1b[102m"
|
||||||
|
#define ANSI_BG_BRIGHT_YELLOW "\x1b[103m"
|
||||||
|
#define ANSI_BG_BRIGHT_BLUE "\x1b[104m"
|
||||||
|
#define ANSI_BG_BRIGHT_MAGENTA "\x1b[105m"
|
||||||
|
#define ANSI_BG_BRIGHT_CYAN "\x1b[106m"
|
||||||
|
#define ANSI_BG_BRIGHT_WHITE "\x1b[107m"
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 256-COLOR MODE (for more colors)
|
||||||
|
// ============================================================================
|
||||||
|
// Foreground: \x1b[38;5;Nm where N is 0-255
|
||||||
|
// Background: \x1b[48;5;Nm where N is 0-255
|
||||||
|
// Example macros:
|
||||||
|
#define ANSI_FG_256(N) "\x1b[38;5;" #N "m"
|
||||||
|
#define ANSI_BG_256(N) "\x1b[48;5;" #N "m"
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TRUE COLOR / 24-BIT COLOR (RGB)
|
||||||
|
// ============================================================================
|
||||||
|
// Foreground: \x1b[38;2;R;G;Bm
|
||||||
|
// Background: \x1b[48;2;R;G;Bm
|
||||||
|
// Example macros:
|
||||||
|
#define ANSI_FG_RGB(R,G,B) "\x1b[38;2;" #R ";" #G ";" #B "m"
|
||||||
|
#define ANSI_BG_RGB(R,G,B) "\x1b[48;2;" #R ";" #G ";" #B "m"
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CURSOR MOVEMENT
|
||||||
|
// ============================================================================
|
||||||
|
#define ANSI_CURSOR_UP(N) "\x1b[" #N "A" // Move up N lines
|
||||||
|
#define ANSI_CURSOR_DOWN(N) "\x1b[" #N "B" // Move down N lines
|
||||||
|
#define ANSI_CURSOR_RIGHT(N) "\x1b[" #N "C" // Move right N columns
|
||||||
|
#define ANSI_CURSOR_LEFT(N) "\x1b[" #N "D" // Move left N columns
|
||||||
|
#define ANSI_CURSOR_HOME "\x1b[H" // Move to home (0,0)
|
||||||
|
#define ANSI_CURSOR_HIDE "\x1b[?25l" // Hide cursor
|
||||||
|
#define ANSI_CURSOR_SHOW "\x1b[?25h" // Show cursor
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SCREEN CONTROL
|
||||||
|
// ============================================================================
|
||||||
|
#define ANSI_CLEAR_SCREEN "\x1b[2J" // Clear entire screen
|
||||||
|
#define ANSI_CLEAR_LINE "\x1b[K" // Clear from cursor to end of line
|
||||||
|
#define ANSI_CLEAR_UP "\x1b[1J" // Clear from cursor to start
|
||||||
|
#define ANSI_CLEAR_DOWN "\x1b[0J" // Clear from cursor to end
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// EXAMPLE USAGE
|
||||||
|
// ============================================================================
|
||||||
|
/*
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
// Simple color example
|
||||||
|
printf("%sHello in Red%s\n", ANSI_RED, ANSI_RESET);
|
||||||
|
|
||||||
|
// Bold and colored
|
||||||
|
printf("%s%sBold Green%s\n", ANSI_BOLD, ANSI_GREEN, ANSI_RESET);
|
||||||
|
|
||||||
|
// Background color
|
||||||
|
printf("%s%sYellow on Blue%s\n", ANSI_YELLOW, ANSI_BG_BLUE, ANSI_RESET);
|
||||||
|
|
||||||
|
// Combine multiple styles
|
||||||
|
printf("%s%s%sUnderlined Bold Cyan%s\n",
|
||||||
|
ANSI_UNDERLINE, ANSI_BOLD, ANSI_CYAN, ANSI_RESET);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endif
|
||||||
+97
-17
@@ -8,33 +8,103 @@
|
|||||||
#include "lisp.h"
|
#include "lisp.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \struct erow
|
* \struct row_t
|
||||||
* \brief Store one editor row
|
* \brief Store one editor row
|
||||||
* \param
|
* \param
|
||||||
* */
|
* */
|
||||||
|
|
||||||
typedef struct erow {
|
typedef struct row {
|
||||||
int size; /**< Size of the line */
|
int size; /**< Size of the line */
|
||||||
int rsize; /**< Size of the render line */
|
int cap; /**< Size of the render line */
|
||||||
char *chars; /**< Characters of the line */
|
char *chars; /**< Characters of the line */
|
||||||
char *render; /**< The actual line we will print */
|
} row_t;
|
||||||
} erow;
|
|
||||||
|
|
||||||
enum editorStatus_e {
|
typedef struct context_buffer_t
|
||||||
|
{
|
||||||
|
int editor_x, editor_y;
|
||||||
|
int width, height;
|
||||||
|
row_t *rows;
|
||||||
|
} ContextBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Split modes for screen layout
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
SPLIT_NONE = 0, // Single buffer fullscreen
|
||||||
|
SPLIT_VERTICAL, // Left-right split
|
||||||
|
SPLIT_HORIZONTAL // Top-bottom split
|
||||||
|
} SplitMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents an editor viewport/pane
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
int buffer_id; // Which buffer this pane displays
|
||||||
|
int height; // Height of this pane
|
||||||
|
int width; // Width of this pane
|
||||||
|
int origin_x, origin_y;
|
||||||
|
int cursor_x; // Local cursor x in this pane
|
||||||
|
int cursor_y; // Local cursor y in this pane
|
||||||
|
int x_offset, y_offset;
|
||||||
|
int is_active; // Is this pane currently active
|
||||||
|
} EditorPane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Screen layout manager
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
SplitMode mode;
|
||||||
|
EditorPane *panes;
|
||||||
|
int num_panes;
|
||||||
|
int active_pane; // Index of active pane
|
||||||
|
} ScreenLayout;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct theme {
|
||||||
|
char *BACKGROUND_COLOR;
|
||||||
|
char *COLOR_KEYWORD;
|
||||||
|
char *COLOR_TYPE;
|
||||||
|
char *COLOR_STRING;
|
||||||
|
char *COLOR_COMMENT;
|
||||||
|
char *COLOR_NUMBER;
|
||||||
|
char *COLOR_DEFAULT;
|
||||||
|
|
||||||
|
} theme_t;
|
||||||
|
|
||||||
|
enum bufferStatus_e {
|
||||||
IDLE,
|
IDLE,
|
||||||
READ_ONLY,
|
READ_ONLY,
|
||||||
READ_AND_WRITE,
|
READ_AND_WRITE,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct const_t {
|
struct const_t {
|
||||||
int TAB_LENGTH;
|
int TAB_LENGTH;
|
||||||
int QUIT_TIMES;
|
int QUIT_TIMES;
|
||||||
|
char *THEME;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct prefix_t {
|
||||||
|
char prefix_name[64];
|
||||||
|
int prefix_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct keyBind_t {
|
struct keyBind_t {
|
||||||
char *key_sequence;
|
char *key_sequence;
|
||||||
|
int prefix_id;
|
||||||
Lisp command;
|
Lisp command;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum buffer_type { FILE_BUFF, TERMINAL_BUFF };
|
||||||
|
|
||||||
|
struct buffer_t {
|
||||||
|
enum buffer_type type;
|
||||||
|
int buffer_id;
|
||||||
|
int x, y; /**< Position in the file */
|
||||||
|
row_t *row;
|
||||||
|
int numrows;
|
||||||
|
char *filename;
|
||||||
|
enum bufferStatus_e state;
|
||||||
|
int dirty; /**< Has this buffer been modified since last save */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,32 +113,42 @@ struct keyBind_t {
|
|||||||
*/
|
*/
|
||||||
struct editorConfig {
|
struct editorConfig {
|
||||||
int cursor_x, cursor_y; /**< Cursor position */
|
int cursor_x, cursor_y; /**< Cursor position */
|
||||||
int rx; /**< Position in the render*/
|
|
||||||
int row_offset; /**< Position scroll of lines */
|
|
||||||
int col_offset; /**< Position scroll of colomns*/
|
|
||||||
int screenrows; /**< Terminal height*/
|
int screenrows; /**< Terminal height*/
|
||||||
int screencols; /**< Terminal width*/
|
int screencols; /**< Terminal width*/
|
||||||
int numrows; /**< Number of rows contained */
|
|
||||||
erow *row; /**< Store all the rows printed */
|
ScreenLayout layout;
|
||||||
|
|
||||||
|
row_t *rows; /**< Store all the rows printed */
|
||||||
|
|
||||||
|
ContextBuffer* context_buffers;
|
||||||
|
|
||||||
int dirty;
|
int dirty;
|
||||||
char *filename;
|
|
||||||
enum editorStatus_e state;
|
char *status_msg;
|
||||||
char status_msg[80];
|
|
||||||
time_t status_msg_time;
|
time_t status_msg_time;
|
||||||
struct termios orig_termios; /**< Terminal communication interface */
|
struct termios orig_termios; /**< Terminal communication interface */
|
||||||
|
|
||||||
struct const_t constantes;
|
struct const_t constantes;
|
||||||
int quit_times_buffer;
|
int quit_times_buffer;
|
||||||
|
|
||||||
|
char *init_file_path;
|
||||||
FILE *fd_init_file;
|
FILE *fd_init_file;
|
||||||
Lisp env;
|
Lisp env;
|
||||||
LispContext ctx; /** Lisp context */
|
LispContext ctx; /** Lisp context */
|
||||||
Lisp ctx_data; /** Lisp data context */
|
Lisp ctx_data; /** Lisp data context */
|
||||||
LispError ctx_error; /** Lisp ctx error */
|
LispError ctx_error; /** Lisp ctx error */
|
||||||
|
|
||||||
struct keyBind_t* key_binds;
|
struct keyBind_t *key_binds;
|
||||||
int number_of_keybinds;
|
int number_of_keybinds;
|
||||||
|
|
||||||
|
struct prefix_t *prefix;
|
||||||
|
int number_of_prefix;
|
||||||
|
int prefix_state;
|
||||||
|
|
||||||
|
struct buffer_t buffers[64];
|
||||||
|
int number_of_buffer;
|
||||||
|
|
||||||
|
theme_t theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+26
-13
@@ -2,28 +2,41 @@
|
|||||||
#define DEFINE_H_
|
#define DEFINE_H_
|
||||||
|
|
||||||
#define CTRL_KEY(k) ((k) & 0x1f)
|
#define CTRL_KEY(k) ((k) & 0x1f)
|
||||||
|
#define ALT(k) ((k) | 0x200) // set bit 9 to mark as Option combo
|
||||||
|
|
||||||
|
|
||||||
#define ESCAPE '\x1b'
|
#define ESCAPE '\x1b'
|
||||||
#define CURSOR_TOP_LEFT "\x1b[H"
|
#define CURSOR_TOP_LEFT "\x1b[H"
|
||||||
#define HIDE_CURSOR "\x1b[?25l"
|
#define HIDE_CURSOR "\x1b[?25l"
|
||||||
#define SHOW_CURSOR "\x1b[?25h"
|
#define SHOW_CURSOR "\x1b[?25h"
|
||||||
#define ERASE_END_LINE "\x1b[K"
|
#define ERASE_END_LINE "\x1b[K"
|
||||||
|
#define TAB "\t"
|
||||||
|
#define SPACE "\x20"
|
||||||
|
#define APP_DEBUG
|
||||||
|
|
||||||
enum editorKey {
|
enum editorKey_e {
|
||||||
BACKSPACE = 127,
|
BACKSPACE = 127,
|
||||||
ARROW_LEFT = 1000,
|
ARROW_LEFT = 1000,
|
||||||
ARROW_RIGHT,
|
ARROW_RIGHT,
|
||||||
ARROW_UP,
|
ARROW_UP,
|
||||||
ARROW_DOWN,
|
ARROW_DOWN,
|
||||||
DEL_KEY,
|
DEL_KEY,
|
||||||
BEG_LINE,
|
BEG_LINE,
|
||||||
END_LINE,
|
END_LINE,
|
||||||
PAGE_UP,
|
PAGE_UP,
|
||||||
PAGE_DOWN,
|
PAGE_DOWN,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ABUF_INIT {NULL, 0}
|
#define ABUF_INIT {NULL, 0}
|
||||||
|
|
||||||
#define BELUGA_VERSION "1.1"
|
#define BELUGA_VERSION "2.4"
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#define CLIPBOARD_COPY_CMD "pbcopy"
|
||||||
|
#define CLIPBOARD_PASTE_CMD "pbpaste"
|
||||||
|
#else
|
||||||
|
#define CLIPBOARD_COPY_CMD "xclip -selection clipboard"
|
||||||
|
#define CLIPBOARD_PASTE_CMD "xclip -selection clipboard -o"
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // DEFINE_H_
|
#endif // DEFINE_H_
|
||||||
|
|||||||
+5
-3
@@ -2,10 +2,12 @@
|
|||||||
#define EDITOR_OP_H_
|
#define EDITOR_OP_H_
|
||||||
|
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
void editorInsertChar(int c);
|
|
||||||
|
|
||||||
void editorInsertNewLine();
|
void editorSetStatusMessage(const char *fmt, ...);
|
||||||
|
int editorRowCharCount(row_t *row, int x);
|
||||||
|
int editorRowCxToByte(const row_t *row, int cursor_x);
|
||||||
|
char *editorGetClipboard(void);
|
||||||
|
void editorSetClipboard(const char *text, int len);
|
||||||
|
|
||||||
void editorDelChar();
|
|
||||||
|
|
||||||
#endif // EDITOR_OP_H_
|
#endif // EDITOR_OP_H_
|
||||||
|
|||||||
+1
-8
@@ -2,21 +2,14 @@
|
|||||||
#define FILE_IO_H_
|
#define FILE_IO_H_
|
||||||
|
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include "row_op.h"
|
|
||||||
#include "terminal.h"
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
char *editorRowsToString(int *buffer_len);
|
|
||||||
|
|
||||||
|
|
||||||
void editorCloseFile(void);
|
void editorCloseFile(void);
|
||||||
|
|
||||||
void editorOpen(char *filename);
|
void editorOpen(struct buffer_t *buffer);
|
||||||
|
|
||||||
void editorSave();
|
void editorSave();
|
||||||
|
|
||||||
void editorFind();
|
|
||||||
|
|
||||||
#endif // FILE_IO_H_
|
#endif // FILE_IO_H_
|
||||||
|
|||||||
+3
-3
@@ -20,11 +20,11 @@
|
|||||||
// END \x1b[4~ || <esc>[8~ || <esc>[F || <esc>OF
|
// END \x1b[4~ || <esc>[8~ || <esc>[F || <esc>OF
|
||||||
// DELETE \x1b[3~
|
// DELETE \x1b[3~
|
||||||
|
|
||||||
char *editorPrompt(char *prompt);
|
char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
|
||||||
|
|
||||||
char *key_to_string(int key);
|
const char *fileCompletion(const char *path);
|
||||||
|
|
||||||
void editorMoveCursor(int key);
|
int editorMoveCursor(int key);
|
||||||
|
|
||||||
int executeKeyBind(char *key_sequence);
|
int executeKeyBind(char *key_sequence);
|
||||||
|
|
||||||
|
|||||||
+16
-13
@@ -1861,8 +1861,8 @@ static Lisp parse_symbol_(Lexer* lex, LispContext ctx)
|
|||||||
char scratch[LISP_IDENTIFIER_MAX];
|
char scratch[LISP_IDENTIFIER_MAX];
|
||||||
size_t length = lexer_copy_token(lex, 0, LISP_IDENTIFIER_MAX, scratch);
|
size_t length = lexer_copy_token(lex, 0, LISP_IDENTIFIER_MAX, scratch);
|
||||||
// always convert symbols to uppercase
|
// always convert symbols to uppercase
|
||||||
for (int i = 0; i < length; ++i)
|
// for (int i = 0; i < length; ++i)
|
||||||
scratch[i] = toupper(scratch[i]);
|
// scratch[i] = toupper(scratch[i]);
|
||||||
return symbol_intern_(ctx.p->symbols, scratch, length, ctx);
|
return symbol_intern_(ctx.p->symbols, scratch, length, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2075,8 +2075,10 @@ static Lisp parse(Lexer* lex, LispError* out_error, LispContext ctx)
|
|||||||
result = lisp_cons(get_sym(SYM_BEGIN, ctx), lisp_list_reverse(result), ctx);
|
result = lisp_cons(get_sym(SYM_BEGIN, ctx), lisp_list_reverse(result), ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out_error) *out_error = error;
|
if (out_error)
|
||||||
|
*out_error = error;
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Lisp lisp_read(const char *program, LispError* out_error, LispContext ctx)
|
Lisp lisp_read(const char *program, LispError* out_error, LispContext ctx)
|
||||||
@@ -2122,6 +2124,7 @@ void* fread_all_(FILE* file, size_t* out_size) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
data = new_data;
|
data = new_data;
|
||||||
|
memset(data + *out_size, 0, cap - *out_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t read = fread(data + *out_size, 1, BLOCK_SIZE, file);
|
size_t read = fread(data + *out_size, 1, BLOCK_SIZE, file);
|
||||||
@@ -3183,17 +3186,17 @@ LispContext lisp_init(void)
|
|||||||
ctx.p->macros = lisp_make_table(ctx);
|
ctx.p->macros = lisp_make_table(ctx);
|
||||||
|
|
||||||
Lisp* c = ctx.p->symbol_cache;
|
Lisp* c = ctx.p->symbol_cache;
|
||||||
c[SYM_IF] = lisp_make_symbol("IF", ctx);
|
c[SYM_IF] = lisp_make_symbol("if", ctx);
|
||||||
c[SYM_BEGIN] = lisp_make_symbol("BEGIN", ctx);
|
c[SYM_BEGIN] = lisp_make_symbol("begin", ctx);
|
||||||
c[SYM_QUOTE] = lisp_make_symbol("QUOTE", ctx);
|
c[SYM_QUOTE] = lisp_make_symbol("quote", ctx);
|
||||||
c[SYM_QUASI_QUOTE] = lisp_make_symbol("QUASIQUOTE", ctx);
|
c[SYM_QUASI_QUOTE] = lisp_make_symbol("quasiquote", ctx);
|
||||||
c[SYM_UNQUOTE] = lisp_make_symbol("UNQUOTE", ctx);
|
c[SYM_UNQUOTE] = lisp_make_symbol("unquote", ctx);
|
||||||
c[SYM_UNQUOTE_SPLICE] = lisp_make_symbol("UNQUOTESPLICE", ctx);
|
c[SYM_UNQUOTE_SPLICE] = lisp_make_symbol("unquotesplice", ctx);
|
||||||
c[SYM_DEFINE] = lisp_make_symbol("_DEF", ctx);
|
c[SYM_DEFINE] = lisp_make_symbol("_def", ctx);
|
||||||
c[SYM_DEFINE_MACRO] = lisp_make_symbol("DEFINE-MACRO", ctx);
|
c[SYM_DEFINE_MACRO] = lisp_make_symbol("define-macro", ctx);
|
||||||
c[SYM_SET] = lisp_make_symbol("_SET!", ctx);
|
c[SYM_SET] = lisp_make_symbol("_set!", ctx);
|
||||||
c[SYM_LAMBDA] = lisp_make_symbol("/\\_", ctx);
|
c[SYM_LAMBDA] = lisp_make_symbol("/\\_", ctx);
|
||||||
c[SYM_CONS] = lisp_make_symbol("CONS", ctx);
|
c[SYM_CONS] = lisp_make_symbol("cons", ctx);
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+200
-203
@@ -36,7 +36,7 @@ static const char* lib_0_sequences_src_ =
|
|||||||
(if (pair? args) \n\
|
(if (pair? args) \n\
|
||||||
(if (pair? (cdr args)) \n\
|
(if (pair? (cdr args)) \n\
|
||||||
(if (pair? (cdr (cdr args))) \n\
|
(if (pair? (cdr (cdr args))) \n\
|
||||||
`(/\\_ ,(car args) ,(cons 'BEGIN (cdr args))) \n\
|
`(/\\_ ,(car args) ,(cons 'begin (cdr args))) \n\
|
||||||
`(/\\_ ,(car args) ,(car (cdr args)))) \n\
|
`(/\\_ ,(car args) ,(car (cdr args)))) \n\
|
||||||
(syntax-error \"lambda missing body expressions: (lambda (args) body)\")) \n\
|
(syntax-error \"lambda missing body expressions: (lambda (args) body)\")) \n\
|
||||||
(syntax-error \"lambda missing argument: (lambda (args) body)\")))) \n\
|
(syntax-error \"lambda missing argument: (lambda (args) body)\")))) \n\
|
||||||
@@ -44,18 +44,18 @@ static const char* lib_0_sequences_src_ =
|
|||||||
(define-macro set! (lambda (var x) \n\
|
(define-macro set! (lambda (var x) \n\
|
||||||
(begin \n\
|
(begin \n\
|
||||||
(if (not (symbol? var)) (syntax-error \"set! not a variable\")) \n\
|
(if (not (symbol? var)) (syntax-error \"set! not a variable\")) \n\
|
||||||
`(_SET! ,var ,x)))) \n\
|
`(_set! ,var ,x)))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro define \n\
|
(define-macro define \n\
|
||||||
(lambda (var . exprs) \n\
|
(lambda (var . exprs) \n\
|
||||||
(if (symbol? var) \n\
|
(if (symbol? var) \n\
|
||||||
(if (pair? (cdr exprs)) \n\
|
(if (pair? (cdr exprs)) \n\
|
||||||
(syntax-error \"define: (define var x)\") \n\
|
(syntax-error \"define: (define var x)\") \n\
|
||||||
`(_DEF ,var ,(car exprs))) \n\
|
`(_def ,var ,(car exprs))) \n\
|
||||||
(if (pair? var) \n\
|
(if (pair? var) \n\
|
||||||
`(_DEF ,(car var) \n\
|
`(_def ,(car var) \n\
|
||||||
(LAMBDA ,(cdr var) \n\
|
(lambda ,(cdr var) \n\
|
||||||
,(if (null? (cdr exprs)) (car exprs) (cons 'BEGIN exprs)))) \n\
|
,(if (null? (cdr exprs)) (car exprs) (cons 'begin exprs)))) \n\
|
||||||
(syntax-error \"define: not a symbol\") )))) \n\
|
(syntax-error \"define: not a symbol\") )))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define (first x) (car x)) \n\
|
(define (first x) (car x)) \n\
|
||||||
@@ -98,13 +98,13 @@ static const char* lib_0_sequences_src_ =
|
|||||||
(define (_expand-shorthand-body path) \n\
|
(define (_expand-shorthand-body path) \n\
|
||||||
(if (null? path) (cons 'pair '()) \n\
|
(if (null? path) (cons 'pair '()) \n\
|
||||||
(list (if (char=? (car path) #\\A) \n\
|
(list (if (char=? (car path) #\\A) \n\
|
||||||
(cons 'CAR (_expand-shorthand-body (cdr path))))))) \n\
|
(cons 'car (_expand-shorthand-body (cdr path))))))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define (_expand-shorthand text) \n\
|
(define (_expand-shorthand text) \n\
|
||||||
(cons 'DEFINE (cons (list (string->symbol (string-append \"C\" text \"R\")) 'pair) \n\
|
(cons 'define (cons (list (string->symbol (string-append \"C\" text \"R\")) 'pair) \n\
|
||||||
(_expand-shorthand-body (string->list text))))) \n\
|
(_expand-shorthand-body (string->list text))))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro _shorthand-accessors (lambda args (cons 'BEGIN (map1 _expand-shorthand args)))) \n\
|
(define-macro _shorthand-accessors (lambda args (cons 'begin (map1 _expand-shorthand args)))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define (vector . args) (list->vector args)) \n\
|
(define (vector . args) (list->vector args)) \n\
|
||||||
\n\
|
\n\
|
||||||
@@ -124,14 +124,14 @@ static const char* lib_0_sequences_src_ =
|
|||||||
|
|
||||||
static const char* lib_1_forms_src_ =
|
static const char* lib_1_forms_src_ =
|
||||||
"(define (_make-lambda args body) \n\
|
"(define (_make-lambda args body) \n\
|
||||||
(list 'LAMBDA args (if (null? (cdr body)) (car body) (cons 'BEGIN body)))) \n\
|
(list 'lambda args (if (null? (cdr body)) (car body) (cons 'begin body)))) \n\
|
||||||
\n\
|
\n\
|
||||||
\n\
|
\n\
|
||||||
; (LET <name> ((<var0> <expr0>) ... (<varN> <expr1>)) <body0> ... <bodyN>) \n\
|
; (let <name> ((<var0> <expr0>) ... (<varN> <expr1>)) <body0> ... <bodyN>) \n\
|
||||||
; => ((LAMBDA (<var0> ... <varN>) (BEGIN <body0> ... <bodyN>)) <expr0> ... <expr1>) \n\
|
; => ((lambda (<var0> ... <varN>) (begin <body0> ... <bodyN>)) <expr0> ... <expr1>) \n\
|
||||||
; => named \n\
|
; => named \n\
|
||||||
; ((lambda () \n\
|
; ((lambda () \n\
|
||||||
; (define <name> (LAMBDA (<var0> ... <varN>) (BEGIN <body0> ... <bodyN>))) \n\
|
; (define <name> (lambda (<var0> ... <varN>) (begin <body0> ... <bodyN>))) \n\
|
||||||
; (<name> <expr0> ... <exprN>))) \n\
|
; (<name> <expr0> ... <exprN>))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define (_check-binding-list bindings) \n\
|
(define (_check-binding-list bindings) \n\
|
||||||
@@ -145,7 +145,7 @@ static const char* lib_1_forms_src_ =
|
|||||||
(define initial-args (map1 (lambda (entry) (second entry)) bindings)) \n\
|
(define initial-args (map1 (lambda (entry) (second entry)) bindings)) \n\
|
||||||
(if (null? var) \n\
|
(if (null? var) \n\
|
||||||
(cons body-func initial-args) \n\
|
(cons body-func initial-args) \n\
|
||||||
(list (_make-lambda '() (list (list 'DEFINE var body-func) (cons var initial-args)))))) \n\
|
(list (_make-lambda '() (list (list 'define var body-func) (cons var initial-args)))))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro let (lambda args \n\
|
(define-macro let (lambda args \n\
|
||||||
(if (pair? (first args)) \n\
|
(if (pair? (first args)) \n\
|
||||||
@@ -153,8 +153,8 @@ static const char* lib_1_forms_src_ =
|
|||||||
(_let->combination (first args) (second args) (cdr (cdr args)))))) \n\
|
(_let->combination (first args) (second args) (cdr (cdr args)))))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define (_let*-helper bindings body) \n\
|
(define (_let*-helper bindings body) \n\
|
||||||
(if (null? bindings) (if (null? (cdr body)) (car body) (cons 'BEGIN body)) \n\
|
(if (null? bindings) (if (null? (cdr body)) (car body) (cons 'begin body)) \n\
|
||||||
(list 'LET (list (car bindings)) (_let*-helper (cdr bindings) body)))) \n\
|
(list 'let (list (car bindings)) (_let*-helper (cdr bindings) body)))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro let* (lambda (bindings . body) \n\
|
(define-macro let* (lambda (bindings . body) \n\
|
||||||
(_check-binding-list bindings) \n\
|
(_check-binding-list bindings) \n\
|
||||||
@@ -163,17 +163,17 @@ static const char* lib_1_forms_src_ =
|
|||||||
(define-macro letrec (lambda (bindings . body) \n\
|
(define-macro letrec (lambda (bindings . body) \n\
|
||||||
(_check-binding-list bindings) \n\
|
(_check-binding-list bindings) \n\
|
||||||
(cons (_make-lambda (map1 (lambda (entry) (first entry)) bindings) \n\
|
(cons (_make-lambda (map1 (lambda (entry) (first entry)) bindings) \n\
|
||||||
(append (map1 (lambda (entry) (list 'SET! (first entry) (second entry))) \n\
|
(append (map1 (lambda (entry) (list 'set! (first entry) (second entry))) \n\
|
||||||
bindings) body)) \n\
|
bindings) body)) \n\
|
||||||
(map1 (lambda (entry) '()) bindings)))) \n\
|
(map1 (lambda (entry) '()) bindings)))) \n\
|
||||||
\n\
|
\n\
|
||||||
\n\
|
\n\
|
||||||
; (COND (<pred0> <expr0>) \n\
|
; (cond (<pred0> <expr0>) \n\
|
||||||
; (<pred1> <expr1>) \n\
|
; (<pred1> <expr1>) \n\
|
||||||
; ... \n\
|
; ... \n\
|
||||||
; (else <expr-1>)) \n\
|
; (else <expr-1>)) \n\
|
||||||
; => \n\
|
; => \n\
|
||||||
; (IF <pred0> <expr0> \n\
|
; (if <pred0> <expr0> \n\
|
||||||
; (if <pred1> <expr1> \n\
|
; (if <pred1> <expr1> \n\
|
||||||
; .... \n\
|
; .... \n\
|
||||||
; (if <predN> <exprN> <expr-1>)) ... ) \n\
|
; (if <predN> <exprN> <expr-1>)) ... ) \n\
|
||||||
@@ -187,11 +187,11 @@ static const char* lib_1_forms_src_ =
|
|||||||
\n\
|
\n\
|
||||||
(define (_cond-helper clauses) \n\
|
(define (_cond-helper clauses) \n\
|
||||||
(if (null? clauses) '() \n\
|
(if (null? clauses) '() \n\
|
||||||
(if (eq? (car (car clauses)) 'ELSE) \n\
|
(if (eq? (car (car clauses)) 'else) \n\
|
||||||
(cons 'BEGIN (cdr (car clauses))) \n\
|
(cons 'begin (cdr (car clauses))) \n\
|
||||||
(list 'IF \n\
|
(list 'if \n\
|
||||||
(car (car clauses)) \n\
|
(car (car clauses)) \n\
|
||||||
(cons 'BEGIN (cdr (car clauses))) \n\
|
(cons 'begin (cdr (car clauses))) \n\
|
||||||
(_cond-helper (cdr clauses)))))) \n\
|
(_cond-helper (cdr clauses)))))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro cond (lambda clauses \n\
|
(define-macro cond (lambda clauses \n\
|
||||||
@@ -206,26 +206,26 @@ static const char* lib_2_forms_src_ =
|
|||||||
(cond ((null? preds) #t) \n\
|
(cond ((null? preds) #t) \n\
|
||||||
((null? (cdr preds)) (car preds)) \n\
|
((null? (cdr preds)) (car preds)) \n\
|
||||||
(else \n\
|
(else \n\
|
||||||
`(IF ,(car preds) ,(_and-helper (cdr preds)) #f)))) \n\
|
`(if ,(car preds) ,(_and-helper (cdr preds)) #f)))) \n\
|
||||||
(define-macro and (lambda preds (_and-helper preds))) \n\
|
(define-macro and (lambda preds (_and-helper preds))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define (_or-helper preds var) \n\
|
(define (_or-helper preds var) \n\
|
||||||
(cond ((null? preds) #f) \n\
|
(cond ((null? preds) #f) \n\
|
||||||
((null? (cdr preds)) (car preds)) \n\
|
((null? (cdr preds)) (car preds)) \n\
|
||||||
(else \n\
|
(else \n\
|
||||||
`(BEGIN (SET! ,var ,(car preds)) \n\
|
`(begin (set! ,var ,(car preds)) \n\
|
||||||
(IF ,var ,var ,(_or-helper (cdr preds) var)))))) \n\
|
(if ,var ,var ,(_or-helper (cdr preds) var)))))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro or (lambda preds \n\
|
(define-macro or (lambda preds \n\
|
||||||
(let ((var (gensym))) \n\
|
(let ((var (gensym))) \n\
|
||||||
`(LET ((,var '())) ,(_or-helper preds var))))) \n\
|
`(let ((,var '())) ,(_or-helper preds var))))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro case (lambda (key . clauses) \n\
|
(define-macro case (lambda (key . clauses) \n\
|
||||||
(let ((expr (gensym))) \n\
|
(let ((expr (gensym))) \n\
|
||||||
`(LET ((,expr ,key)) \n\
|
`(let ((,expr ,key)) \n\
|
||||||
,(cons 'COND (map1 (lambda (entry) \n\
|
,(cons 'cond (map1 (lambda (entry) \n\
|
||||||
(cons (if (pair? (car entry)) \n\
|
(cons (if (pair? (car entry)) \n\
|
||||||
`(MEMV ,expr (quote ,(car entry))) \n\
|
`(memv ,expr (quote ,(car entry))) \n\
|
||||||
(car entry)) \n\
|
(car entry)) \n\
|
||||||
(cdr entry))) clauses)))))) \n\
|
(cdr entry))) clauses)))))) \n\
|
||||||
\n\
|
\n\
|
||||||
@@ -234,23 +234,24 @@ static const char* lib_2_forms_src_ =
|
|||||||
`(begin (set! ,l (cons ,v ,l)) ,l))) \n\
|
`(begin (set! ,l (cons ,v ,l)) ,l))) \n\
|
||||||
\n\
|
\n\
|
||||||
; (DO ((<var0> <init0> <step0>) ...) (<test> <result>) <body>) \n\
|
; (DO ((<var0> <init0> <step0>) ...) (<test> <result>) <body>) \n\
|
||||||
(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\
|
\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\
|
(define-macro dotimes \n\
|
||||||
(lambda (form body) \n\
|
(lambda (form body) \n\
|
||||||
(apply (lambda (i n . result) \n\
|
(apply (lambda (i n . result) \n\
|
||||||
@@ -262,18 +263,18 @@ static const char* lib_2_forms_src_ =
|
|||||||
(define-macro swap! \n\
|
(define-macro swap! \n\
|
||||||
(lambda (x y) \n\
|
(lambda (x y) \n\
|
||||||
(let ((temp (gensym))) \n\
|
(let ((temp (gensym))) \n\
|
||||||
`(LET ((,temp ,x)) \n\
|
`(let ((,temp ,x)) \n\
|
||||||
(SET! ,temp ,x) \n\
|
(set! ,temp ,x) \n\
|
||||||
(SET! ,x ,y) \n\
|
(set! ,x ,y) \n\
|
||||||
(SET! ,y ,temp))))) \n\
|
(set! ,y ,temp))))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro inc! ; CL incf \n\
|
(define-macro inc! ; CL incf \n\
|
||||||
(lambda (x) \n\
|
(lambda (x) \n\
|
||||||
`(SET! ,x (+ ,x 1)))) \n\
|
`(set! ,x (+ ,x 1)))) \n\
|
||||||
\n\
|
\n\
|
||||||
(define-macro dec! ; CL decf \n\
|
(define-macro dec! ; CL decf \n\
|
||||||
(lambda (x) \n\
|
(lambda (x) \n\
|
||||||
`(SET! ,x (- ,x 1))))";
|
`(set! ,x (- ,x 1))))";
|
||||||
|
|
||||||
static const char* lib_3_math_src_ =
|
static const char* lib_3_math_src_ =
|
||||||
"(define (number? x) (real? x)) \n\
|
"(define (number? x) (real? x)) \n\
|
||||||
@@ -445,7 +446,7 @@ static const char* lib_4_sequences_src_ =
|
|||||||
|
|
||||||
static const char* lib_5_streams_src_ =
|
static const char* lib_5_streams_src_ =
|
||||||
"(define-macro delay (lambda (expr) \n\
|
"(define-macro delay (lambda (expr) \n\
|
||||||
`(make-promise ,(cons 'LAMBDA \n\
|
`(make-promise ,(cons 'lambda \n\
|
||||||
(cons '() \n\
|
(cons '() \n\
|
||||||
(cons expr '())))))) \n\
|
(cons expr '())))))) \n\
|
||||||
\n\
|
\n\
|
||||||
@@ -1926,182 +1927,178 @@ static Lisp sch_is_cont(Lisp args, LispError* e, LispContext ctx)
|
|||||||
|
|
||||||
static const LispFuncDef lib_cfunc_defs[] = {
|
static const LispFuncDef lib_cfunc_defs[] = {
|
||||||
|
|
||||||
{ "ERROR", sch_error },
|
{ "error", sch_error },
|
||||||
{ "SYNTAX-ERROR", sch_syntax_error },
|
{ "syntax-error", sch_syntax_error },
|
||||||
|
|
||||||
// Output Procedures https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Output-Procedures.html
|
// Output Procedures
|
||||||
{ "_WRITE", sch_write },
|
{ "_write", sch_write },
|
||||||
{ "_DISPLAY", sch_display },
|
{ "_display", sch_display },
|
||||||
{ "_WRITE-CHAR", sch_write_char },
|
{ "_write-char", sch_write_char },
|
||||||
{ "_FLUSH-OUTPUT-PORT", sch_flush },
|
{ "_flush-output-port", sch_flush },
|
||||||
{ "_READ", sch_read },
|
{ "_read", sch_read },
|
||||||
{ "INPUT-PORT?", sch_is_port_in },
|
{ "input-port?", sch_is_port_in },
|
||||||
{ "OUTPUT-PORT?", sch_is_port_out },
|
{ "output-port?", sch_is_port_out },
|
||||||
{ "OPEN-INPUT-FILE", sch_open_input },
|
{ "open-input-file", sch_open_input },
|
||||||
{ "OPEN-OUTPUT-FILE", sch_open_output },
|
{ "open-output-file", sch_open_output },
|
||||||
{ "CLOSE-INPUT-PORT", sch_port_close },
|
{ "close-input-port", sch_port_close },
|
||||||
{ "CLOSE-OUTPUT-PORT", sch_port_close },
|
{ "close-output-port", sch_port_close },
|
||||||
{ "EOF-OBJECT?", sch_is_eof },
|
{ "eof-object?", sch_is_eof },
|
||||||
|
|
||||||
// Universal Time https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Universal-Time.html
|
// Universal Time
|
||||||
{ "GET-UNIVERSAL-TIME", sch_univeral_time },
|
{ "get-universal-time", sch_univeral_time },
|
||||||
{ "PRINT-GC-STATISTICS", sch_print_gc_stats },
|
{ "print-gc-statistics", sch_print_gc_stats },
|
||||||
|
|
||||||
{ "MACROEXPAND", sch_macroexpand },
|
{ "macroexpand", sch_macroexpand },
|
||||||
|
|
||||||
// Equivalence Predicates https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Equivalence-Predicates.html
|
// Equivalence Predicates
|
||||||
{ "EQ?", sch_exact_eq },
|
{ "eq?", sch_exact_eq },
|
||||||
{ "EQV?", sch_equal },
|
{ "eqv?", sch_equal },
|
||||||
{ "EQUAL?", sch_equal_r },
|
{ "equal?", sch_equal_r },
|
||||||
|
|
||||||
// Booleans https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Booleans.html
|
// Booleans
|
||||||
{ "BOOLEAN?", sch_is_boolean },
|
{ "boolean?", sch_is_boolean },
|
||||||
{ "NOT", sch_not },
|
{ "not", sch_not },
|
||||||
|
|
||||||
// PAIRS
|
// PAIRS
|
||||||
{ "CONS", sch_cons },
|
{ "cons", sch_cons },
|
||||||
{ "CAR", sch_car },
|
{ "car", sch_car },
|
||||||
{ "CDR", sch_cdr },
|
{ "cdr", sch_cdr },
|
||||||
{ "SET-CAR!", sch_set_car },
|
{ "set-car!", sch_set_car },
|
||||||
{ "SET-CDR!", sch_set_cdr },
|
{ "set-cdr!", sch_set_cdr },
|
||||||
{ "NULL?", sch_is_null },
|
{ "null?", sch_is_null },
|
||||||
{ "PAIR?", sch_is_pair },
|
{ "pair?", sch_is_pair },
|
||||||
|
|
||||||
// Lists https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_8.html
|
// Lists
|
||||||
{ "LIST", sch_list },
|
{ "list", sch_list },
|
||||||
{ "LIST?", sch_is_list },
|
{ "list?", sch_is_list },
|
||||||
{ "MAKE-LIST", sch_make_list },
|
{ "make-list", sch_make_list },
|
||||||
{ "LIST-COPY", sch_list_copy },
|
{ "list-copy", sch_list_copy },
|
||||||
{ "LENGTH", sch_length },
|
{ "length", sch_length },
|
||||||
{ "APPEND", sch_append },
|
{ "append", sch_append },
|
||||||
{ "APPEND-REVERSE!", sch_append_reverse },
|
{ "append-reverse!", sch_append_reverse },
|
||||||
{ "LIST-REF", sch_list_ref },
|
{ "list-ref", sch_list_ref },
|
||||||
{ "NTHCDR", sch_list_advance },
|
{ "nthcdr", sch_list_advance },
|
||||||
|
|
||||||
// Vectors https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_9.html#SEC82
|
// Vectors
|
||||||
{ "VECTOR?", sch_is_vector },
|
{ "vector?", sch_is_vector },
|
||||||
{ "MAKE-VECTOR", sch_make_vector },
|
{ "make-vector", sch_make_vector },
|
||||||
{ "VECTOR-GROW", sch_vector_grow },
|
{ "vector-grow", sch_vector_grow },
|
||||||
{ "VECTOR-LENGTH", sch_vector_length },
|
{ "vector-length", sch_vector_length },
|
||||||
{ "VECTOR-SET!", sch_vector_set },
|
{ "vector-set!", sch_vector_set },
|
||||||
{ "VECTOR-SWAP!", sch_vector_swap },
|
{ "vector-swap!", sch_vector_swap },
|
||||||
{ "VECTOR-REF", sch_vector_ref },
|
{ "vector-ref", sch_vector_ref },
|
||||||
{ "VECTOR-FILL!", sch_vector_fill },
|
{ "vector-fill!", sch_vector_fill },
|
||||||
{ "VECTOR-ASSQ", sch_vector_assq },
|
{ "vector-assq", sch_vector_assq },
|
||||||
{ "SUBVECTOR", sch_subvector },
|
{ "subvector", sch_subvector },
|
||||||
{ "LIST->VECTOR", sch_list_to_vector },
|
{ "list->vector", sch_list_to_vector },
|
||||||
{ "VECTOR->LIST", sch_vector_to_list },
|
{ "vector->list", sch_vector_to_list },
|
||||||
|
|
||||||
// Strings https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_7.html#SEC61
|
// Strings
|
||||||
{ "STRING?", sch_is_string },
|
{ "string?", sch_is_string },
|
||||||
{ "MAKE-STRING", sch_make_string },
|
{ "make-string", sch_make_string },
|
||||||
{ "STRING=?", sch_equal_r },
|
{ "string=?", sch_equal_r },
|
||||||
{ "STRING<?", sch_string_less },
|
{ "string<?", sch_string_less },
|
||||||
{ "SUBSTRING", sch_substring },
|
{ "substring", sch_substring },
|
||||||
{ "STRING-NULL?", sch_string_is_null },
|
{ "string-null?", sch_string_is_null },
|
||||||
{ "STRING-LENGTH", sch_string_length },
|
{ "string-length", sch_string_length },
|
||||||
{ "STRING-REF", sch_string_ref },
|
{ "string-ref", sch_string_ref },
|
||||||
{ "STRING-SET!", sch_string_set },
|
{ "string-set!", sch_string_set },
|
||||||
{ "STRING-UPCASE", sch_string_upcase },
|
{ "string-upcase", sch_string_upcase },
|
||||||
{ "STRING-DOWNCASE", sch_string_downcase },
|
{ "string-downcase", sch_string_downcase },
|
||||||
{ "STRING-APPEND", sch_string_append },
|
{ "string-append", sch_string_append },
|
||||||
{ "STRING->LIST", sch_string_to_list },
|
{ "string->list", sch_string_to_list },
|
||||||
{ "LIST->STRING", sch_list_to_string },
|
{ "list->string", sch_list_to_string },
|
||||||
{ "STRING->NUMBER", sch_string_to_number },
|
{ "string->number", sch_string_to_number },
|
||||||
{ "NUMBER->STRING", sch_number_to_string },
|
{ "number->string", sch_number_to_string },
|
||||||
|
|
||||||
// Characters https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Characters.html#Characters
|
// Characters
|
||||||
{ "CHAR?", sch_is_char },
|
{ "char?", sch_is_char },
|
||||||
{ "CHAR=?", sch_equals },
|
{ "char=?", sch_equals },
|
||||||
{ "CHAR<?", sch_char_less },
|
{ "char<?", sch_char_less },
|
||||||
|
|
||||||
{ "CHAR-UPCASE", sch_char_upcase },
|
{ "char-upcase", sch_char_upcase },
|
||||||
{ "CHAR-DOWNCASE", sch_char_downcase },
|
{ "char-downcase", sch_char_downcase },
|
||||||
{ "CHAR-WHITESPACE?", sch_char_is_white },
|
{ "char-whitespace?", sch_char_is_white },
|
||||||
{ "CHAR-ALPHANUMERIC?", sch_char_is_alphanum },
|
{ "char-alphanumeric?", sch_char_is_alphanum },
|
||||||
{ "CHAR-ALPHABETIC?", sch_char_is_alpha },
|
{ "char-alphabetic?", sch_char_is_alpha },
|
||||||
{ "CHAR-NUMERIC?", sch_char_is_number },
|
{ "char-numeric?", sch_char_is_number },
|
||||||
{ "CHAR->INTEGER", sch_char_to_int },
|
{ "char->integer", sch_char_to_int },
|
||||||
|
|
||||||
// Association Lists https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Association-Lists.html
|
// Numerical operations
|
||||||
// Numerical operations https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Numerical-operations.html
|
|
||||||
{ "=", sch_equals },
|
{ "=", sch_equals },
|
||||||
{ "+", sch_add },
|
{ "+", sch_add },
|
||||||
{ "-", sch_sub },
|
{ "-", sch_sub },
|
||||||
{ "*", sch_mult },
|
{ "*", sch_mult },
|
||||||
{ "/", sch_divide },
|
{ "/", sch_divide },
|
||||||
{ "<", sch_less },
|
{ "<", sch_less },
|
||||||
{ "INTEGER?", sch_is_int },
|
{ "integer?", sch_is_int },
|
||||||
{ "EVEN?", sch_is_even },
|
{ "even?", sch_is_even },
|
||||||
{ "REAL?", sch_is_real },
|
{ "real?", sch_is_real },
|
||||||
{ "EXP", sch_exp },
|
{ "exp", sch_exp },
|
||||||
{ "EXPT", sch_power },
|
{ "expt", sch_power },
|
||||||
{ "LOG", sch_log },
|
{ "log", sch_log },
|
||||||
{ "SIN", sch_sin },
|
{ "sin", sch_sin },
|
||||||
{ "COS", sch_cos },
|
{ "cos", sch_cos },
|
||||||
{ "TAN", sch_tan },
|
{ "tan", sch_tan },
|
||||||
{ "ATAN", sch_atan },
|
{ "atan", sch_atan },
|
||||||
{ "SQRT", sch_sqrt },
|
{ "sqrt", sch_sqrt },
|
||||||
{ "ROUND", sch_round },
|
{ "round", sch_round },
|
||||||
{ "FLOOR", sch_floor },
|
{ "floor", sch_floor },
|
||||||
{ "CEILING", sch_ceiling },
|
{ "ceiling", sch_ceiling },
|
||||||
{ "QUOTIENT", sch_quotient },
|
{ "quotient", sch_quotient },
|
||||||
{ "REMAINDER", sch_remainder },
|
{ "remainder", sch_remainder },
|
||||||
{ "MODULO", sch_modulo },
|
{ "modulo", sch_modulo },
|
||||||
{ "ABS", sch_abs },
|
{ "abs", sch_abs },
|
||||||
{ "MAGNITUDE", sch_abs },
|
{ "magnitude", sch_abs },
|
||||||
{ "EXACT?", sch_is_int },
|
{ "exact?", sch_is_int },
|
||||||
{ "EXACT->INEXACT", sch_to_inexact },
|
{ "exact->inexact", sch_to_inexact },
|
||||||
{ "INEXACT->EXACT", sch_to_exact },
|
{ "inexact->exact", sch_to_exact },
|
||||||
|
|
||||||
// Symbols https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Symbols.html
|
// Symbols
|
||||||
{ "SYMBOL?", sch_is_symbol },
|
{ "symbol?", sch_is_symbol },
|
||||||
{ "SYMBOL<?", sch_symbol_less },
|
{ "symbol<?", sch_symbol_less },
|
||||||
{ "STRING->SYMBOL", sch_string_to_symbol },
|
{ "string->symbol", sch_string_to_symbol },
|
||||||
{ "SYMBOL->STRING", sch_symbol_to_string },
|
{ "symbol->string", sch_symbol_to_string },
|
||||||
{ "GENERATE-UNINTERNED-SYMBOL", sch_gensym },
|
{ "generate-uninterned-symbol", sch_gensym },
|
||||||
{ "GENSYM", sch_gensym },
|
{ "gensym", sch_gensym },
|
||||||
|
|
||||||
// Environments https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_14.html
|
// Environments
|
||||||
{ "EVAL", sch_eval },
|
{ "eval", sch_eval },
|
||||||
{ "SCHEME-REPORT-ENVIRONMENT", sch_system_env },
|
{ "scheme-report-environment", sch_system_env },
|
||||||
{ "INTERACTION-ENVIRONMENT", sch_user_env },
|
{ "interaction-environment", sch_user_env },
|
||||||
// { "THE-ENVIRONMENT", sch_current_env },
|
|
||||||
|
|
||||||
// Hash Tables https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Basic-Hash-Table-Operations.html#Basic-Hash-Table-Operations
|
// Hash Tables
|
||||||
{ "HASH-TABLE?", sch_is_table },
|
{ "hash-table?", sch_is_table },
|
||||||
{ "MAKE-HASH-TABLE", sch_table_make },
|
{ "make-hash-table", sch_table_make },
|
||||||
{ "HASH-TABLE-SET!", sch_table_set },
|
{ "hash-table-set!", sch_table_set },
|
||||||
{ "HASH-TABLE-REF", sch_table_get },
|
{ "hash-table-ref", sch_table_get },
|
||||||
{ "HASH-TABLE-SIZE", sch_table_size },
|
{ "hash-table-size", sch_table_size },
|
||||||
{ "HASH-TABLE->ALIST", sch_table_to_alist },
|
{ "hash-table->alist", sch_table_to_alist },
|
||||||
|
|
||||||
{ "PROMISE?", sch_is_promise },
|
{ "promise?", sch_is_promise },
|
||||||
{ "MAKE-PROMISE", sch_make_promise },
|
{ "make-promise", sch_make_promise },
|
||||||
{ "_PROMISE-PROCEDURE", sch_promise_proc },
|
{ "_promise-procedure", sch_promise_proc },
|
||||||
{ "_PROMISE-STORE!", sch_promise_store },
|
{ "_promise-store!", sch_promise_store },
|
||||||
{ "PROMISE-VALUE", sch_promise_val },
|
{ "promise-value", sch_promise_val },
|
||||||
{ "PROMISE-FORCED?", sch_promise_forced },
|
{ "promise-forced?", sch_promise_forced },
|
||||||
|
|
||||||
// Procedures https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Procedure-Operations.html#Procedure-Operations
|
// Procedures
|
||||||
{ "APPLY", sch_apply },
|
{ "apply", sch_apply },
|
||||||
{ "COMPILED-PROCEDURE?", sch_is_func },
|
{ "compiled-procedure?", sch_is_func },
|
||||||
{ "COMPOUND-PROCEDURE?", sch_is_lambda },
|
{ "compound-procedure?", sch_is_lambda },
|
||||||
{ "PROCEDURE-ENVIRONMENT", sch_lambda_env },
|
{ "procedure-environment", sch_lambda_env },
|
||||||
// TOOD: Almost standard
|
{ "procedure-body", sch_lambda_body },
|
||||||
{ "PROCEDURE-BODY", sch_lambda_body },
|
{ "call/cc", sch_call_cc },
|
||||||
{ "CALL/CC", sch_call_cc },
|
{ "continuation?", sch_is_cont },
|
||||||
{ "CONTINUATION?", sch_is_cont },
|
|
||||||
|
|
||||||
// Random Numbers https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Random-Numbers.html
|
// Random Numbers
|
||||||
{ "RANDOM", sch_pseudo_rand },
|
{ "random", sch_pseudo_rand },
|
||||||
{ "RANDOM-SEED!", sch_pseudo_seed },
|
{ "random-seed!", sch_pseudo_seed },
|
||||||
|
|
||||||
// Garbage Collection https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-user/Garbage-Collection.html
|
// Garbage Collection
|
||||||
{ "GC-FLIP", sch_gc_flip },
|
{ "gc-flip", sch_gc_flip },
|
||||||
|
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void lisp_lib_load(LispContext ctx)
|
void lisp_lib_load(LispContext ctx)
|
||||||
@@ -2111,14 +2108,14 @@ void lisp_lib_load(LispContext ctx)
|
|||||||
|
|
||||||
lisp_table_set(
|
lisp_table_set(
|
||||||
table,
|
table,
|
||||||
lisp_make_symbol("_CURRENT-OUTPUT-PORT", ctx),
|
lisp_make_symbol("_current-output-port", ctx),
|
||||||
lisp_make_port(stdout, 0),
|
lisp_make_port(stdout, 0),
|
||||||
ctx
|
ctx
|
||||||
);
|
);
|
||||||
|
|
||||||
lisp_table_set(
|
lisp_table_set(
|
||||||
table,
|
table,
|
||||||
lisp_make_symbol("_CURRENT-INPUT-PORT", ctx),
|
lisp_make_symbol("_current-input-port", ctx),
|
||||||
lisp_make_port(stdin, 1),
|
lisp_make_port(stdin, 1),
|
||||||
ctx
|
ctx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
#ifndef OUTPUT_H_
|
#ifndef OUTPUT_H_
|
||||||
#define OUTPUT_H_
|
#define OUTPUT_H_
|
||||||
|
|
||||||
#include "append_buffer.h"
|
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include "define.h"
|
|
||||||
#include "row_op.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,8 +9,6 @@
|
|||||||
* \brief Draws left rows of the editor.
|
* \brief Draws left rows of the editor.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void editorDrawRows(struct abuf *ab);
|
|
||||||
|
|
||||||
void editorRefreshScreen();
|
void editorRefreshScreen();
|
||||||
|
|
||||||
void editorScroll();
|
void editorScroll();
|
||||||
@@ -23,6 +17,4 @@ void editorDrawStatusBar(struct abuf *ab);
|
|||||||
|
|
||||||
void editorDrawMessageBar(struct abuf *ab);
|
void editorDrawMessageBar(struct abuf *ab);
|
||||||
|
|
||||||
void editorSetStatusMessage(const char *fmt, ...);
|
|
||||||
|
|
||||||
#endif // OUTPUT_H_
|
#endif // OUTPUT_H_
|
||||||
|
|||||||
@@ -8,22 +8,8 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
int editorRowCxToRx(erow *row, int cursor_x);
|
|
||||||
|
|
||||||
int editorRowRxToCx(erow *row, int rx);
|
|
||||||
|
|
||||||
void editorUpdateRow(erow *row);
|
|
||||||
|
|
||||||
void editorInsertRow(int at, char *s, size_t len);
|
|
||||||
|
|
||||||
void editorFreeRow(erow *row);
|
|
||||||
|
|
||||||
void editorDelRow(int at);
|
|
||||||
|
|
||||||
void editorRowInsertChar(erow *row, int at, int c);
|
|
||||||
|
|
||||||
void editorRowAppendString(erow *row, char *s, size_t len);
|
|
||||||
|
|
||||||
void editorRowDelchar(erow *row, int at);
|
|
||||||
|
|
||||||
#endif // ROW_OP_H_
|
#endif // ROW_OP_H_
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* @file split_screen.h
|
||||||
|
* @brief Split screen management for displaying multiple buffers
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SPLIT_SCREEN_H_
|
||||||
|
#define SPLIT_SCREEN_H_
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes split screen system
|
||||||
|
*/
|
||||||
|
void splitScreenInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Splits screen vertically (left-right)
|
||||||
|
* @param buffer_id_left Buffer ID for left pane
|
||||||
|
* @param buffer_id_right Buffer ID for right pane
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int splitScreenVertical(int buffer_id_left, int buffer_id_right);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Splits screen horizontally (top-bottom)
|
||||||
|
* @param buffer_id_top Buffer ID for top pane
|
||||||
|
* @param buffer_id_bottom Buffer ID for bottom pane
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns to single buffer fullscreen
|
||||||
|
*/
|
||||||
|
void splitScreenUnify(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switches active pane (focus moves between splits)
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int splitScreenSwitchPane(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the active pane's buffer
|
||||||
|
* @param buffer_id New buffer ID for active pane
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int splitScreenSetPaneBuffer(int buffer_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets current screen layout
|
||||||
|
* @return Pointer to current ScreenLayout
|
||||||
|
*/
|
||||||
|
ScreenLayout *splitScreenGetLayout(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets active pane
|
||||||
|
* @return Pointer to active EditorPane
|
||||||
|
*/
|
||||||
|
EditorPane *splitScreenGetActivePane(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef SYNTAX_HIGHLIGHTER_H_
|
||||||
|
#define SYNTAX_HIGHLIGHTER_H_
|
||||||
|
|
||||||
|
|
||||||
|
// Color codes that only affect foreground, preserving your background color
|
||||||
|
#define COLOR_RESET "\x1b[39m" // Reset all (4 bytes)
|
||||||
|
|
||||||
|
// Token types
|
||||||
|
typedef enum {
|
||||||
|
TOKEN_KEYWORD,
|
||||||
|
TOKEN_TYPE,
|
||||||
|
TOKEN_STRING,
|
||||||
|
TOKEN_COMMENT,
|
||||||
|
TOKEN_NUMBER,
|
||||||
|
TOKEN_OPERATOR,
|
||||||
|
TOKEN_DEFAULT
|
||||||
|
} TokenType;
|
||||||
|
|
||||||
|
char *highlight_line(const char * line, int *length);
|
||||||
|
|
||||||
|
#endif
|
||||||
+4
-2
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
/* includes */
|
/* includes */
|
||||||
|
|
||||||
#include "data.h"
|
|
||||||
#include "define.h"
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -31,4 +29,8 @@ int getCursorPosition(int *rows, int *cols);
|
|||||||
|
|
||||||
int getWindowSize(int *rows, int *cols);
|
int getWindowSize(int *rows, int *cols);
|
||||||
|
|
||||||
|
char *keyToString(int key);
|
||||||
|
|
||||||
|
void appDebug(const char *fmt, ...);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// Created by Giorgio on 01/05/2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BELUGA_UTF8_H
|
||||||
|
#define BELUGA_UTF8_H
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
uint32_t readUtf8Char(void);
|
||||||
|
int utf8Encode(uint32_t cp, char *buf);
|
||||||
|
int utf8Seqlen(unsigned char c);
|
||||||
|
int codepointWidth(uint32_t codepoint);
|
||||||
|
uint32_t utf8Decode(const char** s);
|
||||||
|
|
||||||
|
|
||||||
|
#endif //BELUGA_UTF8_H
|
||||||
Executable → Regular
+3
-1
@@ -16,8 +16,10 @@ fi
|
|||||||
|
|
||||||
echo "Create config files ..."
|
echo "Create config files ..."
|
||||||
mkdir -pv ~/.beluga/
|
mkdir -pv ~/.beluga/
|
||||||
cp -rv ./assets/ ~/.beluga/
|
mkdir -pv ~/.beluga/assets/
|
||||||
|
cp -rv ./assets/beluga.txt ~/.beluga/assets/
|
||||||
mkdir -pv ~/.beluga/packages/
|
mkdir -pv ~/.beluga/packages/
|
||||||
|
mkdir -pv ~/.beluga/config/
|
||||||
|
|
||||||
read -p "Do you want to replace your config file or keep it (init.lisp.bak) / (init.lisp.new) ? (Y/n)" confirm
|
read -p "Do you want to replace your config file or keep it (init.lisp.bak) / (init.lisp.new) ? (Y/n)" confirm
|
||||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||||
|
|||||||
@@ -5,20 +5,19 @@
|
|||||||
* interactions. \version 0.1 \date 21 septembre 2024
|
* interactions. \version 0.1 \date 21 septembre 2024
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#define _DEFAULT_SOURCE
|
#define _DEFAULT_SOURCE
|
||||||
#define _BSD_SOURCE
|
#define _BSD_SOURCE
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
|
#include "include/buffer.h"
|
||||||
|
#include "include/split_screen.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "include/data.h"
|
#include "include/data.h"
|
||||||
#include "include/file_io.h"
|
|
||||||
#include "include/init.h"
|
#include "include/init.h"
|
||||||
#include "include/input.h"
|
#include "include/input.h"
|
||||||
#include "include/output.h"
|
#include "include/editor_op.h"
|
||||||
#include "include/terminal.h"
|
#include "include/terminal.h"
|
||||||
|
|
||||||
struct editorConfig E;
|
struct editorConfig E;
|
||||||
@@ -26,21 +25,23 @@ struct editorConfig E;
|
|||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
char * splash_screen = (char *) calloc(256, sizeof(char));
|
char * splash_screen = (char *) calloc(256, sizeof(char));
|
||||||
|
|
||||||
enableRawMode();
|
enableRawMode();
|
||||||
initEditor();
|
initEditor();
|
||||||
if (argc >= 2) {
|
if (argc >= 2) {
|
||||||
E.state = READ_AND_WRITE;
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
editorOpen(argv[1]);
|
active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE);
|
||||||
} else {
|
} else {
|
||||||
strcat(splash_screen, getenv("HOME"));
|
strcat(splash_screen, getenv("HOME"));
|
||||||
strcat(splash_screen, "/.beluga/assets/beluga.txt");
|
strcat(splash_screen, "/.beluga/assets/beluga.txt");
|
||||||
fprintf(stderr, "%s\n", splash_screen);
|
|
||||||
editorOpen(splash_screen);
|
appDebug("splash : %s\n", splash_screen);
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
active->buffer_id = bufferCreate(splash_screen, READ_ONLY);
|
||||||
}
|
}
|
||||||
free(splash_screen);
|
free(splash_screen);
|
||||||
|
|
||||||
editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit");
|
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
|
||||||
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|||||||
+7
-1
@@ -1,5 +1,5 @@
|
|||||||
project('beluga', 'c',
|
project('beluga', 'c',
|
||||||
version : '1.1',
|
version : '2.3',
|
||||||
default_options : [
|
default_options : [
|
||||||
'c_std=none',
|
'c_std=none',
|
||||||
]
|
]
|
||||||
@@ -13,6 +13,7 @@ src_files = files(
|
|||||||
'main.c',
|
'main.c',
|
||||||
'src/append_buffer.c',
|
'src/append_buffer.c',
|
||||||
'src/editor_op.c',
|
'src/editor_op.c',
|
||||||
|
'src/syntax_highlighter.c',
|
||||||
'src/file_io.c',
|
'src/file_io.c',
|
||||||
'src/init.c',
|
'src/init.c',
|
||||||
'src/input.c',
|
'src/input.c',
|
||||||
@@ -20,10 +21,15 @@ src_files = files(
|
|||||||
'src/row_op.c',
|
'src/row_op.c',
|
||||||
'src/terminal.c',
|
'src/terminal.c',
|
||||||
'src/builtins.c',
|
'src/builtins.c',
|
||||||
|
'src/buffer.c',
|
||||||
|
'src/split_screen.c',
|
||||||
|
'src/utf8.c'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Executable
|
# Executable
|
||||||
executable('beluga',
|
executable('beluga',
|
||||||
src_files,
|
src_files,
|
||||||
dependencies: [m]
|
dependencies: [m]
|
||||||
|
)
|
||||||
|
dependencies: [m]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "../include/append_buffer.h"
|
#include "../include/append_buffer.h"
|
||||||
|
|
||||||
extern struct editorConfig E;
|
|
||||||
|
|
||||||
void abAppend(struct abuf *ab, const char *s, int len) {
|
void abAppend(struct abuf *ab, const char *s, int len) {
|
||||||
char *new = realloc(ab->b, ab->len + len);
|
char *new = realloc(ab->b, ab->len + len);
|
||||||
|
|
||||||
|
|||||||
+477
@@ -0,0 +1,477 @@
|
|||||||
|
/**
|
||||||
|
* @file buffer.c
|
||||||
|
* @brief Buffer management implementation for multiple open files
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../include/buffer.h"
|
||||||
|
#include "../include/file_io.h"
|
||||||
|
#include "../include/editor_op.h"
|
||||||
|
#include "../include/data.h"
|
||||||
|
#include "include/split_screen.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "include/input.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds a buffer by filename
|
||||||
|
* @param filename The filename to search for
|
||||||
|
* @return Buffer ID if found, -1 if not found
|
||||||
|
*/
|
||||||
|
static int bufferFindByFilename(const char* filename)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < E.number_of_buffer; i++)
|
||||||
|
{
|
||||||
|
if (E.buffers[i].filename != NULL &&
|
||||||
|
strcmp(E.buffers[i].filename, filename) == 0)
|
||||||
|
{
|
||||||
|
return E.buffers[i].buffer_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds a buffer by ID
|
||||||
|
* @param buffer_id The buffer ID to find
|
||||||
|
* @return Pointer to buffer, NULL if not found
|
||||||
|
*/
|
||||||
|
struct buffer_t* bufferFindById(int buffer_id)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < E.number_of_buffer; i++)
|
||||||
|
{
|
||||||
|
if (E.buffers[i].buffer_id == buffer_id)
|
||||||
|
{
|
||||||
|
return &E.buffers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a new buffer for a file
|
||||||
|
* @details Allocates buffer slot, loads file content, saves buffer metadata.
|
||||||
|
* If file is already open, switches to existing buffer instead.
|
||||||
|
* @param filename Path to the file
|
||||||
|
* @return Buffer ID on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferCreate(const char* filename, enum bufferStatus_e state)
|
||||||
|
{
|
||||||
|
// Check if file is already open
|
||||||
|
const int existing_id = bufferFindByFilename(filename);
|
||||||
|
if (existing_id != -1)
|
||||||
|
{
|
||||||
|
return bufferSwitch(existing_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have space for more buffers
|
||||||
|
if (E.number_of_buffer >= 64)
|
||||||
|
{
|
||||||
|
editorSetStatusMessage("Error: maximum buffers reached (64)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize new buffer
|
||||||
|
struct buffer_t* new_buf = &E.buffers[E.number_of_buffer];
|
||||||
|
new_buf->buffer_id = E.number_of_buffer;
|
||||||
|
new_buf->filename = strdup(filename);
|
||||||
|
new_buf->type = FILE_BUFF;
|
||||||
|
new_buf->state = state;
|
||||||
|
new_buf->x = 0;
|
||||||
|
new_buf->y = 0;
|
||||||
|
new_buf->dirty = 0; // New file starts clean
|
||||||
|
|
||||||
|
// Load file content using existing editorOpen
|
||||||
|
editorOpen(new_buf);
|
||||||
|
|
||||||
|
E.number_of_buffer++;
|
||||||
|
|
||||||
|
editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id);
|
||||||
|
return new_buf->buffer_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switches to a specific buffer by ID
|
||||||
|
* @details Saves current buffer state, loads target buffer content and state.
|
||||||
|
* @param buffer_id The buffer ID to switch to
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferSwitch(int buffer_id)
|
||||||
|
{
|
||||||
|
struct buffer_t* target = bufferFindById(buffer_id);
|
||||||
|
if (target == NULL)
|
||||||
|
{
|
||||||
|
E.layout.panes[E.layout.active_pane].buffer_id = buffer_id;
|
||||||
|
editorSetStatusMessage("Error: buffer not found");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
editorSetStatusMessage("Switched to: %s (buffer %d)",
|
||||||
|
target->filename, buffer_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Closes a buffer by ID
|
||||||
|
* @details Frees buffer resources and removes from buffer list.
|
||||||
|
* Prompts to save unsaved changes. If closing current buffer,
|
||||||
|
* switches to next available buffer.
|
||||||
|
* @param buffer_id The buffer ID to close
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferClose(int buffer_id)
|
||||||
|
{
|
||||||
|
struct buffer_t* buf = bufferFindById(buffer_id);
|
||||||
|
if (buf == NULL)
|
||||||
|
{
|
||||||
|
editorSetStatusMessage("Error: buffer not found");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
|
|
||||||
|
// If this is the current buffer, find next buffer to switch to
|
||||||
|
if (active->buffer_id == buffer_id)
|
||||||
|
{
|
||||||
|
int next_id = -1;
|
||||||
|
|
||||||
|
// Try to switch to next buffer
|
||||||
|
for (int i = 0; i < E.number_of_buffer; i++)
|
||||||
|
{
|
||||||
|
if (E.buffers[i].buffer_id != buffer_id)
|
||||||
|
{
|
||||||
|
next_id = E.buffers[i].buffer_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_id != -1)
|
||||||
|
{
|
||||||
|
bufferSwitch(next_id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No other buffers, clear editor
|
||||||
|
editorCloseFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free buffer resources
|
||||||
|
free(buf->filename);
|
||||||
|
buf->filename = NULL;
|
||||||
|
buf->buffer_id = -1;
|
||||||
|
|
||||||
|
// Remove from buffer list by shifting
|
||||||
|
for (int i = 0; i < E.number_of_buffer; i++)
|
||||||
|
{
|
||||||
|
if (E.buffers[i].buffer_id == buffer_id)
|
||||||
|
{
|
||||||
|
for (int j = i; j < E.number_of_buffer - 1; j++)
|
||||||
|
{
|
||||||
|
E.buffers[j] = E.buffers[j + 1];
|
||||||
|
}
|
||||||
|
E.number_of_buffer--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editorSetStatusMessage("Closed buffer %d", buffer_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current active buffer
|
||||||
|
* @return Pointer to current buffer_t, NULL if none
|
||||||
|
*/
|
||||||
|
struct buffer_t* bufferGetCurrent(void)
|
||||||
|
{
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
|
return bufferFindById(active->buffer_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lists all open buffers
|
||||||
|
* @details Prints buffer information to status message
|
||||||
|
* @return Number of open buffers
|
||||||
|
*/
|
||||||
|
int bufferListAll(void)
|
||||||
|
{
|
||||||
|
if (E.number_of_buffer == 0)
|
||||||
|
{
|
||||||
|
editorSetStatusMessage("No buffers open");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[256] = "";
|
||||||
|
int offset = 0;
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
|
|
||||||
|
for (int i = 0; i < E.number_of_buffer; i++)
|
||||||
|
{
|
||||||
|
struct buffer_t* b = &E.buffers[i];
|
||||||
|
char marker = (b->buffer_id == active->buffer_id) ? '*' : ' ';
|
||||||
|
offset += snprintf(&buf[offset], sizeof(buf) - offset,
|
||||||
|
"%c%d:%s ", marker, b->buffer_id,
|
||||||
|
b->filename ? b->filename : "[No Name]");
|
||||||
|
}
|
||||||
|
|
||||||
|
editorSetStatusMessage("Buffers: %s", buf);
|
||||||
|
return E.number_of_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Saves current buffer to disk
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferSave(void)
|
||||||
|
{
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
|
if (active->buffer_id == -1)
|
||||||
|
{
|
||||||
|
editorSetStatusMessage("Error: no buffer active");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
editorSave();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Saves all buffers to disk
|
||||||
|
* @details Iterates through all buffers, saves each one
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int bufferSaveAll(void)
|
||||||
|
{
|
||||||
|
int saved = 0;
|
||||||
|
int failed = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < E.number_of_buffer; i++)
|
||||||
|
{
|
||||||
|
if (bufferSwitch(E.buffers[i].buffer_id) == 0)
|
||||||
|
{
|
||||||
|
if (E.dirty && bufferSave() == 0)
|
||||||
|
{
|
||||||
|
saved++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editorSetStatusMessage("Saved %d buffers (%d failed)", saved, failed);
|
||||||
|
return (failed == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Searches for a string in the document
|
||||||
|
* @details Prompts user for a search query, then searches forward from current
|
||||||
|
* cursor position. Updates cursor position to the first match found.
|
||||||
|
* @note Updates global editor state E (cursor position, row_offset)
|
||||||
|
* @note Search is case-sensitive and operates on rendered line content
|
||||||
|
* @note Searches begin from the line after current cursor position
|
||||||
|
* @see editorPrompt()
|
||||||
|
* @see editorRowRxToCx()
|
||||||
|
*/
|
||||||
|
void bufferFind(struct buffer_t* buf)
|
||||||
|
{
|
||||||
|
appDebug("searching\n");
|
||||||
|
char* query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
|
||||||
|
|
||||||
|
if (query == NULL)
|
||||||
|
return;
|
||||||
|
int i;
|
||||||
|
for (i = buf->y+1; i < buf->numrows; i++)
|
||||||
|
{
|
||||||
|
row_t* row = &buf->row[i];
|
||||||
|
char* match = strstr(row->chars, query);
|
||||||
|
if (match)
|
||||||
|
{
|
||||||
|
buf->y = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bufferFindReverse(struct buffer_t* buf)
|
||||||
|
{
|
||||||
|
appDebug("searching\n");
|
||||||
|
char* query = editorPrompt("Reverse search: %s (ESC to cancel)", "", 0);
|
||||||
|
|
||||||
|
if (query == NULL)
|
||||||
|
return;
|
||||||
|
int i;
|
||||||
|
if (!buf->y)
|
||||||
|
return;
|
||||||
|
for (i = buf->y - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
row_t* row = &buf->row[i];
|
||||||
|
char* match = strstr(row->chars, query);
|
||||||
|
if (match)
|
||||||
|
{
|
||||||
|
buf->y = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) {
|
||||||
|
if (at < 0 || at > buffer->numrows)
|
||||||
|
return;
|
||||||
|
|
||||||
|
row_t *tmp = realloc(buffer->row, sizeof(row_t) * (buffer->numrows + 1));
|
||||||
|
if (!tmp)
|
||||||
|
return;
|
||||||
|
buffer->row = tmp;
|
||||||
|
|
||||||
|
/* Shift existing rows to make room at 'at' — no at++ */
|
||||||
|
if (at < buffer->numrows) {
|
||||||
|
memmove(&buffer->row[at + 1], &buffer->row[at],
|
||||||
|
sizeof(row_t) * (buffer->numrows - at)); /* not -at+1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer->row[at].size = len;
|
||||||
|
buffer->row[at].cap = len + 1;
|
||||||
|
buffer->row[at].chars = malloc(len + 1);
|
||||||
|
if (!buffer->row[at].chars)
|
||||||
|
return;
|
||||||
|
memcpy(buffer->row[at].chars, s, len);
|
||||||
|
buffer->row[at].chars[len] = '\0'; /* always NUL-terminate */
|
||||||
|
|
||||||
|
buffer->numrows++;
|
||||||
|
buffer->dirty++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bufferFreeRow(row_t *row) { free(row->chars); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \fn editorRowInsertChar(erow *row, int at, int c)
|
||||||
|
* \param at Index of where we want to insert the char */
|
||||||
|
|
||||||
|
void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at,
|
||||||
|
const char *src, int n) {
|
||||||
|
if (buffer->state == READ_ONLY)
|
||||||
|
return;
|
||||||
|
if (row->size + n + 1 > row->cap) {
|
||||||
|
row->cap = (row->size + n + 1) * 2;
|
||||||
|
row->chars = realloc(row->chars, row->cap);
|
||||||
|
}
|
||||||
|
memmove(row->chars + at + n, row->chars + at, row->size - at);
|
||||||
|
memcpy(row->chars + at, src, n);
|
||||||
|
row->size += n;
|
||||||
|
row->chars = realloc(row->chars, row->size + 2);
|
||||||
|
++buffer->dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \fn bufferRowDelChar(struct bufferConfig *E, frow *frow, int at)
|
||||||
|
* \brief Delete the a char at the chosen position on the given row
|
||||||
|
* \param at Index of the char to delete
|
||||||
|
* \param row Row on operation is made */
|
||||||
|
void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n) {
|
||||||
|
if (buffer->state == READ_ONLY)
|
||||||
|
return;
|
||||||
|
if (at < 0 || at >= row->size)
|
||||||
|
return;
|
||||||
|
memmove(row->chars + at, row->chars + at + n, row->size - at - n);
|
||||||
|
row->size -= n;
|
||||||
|
row->chars[row->size] = '\0';
|
||||||
|
buffer->x -= n;
|
||||||
|
++buffer->dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void bufferInsertBytes(const char* src, int n)
|
||||||
|
{
|
||||||
|
appDebug("bufferInsertBytes \r\n");
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||||
|
if (buf->y == buf->numrows)
|
||||||
|
{
|
||||||
|
bufferInsertRow(buf, buf->numrows, "", 0);
|
||||||
|
}
|
||||||
|
bufferRowInsertBytes(buf, &buf->row[buf->y], buf->x, src, n);
|
||||||
|
buf->x += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bufferDelBytes(void)
|
||||||
|
{
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||||
|
|
||||||
|
/* Nothing to delete */
|
||||||
|
if (buf->numrows == 0) return;
|
||||||
|
if (buf->x == 0 && buf->y == 0) return;
|
||||||
|
|
||||||
|
/* Use row_offset, not col_offset, for row indexing */
|
||||||
|
row_t* r = &buf->row[buf->y];
|
||||||
|
|
||||||
|
if (buf->x > 0)
|
||||||
|
{
|
||||||
|
int byte_end = editorRowCxToByte(r, buf->x);
|
||||||
|
int byte_start = editorRowCxToByte(r, buf->x - 1);
|
||||||
|
int char_width = byte_end - byte_start; /* byte width of the character */
|
||||||
|
|
||||||
|
bufferRowDelByte(buf, r, byte_start, char_width);
|
||||||
|
E.dirty = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Merge current row into the previous one */
|
||||||
|
row_t* prev = &buf->row[buf->y - 1]; // FIX: was buf->y (same as r)
|
||||||
|
int prev_char_count = editorRowCharCount(prev, prev->size);
|
||||||
|
|
||||||
|
bufferRowInsertBytes(buf, prev, prev->size, r->chars, r->size);
|
||||||
|
free(r->chars);
|
||||||
|
r->chars = NULL;
|
||||||
|
|
||||||
|
memmove(&buf->row[buf->y],
|
||||||
|
&buf->row[buf->y + 1],
|
||||||
|
sizeof(row_t) * (buf->numrows - buf->y - 1));
|
||||||
|
buf->numrows--;
|
||||||
|
|
||||||
|
active->cursor_x = prev_char_count;
|
||||||
|
buf->x = prev_char_count;
|
||||||
|
active->cursor_y--;
|
||||||
|
buf->y--;
|
||||||
|
E.dirty = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bufferInsertNewLine(void) {
|
||||||
|
appDebug("Inserting new line\n");
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||||
|
|
||||||
|
appDebug("buf x %d\n", buf->x);
|
||||||
|
|
||||||
|
if (buf->y >= buf->numrows) {
|
||||||
|
/* Cursor is past the last row: just append a blank line */
|
||||||
|
bufferInsertRow(buf, buf->numrows, "", 0);
|
||||||
|
} else {
|
||||||
|
row_t *row = &buf->row[buf->y];
|
||||||
|
|
||||||
|
/* Insert the tail (from cursor to end) as a new row below */
|
||||||
|
bufferInsertRow(buf, buf->y + 1, &row->chars[buf->x], row->size - buf->x);
|
||||||
|
|
||||||
|
/* Re-fetch: realloc inside bufferInsertRow may have moved the array */
|
||||||
|
row = &buf->row[buf->y];
|
||||||
|
|
||||||
|
/* Truncate the current row at the cursor */
|
||||||
|
row->size = buf->x;
|
||||||
|
row->chars[row->size] = '\0';
|
||||||
|
/* Do NOT touch row->cap — the allocation is still valid */
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->y++;
|
||||||
|
buf->x = 0;
|
||||||
|
appDebug("Insert new line done\n");
|
||||||
|
}
|
||||||
+462
-69
@@ -1,109 +1,296 @@
|
|||||||
|
/**
|
||||||
|
* @file builtins.c
|
||||||
|
* @brief Built-in Lisp functions for editor operations
|
||||||
|
* @details Provides Lisp bindings for core editor functionality including
|
||||||
|
* cursor movement, file operations, keybinding management, and text
|
||||||
|
* manipulation
|
||||||
|
*/
|
||||||
|
|
||||||
#include "../include/builtins.h"
|
#include "../include/builtins.h"
|
||||||
#include "../include/define.h"
|
#include "../include/buffer.h"
|
||||||
#include "../include/input.h"
|
|
||||||
#include "../include/file_io.h"
|
|
||||||
#include "../include/editor_op.h"
|
|
||||||
#include "../include/row_op.h"
|
|
||||||
#include "../include/data.h"
|
#include "../include/data.h"
|
||||||
|
#include "../include/define.h"
|
||||||
|
#include "../include/editor_op.h"
|
||||||
|
#include "../include/file_io.h"
|
||||||
|
#include "../include/input.h"
|
||||||
|
#include "../include/terminal.h"
|
||||||
|
#include "../include/split_screen.h"
|
||||||
|
#include "../include/completion.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds a prefix configuration by name
|
||||||
|
* @details Searches the prefix array for a prefix matching the given name.
|
||||||
|
* Returns the first prefix (default) if no match is found.
|
||||||
|
* @param prefix_name Name of the prefix to search for (max 64 chars)
|
||||||
|
* @return Matching prefix_t structure, or E.prefix[0] if not found
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
|
struct prefix_t find_prefix(const char prefix_name[64]) {
|
||||||
|
int i = E.number_of_prefix + 1;
|
||||||
|
while (i--) {
|
||||||
|
if (!strcmp(prefix_name, E.prefix[i].prefix_name)) {
|
||||||
|
return E.prefix[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return E.prefix[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to bind a command to a key sequence
|
||||||
|
* @details Registers a keybinding with an associated Lisp command and optional
|
||||||
|
* prefix modifier. Dynamically extends the keybind array.
|
||||||
|
* @param args Lisp list of 3 arguments: (key-sequence command prefix-name)
|
||||||
|
* - key_sequence (string): The key or key combination to bind
|
||||||
|
* - command (Lisp): The Lisp command/function to execute
|
||||||
|
* - prefix_name (string): Prefix modifier name
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E (key_binds array)
|
||||||
|
*/
|
||||||
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
/*
|
||||||
|
* 3 arguments keybind command prefix
|
||||||
|
*/
|
||||||
const char *key_sequence = lisp_string(lisp_car(args));
|
const char *key_sequence = lisp_string(lisp_car(args));
|
||||||
args = lisp_cdr(args);
|
args = lisp_cdr(args);
|
||||||
// second argument
|
// second argument
|
||||||
Lisp func = lisp_car(args);
|
const Lisp func = lisp_car(args);
|
||||||
|
|
||||||
E.key_binds =
|
E.key_binds = (struct keyBind_t *)realloc(
|
||||||
(struct keyBind_t *)realloc(E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
|
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
|
||||||
E.key_binds[E.number_of_keybinds - 1].key_sequence = (char *) malloc(50 * sizeof(char));
|
E.key_binds[E.number_of_keybinds - 1].key_sequence =
|
||||||
|
(char *)malloc(50 * sizeof(char));
|
||||||
|
|
||||||
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
|
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
|
||||||
|
|
||||||
E.key_binds[E.number_of_keybinds - 1].command = func;
|
E.key_binds[E.number_of_keybinds - 1].command = func;
|
||||||
|
|
||||||
|
// Third argument
|
||||||
|
args = lisp_cdr(args);
|
||||||
|
const char *prefix_name = lisp_string(lisp_car(args));
|
||||||
|
struct prefix_t prefix = find_prefix(prefix_name);
|
||||||
|
|
||||||
|
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
|
||||||
|
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor in a specified direction
|
||||||
|
* @details Moves the editor cursor up, down, left, or right based on direction
|
||||||
|
* string.
|
||||||
|
* @param args Lisp list with one argument: direction string
|
||||||
|
* - "u": up, "d": down, "r": right, "l": left
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return Lisp boolean indicating whether movement was valid
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorMoveCursor()
|
||||||
|
*/
|
||||||
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
|
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
|
||||||
const char *direction = lisp_string(lisp_car(args));
|
const char *direction = lisp_string(lisp_car(args));
|
||||||
|
int is_in = 0;
|
||||||
switch (direction[0]) {
|
switch (direction[0]) {
|
||||||
case 'u':
|
case 'u':
|
||||||
editorMoveCursor(ARROW_UP);
|
is_in = editorMoveCursor(ARROW_UP);
|
||||||
break;
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
editorMoveCursor(ARROW_DOWN);
|
is_in = editorMoveCursor(ARROW_DOWN);
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
editorMoveCursor(ARROW_RIGHT);
|
is_in = editorMoveCursor(ARROW_RIGHT);
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
editorMoveCursor(ARROW_LEFT);
|
is_in = editorMoveCursor(ARROW_LEFT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
appDebug("move lisp %d\n", is_in);
|
||||||
|
return lisp_make_bool(is_in);
|
||||||
return lisp_null();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Lisp editorQuit(Lisp args, LispError* e, LispContext ctx) {
|
/**
|
||||||
|
* @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, j;
|
||||||
|
free(E.prefix);
|
||||||
|
for (i = 0; i < E.number_of_keybinds; ++i) {
|
||||||
|
free(E.key_binds[i].key_sequence);
|
||||||
|
}
|
||||||
|
free(E.key_binds);
|
||||||
|
// free layout
|
||||||
|
free(E.layout.panes);
|
||||||
|
|
||||||
|
// Free buffers
|
||||||
|
for (i = 0; i < E.number_of_buffer; ++i) {
|
||||||
|
free(E.buffers[i].filename);
|
||||||
|
for (j = 0; j < E.buffers[i].numrows; ++j) {
|
||||||
|
free(E.buffers[i].row[j].chars);
|
||||||
|
}
|
||||||
|
free(E.buffers[i].row);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(E.init_file_path);
|
||||||
|
fclose(E.fd_init_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to quit the editor
|
||||||
|
* @details Closes editor with unsaved changes protection. Prompts user to
|
||||||
|
* confirm quit after multiple attempts if file is dirty. Cleans up resources
|
||||||
|
* and restores terminal.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null() (never returns on successful exit)
|
||||||
|
* @note Calls exit(0) to terminate program
|
||||||
|
* @note Updates quit_times_buffer counter
|
||||||
|
* @see free_structs()
|
||||||
|
*/
|
||||||
|
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
|
||||||
if (E.dirty && E.quit_times_buffer > 0) {
|
if (E.dirty && E.quit_times_buffer > 0) {
|
||||||
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
|
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
|
||||||
"another time to quit.");
|
"another time to quit.");
|
||||||
--E.quit_times_buffer;
|
--E.quit_times_buffer;
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
free_structs();
|
||||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||||
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
||||||
disableRawMode();
|
disableRawMode();
|
||||||
|
lisp_shutdown(E.ctx);
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to save the current file
|
||||||
|
* @details Wrapper around editorSave() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorSave()
|
||||||
|
*/
|
||||||
|
|
||||||
Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx) {
|
Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
if (E.number_of_buffer >= 2) {
|
||||||
editorSave();
|
splitScreenVertical(E.buffers[0].buffer_id, E.buffers[1].buffer_id);
|
||||||
|
} else {
|
||||||
return lisp_null();
|
editorSetStatusMessage("Need at least 2 buffers open");
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx) {
|
|
||||||
|
|
||||||
editorInsertNewLine();
|
|
||||||
|
|
||||||
return lisp_null();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
|
|
||||||
E.cursor_x = 0;
|
|
||||||
return lisp_null();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
|
||||||
|
editorSave();
|
||||||
|
|
||||||
Lisp deletePreviousChar(Lisp args, LispError* e, LispContext ctx) {
|
|
||||||
editorDelChar();
|
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) {
|
/**
|
||||||
E.cursor_y = E.row_offset;
|
* @brief Lisp function to insert a new line at cursor
|
||||||
|
* @details Wrapper around editorInsertNewLine() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorInsertNewLine()
|
||||||
|
*/
|
||||||
|
Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
|
||||||
|
bufferInsertNewLine();
|
||||||
|
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to insert a tab (spaces) at cursor
|
||||||
|
* @details Inserts TAB_LENGTH spaces at the current cursor position.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Uses E.constantes.TAB_LENGTH for indentation width
|
||||||
|
*/
|
||||||
|
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
for (int i = 0; i < E.constantes.TAB_LENGTH; ++i) {
|
||||||
|
bufferInsertBytes(" ", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor to beginning of line
|
||||||
|
* @details Moves cursor to column 0 of the current line.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
|
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||||
|
buf->x = 0;
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor to end of line
|
||||||
|
* @details Moves cursor to the end of the current line content.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
|
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||||
|
buf->x = buf->row[buf->y].size;
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to delete character before cursor
|
||||||
|
* @details Wrapper around editorDelChar() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorDelChar()
|
||||||
|
*/
|
||||||
|
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
bufferDelBytes();
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor up by one screen
|
||||||
|
* @details Scrolls up one full screen height, moving cursor to top of visible
|
||||||
|
* area.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorMoveCursor()
|
||||||
|
*/
|
||||||
|
Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
active->cursor_y = active->y_offset;
|
||||||
int times = E.screenrows;
|
int times = E.screenrows;
|
||||||
while (--times) {
|
while (--times) {
|
||||||
editorMoveCursor(ARROW_UP);
|
editorMoveCursor(ARROW_UP);
|
||||||
@@ -111,10 +298,23 @@ Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx) {
|
/**
|
||||||
E.cursor_y = E.row_offset + E.screenrows - 1;
|
* @brief Lisp function to move cursor down by one screen
|
||||||
if (E.cursor_y > E.numrows) {
|
* @details Scrolls down one full screen height, moving cursor to bottom of
|
||||||
E.cursor_y = E.numrows;
|
* visible area.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorMoveCursor()
|
||||||
|
*/
|
||||||
|
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||||
|
active->cursor_y = active->y_offset + E.screenrows - 1;
|
||||||
|
if (active->cursor_y > buffer->numrows) {
|
||||||
|
active->cursor_y = buffer->numrows;
|
||||||
}
|
}
|
||||||
int times = E.screenrows;
|
int times = E.screenrows;
|
||||||
while (--times) {
|
while (--times) {
|
||||||
@@ -124,45 +324,238 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to open a file
|
||||||
|
* @details Prompts user for filename and opens the file for editing.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorOpen()
|
||||||
|
* @see editorPrompt()
|
||||||
|
*/
|
||||||
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
||||||
char *filename = editorPrompt("Path : %s");
|
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
|
||||||
if (filename)
|
if (filename) {
|
||||||
editorOpen(filename);
|
// editorOpen(filename);
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
active->buffer_id = bufferCreate(filename, READ_AND_WRITE);
|
||||||
|
}
|
||||||
|
free(filename);
|
||||||
|
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to insert a character
|
||||||
|
* @details Extracts a character from Lisp string argument and inserts it at
|
||||||
|
* cursor.
|
||||||
|
* @param args Lisp list with one string argument
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Uses first character of the string argument
|
||||||
|
*/
|
||||||
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
|
||||||
char c = lisp_string(lisp_car(args))[0];
|
const char *src = lisp_string(lisp_car(args));
|
||||||
editorInsertChar(c);
|
bufferInsertBytes(src, strlen(src));
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to load and execute a package
|
||||||
|
* @details Loads a Lisp package from the user's packages directory
|
||||||
|
* (~/.beluga/packages/<package_name>/init.lisp) and evaluates it.
|
||||||
|
* @param args Lisp list with one argument: package name (string)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Package files must be valid Lisp code
|
||||||
|
* @note Package directory defaults to ~/.beluga/packages/
|
||||||
|
*/
|
||||||
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
|
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
|
||||||
const char *package_name = lisp_string(lisp_car(args));
|
const char *package_name = lisp_string(lisp_car(args));
|
||||||
char *package_dir = (char *) calloc(256, sizeof(char));
|
appDebug("%s\n", package_name);
|
||||||
|
char *package_dir = (char *)calloc(256, sizeof(char));
|
||||||
FILE *fd_package = NULL;
|
FILE *fd_package = NULL;
|
||||||
strcat(package_dir, getenv("HOME"));
|
strcat(package_dir, getenv("HOME"));
|
||||||
strcat(package_dir, "/.beluga/packages/");
|
strcat(package_dir, "/.beluga/packages/");
|
||||||
strcat(package_dir, package_name);
|
strcat(package_dir, package_name);
|
||||||
strcat(package_dir, "/init.lisp");
|
strcat(package_dir, "/init.lisp");
|
||||||
|
appDebug("%s\n", package_dir);
|
||||||
fd_package = fopen(package_dir, "r");
|
fd_package = fopen(package_dir, "r");
|
||||||
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
|
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
|
||||||
E.ctx);
|
E.ctx);
|
||||||
fclose(fd_package);
|
fclose(fd_package);
|
||||||
free(package_dir);
|
free(package_dir);
|
||||||
|
|
||||||
return lisp_null();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) {
|
|
||||||
editorDelRow(E.cursor_y);
|
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) {
|
||||||
editorFind();
|
appDebug("switch buffer\n");
|
||||||
return lisp_null();
|
if (E.number_of_buffer > 0) {
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
int next_idx = (active->buffer_id + 1) % E.number_of_buffer;
|
||||||
|
active->buffer_id = next_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
splitScreenSwitchPane();
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
if (E.layout.num_panes - 1) {
|
||||||
|
splitScreenUnify();
|
||||||
|
}
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to search for text
|
||||||
|
* @details Wrapper around editorFind() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorFind()
|
||||||
|
*/
|
||||||
|
Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
appDebug("LispFind\n");
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||||
|
bufferFind(buffer);
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to search for text
|
||||||
|
* @details Wrapper around editorFind() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorFind()
|
||||||
|
*/
|
||||||
|
Lisp bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
appDebug("LispFind\n");
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||||
|
bufferFindReverse(buffer);
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to read character at cursor
|
||||||
|
* @details Returns the character at the current cursor position as a Lisp
|
||||||
|
* character. Returns 'a' if at end of line.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return Lisp character object representing the character at cursor
|
||||||
|
*/
|
||||||
|
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
Lisp returned_char;
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||||
|
if (buffer->row[buffer->y].chars[buffer->x] == 0) {
|
||||||
|
returned_char = lisp_make_char('a');
|
||||||
|
} else {
|
||||||
|
returned_char = lisp_make_char(buffer->row[buffer->y].chars[buffer->x]);
|
||||||
|
}
|
||||||
|
return returned_char;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to set the current prefix mode
|
||||||
|
* @details Changes the editor's prefix state to a named prefix, affecting which
|
||||||
|
* keybindings are active. Updates status message to show active prefix.
|
||||||
|
* @param args Lisp list with one argument: prefix name (string)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E (prefix_state)
|
||||||
|
* @see find_prefix()
|
||||||
|
*/
|
||||||
|
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
/*
|
||||||
|
* Set the prefix state of editor to the prefix in argument
|
||||||
|
*/
|
||||||
|
const char *prefix_name = lisp_string(lisp_car(args));
|
||||||
|
struct prefix_t prefix = find_prefix(prefix_name);
|
||||||
|
E.prefix_state = prefix.prefix_id;
|
||||||
|
editorSetStatusMessage("prefix %s", prefix.prefix_name);
|
||||||
|
appDebug("%s set\n", prefix_name);
|
||||||
|
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to define a new prefix modifier
|
||||||
|
* @details Registers a named prefix modifier that can be used with keybindings
|
||||||
|
* to create context-aware command sequences (e.g., Ctrl-X as a prefix).
|
||||||
|
* @param args Lisp list with one argument: prefix name (string, max 64 chars)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E (prefix array)
|
||||||
|
*/
|
||||||
|
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
E.prefix = (struct prefix_t *)realloc(E.prefix, (++(E.number_of_prefix) + 1) *
|
||||||
|
sizeof(struct prefix_t));
|
||||||
|
E.prefix[E.number_of_prefix].prefix_id = E.number_of_prefix;
|
||||||
|
strncpy(E.prefix[E.number_of_prefix].prefix_name, lisp_string(lisp_car(args)),
|
||||||
|
64);
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
Lisp editorPaste(Lisp args, LispError *e, LispContext ctx)
|
||||||
|
{
|
||||||
|
const char *char_to_paste = editorGetClipboard();
|
||||||
|
appDebug("editor-paste, %s\n", char_to_paste);
|
||||||
|
bufferInsertBytes(char_to_paste, (int) strlen(char_to_paste));
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
Lisp editorCutEndLine(Lisp args, LispError *e, LispContext ctx)
|
||||||
|
{
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||||
|
int bytes_to_delete = buffer->row[buffer->y].size - buffer->x;
|
||||||
|
editorSetClipboard(&buffer->row[buffer->y].chars[buffer->x], bytes_to_delete);
|
||||||
|
buffer->x = buffer->row[buffer->y].size;
|
||||||
|
while (bytes_to_delete--)
|
||||||
|
{
|
||||||
|
bufferDelBytes();
|
||||||
|
}
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
Lisp editorMoveBegBuffer(Lisp args, LispError *e, LispContext ctx)
|
||||||
|
{
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||||
|
buffer->x = 0;
|
||||||
|
buffer->y = 0;
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
Lisp editorMoveEndBuffer(Lisp args, LispError *e, LispContext ctx)
|
||||||
|
{
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||||
|
buffer->x = buffer->row[buffer->numrows-1].size;
|
||||||
|
buffer->y = buffer->numrows-1;
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
Lisp editorAutoComplete(Lisp args, LispError *e, LispContext ctx)
|
||||||
|
{
|
||||||
|
createContextBuffer(E.cursor_x - 2, E.cursor_y + 1, "hello");
|
||||||
|
return lisp_null();
|
||||||
|
}
|
||||||
+118
-35
@@ -1,47 +1,130 @@
|
|||||||
#include "../include/editor_op.h"
|
#include "../include/editor_op.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
#include "../include/row_op.h"
|
#include "../include/row_op.h"
|
||||||
|
#include "../include/buffer.h"
|
||||||
|
#include "../include/data.h"
|
||||||
|
#include "../include/split_screen.h"
|
||||||
|
#include "../include/terminal.h"
|
||||||
|
#include "../include/utf8.h"
|
||||||
|
|
||||||
|
|
||||||
extern struct editorConfig E;
|
extern struct editorConfig E;
|
||||||
|
|
||||||
void editorInsertChar(int c) {
|
|
||||||
if (E.cursor_y == E.numrows) {
|
/**
|
||||||
editorInsertRow(E.numrows, "", 0);
|
* @brief Sets a temporary status message for display
|
||||||
|
* @details Formats and stores a message that will be displayed in the message
|
||||||
|
* bar for 5 seconds. Uses printf-style variable argument formatting.
|
||||||
|
* @param fmt Printf-style format string
|
||||||
|
* @param ... Variable arguments for format string
|
||||||
|
* @note Updates global editor state E (status_msg, status_msg_time)
|
||||||
|
* @see editorDrawMessageBar()
|
||||||
|
*/
|
||||||
|
void editorSetStatusMessage(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(E.status_msg, E.screencols, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
E.status_msg_time = time(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Moves the cursor based on arrow key input
|
||||||
|
* @details Updates cursor position (E.cursor_x, E.cursor_y) based on the given
|
||||||
|
* key direction. Handles line wrapping and boundary conditions. Prevents cursor
|
||||||
|
* from exceeding line lengths.
|
||||||
|
* @param key The arrow key code (ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT)
|
||||||
|
* @return 1 if cursor movement was valid, 0 if cursor was constrained to line boundary
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
|
int editorMoveCursor(int key) {
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||||
|
row_t *row = &buf->row[buf->y];
|
||||||
|
switch (key) {
|
||||||
|
case ARROW_RIGHT:
|
||||||
|
if (row && buf->x < row->size) {
|
||||||
|
int len = utf8Seqlen(row->chars[buf->x]);
|
||||||
|
buf->x += len;
|
||||||
|
} else if (row && buf->y < buf->numrows) {
|
||||||
|
buf->y++;
|
||||||
|
buf->x = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ARROW_DOWN:
|
||||||
|
if (buf->y < buf->numrows) {
|
||||||
|
|
||||||
|
buf->y++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ARROW_UP:
|
||||||
|
if (buf->y != 0) {
|
||||||
|
--buf->y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ARROW_LEFT:
|
||||||
|
if (buf->x != 0) {
|
||||||
|
--buf->x;
|
||||||
|
} else if (buf->y > 0) {
|
||||||
|
--buf->y;
|
||||||
|
buf->x = buf->row[buf->y].size;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, c);
|
return 1;
|
||||||
E.cursor_x++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void editorInsertNewLine() {
|
char *editorGetClipboard(void) {
|
||||||
erow *row;
|
FILE *pipe = popen(CLIPBOARD_PASTE_CMD, "r");
|
||||||
if (!E.cursor_x) {
|
if (!pipe) return NULL;
|
||||||
editorInsertRow(E.cursor_y, "", 0);
|
|
||||||
} else {
|
size_t cap = 4096;
|
||||||
row = &E.row[E.cursor_y];
|
size_t len = 0;
|
||||||
editorInsertRow(E.cursor_y + 1, &row->chars[E.cursor_x],
|
char *buf = malloc(cap);
|
||||||
row->size - E.cursor_x);
|
|
||||||
row = &E.row[E.cursor_y];
|
int c;
|
||||||
row->size = E.cursor_x;
|
while ((c = fgetc(pipe)) != EOF) {
|
||||||
row->chars[row->size] = '\0';
|
if (len + 1 >= cap) {
|
||||||
editorUpdateRow(row);
|
cap *= 2;
|
||||||
}
|
buf = realloc(buf, cap);
|
||||||
++E.cursor_y;
|
}
|
||||||
E.cursor_x = 0;
|
buf[len++] = (char)c;
|
||||||
|
}
|
||||||
|
buf[len] = '\0';
|
||||||
|
pclose(pipe);
|
||||||
|
return buf; // caller must free
|
||||||
}
|
}
|
||||||
|
|
||||||
void editorDelChar() {
|
void editorSetClipboard(const char *text, int len) {
|
||||||
erow *row;
|
FILE *pipe = popen(CLIPBOARD_COPY_CMD, "w");
|
||||||
if (E.cursor_y == E.numrows || !(E.cursor_x || E.cursor_y)) {
|
if (!pipe) return;
|
||||||
return;
|
fwrite(text, 1, len, pipe);
|
||||||
}
|
pclose(pipe);
|
||||||
row = &E.row[E.cursor_y];
|
|
||||||
if (E.cursor_x > 0) {
|
|
||||||
editorRowDelchar(row, E.cursor_x - 1);
|
|
||||||
--E.cursor_x;
|
|
||||||
} else {
|
|
||||||
E.cursor_x = E.row[E.cursor_y - 1].size;
|
|
||||||
editorRowAppendString(&E.row[E.cursor_y - 1], row->chars, row->size);
|
|
||||||
editorDelRow(E.cursor_y);
|
|
||||||
--E.cursor_y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int editorRowCxToByte(const row_t *row, int cursor_x) {
|
||||||
|
int i = 0, col = 0;
|
||||||
|
while (col < cursor_x && i < row->size) {
|
||||||
|
int sl = utf8Seqlen((unsigned char)row->chars[i]);
|
||||||
|
if (sl < 1)
|
||||||
|
sl = 1;
|
||||||
|
col++;
|
||||||
|
i += sl;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
int editorRowCharCount(row_t *row, int x) {
|
||||||
|
int n = 0, i = 0;
|
||||||
|
while (i < x && i < row->size) {
|
||||||
|
int sl = utf8Seqlen((unsigned char)row->chars[i]);
|
||||||
|
if (sl < 1)
|
||||||
|
sl = 1;
|
||||||
|
n++;
|
||||||
|
i += sl;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
+107
-107
@@ -1,130 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* @file file_io.c
|
||||||
|
* @brief File I/O operations module for the Beluga text editor
|
||||||
|
* @details Handles file loading, saving, searching, and buffer management.
|
||||||
|
* Provides functionality for opening/closing files, persisting changes to disk,
|
||||||
|
* and searching for text patterns within the document.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "../include/file_io.h"
|
#include "../include/file_io.h"
|
||||||
|
#include "../include/editor_op.h"
|
||||||
#include "../include/input.h"
|
#include "../include/input.h"
|
||||||
#include "../include/output.h"
|
#include "../include/buffer.h"
|
||||||
|
#include "../include/data.h"
|
||||||
|
#include "../include/split_screen.h"
|
||||||
|
#include "../include/row_op.h"
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
extern char *strdup(const char *);
|
|
||||||
extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
|
|
||||||
FILE *restrict stream);
|
|
||||||
extern int ftruncate(int fd, off_t length);
|
|
||||||
extern struct editorConfig E;
|
extern struct editorConfig E;
|
||||||
|
|
||||||
char *editorRowsToString(int *buffer_len) {
|
/**
|
||||||
int tot_len = 0;
|
* @brief Closes the current file and resets editor state
|
||||||
int j;
|
* @details Clears all rows, resets cursor position, scroll offsets, and file
|
||||||
char *buf;
|
* metadata. Does not prompt to save unsaved changes.
|
||||||
char *p;
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
for (j = 0; j < E.numrows; ++j) {
|
|
||||||
tot_len += E.row[j].size + 1;
|
|
||||||
}
|
|
||||||
*buffer_len = tot_len;
|
|
||||||
buf = malloc(tot_len);
|
|
||||||
p = buf;
|
|
||||||
for (j = 0; j < E.numrows; ++j) {
|
|
||||||
memcpy(p, E.row[j].chars, E.row[j].size);
|
|
||||||
p += E.row[j].size;
|
|
||||||
*p = '\n';
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
void editorCloseFile(void) {
|
void editorCloseFile(void) {
|
||||||
E.cursor_x = 0;
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
E.cursor_y = 0;
|
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||||
E.rx = 0;
|
active->cursor_x = 0;
|
||||||
E.row_offset = 0;
|
active->cursor_y = 0;
|
||||||
E.col_offset = 0;
|
active->x_offset = 0;
|
||||||
E.numrows = 0;
|
active->y_offset = 0;
|
||||||
E.row = NULL;
|
for (int i = 0; i < buf->numrows; ++i) {
|
||||||
E.dirty = 0;
|
free(buf->row[i].chars);
|
||||||
E.filename = NULL;
|
}
|
||||||
E.status_msg[0] = '\0';
|
buf->numrows = 0;
|
||||||
E.status_msg_time = 0;
|
free(buf->row);
|
||||||
|
buf->row = NULL;
|
||||||
|
buf->dirty = 0;
|
||||||
|
free(buf->filename);
|
||||||
|
buf->filename = NULL;
|
||||||
|
E.status_msg[0] = '\0';
|
||||||
|
E.status_msg_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void editorOpen(char *filename) {
|
/**
|
||||||
FILE *fp;
|
* @brief Opens a file for editing
|
||||||
|
* @details Loads file content into editor rows, one line per row. If another
|
||||||
|
* file is already open, it is closed first (without saving). File is opened in
|
||||||
|
* a+ (read/append) mode to allow both reading and modification.
|
||||||
|
* @param filename Path to the file to open (relative or absolute)
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @note Calls die() on file open failure
|
||||||
|
* @note Newline characters are stripped from loaded lines
|
||||||
|
* @see editorInsertRow()
|
||||||
|
*/
|
||||||
|
void editorOpen(struct buffer_t* buffer) {
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
// Test if a file is already open
|
fp = fopen(buffer->filename, "a+");
|
||||||
if (E.filename != NULL) {
|
if (!fp)
|
||||||
editorCloseFile();
|
die("fopen");
|
||||||
E.state = READ_AND_WRITE;
|
|
||||||
}
|
char *line = NULL;
|
||||||
|
size_t line_cap;
|
||||||
free(E.filename);
|
ssize_t line_len;
|
||||||
E.filename = strdup(filename);
|
|
||||||
|
rewind(fp);
|
||||||
fp = fopen(filename, "a+");
|
|
||||||
if (!fp)
|
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
|
||||||
die("fopen");
|
while (line_len > 0 &&
|
||||||
|
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
|
||||||
char *line = NULL;
|
--line_len;
|
||||||
size_t line_cap = 0;
|
|
||||||
ssize_t line_len;
|
|
||||||
|
|
||||||
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
|
|
||||||
while (line_len > 0 &&
|
|
||||||
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
|
|
||||||
--line_len;
|
|
||||||
}
|
|
||||||
editorInsertRow(E.numrows, line, line_len);
|
|
||||||
}
|
}
|
||||||
|
appDebug("line %s\n", line);
|
||||||
|
bufferInsertRow(buffer, buffer->numrows, line, line_len);
|
||||||
free(line);
|
free(line);
|
||||||
fclose(fp);
|
line = NULL;
|
||||||
E.dirty = 0;
|
}
|
||||||
|
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() {
|
void editorSave() {
|
||||||
int len;
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
char *buf;
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||||
int fd;
|
int len;
|
||||||
if (E.filename == NULL) {
|
int fd;
|
||||||
E.filename = editorPrompt("Save as: %s (ESC to cancel)");
|
if (buffer->filename == NULL) {
|
||||||
if (E.filename == NULL) {
|
buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
|
||||||
editorSetStatusMessage("Save aborted");
|
if (buffer->filename == NULL) {
|
||||||
return;
|
editorSetStatusMessage("Save aborted");
|
||||||
}
|
return;
|
||||||
}
|
|
||||||
buf = editorRowsToString(&len);
|
|
||||||
fd = open(E.filename, O_RDWR | O_CREAT, 0644);
|
|
||||||
if (fd != -1) {
|
|
||||||
if (ftruncate(fd, len) != -1) {
|
|
||||||
if (write(fd, buf, len) == len) {
|
|
||||||
close(fd);
|
|
||||||
free(buf);
|
|
||||||
E.dirty = 0;
|
|
||||||
editorSetStatusMessage("%d bytes written to disk", len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
void editorFind() {
|
|
||||||
char *query = editorPrompt("Search: %s (ESC to cancel)");
|
|
||||||
if (query == NULL) return;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < E.numrows; i++) {
|
|
||||||
erow *row = &E.row[i];
|
|
||||||
char *match = strstr(row->render, query);
|
|
||||||
if (match) {
|
|
||||||
E.cursor_y = i;
|
|
||||||
E.cursor_x = editorRowRxToCx(row, match - row->render);
|
|
||||||
E.row_offset = E.numrows;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(query);
|
fd = open(buffer->filename, O_RDWR | O_CREAT, 0644);
|
||||||
}
|
if (fd != -1) {
|
||||||
|
for (int i = 0; i < buffer->numrows; ++i)
|
||||||
|
{
|
||||||
|
len = strlen(buffer->row[i].chars);
|
||||||
|
if (write(fd, buffer->row[i].chars, len) != len) {
|
||||||
|
close(fd);
|
||||||
|
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
write(fd, "\n", 1);
|
||||||
|
}
|
||||||
|
buffer->dirty = 0;
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
editorSetStatusMessage("File saved");
|
||||||
|
}
|
||||||
|
|||||||
+107
-46
@@ -1,67 +1,60 @@
|
|||||||
#include "../include/init.h"
|
#include "../include/init.h"
|
||||||
|
#include "../include/builtins.h"
|
||||||
|
#include "../include/color.h"
|
||||||
#include "../include/data.h"
|
#include "../include/data.h"
|
||||||
#include "../include/terminal.h"
|
#include "../include/terminal.h"
|
||||||
#include "../include/builtins.h"
|
#include "../include/split_screen.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#define LISP_IMPLEMENTATION
|
#define LISP_IMPLEMENTATION
|
||||||
#include "../include/lisp.h"
|
#include "../include/lisp.h"
|
||||||
#include "../include/lisp_lib.h"
|
#include "../include/lisp_lib.h"
|
||||||
|
|
||||||
extern struct editorConfig;
|
struct editorConfig;
|
||||||
|
|
||||||
|
|
||||||
void registerBuiltin(char *key_sequence, LispCFunc f) {
|
void registerBuiltin(char *key_sequence, LispCFunc f) {
|
||||||
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
|
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
|
||||||
lisp_make_func(f), E.ctx);
|
lisp_make_func(f), E.ctx);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void initBuiltins() {
|
void initBuiltins() {
|
||||||
// move cursor
|
// Registering C functions as lisp macro
|
||||||
registerBuiltin("MOVE-CURSOR", moveCursor);
|
registerBuiltin("move-cursor", moveCursor);
|
||||||
registerBuiltin("MAP-KEY", mapKey);
|
registerBuiltin("map-key", mapKey);
|
||||||
registerBuiltin("EDITOR-QUIT", editorQuit);
|
registerBuiltin("editor-quit", editorQuit);
|
||||||
registerBuiltin("EDITOR-SAVE", l_editorSave);
|
registerBuiltin("editor-save", l_editorSave);
|
||||||
registerBuiltin("EDITOR-INSERT-NEW-LINE", l_editorInsertNewLine);
|
registerBuiltin("editor-insert-new-line", l_editorInsertNewLine);
|
||||||
registerBuiltin("MOVE-CURSOR-BEG-LINE", moveCursorBeginLine);
|
registerBuiltin("move-cursor-beg-line", moveCursorBeginLine);
|
||||||
registerBuiltin("MOVE-CURSOR-END-LINE", moveCursorEndLine);
|
registerBuiltin("move-cursor-end-line", moveCursorEndLine);
|
||||||
registerBuiltin("EDITOR-DELETE-PREVIOUS-CHAR", deletePreviousChar);
|
registerBuiltin("editor-delete-previous-char", deletePreviousChar);
|
||||||
registerBuiltin("MOVE-CURSOR-PAGE-UP", editorMoveCursorPageUp);
|
registerBuiltin("move-cursor-page-up", editorMoveCursorPageUp);
|
||||||
registerBuiltin("MOVE-CURSOR-PAGE-DOWN", editorMoveCursorPageDown);
|
registerBuiltin("move-cursor-page-down", editorMoveCursorPageDown);
|
||||||
registerBuiltin("EDITOR-OPEN-FILE", editorOpenFile);
|
registerBuiltin("editor-open-file", editorOpenFile);
|
||||||
registerBuiltin("EDITOR-INSERT-CHAR", editorPrintC);
|
registerBuiltin("editor-insert-char", editorPrintC);
|
||||||
registerBuiltin("ADD-PACKAGE", addPackage);
|
registerBuiltin("add-package", addPackage);
|
||||||
registerBuiltin("EDITOR-DEL-ROW", editorDelRow_L);
|
registerBuiltin("buffer-find", bufferFind_L);
|
||||||
registerBuiltin("EDITOR-FIND", editorFind_L);
|
registerBuiltin("buffer-find-reverse", bufferFindReverse_L);
|
||||||
|
registerBuiltin("editor-read-char", editorReadChar_L);
|
||||||
|
registerBuiltin("add-prefix", editorPrefix);
|
||||||
|
registerBuiltin("editor-set-prefix", editorSetPrefix);
|
||||||
|
registerBuiltin("editor-insert-tab", l_editorInserTab);
|
||||||
|
registerBuiltin("editor-switch-next-buffer", editorSwitchNextBuffer);
|
||||||
|
registerBuiltin("editor-split-screen-vertical", l_editorSplitScreenVertical);
|
||||||
|
registerBuiltin("editor-switch-next-pane", editorSwitchNextPane);
|
||||||
|
registerBuiltin("editor-unify-panes", editorUnifiedPanes);
|
||||||
|
registerBuiltin("editor-paste", editorPaste);
|
||||||
|
registerBuiltin("editor-cut-end-line", editorCutEndLine);
|
||||||
|
registerBuiltin("editor-move-cursor-beg-buffer", editorMoveBegBuffer);
|
||||||
|
registerBuiltin("editor-move-cursor-end-buffer", editorMoveEndBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void initEditor() {
|
void initConfig() {
|
||||||
char * init_file_path = (char *) calloc(256, sizeof(char));
|
|
||||||
E.cursor_x = 0;
|
|
||||||
E.cursor_y = 0;
|
|
||||||
E.rx = 0;
|
|
||||||
E.row_offset = 0;
|
|
||||||
E.col_offset = 0;
|
|
||||||
E.numrows = 0;
|
|
||||||
E.row = NULL;
|
|
||||||
E.dirty = 0;
|
|
||||||
E.filename = NULL;
|
|
||||||
E.state = READ_ONLY;
|
|
||||||
E.status_msg[0] = '\0';
|
|
||||||
E.status_msg_time = 0;
|
|
||||||
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
|
|
||||||
die("getWindowSize");
|
|
||||||
}
|
|
||||||
E.screenrows -= 2;
|
|
||||||
|
|
||||||
E.number_of_keybinds = 0;
|
|
||||||
|
|
||||||
strcat(init_file_path, getenv("HOME"));
|
|
||||||
strcat(init_file_path, "/.beluga/config/init.lisp");
|
|
||||||
printf("%s\n", init_file_path);
|
|
||||||
E.fd_init_file = fopen(init_file_path, "r");
|
|
||||||
E.ctx = lisp_init();
|
E.ctx = lisp_init();
|
||||||
|
E.ctx.p->err_port = fopen("log.err", "w");
|
||||||
E.env = lisp_env(E.ctx);
|
E.env = lisp_env(E.ctx);
|
||||||
lisp_lib_load(E.ctx);
|
lisp_lib_load(E.ctx);
|
||||||
// Init builtins lisp functions
|
// Init builtins lisp functions
|
||||||
@@ -69,12 +62,80 @@ void initEditor() {
|
|||||||
|
|
||||||
// Read config file
|
// Read config file
|
||||||
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
|
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
|
||||||
free(init_file_path);
|
|
||||||
if (E.ctx_error != LISP_ERROR_NONE) {
|
if (E.ctx_error != LISP_ERROR_NONE) {
|
||||||
die("init failed");
|
die("init failed");
|
||||||
}
|
}
|
||||||
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
|
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initTheme() {
|
||||||
|
E.constantes.THEME = (char *)lisp_string(
|
||||||
|
lisp_eval(lisp_read("THEME", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx));
|
||||||
|
if (strcmp(E.constantes.THEME, "dark") == 0) {
|
||||||
|
E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(40, 44, 52);
|
||||||
|
E.theme.COLOR_KEYWORD = ANSI_FG_RGB(198, 120, 221);
|
||||||
|
E.theme.COLOR_TYPE = ANSI_FG_RGB(97, 175, 239);
|
||||||
|
E.theme.COLOR_STRING = ANSI_FG_RGB(152, 195, 121);
|
||||||
|
E.theme.COLOR_COMMENT = ANSI_FG_RGB(92, 99, 112);
|
||||||
|
E.theme.COLOR_NUMBER = ANSI_FG_RGB(209, 154, 102);
|
||||||
|
E.theme.COLOR_DEFAULT = ANSI_FG_RGB(171, 178, 191);
|
||||||
|
} else {
|
||||||
|
E.theme.BACKGROUND_COLOR = ANSI_BG_RGB(250, 251, 252);
|
||||||
|
E.theme.COLOR_KEYWORD = ANSI_FG_RGB(166, 38, 164);
|
||||||
|
E.theme.COLOR_TYPE = ANSI_FG_RGB(4, 90, 180);
|
||||||
|
E.theme.COLOR_STRING = ANSI_FG_RGB(80, 161, 79);
|
||||||
|
E.theme.COLOR_COMMENT = ANSI_FG_RGB(152, 152, 152);
|
||||||
|
E.theme.COLOR_NUMBER = ANSI_FG_RGB(206, 102, 54);
|
||||||
|
E.theme.COLOR_DEFAULT = ANSI_FG_RGB(86, 89, 90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initEditor() {
|
||||||
|
|
||||||
|
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
|
||||||
|
die("getWindowSize");
|
||||||
|
}
|
||||||
|
appDebug("%d %d\n", E.screenrows, E.screencols);
|
||||||
|
E.screenrows -= 2;
|
||||||
|
|
||||||
|
|
||||||
|
// Init graphics variables
|
||||||
|
splitScreenInit();
|
||||||
|
EditorPane *active = splitScreenGetActivePane();
|
||||||
|
E.cursor_x = 0;
|
||||||
|
E.cursor_y = 0;
|
||||||
|
|
||||||
|
active->cursor_x = 0;
|
||||||
|
active->cursor_y = 0;
|
||||||
|
active->origin_x = 0;
|
||||||
|
active->origin_y = 0;
|
||||||
|
active->width = E.screencols;
|
||||||
|
active->height = E.screenrows;
|
||||||
|
|
||||||
|
E.init_file_path = (char *)calloc(256, sizeof(char));
|
||||||
|
strcat(E.init_file_path, getenv("HOME"));
|
||||||
|
strcat(E.init_file_path, "/.beluga/config/init.lisp");
|
||||||
|
appDebug("%s\n", E.init_file_path);
|
||||||
|
E.fd_init_file = fopen(E.init_file_path, "r");
|
||||||
|
|
||||||
|
// Status bar
|
||||||
|
E.status_msg = (char *)calloc(E.screencols, sizeof(char));
|
||||||
|
E.status_msg[0] = '\0';
|
||||||
|
E.status_msg_time = 0;
|
||||||
|
|
||||||
|
// Key binds
|
||||||
|
E.number_of_keybinds = 0;
|
||||||
|
E.number_of_prefix = 0;
|
||||||
|
// General prefix is 0 (no prefix)
|
||||||
|
E.prefix = (struct prefix_t *)malloc(sizeof(struct prefix_t));
|
||||||
|
E.prefix[0].prefix_id = 0;
|
||||||
|
strncpy(E.prefix[0].prefix_name, "no-prefix", 64);
|
||||||
|
E.prefix_state = 0;
|
||||||
|
|
||||||
|
initConfig();
|
||||||
|
|
||||||
|
initTheme();
|
||||||
|
|
||||||
// To modify
|
// To modify
|
||||||
|
|
||||||
E.constantes.TAB_LENGTH =
|
E.constantes.TAB_LENGTH =
|
||||||
|
|||||||
+159
-119
@@ -1,26 +1,125 @@
|
|||||||
#include "../include/input.h"
|
#include "../include/input.h"
|
||||||
|
#include "../include/define.h"
|
||||||
#include "../include/editor_op.h"
|
#include "../include/editor_op.h"
|
||||||
#include "../include/output.h"
|
#include "../include/output.h"
|
||||||
#include "../include/define.h"
|
#include "include/data.h"
|
||||||
|
#include "include/buffer.h"
|
||||||
|
#include "include/data.h"
|
||||||
|
#include "include/split_screen.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "include/terminal.h"
|
||||||
|
#include "include/utf8.h"
|
||||||
|
|
||||||
extern struct editorConfig E;
|
extern struct editorConfig E;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \fn char * editorPrompt(struct editorConfig *E, char *prompt)
|
* @file input.c
|
||||||
* \brief Return user input in a prompt when enter is hit. */
|
* @brief Input handling module for the Beluga text editor
|
||||||
|
* @details Manages user input processing, key bindings, cursor movement, and file path completion
|
||||||
|
*/
|
||||||
|
|
||||||
char *editorPrompt(char *prompt) {
|
/**
|
||||||
size_t buf_size = 128;
|
* @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 *fileCompletion(const char *path) {
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *entry;
|
||||||
|
char directory[256];
|
||||||
|
char predict[128];
|
||||||
|
const char *last_slash;
|
||||||
|
int predict_len = 0;
|
||||||
|
size_t dir_len;
|
||||||
|
|
||||||
|
// path is a directory
|
||||||
|
if (path[strlen(path) - 1] == '/') {
|
||||||
|
appDebug("[FILE COMP] is dir\n");
|
||||||
|
strncpy(directory, path, 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find dir name
|
||||||
|
last_slash = strrchr(path, '/');
|
||||||
|
if (last_slash) {
|
||||||
|
dir_len = last_slash - path + 1; // length of dir_path
|
||||||
|
strncpy(directory, path, dir_len);
|
||||||
|
predict_len = strlen(path) - dir_len;
|
||||||
|
strncpy(predict, last_slash + 1, predict_len);
|
||||||
|
directory[dir_len] = '\0';
|
||||||
|
predict[predict_len] = '\0';
|
||||||
|
appDebug("%s %s\n", directory, predict);
|
||||||
|
} else {
|
||||||
|
appDebug("[FILE COMP] dir not found\n");
|
||||||
|
return strdup(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
dir = opendir(directory);
|
||||||
|
if (!dir)
|
||||||
|
return strdup(path);
|
||||||
|
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
if (strncmp(entry->d_name, predict, predict_len) == 0) {
|
||||||
|
static char full_path[1024];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
|
strcat(full_path, "/"); // add slash for directories
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
return strdup(full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup when no more entries
|
||||||
|
closedir(dir);
|
||||||
|
dir = NULL;
|
||||||
|
free(entry);
|
||||||
|
appDebug("[FILE COMP] no entries\n");
|
||||||
|
return strdup(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Displays an interactive prompt and returns user input
|
||||||
|
* @details Allows the user to enter text in a prompt with optional path completion
|
||||||
|
* via Tab key. Supports backspace, delete, and escape key handling. Dynamically
|
||||||
|
* allocates memory for the input buffer.
|
||||||
|
* @param prompt The prompt message format string (printf-style)
|
||||||
|
* @param placeHolder Initial text to display in the input buffer
|
||||||
|
* @param bPathMode If non-zero, enables Tab key file path completion
|
||||||
|
* @return Pointer to the user-entered text (dynamically allocated), or NULL if:
|
||||||
|
* - User pressed ESC to cancel
|
||||||
|
* - Input buffer is empty when Enter is pressed
|
||||||
|
* @note Caller is responsible for freeing the returned string
|
||||||
|
* @note Uses editorReadKey() for input and editorRefreshScreen() for display
|
||||||
|
*/
|
||||||
|
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||||
|
size_t buf_size = 256;
|
||||||
|
appDebug("[FILE COMP] %s %d\n", placeHolder, strlen(placeHolder));
|
||||||
char *buf = malloc(buf_size);
|
char *buf = malloc(buf_size);
|
||||||
size_t buf_len = 0;
|
size_t buf_len = 0;
|
||||||
int c = 0;
|
int c = 0;
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
|
strcpy(buf, placeHolder);
|
||||||
|
buf_len = strlen(placeHolder);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
editorSetStatusMessage(prompt, buf);
|
editorSetStatusMessage(prompt, buf);
|
||||||
@@ -39,7 +138,27 @@ char *editorPrompt(char *prompt) {
|
|||||||
editorSetStatusMessage("");
|
editorSetStatusMessage("");
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
} else if (!iscntrl(c) && c < 128) {
|
} else if (bPathMode && c == '\t') {
|
||||||
|
char path[256];
|
||||||
|
char *pwd;
|
||||||
|
if (buf[0] != '/') {
|
||||||
|
pwd = getenv("PWD");
|
||||||
|
appDebug("%s\n", pwd);
|
||||||
|
memcpy(path, pwd, strlen(pwd));
|
||||||
|
path[strlen(pwd)] = '/';
|
||||||
|
strncat(path, buf, buf_len);
|
||||||
|
} else {
|
||||||
|
strcpy(path, buf);
|
||||||
|
}
|
||||||
|
memset(buf, 0, 256);
|
||||||
|
buf_len = 0;
|
||||||
|
char * buf_complete = (char *) fileCompletion(path);
|
||||||
|
strcpy(buf, buf_complete);
|
||||||
|
free(buf_complete);
|
||||||
|
buf_len = strlen(buf);
|
||||||
|
buf[buf_len] = '\0';
|
||||||
|
|
||||||
|
} else if (!iscntrl(c) && c < 256) {
|
||||||
if (buf_len == buf_size - 1) {
|
if (buf_len == buf_size - 1) {
|
||||||
buf_size *= 2;
|
buf_size *= 2;
|
||||||
buf = realloc(buf, buf_size);
|
buf = realloc(buf, buf_size);
|
||||||
@@ -50,134 +169,55 @@ char *editorPrompt(char *prompt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *key_to_string(int key) {
|
|
||||||
static char key_str[32];
|
|
||||||
|
|
||||||
char tmp[10];
|
|
||||||
sprintf(tmp, "%d", key);
|
|
||||||
|
|
||||||
|
|
||||||
// First test enter key
|
/**
|
||||||
|
* @brief Executes the command bound to a key sequence
|
||||||
if (key == '\r') {
|
* @details Searches the keybinding table for a matching key sequence and
|
||||||
strcpy(key_str, "ENTER");
|
* prefix state, then evaluates the associated Lisp command.
|
||||||
} else if (key >= 1 && key <= 26) { // CTRL keys
|
* @param key_sequence The string representation of the key sequence
|
||||||
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
|
* @return 1 if a matching keybinding was found and executed, 0 otherwise
|
||||||
} else {
|
* @note Updates global editor state E (prefix_state)
|
||||||
switch (key) {
|
* @note Uses Lisp interpreter to evaluate bound commands
|
||||||
case ARROW_UP:
|
*/
|
||||||
strcpy(key_str, "ARROW-UP");
|
|
||||||
break;
|
|
||||||
case ARROW_DOWN:
|
|
||||||
strcpy(key_str, "ARROW-DOWN");
|
|
||||||
break;
|
|
||||||
case ARROW_LEFT:
|
|
||||||
strcpy(key_str, "ARROW-LEFT");
|
|
||||||
break;
|
|
||||||
case ARROW_RIGHT:
|
|
||||||
strcpy(key_str, "ARROW-RIGHT");
|
|
||||||
break;
|
|
||||||
case PAGE_UP:
|
|
||||||
strcpy(key_str, "PAGE-UP");
|
|
||||||
fprintf(stderr, "pagr up\n");
|
|
||||||
break;
|
|
||||||
case PAGE_DOWN:
|
|
||||||
strcpy(key_str, "PAGE-DOWN");
|
|
||||||
break;
|
|
||||||
case DEL_KEY:
|
|
||||||
fprintf(stderr, "delete key\n");
|
|
||||||
strcpy(key_str, "DEL");
|
|
||||||
|
|
||||||
break;
|
|
||||||
case BACKSPACE:
|
|
||||||
strcpy(key_str, "BACKSPACE");
|
|
||||||
break;
|
|
||||||
case '\r':
|
|
||||||
strcpy(key_str, "ENTER");
|
|
||||||
break;
|
|
||||||
case '\x1b':
|
|
||||||
strcpy(key_str, "ESCAPE");
|
|
||||||
break;
|
|
||||||
case BEG_LINE:
|
|
||||||
strcpy(key_str, "HOME");
|
|
||||||
break;
|
|
||||||
case END_LINE:
|
|
||||||
strcpy(key_str, "END");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// For regular characters
|
|
||||||
if (isprint(key)) {
|
|
||||||
snprintf(key_str, sizeof(key_str), "%c", key);
|
|
||||||
} else {
|
|
||||||
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return key_str;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void editorMoveCursor(int key) {
|
|
||||||
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
|
|
||||||
int row_len;
|
|
||||||
switch (key) {
|
|
||||||
case ARROW_RIGHT:
|
|
||||||
if (row && E.cursor_x < row->size) {
|
|
||||||
++E.cursor_x;
|
|
||||||
} else if (row && E.cursor_x == row->size) {
|
|
||||||
E.cursor_y++;
|
|
||||||
E.cursor_x = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ARROW_DOWN:
|
|
||||||
if (E.cursor_y < E.numrows) {
|
|
||||||
++E.cursor_y;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ARROW_UP:
|
|
||||||
if (E.cursor_y != 0) {
|
|
||||||
--E.cursor_y;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ARROW_LEFT:
|
|
||||||
if (E.cursor_x != 0) {
|
|
||||||
--E.cursor_x;
|
|
||||||
} else if (E.cursor_y > 0) {
|
|
||||||
--E.cursor_y;
|
|
||||||
E.cursor_x = E.row[E.cursor_y].size;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
|
|
||||||
row_len = row ? row->size : 0;
|
|
||||||
if (E.cursor_x > row_len) {
|
|
||||||
E.cursor_x = row_len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int executeKeyBind(char *key_sequence) {
|
int executeKeyBind(char *key_sequence) {
|
||||||
int i;
|
int i;
|
||||||
|
int previous_state = 0;
|
||||||
|
appDebug("pressed %s\n", key_sequence);
|
||||||
for (i = 0; i < E.number_of_keybinds; ++i) {
|
for (i = 0; i < E.number_of_keybinds; ++i) {
|
||||||
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
|
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
|
||||||
|
if (E.prefix_state != E.key_binds[i].prefix_id) {
|
||||||
fprintf(stderr, "lisp function %s\n", key_sequence);
|
continue;
|
||||||
// It's a symbol, create a function call
|
}
|
||||||
lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx),
|
previous_state = E.prefix_state;
|
||||||
&E.ctx_error, E.ctx);
|
// It's a symbol, create a function call
|
||||||
return 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Processes a single keypress from the user
|
||||||
|
* @details Reads a key, checks if it matches any registered keybinding,
|
||||||
|
* and either executes the bound command or inserts the character. Resets
|
||||||
|
* the quit buffer counter on successful key processing.
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @note Calls editorReadKey() to get input and editorInsertChar() for unbound keys
|
||||||
|
*/
|
||||||
void editorProcessKeypress() {
|
void editorProcessKeypress() {
|
||||||
int c = editorReadKey();
|
int c = editorReadKey();
|
||||||
|
char key_sequence[8];
|
||||||
|
|
||||||
if (executeKeyBind(key_to_string(c))) {
|
if (executeKeyBind(keyToString(c))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editorInsertChar(c);
|
int seq_len = utf8Encode(c, key_sequence);
|
||||||
|
appDebug("key seq : %s\n", key_sequence);
|
||||||
|
bufferInsertBytes(key_sequence, seq_len);
|
||||||
E.quit_times_buffer = E.constantes.QUIT_TIMES;
|
E.quit_times_buffer = E.constantes.QUIT_TIMES;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+356
-118
@@ -1,137 +1,375 @@
|
|||||||
|
/**
|
||||||
|
* @file output.c
|
||||||
|
* @brief Screen rendering and output module for the Beluga text editor
|
||||||
|
* @details Handles all screen updates, cursor positioning, status bar
|
||||||
|
* rendering, and display synchronization using ANSI escape sequences
|
||||||
|
*/
|
||||||
|
|
||||||
#include "../include/output.h"
|
#include "../include/output.h"
|
||||||
#include <stdarg.h>
|
#include "../include/append_buffer.h"
|
||||||
|
#include "../include/buffer.h"
|
||||||
|
#include "../include/editor_op.h"
|
||||||
|
#include "../include/data.h"
|
||||||
|
#include "../include/define.h"
|
||||||
|
#include "../include/row_op.h"
|
||||||
|
#include "../include/split_screen.h"
|
||||||
|
#include "../include/syntax_highlighter.h"
|
||||||
|
#include "../include/terminal.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
extern struct editorConfig E;
|
extern struct editorConfig E;
|
||||||
|
|
||||||
void editorDrawRows(struct abuf *ab) {
|
/**
|
||||||
int y;
|
* @brief Renders a single pane with its buffer content
|
||||||
char welcome[80];
|
*/
|
||||||
int welcome_len;
|
static void editorDrawPane(struct abuf* ab, EditorPane* pane)
|
||||||
int padding;
|
{
|
||||||
int len;
|
int file_row;
|
||||||
int file_row;
|
char pos_buf[32];
|
||||||
for (y = 0; y < E.screenrows; ++y) {
|
int pos_len;
|
||||||
file_row = y + E.row_offset;
|
int byte_len_to_print;
|
||||||
if (file_row >= E.numrows) {
|
int bytes_to_print;
|
||||||
if (E.numrows == 0 && y == E.screenrows / 3) {
|
char* highlighted;
|
||||||
welcome_len =
|
|
||||||
snprintf(welcome, sizeof(welcome),
|
if (pane == NULL || pane->buffer_id < 0)
|
||||||
"Beluga text editor -- version %s", BELUGA_VERSION);
|
return;
|
||||||
if (welcome_len > E.screencols) {
|
|
||||||
welcome_len = E.screencols;
|
const struct buffer_t* buf = bufferFindById(pane->buffer_id);
|
||||||
|
if (buf == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Draw all pane
|
||||||
|
for (int y = 0; y < pane->height; y++)
|
||||||
|
{
|
||||||
|
file_row = y + pane->y_offset;
|
||||||
|
|
||||||
|
// Set cursor at start of pane row
|
||||||
|
pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH",
|
||||||
|
pane->origin_y + y + 1, pane->origin_x + 1);
|
||||||
|
abAppend(ab, pos_buf, pos_len);
|
||||||
|
|
||||||
|
// Apply background color (6 bytes for RGB format)
|
||||||
|
abAppend(ab, E.theme.BACKGROUND_COLOR, (int) strlen(E.theme.BACKGROUND_COLOR));
|
||||||
|
|
||||||
|
|
||||||
|
// pane line is out of buffer
|
||||||
|
if (file_row >= buf->numrows)
|
||||||
|
{
|
||||||
|
// Empty line - show tilde
|
||||||
|
|
||||||
|
abAppend(ab, "~", 1);
|
||||||
|
for (int i = 0;
|
||||||
|
i < pane->width - 1;
|
||||||
|
++i)
|
||||||
|
{
|
||||||
|
abAppend(ab, " ", 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
padding = (E.screencols - welcome_len) / 2;
|
else
|
||||||
if (padding) {
|
{
|
||||||
abAppend(ab, "~", 1);
|
|
||||||
--padding;
|
|
||||||
|
if (buf->filename[strlen(buf->filename) - 1] == 'c' || buf->filename[strlen(buf->filename) - 1] == 'h')
|
||||||
|
{
|
||||||
|
// Render line with syntax highlighting, constrain to pane width
|
||||||
|
highlighted = highlight_line(
|
||||||
|
&buf->row[file_row].chars[pane->x_offset], &byte_len_to_print);
|
||||||
|
|
||||||
|
// Print only up to pane width
|
||||||
|
abAppend(ab, highlighted, byte_len_to_print);
|
||||||
|
free(highlighted);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Render basic line
|
||||||
|
bytes_to_print =
|
||||||
|
buf->row[file_row].size - pane->x_offset < pane->width
|
||||||
|
? buf->row[file_row].size - pane->x_offset
|
||||||
|
: pane->width;
|
||||||
|
abAppend(ab, &buf->row[file_row].chars[pane->x_offset], bytes_to_print);
|
||||||
|
}
|
||||||
|
// Fill remaining space with background color to pane width
|
||||||
|
for (int i = 0;
|
||||||
|
i < pane->width - editorRowCharCount(&buf->row[file_row], pane->width) + pane->x_offset;
|
||||||
|
++i)
|
||||||
|
{
|
||||||
|
abAppend(ab, " ", 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
while (padding--) {
|
}
|
||||||
abAppend(ab, " ", 1);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renders all panes based on current split configuration
|
||||||
|
*/
|
||||||
|
static void editorDrawAllPanes(struct abuf* ab)
|
||||||
|
{
|
||||||
|
const ScreenLayout* layout = splitScreenGetLayout();
|
||||||
|
|
||||||
|
if (layout->num_panes == 1)
|
||||||
|
{
|
||||||
|
// Single pane fullscreen
|
||||||
|
editorDrawPane(ab, &layout->panes[0]);
|
||||||
|
}
|
||||||
|
else if (layout->num_panes == 2)
|
||||||
|
{
|
||||||
|
// Draw both panes
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
editorDrawPane(ab, &layout->panes[i]);
|
||||||
|
|
||||||
|
// Draw pane border/divider if not the last pane
|
||||||
|
if (layout->mode == SPLIT_VERTICAL && i == 0)
|
||||||
|
{
|
||||||
|
// Draw vertical divider
|
||||||
|
int divider_col = layout->panes[0].width;
|
||||||
|
for (int y = 0; y < layout->panes[0].height; y++)
|
||||||
|
{
|
||||||
|
char pos_buf[32];
|
||||||
|
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", y + 1, divider_col);
|
||||||
|
abAppend(ab, pos_buf, (int) strlen(pos_buf));
|
||||||
|
abAppend(ab, "\x1b[1m|\x1b[0m", 9); // Bold pipe divider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (layout->mode == SPLIT_HORIZONTAL && i == 0)
|
||||||
|
{
|
||||||
|
// Draw horizontal divider
|
||||||
|
int divider_row = layout->panes[0].height;
|
||||||
|
char pos_buf[32];
|
||||||
|
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", divider_row + 1, 1);
|
||||||
|
abAppend(ab, pos_buf, (int) strlen(pos_buf));
|
||||||
|
for (int x = 0; x < E.screencols; x++)
|
||||||
|
{
|
||||||
|
abAppend(ab, "\x1b[1m-\x1b[0m", 9); // Bold dash divider
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
abAppend(ab, welcome, welcome_len);
|
}
|
||||||
} else {
|
}
|
||||||
abAppend(ab, "~", 1);
|
|
||||||
}
|
/**
|
||||||
} else {
|
* @brief Updates scroll offsets to keep cursor visible on screen
|
||||||
len = E.row[file_row].rsize - E.col_offset;
|
* @details Adjusts E.row_offset and E.col_offset to ensure the cursor remains
|
||||||
if (len < 0)
|
* within the visible viewport. Also updates E.rx (rendered x-coordinate).
|
||||||
len = 0;
|
* @note Updates global editor state E
|
||||||
if (len > E.screencols)
|
* @see editorRowCxToRx()
|
||||||
|
*/
|
||||||
|
void editorScroll()
|
||||||
|
{
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||||
|
int rel_x, rel_y;
|
||||||
|
|
||||||
|
// compute relative coordinates
|
||||||
|
rel_x = editorRowCharCount(&buf->row[buf->y], buf->x);
|
||||||
|
appDebug("counting %d\n", rel_x);
|
||||||
|
rel_y = buf->y;
|
||||||
|
|
||||||
|
appDebug("%d %d / %d %d\n", active->cursor_x, active->x_offset,
|
||||||
|
active->cursor_y, active->y_offset);
|
||||||
|
|
||||||
|
while (rel_x != active->cursor_x + active->x_offset ||
|
||||||
|
rel_y != active->cursor_y + active->y_offset)
|
||||||
|
{
|
||||||
|
if (rel_x < active->cursor_x + active->x_offset)
|
||||||
|
{
|
||||||
|
// LEFT
|
||||||
|
if (active->cursor_x == 0 && active->x_offset)
|
||||||
|
{
|
||||||
|
active->x_offset--;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
active->cursor_x--;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
// RIGHT
|
||||||
|
if (rel_x > active->cursor_x + active->x_offset)
|
||||||
|
{
|
||||||
|
if (active->cursor_x == active->width - 1)
|
||||||
|
{
|
||||||
|
active->x_offset++;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
active->cursor_x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rel_y < active->cursor_y + active->y_offset)
|
||||||
|
{
|
||||||
|
if (active->cursor_y == 0 && active->y_offset)
|
||||||
|
{
|
||||||
|
active->y_offset--;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
active->cursor_y--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rel_y > active->cursor_y + active->y_offset)
|
||||||
|
{
|
||||||
|
if (active->cursor_y == active->height - 1)
|
||||||
|
{
|
||||||
|
active->y_offset++;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
active->cursor_y++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char * basename(char *path)
|
||||||
|
{
|
||||||
|
int len = (int) strlen(path);
|
||||||
|
|
||||||
|
|
||||||
|
for(int i=len-1; i>0; i--)
|
||||||
|
{
|
||||||
|
if(path[i]=='/' )
|
||||||
|
{
|
||||||
|
path = path+i+1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renders the status bar at the bottom of the screen
|
||||||
|
* @details Displays filename, line count, dirty flag, and current cursor
|
||||||
|
* position in an inverted color bar. Right-aligns the cursor position
|
||||||
|
* indicator.
|
||||||
|
* @param ab Pointer to append buffer structure for accumulating output
|
||||||
|
* @note Uses ANSI escape codes for color inversion
|
||||||
|
*/
|
||||||
|
void editorDrawStatusBar(struct abuf* ab)
|
||||||
|
{
|
||||||
|
// TO MODIFY
|
||||||
|
int len, render_len;
|
||||||
|
char status[E.screencols], render_status[E.screencols * 4];
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
|
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||||
|
|
||||||
|
abAppend(ab, "\x1b[7m", 4); // inverting colors
|
||||||
|
|
||||||
|
const char* mode_str = "";
|
||||||
|
ScreenLayout* layout = splitScreenGetLayout();
|
||||||
|
if (layout->mode == SPLIT_VERTICAL)
|
||||||
|
mode_str = " [V-SPLIT]";
|
||||||
|
else if (layout->mode == SPLIT_HORIZONTAL)
|
||||||
|
mode_str = " [H-SPLIT]";
|
||||||
|
|
||||||
|
// Build buffer status showing all buffers with dirty indicators
|
||||||
|
char buf_status[1024] = "";
|
||||||
|
int offset = 0;
|
||||||
|
for (int i = 0; i < E.number_of_buffer; i++)
|
||||||
|
{
|
||||||
|
struct buffer_t* b = &E.buffers[i];
|
||||||
|
char marker = (b->buffer_id == active->buffer_id) ? '>' : ' ';
|
||||||
|
char dirty_marker = b->dirty ? '*' : ' ';
|
||||||
|
offset += snprintf(&buf_status[offset], sizeof(buf_status) - offset,
|
||||||
|
"%c%d:%s%c ", marker, b->buffer_id,
|
||||||
|
b->filename ? basename(b->filename) : "[No Name]", dirty_marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
len = snprintf(status, sizeof(status), "%s%s", buf_status, mode_str);
|
||||||
|
|
||||||
|
render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
|
||||||
|
active->cursor_y + 1, buf->numrows);
|
||||||
|
|
||||||
|
if (len > E.screencols)
|
||||||
|
{
|
||||||
len = E.screencols;
|
len = E.screencols;
|
||||||
abAppend(ab, &E.row[file_row].render[E.col_offset], len);
|
|
||||||
}
|
}
|
||||||
abAppend(ab, ERASE_END_LINE, 3);
|
|
||||||
|
abAppend(ab, status, len);
|
||||||
|
while (len < E.screencols)
|
||||||
|
{
|
||||||
|
if (E.screencols - len == render_len + 1)
|
||||||
|
{
|
||||||
|
abAppend(ab, render_status, render_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
abAppend(ab, " ", 1);
|
||||||
|
++len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abAppend(ab, "\x1b[m", 3); // normal text mode
|
||||||
abAppend(ab, "\r\n", 2);
|
abAppend(ab, "\r\n", 2);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void editorScroll() {
|
/**
|
||||||
E.rx = E.cursor_x;
|
* @brief Renders the message bar below the status bar
|
||||||
if (E.cursor_y < E.numrows) {
|
* @details Displays temporary status messages for a limited time (5 seconds).
|
||||||
E.rx = editorRowCxToRx(&E.row[E.cursor_y], E.cursor_x);
|
* 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()
|
||||||
if (E.cursor_y < E.row_offset) {
|
*/
|
||||||
E.row_offset = E.cursor_y;
|
void editorDrawMessageBar(struct abuf* ab)
|
||||||
}
|
{
|
||||||
if (E.cursor_y >= E.row_offset + E.screenrows) {
|
int msg_len = (int) strlen(E.status_msg);
|
||||||
E.row_offset = E.cursor_y - E.screenrows + 1;
|
abAppend(ab, ERASE_END_LINE, 3);
|
||||||
}
|
if (msg_len > E.screencols)
|
||||||
if (E.rx < E.col_offset) {
|
{
|
||||||
E.col_offset = E.rx;
|
msg_len = E.screencols;
|
||||||
}
|
}
|
||||||
if (E.rx >= E.col_offset + E.screencols) {
|
if (msg_len && time(NULL) - E.status_msg_time < 5)
|
||||||
E.col_offset = E.rx - E.screencols + 1;
|
{
|
||||||
}
|
abAppend(ab, E.status_msg, msg_len);
|
||||||
}
|
|
||||||
|
|
||||||
void editorDrawStatusBar(struct abuf *ab) {
|
|
||||||
int len, render_len;
|
|
||||||
char status[80], render_status[80];
|
|
||||||
|
|
||||||
abAppend(ab, "\x1b[7m", 4); // inverting colors
|
|
||||||
len = snprintf(status, sizeof(status), "%.20s - %d lines%s",
|
|
||||||
E.filename ? E.filename : "[No Name]", E.numrows,
|
|
||||||
E.dirty ? "*" : "");
|
|
||||||
render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
|
|
||||||
E.cursor_y + 1, E.numrows);
|
|
||||||
if (len > E.screencols) {
|
|
||||||
len = E.screencols;
|
|
||||||
}
|
|
||||||
abAppend(ab, status, len);
|
|
||||||
while (len < E.screencols) {
|
|
||||||
if (E.screencols - len == render_len) {
|
|
||||||
abAppend(ab, render_status, render_len);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
abAppend(ab, " ", 1);
|
|
||||||
++len;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
abAppend(ab, "\x1b[m", 3); // normal text mode
|
|
||||||
abAppend(ab, "\r\n", 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void editorDrawMessageBar(struct abuf *ab) {
|
|
||||||
int msg_len = strlen(E.status_msg);
|
/**
|
||||||
abAppend(ab, ERASE_END_LINE, 3);
|
* @brief Performs complete screen refresh and buffer synchronization
|
||||||
if (msg_len > E.screencols) {
|
* @details Clears screen, redraws all visible content (rows, status bar,
|
||||||
msg_len = E.screencols;
|
* message bar), positions cursor, and writes accumulated buffer to stdout. This
|
||||||
}
|
* is the main rendering function called each frame.
|
||||||
if (msg_len && time(NULL) - E.status_msg_time < 5) {
|
* @note Updates global editor state E (via editorScroll())
|
||||||
abAppend(ab, E.status_msg, msg_len);
|
* @see editorDrawRows()
|
||||||
}
|
* @see editorDrawStatusBar()
|
||||||
}
|
* @see editorDrawMessageBar()
|
||||||
|
*/
|
||||||
void editorRefreshScreen() {
|
|
||||||
editorScroll();
|
void editorRefreshScreen()
|
||||||
struct abuf ab = ABUF_INIT;
|
{
|
||||||
char buf[32];
|
struct abuf ab = ABUF_INIT;
|
||||||
|
char buf[32];
|
||||||
abAppend(&ab, HIDE_CURSOR, 6);
|
|
||||||
abAppend(&ab, CURSOR_TOP_LEFT, 3);
|
abAppend(&ab, HIDE_CURSOR, 6);
|
||||||
|
abAppend(&ab, CURSOR_TOP_LEFT, 3);
|
||||||
editorDrawRows(&ab);
|
abAppend(&ab, E.theme.BACKGROUND_COLOR,
|
||||||
editorDrawStatusBar(&ab);
|
(int) strlen(E.theme.BACKGROUND_COLOR)); // RGB background is 12 bytes
|
||||||
editorDrawMessageBar(&ab);
|
|
||||||
|
// Draw all panes
|
||||||
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cursor_y - E.row_offset) + 1,
|
editorScroll();
|
||||||
(E.rx - E.col_offset) + 1);
|
editorDrawAllPanes(&ab);
|
||||||
abAppend(&ab, buf, strlen(buf));
|
|
||||||
|
// Draw status bar and message bar
|
||||||
abAppend(&ab, SHOW_CURSOR, 6);
|
editorDrawStatusBar(&ab);
|
||||||
|
editorDrawMessageBar(&ab);
|
||||||
write(STDOUT_FILENO, ab.b, ab.len);
|
// editorDrawContextBuffer(&ab);
|
||||||
abFree(&ab);
|
|
||||||
}
|
// Position cursor in active pane
|
||||||
|
EditorPane* active = splitScreenGetActivePane();
|
||||||
void editorSetStatusMessage(const char *fmt, ...) {
|
if (active != NULL)
|
||||||
va_list ap;
|
{
|
||||||
va_start(ap, fmt);
|
snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
|
||||||
vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
|
active->cursor_y + active->origin_y + 1,
|
||||||
va_end(ap);
|
active->cursor_x + active->origin_x + 1);
|
||||||
E.status_msg_time = time(NULL);
|
abAppend(&ab, buf, (int) strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
abAppend(&ab, SHOW_CURSOR, 6);
|
||||||
|
write(STDOUT_FILENO, ab.b, ab.len);
|
||||||
|
abFree(&ab);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-141
@@ -1,4 +1,6 @@
|
|||||||
#include "../include/row_op.h"
|
#include "../include/row_op.h"
|
||||||
|
#include "../include/data.h"
|
||||||
|
#include "../include/utf8.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -6,146 +8,5 @@
|
|||||||
|
|
||||||
extern struct editorConfig E;
|
extern struct editorConfig E;
|
||||||
|
|
||||||
int editorRowCxToRx(erow *row, int cursor_x) {
|
|
||||||
int render_x = 0;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < cursor_x; ++i) {
|
|
||||||
if (row->chars[i] == '\t') {
|
|
||||||
render_x += (E.constantes.TAB_LENGTH - 1) - (render_x % E.constantes.TAB_LENGTH);
|
|
||||||
}
|
|
||||||
render_x++;
|
|
||||||
}
|
|
||||||
return render_x;
|
|
||||||
}
|
|
||||||
|
|
||||||
int editorRowRxToCx(erow *row, int rx) {
|
|
||||||
int cur_rx = 0;
|
|
||||||
int cx;
|
|
||||||
for (cx = 0; cx < row->size; cx++) {
|
|
||||||
if (row->chars[cx] == '\t')
|
|
||||||
cur_rx += (E.constantes.TAB_LENGTH - 1) - (cur_rx % E.constantes.TAB_LENGTH);
|
|
||||||
cur_rx++;
|
|
||||||
if (cur_rx > rx) return cx;
|
|
||||||
}
|
|
||||||
return cx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \fn editorUpdateRow(erow *row)
|
|
||||||
* \brief Copy content of \p row in \p row->render.
|
|
||||||
* */
|
|
||||||
|
|
||||||
void editorUpdateRow(erow *row) {
|
|
||||||
int i, i_render;
|
|
||||||
int tabs = 0;
|
|
||||||
|
|
||||||
// counting number of tabs
|
|
||||||
|
|
||||||
for (i = 0; i < row->size; ++i) {
|
|
||||||
tabs +=
|
|
||||||
(row->chars[i] == '\t'); /**< increment tabs of 1 if chars[i] is one. */
|
|
||||||
}
|
|
||||||
|
|
||||||
free(row->render);
|
|
||||||
row->render = malloc(row->size + tabs * (E.constantes.TAB_LENGTH - 1) +
|
|
||||||
1); /**< Tabs needs E.constantes.TAB_LENGTH chars so E.constantes.TAB_LENGTH - 1
|
|
||||||
more than the first already counted. */
|
|
||||||
|
|
||||||
// end of counting
|
|
||||||
i_render = 0;
|
|
||||||
for (i = 0; i < row->size; ++i) {
|
|
||||||
if (row->chars[i] == '\t') {
|
|
||||||
row->render[i_render++] = ' ';
|
|
||||||
while (i_render % E.constantes.TAB_LENGTH) {
|
|
||||||
row->render[i_render++] =
|
|
||||||
' '; /**< Addind the right amount of spaces for tabs */
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
row->render[i_render++] = row->chars[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row->render[i_render] = '\0'; // Don't forget the end of string character.
|
|
||||||
row->rsize = i_render;
|
|
||||||
}
|
|
||||||
|
|
||||||
void editorInsertRow(int at, char *s, size_t len) {
|
|
||||||
if (at < 0 || at > E.numrows) {
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
erow *tmp = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1));
|
|
||||||
if (!tmp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
E.row = tmp;
|
|
||||||
memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));
|
|
||||||
|
|
||||||
E.row[at].size = len;
|
|
||||||
E.row[at].chars = malloc(len + 1);
|
|
||||||
memcpy(E.row[at].chars, s, len);
|
|
||||||
E.row[at].chars[len] = '\0';
|
|
||||||
|
|
||||||
E.row[at].rsize = 0;
|
|
||||||
E.row[at].render = NULL;
|
|
||||||
editorUpdateRow(&E.row[at]);
|
|
||||||
|
|
||||||
++E.numrows;
|
|
||||||
++E.dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
void editorFreeRow(erow *row) {
|
|
||||||
free(row->render);
|
|
||||||
free(row->chars);
|
|
||||||
}
|
|
||||||
|
|
||||||
void editorDelRow(int at) {
|
|
||||||
if (at < 0 || at >= E.numrows) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
editorFreeRow(&E.row[at]);
|
|
||||||
memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1));
|
|
||||||
--E.numrows;
|
|
||||||
++E.dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \fn editorRowInsertChar(erow *row, int at, int c)
|
|
||||||
* \param at Index of where we want to insert the char */
|
|
||||||
|
|
||||||
void editorRowInsertChar(erow *row, int at, int c) {
|
|
||||||
if (E.state == READ_ONLY)
|
|
||||||
return;
|
|
||||||
if (at < 0 || at > row->size) {
|
|
||||||
at = row->size;
|
|
||||||
}
|
|
||||||
row->chars = realloc(row->chars, row->size + 2);
|
|
||||||
memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
|
|
||||||
++row->size;
|
|
||||||
row->chars[at] = c;
|
|
||||||
editorUpdateRow(row);
|
|
||||||
++E.dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
void editorRowAppendString(erow *row, char *s, size_t len) {
|
|
||||||
row->chars = realloc(row->chars, row->size + len + 1);
|
|
||||||
memcpy(&row->chars[row->size], s, len);
|
|
||||||
row->size += len;
|
|
||||||
row->chars[row->size] = '\0';
|
|
||||||
editorUpdateRow(row);
|
|
||||||
++E.dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \fn editorRowDelChar(struct editorConfig *E, erow *erow, int at)
|
|
||||||
* \brief Delete the a char at the chosen position on the given row
|
|
||||||
* \param at Index of the char to delete
|
|
||||||
* \param row Row on operation is made */
|
|
||||||
void editorRowDelchar(erow *row, int at) {
|
|
||||||
if (at < 0 || at >= row->size) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
|
|
||||||
--row->size;
|
|
||||||
editorUpdateRow(row);
|
|
||||||
++E.dirty;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* @file split_screen.c
|
||||||
|
* @brief Split screen implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../include/split_screen.h"
|
||||||
|
#include "../include/buffer.h"
|
||||||
|
#include "../include/editor_op.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
extern struct editorConfig E;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes split screen system with single fullscreen pane
|
||||||
|
*/
|
||||||
|
void splitScreenInit(void) {
|
||||||
|
E.layout.mode = SPLIT_NONE;
|
||||||
|
E.layout.num_panes = 1;
|
||||||
|
E.layout.active_pane = 0;
|
||||||
|
|
||||||
|
E.layout.panes = malloc(sizeof(EditorPane) * 2);
|
||||||
|
|
||||||
|
// Initialize single fullscreen pane
|
||||||
|
E.layout.panes[0].buffer_id = -1; // No buffer for now
|
||||||
|
E.layout.panes[0].origin_y = 0;
|
||||||
|
E.layout.panes[0].origin_x = 0;
|
||||||
|
E.layout.panes[0].height = E.screenrows - 2; // Leave room for status bar
|
||||||
|
E.layout.panes[0].width = E.screencols;
|
||||||
|
E.layout.panes[0].cursor_x = 0;
|
||||||
|
E.layout.panes[0].cursor_y = 0;
|
||||||
|
E.layout.panes[0].y_offset = 0;
|
||||||
|
E.layout.panes[0].x_offset = 0;
|
||||||
|
E.layout.panes[0].is_active = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Splits screen vertically (left-right)
|
||||||
|
* @param buffer_id_left Buffer ID for left pane
|
||||||
|
* @param buffer_id_right Buffer ID for right pane
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int splitScreenVertical(int buffer_id_left, int buffer_id_right) {
|
||||||
|
// Verify both buffers exist
|
||||||
|
if (bufferFindById(buffer_id_left) == NULL ||
|
||||||
|
bufferFindById(buffer_id_right) == NULL) {
|
||||||
|
editorSetStatusMessage("Error: invalid buffer IDs");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reallocate panes array
|
||||||
|
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
|
||||||
|
E.layout.mode = SPLIT_VERTICAL;
|
||||||
|
E.layout.num_panes = 2;
|
||||||
|
E.layout.active_pane = 0;
|
||||||
|
|
||||||
|
int half_width = E.screencols / 2;
|
||||||
|
int pane_height = E.screenrows; // Leave room for status bar
|
||||||
|
|
||||||
|
// Left pane
|
||||||
|
E.layout.panes[0].buffer_id = buffer_id_left;
|
||||||
|
E.layout.panes[0].origin_y = 0;
|
||||||
|
E.layout.panes[0].origin_x = 0;
|
||||||
|
E.layout.panes[0].height = pane_height;
|
||||||
|
E.layout.panes[0].width = half_width;
|
||||||
|
E.layout.panes[0].cursor_x = 0;
|
||||||
|
E.layout.panes[0].cursor_y = 0;
|
||||||
|
E.layout.panes[0].y_offset = 0;
|
||||||
|
E.layout.panes[0].x_offset = 0;
|
||||||
|
E.layout.panes[0].is_active = 1;
|
||||||
|
|
||||||
|
// Right pane
|
||||||
|
E.layout.panes[1].buffer_id = buffer_id_right;
|
||||||
|
E.layout.panes[1].origin_y = 0;
|
||||||
|
E.layout.panes[1].origin_x = half_width;
|
||||||
|
E.layout.panes[1].height = pane_height;
|
||||||
|
E.layout.panes[1].width = E.screencols - half_width;
|
||||||
|
E.layout.panes[1].cursor_x = 0;
|
||||||
|
E.layout.panes[1].cursor_y = 0;
|
||||||
|
E.layout.panes[1].y_offset = 0;
|
||||||
|
E.layout.panes[1].x_offset = 0;
|
||||||
|
E.layout.panes[1].is_active = 0;
|
||||||
|
|
||||||
|
editorSetStatusMessage("Vertical split: %d | %d", buffer_id_left, buffer_id_right);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Splits screen horizontally (top-bottom)
|
||||||
|
* @param buffer_id_top Buffer ID for top pane
|
||||||
|
* @param buffer_id_bottom Buffer ID for bottom pane
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom) {
|
||||||
|
// Verify both buffers exist
|
||||||
|
if (bufferFindById(buffer_id_top) == NULL ||
|
||||||
|
bufferFindById(buffer_id_bottom) == NULL) {
|
||||||
|
editorSetStatusMessage("Error: invalid buffer IDs");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reallocate panes array
|
||||||
|
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
|
||||||
|
E.layout.mode = SPLIT_HORIZONTAL;
|
||||||
|
E.layout.num_panes = 2;
|
||||||
|
E.layout.active_pane = 0;
|
||||||
|
|
||||||
|
int half_height = (E.screenrows - 2) / 2; // Account for status bar
|
||||||
|
|
||||||
|
// Top pane
|
||||||
|
E.layout.panes[0].buffer_id = buffer_id_top;
|
||||||
|
E.layout.panes[0].origin_y = 0;
|
||||||
|
E.layout.panes[0].origin_x = 0;
|
||||||
|
E.layout.panes[0].height = half_height;
|
||||||
|
E.layout.panes[0].width = E.screencols;
|
||||||
|
E.layout.panes[0].cursor_x = 0;
|
||||||
|
E.layout.panes[0].cursor_y = 0;
|
||||||
|
E.layout.panes[0].y_offset = 0;
|
||||||
|
E.layout.panes[0].x_offset = 0;
|
||||||
|
E.layout.panes[0].is_active = 1;
|
||||||
|
|
||||||
|
// Bottom pane
|
||||||
|
E.layout.panes[1].buffer_id = buffer_id_bottom;
|
||||||
|
E.layout.panes[1].origin_y = half_height;
|
||||||
|
E.layout.panes[1].origin_x = 0;
|
||||||
|
E.layout.panes[1].height = E.screenrows - 2 - half_height;
|
||||||
|
E.layout.panes[1].width = E.screencols;
|
||||||
|
E.layout.panes[1].cursor_x = 0;
|
||||||
|
E.layout.panes[1].cursor_y = 0;
|
||||||
|
E.layout.panes[1].x_offset = 0;
|
||||||
|
E.layout.panes[1].y_offset = 0;
|
||||||
|
E.layout.panes[1].is_active = 0;
|
||||||
|
|
||||||
|
editorSetStatusMessage("Horizontal split: %d / %d", buffer_id_top, buffer_id_bottom);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns to single buffer fullscreen
|
||||||
|
*/
|
||||||
|
void splitScreenUnify(void) {
|
||||||
|
E.layout.mode = SPLIT_NONE;
|
||||||
|
E.layout.num_panes = 1;
|
||||||
|
E.layout.active_pane = 0;
|
||||||
|
|
||||||
|
E.layout.panes[0].origin_x = 0;
|
||||||
|
E.layout.panes[0].origin_y = 0;
|
||||||
|
E.layout.panes[0].height = E.screenrows;
|
||||||
|
E.layout.panes[0].width = E.screencols;
|
||||||
|
E.layout.panes[0].is_active = 1;
|
||||||
|
|
||||||
|
editorSetStatusMessage("Unified view");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switches active pane (focus moves between splits)
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int splitScreenSwitchPane(void) {
|
||||||
|
if (E.layout.num_panes < 2) {
|
||||||
|
editorSetStatusMessage("No split to switch");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate current pane
|
||||||
|
E.layout.panes[E.layout.active_pane].is_active = 0;
|
||||||
|
|
||||||
|
// Move to next pane
|
||||||
|
E.layout.active_pane = (E.layout.active_pane + 1) % E.layout.num_panes;
|
||||||
|
|
||||||
|
// Activate new pane
|
||||||
|
E.layout.panes[E.layout.active_pane].is_active = 1;
|
||||||
|
|
||||||
|
editorSetStatusMessage("Switched to pane %d (buffer %d)",
|
||||||
|
E.layout.active_pane,
|
||||||
|
E.layout.panes[E.layout.active_pane].buffer_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the active pane's buffer
|
||||||
|
* @param buffer_id New buffer ID for active pane
|
||||||
|
* @return 0 on success, -1 on failure
|
||||||
|
*/
|
||||||
|
int splitScreenSetPaneBuffer(int buffer_id) {
|
||||||
|
if (bufferFindById(buffer_id) == NULL) {
|
||||||
|
editorSetStatusMessage("Error: invalid buffer ID");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorPane *active = &E.layout.panes[E.layout.active_pane];
|
||||||
|
active->buffer_id = buffer_id;
|
||||||
|
active->cursor_x = 0;
|
||||||
|
active->cursor_y = 0;
|
||||||
|
active->origin_x = 0;
|
||||||
|
active->origin_y = 0;
|
||||||
|
|
||||||
|
editorSetStatusMessage("Pane %d now showing buffer %d",
|
||||||
|
E.layout.active_pane, buffer_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets current screen layout
|
||||||
|
* @return Pointer to current ScreenLayout
|
||||||
|
*/
|
||||||
|
ScreenLayout *splitScreenGetLayout(void) {
|
||||||
|
return &E.layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets active pane
|
||||||
|
* @return Pointer to active EditorPane
|
||||||
|
*/
|
||||||
|
EditorPane *splitScreenGetActivePane(void) {
|
||||||
|
if (E.layout.num_panes == 0) return NULL;
|
||||||
|
return &E.layout.panes[E.layout.active_pane];
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
#include "../include/syntax_highlighter.h"
|
||||||
|
#include "../include/data.h"
|
||||||
|
#include "../include/utf8.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
extern struct editorConfig E;
|
||||||
|
|
||||||
|
const char *c_keywords[] = {
|
||||||
|
"if", "else", "while", "for", "do", "switch",
|
||||||
|
"case", "break", "#include", "#define", "#if", "#endif", "#ifndef", "continue", "return",
|
||||||
|
"goto", "struct", "union", "enum", "typedef", "static",
|
||||||
|
"extern", "const", "volatile", "sizeof", "auto", "register",
|
||||||
|
"inline", "restrict", NULL};
|
||||||
|
|
||||||
|
// C types
|
||||||
|
const char *c_types[] = {"int", "char", "float", "double",
|
||||||
|
"void", "long", "short", "unsigned",
|
||||||
|
"signed", "bool", NULL};
|
||||||
|
|
||||||
|
// Returns the byte length of the UTF-8 character starting at s.
|
||||||
|
// Never returns 0 for a non-NUL byte, so callers won't infinite-loop.
|
||||||
|
static int utf8_char_len(const char *s)
|
||||||
|
{
|
||||||
|
unsigned char c = (unsigned char)*s;
|
||||||
|
if (c == 0) return 0;
|
||||||
|
if (c < 0x80) return 1;
|
||||||
|
if ((c & 0xE0) == 0xC0) return 2;
|
||||||
|
if ((c & 0xF0) == 0xE0) return 3;
|
||||||
|
if ((c & 0xF8) == 0xF0) return 4;
|
||||||
|
return 1; // continuation byte or invalid — advance 1 to avoid infinite loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy one full UTF-8 character from src+i into dst+pos, advance both indices.
|
||||||
|
static void copy_utf8_char(char *dst, int *dst_pos, const char *src, int *src_pos)
|
||||||
|
{
|
||||||
|
int len = utf8_char_len(&src[*src_pos]);
|
||||||
|
for (int b = 0; b < len; b++)
|
||||||
|
dst[(*dst_pos)++] = src[(*src_pos)++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if character is alphanumeric or underscore
|
||||||
|
int is_word_char(const char *s)
|
||||||
|
{
|
||||||
|
uint32_t cp = utf8Decode(&s);
|
||||||
|
|
||||||
|
if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') ||
|
||||||
|
(cp >= '0' && cp <= '9') || cp == '_' || cp == '#')
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (cp == 0xFFFD) return 0;
|
||||||
|
|
||||||
|
if (cp >= 0x00C0 && cp <= 0x017F) return 1;
|
||||||
|
if (cp >= 0x0370 && cp <= 0x03FF) return 1;
|
||||||
|
if (cp >= 0x0400 && cp <= 0x04FF) return 1;
|
||||||
|
if (cp >= 0x0600 && cp <= 0x06FF) return 1;
|
||||||
|
if (cp >= 0x05D0 && cp <= 0x05EA) return 1;
|
||||||
|
if (cp >= 0x0900 && cp <= 0x097F) return 1;
|
||||||
|
if (cp >= 0x4E00 && cp <= 0x9FFF) return 1;
|
||||||
|
if ((cp >= 0x3040 && cp <= 0x309F) ||
|
||||||
|
(cp >= 0x30A0 && cp <= 0x30FF)) return 1;
|
||||||
|
if (cp >= 0xAC00 && cp <= 0xD7A3) return 1;
|
||||||
|
if ((cp >= 0x0660 && cp <= 0x0669) ||
|
||||||
|
(cp >= 0x06F0 && cp <= 0x06F9)) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if string is a keyword
|
||||||
|
int is_keyword(const char *word) {
|
||||||
|
for (int i = 0; c_keywords[i] != NULL; i++) {
|
||||||
|
if (strcmp(word, c_keywords[i]) == 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if string is a type
|
||||||
|
int is_type(const char *word) {
|
||||||
|
for (int i = 0; c_types[i] != NULL; i++) {
|
||||||
|
if (strcmp(word, c_types[i]) == 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get color code for token type
|
||||||
|
const char *get_color(TokenType type) {
|
||||||
|
switch (type) {
|
||||||
|
case TOKEN_KEYWORD:
|
||||||
|
return E.theme.COLOR_KEYWORD;
|
||||||
|
case TOKEN_TYPE:
|
||||||
|
return E.theme.COLOR_TYPE;
|
||||||
|
case TOKEN_STRING:
|
||||||
|
return E.theme.COLOR_STRING;
|
||||||
|
case TOKEN_COMMENT:
|
||||||
|
return E.theme.COLOR_COMMENT;
|
||||||
|
case TOKEN_NUMBER:
|
||||||
|
return E.theme.COLOR_NUMBER;
|
||||||
|
default:
|
||||||
|
return E.theme.COLOR_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int comment_section = 0;
|
||||||
|
|
||||||
|
// Highlight a line of C code and return the highlighted string
|
||||||
|
// Returns a newly allocated string that must be freed by the caller
|
||||||
|
char *highlight_line(const char *line, int *length) {
|
||||||
|
// Each byte can expand to at most (color_prefix + 4 bytes + color_reset).
|
||||||
|
// Allocate generously based on line length to avoid overflow.
|
||||||
|
int line_len = strlen(line);
|
||||||
|
int buf_size = line_len * 32 + 256;
|
||||||
|
char *result = malloc(buf_size);
|
||||||
|
int result_pos = 0;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (line[i] != '\0' && line[i] != '\n') {
|
||||||
|
// Skip whitespace — copy full UTF-8 char (whitespace is always ASCII,
|
||||||
|
// but using copy_utf8_char keeps the pattern consistent)
|
||||||
|
if (line[i] == ' ' || line[i] == '\t') {
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment_section) {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
|
||||||
|
while (line[i] != '\0' && line[i] != '\n') {
|
||||||
|
if (line[i] == '*' && line[i + 1] == '/') {
|
||||||
|
comment_section = 0;
|
||||||
|
}
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
}
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle line comments
|
||||||
|
if (line[i] == '/' && line[i + 1] == '/') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
|
||||||
|
while (line[i] != '\0' && line[i] != '\n') {
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
}
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle block comments
|
||||||
|
if (line[i] == '/' && line[i + 1] == '*') {
|
||||||
|
comment_section = 1;
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
while (line[i] != '\0') {
|
||||||
|
if (line[i] == '*' && line[i + 1] == '/') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
comment_section = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
}
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle strings
|
||||||
|
if (line[i] == '"') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s\"", E.theme.COLOR_STRING);
|
||||||
|
i++;
|
||||||
|
while (line[i] != '\0' && line[i] != '"') {
|
||||||
|
if (line[i] == '\\') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
} else {
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line[i] == '"')
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle character literals
|
||||||
|
if (line[i] == '\'') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s'", E.theme.COLOR_STRING);
|
||||||
|
i++;
|
||||||
|
while (line[i] != '\0' && line[i] != '\'') {
|
||||||
|
if (line[i] == '\\') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
} else {
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line[i] == '\'')
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle numbers
|
||||||
|
if (line[i] >= '0' && line[i] <= '9') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_NUMBER);
|
||||||
|
while (is_word_char(&line[i]) || line[i] == '.') {
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
}
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle identifiers and keywords
|
||||||
|
if (is_word_char(&line[i])) {
|
||||||
|
int start = i;
|
||||||
|
// Advance by full UTF-8 character widths, not single bytes
|
||||||
|
while (line[i] != '\0' && is_word_char(&line[i]))
|
||||||
|
i += utf8_char_len(&line[i]);
|
||||||
|
|
||||||
|
char word[256];
|
||||||
|
int word_len = i - start;
|
||||||
|
if (word_len >= (int)sizeof(word))
|
||||||
|
word_len = (int)sizeof(word) - 1;
|
||||||
|
strncpy(word, &line[start], word_len);
|
||||||
|
word[word_len] = '\0';
|
||||||
|
|
||||||
|
TokenType type = TOKEN_DEFAULT;
|
||||||
|
if (is_keyword(word))
|
||||||
|
type = TOKEN_KEYWORD;
|
||||||
|
else if (is_type(word))
|
||||||
|
type = TOKEN_TYPE;
|
||||||
|
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s%s%s", get_color(type),
|
||||||
|
word, COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle operators and other characters (including non-ASCII multi-byte)
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_DEFAULT);
|
||||||
|
copy_utf8_char(result, &result_pos, line, &i);
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
result[result_pos] = '\0';
|
||||||
|
*length = result_pos + 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
+112
-23
@@ -1,7 +1,16 @@
|
|||||||
#include "../include/terminal.h"
|
#include "../include/terminal.h"
|
||||||
#include "../include/data.h"
|
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "../include/data.h"
|
||||||
|
#include "../include/define.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "include/utf8.h"
|
||||||
|
|
||||||
void die(const char *s) {
|
void die(const char *s) {
|
||||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||||
@@ -35,26 +44,88 @@ void enableRawMode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int editorReadKey() {
|
#include <ctype.h> /* isprint */
|
||||||
int nread;
|
|
||||||
char c;
|
char *keyToString(int key) {
|
||||||
char seq[3];
|
static char key_str[32];
|
||||||
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
|
|
||||||
if (nread == -1 && errno != EAGAIN) {
|
if (key == '\r') {
|
||||||
die("read");
|
strcpy(key_str, "ENTER");
|
||||||
|
} else if (key == 0x09) {
|
||||||
|
strcpy(key_str, "TAB");
|
||||||
|
} else if (key >= 1 && key <= 26) {
|
||||||
|
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
|
||||||
|
} else {
|
||||||
|
switch (key) {
|
||||||
|
case ARROW_UP:
|
||||||
|
strcpy(key_str, "ARROW-UP");
|
||||||
|
break;
|
||||||
|
case ARROW_DOWN:
|
||||||
|
strcpy(key_str, "ARROW-DOWN");
|
||||||
|
break;
|
||||||
|
case ARROW_LEFT:
|
||||||
|
strcpy(key_str, "ARROW-LEFT");
|
||||||
|
break;
|
||||||
|
case ARROW_RIGHT:
|
||||||
|
strcpy(key_str, "ARROW-RIGHT");
|
||||||
|
break;
|
||||||
|
case PAGE_UP:
|
||||||
|
strcpy(key_str, "PAGE-UP");
|
||||||
|
break;
|
||||||
|
case PAGE_DOWN:
|
||||||
|
strcpy(key_str, "PAGE-DOWN");
|
||||||
|
break;
|
||||||
|
case DEL_KEY:
|
||||||
|
strcpy(key_str, "DEL");
|
||||||
|
break;
|
||||||
|
case BACKSPACE:
|
||||||
|
strcpy(key_str, "BACKSPACE");
|
||||||
|
break;
|
||||||
|
case BEG_LINE:
|
||||||
|
strcpy(key_str, "HOME");
|
||||||
|
break;
|
||||||
|
case END_LINE:
|
||||||
|
strcpy(key_str, "END");
|
||||||
|
break;
|
||||||
|
case '\x1b':
|
||||||
|
strcpy(key_str, "ESCAPE");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (key > 127) {
|
||||||
|
/* UTF-8 code point — re-encode into the buffer */
|
||||||
|
char buf[5] = {0};
|
||||||
|
int n = utf8Encode((uint32_t)key, buf);
|
||||||
|
snprintf(key_str, sizeof(key_str), "%.*s", n, buf);
|
||||||
|
} else if (isprint(key)) {
|
||||||
|
snprintf(key_str, sizeof(key_str), "%c", key);
|
||||||
|
} else {
|
||||||
|
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return key_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
int editorReadKey() {
|
||||||
|
char c;
|
||||||
|
/* read first byte — may be start of UTF-8 or escape */
|
||||||
|
while (read(STDIN_FILENO, &c, 1) != 1)
|
||||||
|
;
|
||||||
|
appDebug("f : %hhu %ld\r\n", c, 0x200);
|
||||||
|
|
||||||
if (c == '\x1b') {
|
if (c == '\x1b') {
|
||||||
if (read(STDIN_FILENO, &seq[0], 1) != 1 ||
|
char seq[6];
|
||||||
read(STDIN_FILENO, &seq[1], 1) != 1) {
|
/* try to read escape sequence */
|
||||||
|
if (read(STDIN_FILENO, &seq[0], 1) != 1)
|
||||||
return '\x1b';
|
return '\x1b';
|
||||||
}
|
if (read(STDIN_FILENO, &seq[1], 1) != 1)
|
||||||
|
return '\x1b';
|
||||||
|
appDebug("f2 : %s\r\n", seq);
|
||||||
|
|
||||||
if (seq[0] == '[') {
|
if (seq[0] == '[') {
|
||||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||||
if (read(STDIN_FILENO, &seq[2], 1) != 1) {
|
if (read(STDIN_FILENO, &seq[2], 1) != 1)
|
||||||
return '\x1b';
|
return '\x1b';
|
||||||
}
|
|
||||||
if (seq[2] == '~') {
|
if (seq[2] == '~') {
|
||||||
switch (seq[1]) {
|
switch (seq[1]) {
|
||||||
case '1':
|
case '1':
|
||||||
@@ -74,7 +145,6 @@ int editorReadKey() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
switch (seq[1]) {
|
switch (seq[1]) {
|
||||||
case 'A':
|
case 'A':
|
||||||
return ARROW_UP;
|
return ARROW_UP;
|
||||||
@@ -90,18 +160,26 @@ int editorReadKey() {
|
|||||||
return END_LINE;
|
return END_LINE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (seq[0] == 'O') {
|
|
||||||
switch (seq[1]) {
|
|
||||||
case 'H':
|
|
||||||
return BEG_LINE;
|
|
||||||
case 'F':
|
|
||||||
return END_LINE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return '\x1b';
|
return '\x1b';
|
||||||
} else {
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* multi-byte UTF-8: read remaining bytes */
|
||||||
|
int seqlen = utf8Seqlen((unsigned char)c);
|
||||||
|
if (seqlen > 1) {
|
||||||
|
/* pack into a pseudo-codepoint just to pass bytes through;
|
||||||
|
we handle encoding/decoding at the row level */
|
||||||
|
char buf[4] = {c, 0, 0, 0};
|
||||||
|
for (int i = 1; i < seqlen; i++)
|
||||||
|
if (read(STDIN_FILENO, &buf[i], 1) != 1)
|
||||||
|
break;
|
||||||
|
/* decode and return as uint32, but we need int — use high range */
|
||||||
|
const char *p = buf;
|
||||||
|
uint32_t cp = utf8Decode(&p);
|
||||||
|
return (int)cp; /* caller re-encodes when inserting */
|
||||||
|
}
|
||||||
|
|
||||||
|
return (unsigned char)c;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getCursorPosition(int *rows, int *cols) {
|
int getCursorPosition(int *rows, int *cols) {
|
||||||
@@ -147,3 +225,14 @@ int getWindowSize(int *rows, int *cols) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void appDebug(const char *fmt, ...) {
|
||||||
|
#ifdef APP_DEBUG
|
||||||
|
va_list ap;
|
||||||
|
char message[256];
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(message, 256, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
fprintf(stderr, "%s\n", message);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* @file utf8.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../include/utf8.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t readUtf8Char(void)
|
||||||
|
{
|
||||||
|
unsigned char buf[4];
|
||||||
|
|
||||||
|
read(STDIN_FILENO, &buf[0], 1);
|
||||||
|
|
||||||
|
int extra;
|
||||||
|
uint32_t cp;
|
||||||
|
|
||||||
|
if (buf[0] < 0x80)
|
||||||
|
{
|
||||||
|
cp = buf[0];
|
||||||
|
extra = 0;
|
||||||
|
}
|
||||||
|
else if (buf[0] < 0xC0) { return 0xFFFD; } // stray continuation
|
||||||
|
else if (buf[0] < 0xE0)
|
||||||
|
{
|
||||||
|
cp = buf[0] & 0x1F;
|
||||||
|
extra = 1;
|
||||||
|
}
|
||||||
|
else if (buf[0] < 0xF0)
|
||||||
|
{
|
||||||
|
cp = buf[0] & 0x0F;
|
||||||
|
extra = 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cp = buf[0] & 0x07;
|
||||||
|
extra = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra > 0)
|
||||||
|
{
|
||||||
|
read(STDIN_FILENO, &buf[1], extra); // read remaining bytes at once
|
||||||
|
for (int i = 0; i < extra; i++)
|
||||||
|
cp = (cp << 6) | (buf[1 + i] & 0x3F);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t utf8Decode(const char** s)
|
||||||
|
{
|
||||||
|
unsigned char c = (unsigned char)**s;
|
||||||
|
uint32_t cp;
|
||||||
|
int extra;
|
||||||
|
if (c < 0x80)
|
||||||
|
{
|
||||||
|
cp = c;
|
||||||
|
extra = 0;
|
||||||
|
}
|
||||||
|
else if (c < 0xC0)
|
||||||
|
{
|
||||||
|
(*s)++;
|
||||||
|
return 0xFFFD;
|
||||||
|
}
|
||||||
|
else if (c < 0xE0)
|
||||||
|
{
|
||||||
|
cp = c & 0x1F;
|
||||||
|
extra = 1;
|
||||||
|
}
|
||||||
|
else if (c < 0xF0)
|
||||||
|
{
|
||||||
|
cp = c & 0x0F;
|
||||||
|
extra = 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cp = c & 0x07;
|
||||||
|
extra = 3;
|
||||||
|
}
|
||||||
|
(*s)++;
|
||||||
|
while (extra--)
|
||||||
|
{
|
||||||
|
c = (unsigned char)**s;
|
||||||
|
if ((c & 0xC0) != 0x80) return 0xFFFD;
|
||||||
|
cp = (cp << 6) | (c & 0x3F);
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// buf must have at least 4 bytes; returns bytes written
|
||||||
|
int utf8Encode(uint32_t cp, char* buf)
|
||||||
|
{
|
||||||
|
if (cp < 0x80)
|
||||||
|
{
|
||||||
|
buf[0] = cp;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (cp < 0x800)
|
||||||
|
{
|
||||||
|
buf[0] = 0xC0 | (cp >> 6);
|
||||||
|
buf[1] = 0x80 | (cp & 0x3F);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (cp < 0x10000)
|
||||||
|
{
|
||||||
|
buf[0] = 0xE0 | (cp >> 12);
|
||||||
|
buf[1] = 0x80 | ((cp >> 6) & 0x3F);
|
||||||
|
buf[2] = 0x80 | (cp & 0x3F);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
buf[0] = 0xF0 | (cp >> 18);
|
||||||
|
buf[1] = 0x80 | ((cp >> 12) & 0x3F);
|
||||||
|
buf[2] = 0x80 | ((cp >> 6) & 0x3F);
|
||||||
|
buf[3] = 0x80 | (cp & 0x3F);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int utf8Seqlen(unsigned char c)
|
||||||
|
{
|
||||||
|
if (c < 0x80) return 1;
|
||||||
|
if (c < 0xC0) return 0; /* continuation — shouldn't be leading */
|
||||||
|
if (c < 0xE0) return 2;
|
||||||
|
if (c < 0xF0) return 3;
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param codepoint utf8 codepoint of a char
|
||||||
|
* @return length of the codepoint
|
||||||
|
*/
|
||||||
|
int codepointWidth(uint32_t codepoint)
|
||||||
|
{
|
||||||
|
if (codepoint < 0x20 || codepoint == 0x7F) return 0;
|
||||||
|
/* rough double-width ranges */
|
||||||
|
if ((codepoint >= 0x1100 && codepoint <= 0x115F) ||
|
||||||
|
(codepoint >= 0x2E80 && codepoint <= 0x303E) ||
|
||||||
|
(codepoint >= 0x3041 && codepoint <= 0x33BF) ||
|
||||||
|
(codepoint >= 0xAC00 && codepoint <= 0xD7AF) ||
|
||||||
|
(codepoint >= 0xF900 && codepoint <= 0xFAFF) ||
|
||||||
|
(codepoint >= 0xFF01 && codepoint <= 0xFF60) ||
|
||||||
|
(codepoint >= 0x1F300 && codepoint <= 0x1FAFF))
|
||||||
|
return 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user