69 Commits

Author SHA1 Message Date
arthur 6b6b3d1766 meson error
Build project / build (push) Has been cancelled
2026-05-26 08:31:59 +02:00
arthur 3d49a0e2eb copy paste functions
Build project / build (push) Has been cancelled
2026-05-26 08:28:38 +02:00
arthur 777dc5cb74 Version update
Build project / build (push) Successful in 28s
2026-05-23 15:44:05 +02:00
arthur c4d772e465 READ ONLY state for buffers
Build project / build (push) Successful in 36s
2026-05-23 15:42:17 +02:00
arthur 310498d582 lisp changes
Build project / build (push) Successful in 1m26s
2026-05-22 14:37:32 +02:00
arthur edb5384f0e temp commit 2026-05-22 13:40:03 +02:00
arthur bc66e673fa Merge branch 'refs/heads/char_encode'
# Conflicts:
#	include/builtins.h
#	include/data.h
#	include/editor_op.h
#	include/file_io.h
#	include/input.h
#	include/row_op.h
#	install.sh
#	main.c
#	meson.build
#	src/builtins.c
#	src/editor_op.c
#	src/file_io.c
#	src/init.c
#	src/input.c
#	src/output.c
#	src/row_op.c
2026-05-04 00:31:48 +02:00
arthur 8e1b4d2f86 utf8 processing without struct 2026-05-03 23:32:40 +02:00
Arthur Barraux 87746f09ce Merge remote-tracking branch 'gitea/main'
Build project / build (push) Successful in 56s
2026-01-24 19:47:59 +01:00
Arthur Barraux 5a7b073041 Adding splitting screen and buffer switching 2026-01-24 19:44:34 +01:00
Arthur Barraux 557fc8894a Adding splitting screen and buffer switching
Build project / build (push) Successful in 52s
2026-01-24 19:29:46 +01:00
Arthur Barraux b039cf3ded Forget delete file
Build project / build (push) Successful in 24s
2026-01-22 14:07:13 +01:00
Arthur Barraux af1835d75b config correct
Build project / build (push) Failing after 1m0s
2026-01-22 14:03:59 +01:00
Arthur Barraux f3bd5dd1c9 Color theming 2026-01-22 14:00:47 +01:00
Arthur Barraux fa7f8d39d8 update splash screen
Build project / build (push) Successful in 23s
2026-01-14 20:38:33 +01:00
Arthur Barraux 23036cf3d3 warranty 0 memoey leak
Build project / build (push) Successful in 58s
2026-01-14 20:24:37 +01:00
Arthur Barraux d0afd4f304 patch file_completion crash
Build project / build (push) Successful in 1m0s
2026-01-13 15:20:18 +01:00
Arthur Barraux de4fa78221 Merge remote-tracking branch 'gitea/main'
Build project / build (push) Successful in 48s
2026-01-11 19:41:57 +01:00
Arthur Barraux fc93832130 Syntax highlighting and comment 2026-01-11 19:41:30 +01:00
arthur 39e07902f4 Update README.md
Build project / build (push) Successful in 24s
2026-01-09 15:03:08 +01:00
Arthur Barraux 815114923d Add prefix feature
Build project / build (push) Successful in 56s
2026-01-09 14:54:07 +01:00
arthur 410f382592 Add go to end of word function
Build project / build (push) Successful in 1m22s
2025-12-24 14:50:14 +01:00
arthur 80c0bf73e0 Update README.md
Build project / build (push) Successful in 1m1s
2025-12-08 00:25:05 +01:00
arthur d4ee0d86c2 Update .gitea/workflows/build.yml
Build project / build (push) Failing after 2m18s
2025-12-04 01:34:33 +01:00
Arthur Barraux eae85c32ca add utf8_char_t struct 2025-11-19 10:37:41 +01:00
arthur c06c820dfb Patch path complete and read char lisp function
Build project / build (push) Has been cancelled
2025-11-07 16:23:56 +01:00
arthur 5588b0a8d7 add path autocomplete
Build project / build (push) Has been cancelled
2025-11-05 15:49:01 +01:00
arthur 419e924650 Add search function
Build project / build (push) Has been cancelled
2025-11-03 16:45:23 +01:00
arthur 6a201b3ebc delete line macro
Build project / build (push) Has been cancelled
2025-11-03 16:05:25 +01:00
arthur 1d253e51ef Update README.md
Build project / build (push) Failing after 3m27s
2025-11-01 13:22:10 +01:00
Arthur Barraux 42f82e2e0d add packages
Build project / build (push) Has been cancelled
2025-11-01 13:03:07 +01:00
arthur fa32f4b177 Update src/init.c
Build project / build (push) Has been cancelled
2025-10-31 09:10:57 +01:00
arthur 65f997e964 Update install.sh
Build project / build (push) Has been cancelled
2025-10-31 09:09:20 +01:00
Arthur Barraux 7eaf6913cb Merge remote-tracking branch 'gitea/main'
Build project / build (push) Has been cancelled
2025-10-30 18:18:12 +01:00
Arthur Barraux 8f7dcf3534 Adding installer script 2025-10-30 18:17:19 +01:00
arthur d8c6b9ace3 Update README.md
Build project / build (push) Has been cancelled
2025-10-28 09:16:36 +01:00
arthur d0173d7308 Update README.md
Build project / build (push) Has been cancelled
2025-10-28 09:02:29 +01:00
arthur 40fc234eeb Update .gitea/workflows/build.yml
Build project / build (push) Failing after 39s
2025-10-18 15:35:57 +02:00
Arthur Barraux 756deba83e remove .cache repo for cleaning
Build project / build (push) Failing after 33s
2025-10-18 15:12:06 +02:00
Arthur Barraux 85e8067e41 clean up the repo
Build project / build (push) Failing after 1m22s
2025-10-18 15:06:23 +02:00
Arthur Barraux 09d78f48e9 Merge remote-tracking branch 'gitea/lisp'
Build project / build (push) Successful in 1m3s
2025-10-06 11:37:39 +02:00
Arthur Barraux ce94e9fb87 permission files
Build and Deploy Docs / build (push) Failing after 43s
2025-10-06 11:28:49 +02:00
Arthur Barraux 29a92ce904 Merge Lisp branch
Build and Deploy Docs / build (push) Failing after 1m24s
2025-10-05 22:05:21 +02:00
Arthur Barraux 9348ae668a Add open file key-bind
Build and Deploy Docs / build (push) Failing after 40s
2025-10-05 21:44:58 +02:00
Arthur Barraux 9157b94398 Adding usefull keybinds
Build and Deploy Docs / build (push) Successful in 50s
2025-10-02 14:59:28 +02:00
Arthur Barraux 3b6c60a49e functional lisp config files
Build and Deploy Docs / build (push) Successful in 47s
2025-10-02 11:26:18 +02:00
arthur 53d6572c8c Revert doxygen deployement
Build and Deploy Docs / build (push) Successful in 37s
2025-09-25 12:10:44 +02:00
arthur d083948dfe Update .gitea/workflows/build.yml
Build and Deploy Docs / build (push) Failing after 30s
2025-09-25 11:35:41 +02:00
arthur 09ef5c0f3b Update .gitea/workflows/build.yml
Build and Deploy Docs / build (push) Failing after 1m26s
2025-09-25 11:25:52 +02:00
arthur e4691669b8 Update .gitea/workflows/build.yml
Build and Deploy Docs / build (push) Failing after 1m54s
2025-09-25 11:22:11 +02:00
arthur 27ae0a684f Update .gitea/workflows/build.yml
Build and Deploy Docs / build (push) Failing after 1m33s
2025-09-25 11:06:53 +02:00
Arthur Barraux 3505084527 second try
Meson Build and Deploy / build (push) Failing after 1m23s
2025-09-25 11:00:53 +02:00
Arthur Barraux 02d7f27ec3 Merge remote-tracking branch 'gitea/lisp' into lisp
Meson Build and Deploy / build (push) Failing after 53s
2025-09-25 10:48:09 +02:00
Arthur Barraux 7dded62db9 add doxygen deployement 2025-09-25 10:47:07 +02:00
arthur 6cd79b5c76 downgrade version of runner upload
Meson Build and Deploy / build (push) Successful in 25s
2025-09-24 11:14:22 +02:00
arthur d9aab06c1c Downgrade upload version to 3
CMake Build and Deploy / build (push) Failing after 2m34s
2025-09-24 11:07:22 +02:00
Arthur Barraux 8844d2f064 meson bulid changes
Meson Build and Deploy / build (push) Failing after 29s
2025-09-24 11:04:16 +02:00
Arthur Barraux d8fc7d2d67 adding lisp-interpreter
Meson Build and Deploy / build (push) Failing after 29s
2025-09-24 10:58:09 +02:00
Arthur Barraux ab482df604 get submodules
Meson Build and Deploy / build (push) Failing after 1m31s
2025-09-24 10:48:45 +02:00
Arthur Barraux be31e83fb9 update build.yml
Meson Build and Deploy / build (push) Failing after 1m21s
2025-09-24 10:45:11 +02:00
Arthur Barraux 54db6321ad add CI/CD config
Meson Build and Deploy / build (push) Failing after 1m54s
2025-09-24 10:29:31 +02:00
arthur ec0eba849a Update .gitea/workflows/build.yml
CMake Build and Deploy / build (push) Failing after 1m58s
2025-09-24 08:33:08 +02:00
arthur 2ca64fae41 add build.yml
CMake Build and Deploy / build (push) Has been cancelled
2025-09-24 08:32:22 +02:00
Arthur Barraux 8ce621dfde Made Editor Config global 2025-09-19 14:31:12 +02:00
Arthur Barraux 91e247d1de adding meson build 2025-09-19 11:02:44 +02:00
Arthur Barraux 72178adebb submodeule update 2025-06-12 16:56:10 +02:00
Arthur Barraux 7a98f89531 readme update 2025-06-12 16:53:21 +02:00
Arthur Barraux 3e562c1071 add blisp submodule 2025-06-02 10:36:22 +02:00
Arthur Barraux bff3f84ecd Configurable settings with blisp 2025-06-02 10:31:31 +02:00
38 changed files with 8892 additions and 608 deletions
+35
View File
@@ -0,0 +1,35 @@
name: Build project
on:
push:
branches: ["main"]
jobs:
build:
runs-on: giorgio-runner
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
tokens: ${{ secrets.GITEA_TOKEN }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y meson ninja-build gcc
- name: Configure Meson
run: meson setup build
- name: Build project
run: meson compile -C build
- name: Run tests
run: ctest --test-dir build || true
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: beluga
path: build/beluga
+2 -2
View File
@@ -1,3 +1,3 @@
tmp/* build/*
bin/*
doc/* doc/*
beluga.wiki/*
-32
View File
@@ -1,32 +0,0 @@
##
# TEST
#
# @file
# @version 0.1
BELUGA_OUTPUT=bin
BUILD_FLAGS=-Wall -Wextra -pedantic
build: main.c src/*
if [ ! -d $(BELUGA_OUTPUT) ]; then mkdir $(BELUGA_OUTPUT); fi
$(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* $(BUILD_FLAGS)
DEBUG_FLAGS=-Wall -Wextra -pedantic -Werror -fsanitize=address -g
debug: main.c src/*
if [ ! -d $(BELUGA_OUTPUT) ]; then mkdir $(BELUGA_OUTPUT); fi
$(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* $(DEBUG_FLAGS)
doc:
if [ ! -d doc/ ]; then mkdir doc; fi
doxygen
clean:
rm -r $(BELUGA_OUTPUT)
rm -rf doc/
rm -rf tmp/
all: build doc
# end
+16 -10
View File
@@ -1,26 +1,32 @@
# Beluga # Beluga
Beluga is a project of CLI text editor that will fit perfectly with your azerty keyboard. Beluga is a project of CLI text editor that uses lisp as configuration language.
## Requirements ## Requirements
You will only need **make** or **gcc** to compile the editor. You will only need **meson** and a **C compiler** to compile the editor.
## Installation ## Installation
### From source
Here is the installation line for development version:
Here is the installation line : ```git clone https://git.giorgio-nas.fr/arthur/beluga.git && cd beluga && meson setup build && meson compile -C build```
```git clone --branch V1.0 --single-branch https://github.com/le-cocotier/beluga.git ~/.beluga && cd ~/.beluga && make build``` The executable file will be `build/beluga`. Feel free to add it to your path.
The executable file will be in `bin/beluga`. Feel free to add it to your path. ### From installation script ( prefered )
You can either run `make all` if you're interested by the doxygen documentation. Just clone the repo and execute the script `install.sh`. It will automatically add beluga to your path.
## Getting start ## Getting start
To open an existing file just type : To open an existing file just type :
```beluga path_to_my_file``` ```./build/beluga path_to_my_file```
The only keybinds that you will need will be : Here is some few command that you will need :
- Ctrl-Q : leave the editor
- Ctrl-S : Save a file | keybind | command |
|---------------|------------------|
| Ctrl-x Ctrl-c | leave the editor |
| Ctrl-x Ctrl-s | Save a file |
| Ctrl-x f | open file |
+43
View File
@@ -0,0 +1,43 @@
**#%#*****###%**
*##+--------------------=##*
#*=----------------------------=*#*
#*------------------------------------*#
%+----------------------------------------=#*
#+---------------------------------------------##
*#-------------------------------------------------=##
*#----------------------------------------------------:-##
#----------------------------------------------------------##
#=--------------------------------------------------------------##
+--------------------------+@#-%*-----------------------------------#*
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.4
*-=-------------------------#@@*---------------------------------------=%
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
*%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave
%=--------------------------------------------------------------------------#* CTRL-x CTRL-s save
%-----------------------------------------------------------------------------** CTRL-x f open-file
*+--=---===----=---------------=*-----------------------------------------------**
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
*%**=-----------==== ==---------------------------------=+#+-----------------=#
*%=----------------------------------------------------------=#*---------------#
##=----------------------------------=------------------------+%=------------+#
#%+---------------------------------=*------------------------+%------------#
*#%*=-------------=-----------------#-------------------------#+----------#
**#%#*******+=======-------------#=------------------------#----------#
#===#*=======------------------#*----=-----------=--=##*-----------#
-====##=------------------------*%+------------=*#+=====----------#
--=====+#*=----------------------=-=+*#####***+=======-----------=*
%------=====*%*=-------------------------========-----------------+*
*-=--------====%%###+=--------------------------=-----------------#
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
%=-------#* #%*=-----------------+#
*%+--=## ##=-----------------=#*
** #+----=-------------------#*
%+----------------------------#*
*%-------------==----------------+#
##--------------==------------------#
*#--------------===%-----------------=%
##---------------=-##*-----------------+#
*#---------------==#+=#%-----------------%
*%---------------+# %*---------------#*
*#------------=+#* #%*=-----------#*
#****##****** *#%%##+=----%
+92
View File
@@ -0,0 +1,92 @@
;; MACROS
(define TAB-LENGTH 4)
(define QUIT-TIMES 1)
(define THEME "dark")
;; PACKAGES
;; First git clone it
;; (add-package "smart_delimiters")
;; FUNCTIONS
(define editor-delete-next-char (lambda () (
(move-cursor "right")
(editor-delete-previous-char)
)
)
)
(define (char-between ch lo hi)
(if (char>=? ch lo)
(char<=? ch hi)
#f))
(define (alphanumericp ch)
(if ch
(if (char-between ch #\a #\z)
#t
(if (char-between ch #\A #\Z)
#t
(if (char-between ch #\0 #\9)
#t
#f)))
#f))
(define (word-char-p ch)
(if (alphanumericp ch)
#t
#f))
(define editor-move-to-end-of-word (lambda () (
(if (word-char-p (editor-read-char))
((move-cursor "right")
(editor-move-to-end-of-word))
))
))
(define enter-and-tab
(lambda ()
(editor-insert-new-line)
(let ((is-in (move-cursor "up")))
(do ((ch (editor-read-char) (editor-read-char)))
((and (not (char=? ch #\space)) is-in) #f)
(move-cursor "down")
(editor-insert-char " ")
(set! is-in (move-cursor "up")))
(move-cursor "down"))))
(add-prefix "user")
(map-key "CTRL-x" '(editor-set-prefix "user") "no-prefix")
(map-key "CTRL-g" '(editor-set-prefix "no-prefix") "user")
(map-key "CTRL-c" editor-quit "user")
(map-key "CTRL-s" editor-save "user")
(map-key "ARROW-UP" '(move-cursor "up") "no-prefix")
(map-key "ARROW-DOWN" '(move-cursor "down") "no-prefix")
(map-key "ARROW-RIGHT" '(move-cursor "right") "no-prefix")
(map-key "ARROW-LEFT" '(move-cursor "left") "no-prefix")
(map-key "ENTER" enter-and-tab "no-prefix")
(map-key "CTRL-a" move-cursor-beg-line "no-prefix")
(map-key "CTRL-e" move-cursor-end-line "no-prefix")
(map-key "BACKSPACE" editor-delete-previous-char "no-prefix")
(map-key "DEL" editor-delete-next-char "no-prefix")
(map-key "PAGE-UP" move-cursor-page-up "no-prefix")
(map-key "PAGE-DOWN" move-cursor-page-down "no-prefix")
(map-key "f" editor-open-file "user")
(map-key "TAB" editor-insert-tab "no-prefix")
(map-key "CTRL-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")
+69
View File
@@ -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
+60
View File
@@ -0,0 +1,60 @@
#ifndef BUILTINS_H_
#define BUILTINS_H_
#include "lisp.h"
// Mouvement
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx);
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx);
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx);
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
Lisp editorMoveCursorPageDown(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 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);
// 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
+123
View File
@@ -0,0 +1,123 @@
#ifndef COLOR_H_
#define COLOR_H_
// ============================================================================
// TEXT STYLES / ATTRIBUTES
// ============================================================================
#define ANSI_RESET "\x1b[0m" // Reset all attributes
#define ANSI_BOLD "\x1b[1m" // Bold text
#define ANSI_DIM "\x1b[2m" // Dim/faint text
#define ANSI_ITALIC "\x1b[3m" // Italic text
#define ANSI_UNDERLINE "\x1b[4m" // Underline text
#define ANSI_BLINK "\x1b[5m" // Blinking text
#define ANSI_REVERSE "\x1b[7m" // Reverse video (invert colors)
#define ANSI_HIDDEN "\x1b[8m" // Hidden/invisible text
#define ANSI_STRIKETHROUGH "\x1b[9m" // Strikethrough text
// ============================================================================
// FOREGROUND COLORS (30-37 standard, 90-97 bright)
// ============================================================================
#define ANSI_BLACK "\x1b[30m"
#define ANSI_RED "\x1b[31m"
#define ANSI_GREEN "\x1b[32m"
#define ANSI_YELLOW "\x1b[33m"
#define ANSI_BLUE "\x1b[34m"
#define ANSI_MAGENTA "\x1b[35m"
#define ANSI_CYAN "\x1b[36m"
#define ANSI_WHITE "\x1b[37m"
// Bright/Light foreground colors (90-97)
#define ANSI_BRIGHT_BLACK "\x1b[90m"
#define ANSI_BRIGHT_RED "\x1b[91m"
#define ANSI_BRIGHT_GREEN "\x1b[92m"
#define ANSI_BRIGHT_YELLOW "\x1b[93m"
#define ANSI_BRIGHT_BLUE "\x1b[94m"
#define ANSI_BRIGHT_MAGENTA "\x1b[95m"
#define ANSI_BRIGHT_CYAN "\x1b[96m"
#define ANSI_BRIGHT_WHITE "\x1b[97m"
// ============================================================================
// BACKGROUND COLORS (40-47 standard, 100-107 bright)
// ============================================================================
#define ANSI_BG_BLACK "\x1b[40m"
#define ANSI_BG_RED "\x1b[41m"
#define ANSI_BG_GREEN "\x1b[42m"
#define ANSI_BG_YELLOW "\x1b[43m"
#define ANSI_BG_BLUE "\x1b[44m"
#define ANSI_BG_MAGENTA "\x1b[45m"
#define ANSI_BG_CYAN "\x1b[46m"
#define ANSI_BG_WHITE "\x1b[47m"
// Bright/Light background colors (100-107)
#define ANSI_BG_BRIGHT_BLACK "\x1b[100m"
#define ANSI_BG_BRIGHT_RED "\x1b[101m"
#define ANSI_BG_BRIGHT_GREEN "\x1b[102m"
#define ANSI_BG_BRIGHT_YELLOW "\x1b[103m"
#define ANSI_BG_BRIGHT_BLUE "\x1b[104m"
#define ANSI_BG_BRIGHT_MAGENTA "\x1b[105m"
#define ANSI_BG_BRIGHT_CYAN "\x1b[106m"
#define ANSI_BG_BRIGHT_WHITE "\x1b[107m"
// ============================================================================
// 256-COLOR MODE (for more colors)
// ============================================================================
// Foreground: \x1b[38;5;Nm where N is 0-255
// Background: \x1b[48;5;Nm where N is 0-255
// Example macros:
#define ANSI_FG_256(N) "\x1b[38;5;" #N "m"
#define ANSI_BG_256(N) "\x1b[48;5;" #N "m"
// ============================================================================
// TRUE COLOR / 24-BIT COLOR (RGB)
// ============================================================================
// Foreground: \x1b[38;2;R;G;Bm
// Background: \x1b[48;2;R;G;Bm
// Example macros:
#define ANSI_FG_RGB(R,G,B) "\x1b[38;2;" #R ";" #G ";" #B "m"
#define ANSI_BG_RGB(R,G,B) "\x1b[48;2;" #R ";" #G ";" #B "m"
// ============================================================================
// CURSOR MOVEMENT
// ============================================================================
#define ANSI_CURSOR_UP(N) "\x1b[" #N "A" // Move up N lines
#define ANSI_CURSOR_DOWN(N) "\x1b[" #N "B" // Move down N lines
#define ANSI_CURSOR_RIGHT(N) "\x1b[" #N "C" // Move right N columns
#define ANSI_CURSOR_LEFT(N) "\x1b[" #N "D" // Move left N columns
#define ANSI_CURSOR_HOME "\x1b[H" // Move to home (0,0)
#define ANSI_CURSOR_HIDE "\x1b[?25l" // Hide cursor
#define ANSI_CURSOR_SHOW "\x1b[?25h" // Show cursor
// ============================================================================
// SCREEN CONTROL
// ============================================================================
#define ANSI_CLEAR_SCREEN "\x1b[2J" // Clear entire screen
#define ANSI_CLEAR_LINE "\x1b[K" // Clear from cursor to end of line
#define ANSI_CLEAR_UP "\x1b[1J" // Clear from cursor to start
#define ANSI_CLEAR_DOWN "\x1b[0J" // Clear from cursor to end
// ============================================================================
// EXAMPLE USAGE
// ============================================================================
/*
#include <stdio.h>
int main(void) {
// Simple color example
printf("%sHello in Red%s\n", ANSI_RED, ANSI_RESET);
// Bold and colored
printf("%s%sBold Green%s\n", ANSI_BOLD, ANSI_GREEN, ANSI_RESET);
// Background color
printf("%s%sYellow on Blue%s\n", ANSI_YELLOW, ANSI_BG_BLUE, ANSI_RESET);
// Combine multiple styles
printf("%s%s%sUnderlined Bold Cyan%s\n",
ANSI_UNDERLINE, ANSI_BOLD, ANSI_CYAN, ANSI_RESET);
return 0;
}
*/
#endif
+129 -12
View File
@@ -1,21 +1,111 @@
#ifndef DATA_H_ #ifndef DATA_H_
#define DATA_H_ #define DATA_H_
#include <stdio.h>
#include <termios.h> #include <termios.h>
#include <time.h> #include <time.h>
#include "lisp.h"
/** /**
* \struct erow * \struct 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;
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,
READ_ONLY,
READ_AND_WRITE,
};
struct const_t {
int TAB_LENGTH;
int QUIT_TIMES;
char *THEME;
};
struct prefix_t {
char prefix_name[64];
int prefix_id;
};
struct keyBind_t {
char *key_sequence;
int prefix_id;
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 */
};
/** /**
* \struct editorConfig * \struct editorConfig
@@ -23,18 +113,42 @@ typedef struct erow {
*/ */
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;
char status_msg[80]; char *status_msg;
time_t status_msg_time; time_t status_msg_time;
struct termios orig_termios; /**< Terminal communication interface */ struct termios orig_termios; /**< Terminal communication interface */
struct const_t constantes;
int quit_times_buffer;
char *init_file_path;
FILE *fd_init_file;
Lisp env;
LispContext ctx; /** Lisp context */
Lisp ctx_data; /** Lisp data context */
LispError ctx_error; /** Lisp ctx error */
struct keyBind_t *key_binds;
int number_of_keybinds;
struct prefix_t *prefix;
int number_of_prefix;
int prefix_state;
struct buffer_t buffers[64];
int number_of_buffer;
theme_t theme;
}; };
/** /**
@@ -47,4 +161,7 @@ struct abuf {
int len; /**< Length of the text */ int len; /**< Length of the text */
}; };
extern struct editorConfig E;
#endif #endif
+16 -5
View File
@@ -2,14 +2,19 @@
#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,
@@ -20,12 +25,18 @@ enum editorKey {
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.0" #define BELUGA_VERSION "2.4"
#define TAB_LENGTH 4
#define QUIT_TIMES 1 #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
View File
@@ -2,10 +2,12 @@
#define EDITOR_OP_H_ #define EDITOR_OP_H_
#include "data.h" #include "data.h"
void editorInsertChar(struct editorConfig *E, int c);
void editorInsertNewLine(struct editorConfig *E); 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(struct editorConfig *E);
#endif // EDITOR_OP_H_ #endif // EDITOR_OP_H_
+3 -5
View File
@@ -2,16 +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(struct editorConfig *E, int *buffer_len); void editorCloseFile(void);
void editorOpen(struct editorConfig *E, char *filename); void editorOpen(struct buffer_t *buffer);
void editorSave(struct editorConfig *E); void editorSave();
#endif // FILE_IO_H_ #endif // FILE_IO_H_
+4 -1
View File
@@ -1,6 +1,7 @@
#ifndef INIT_H_ #ifndef INIT_H_
#define INIT_H_ #define INIT_H_
#include "builtins.h"
#include "data.h" #include "data.h"
#include "terminal.h" #include "terminal.h"
#include <stdio.h> #include <stdio.h>
@@ -10,6 +11,8 @@
* \brief Job's function is to initialize all the fields of editorConfig. * \brief Job's function is to initialize all the fields of editorConfig.
* */ * */
void initEditor(struct editorConfig *E); void initBuiltins();
void initEditor();
#endif // INIT_H_ #endif // INIT_H_
+8 -3
View File
@@ -5,6 +5,7 @@
#include "define.h" #include "define.h"
#include "output.h" #include "output.h"
#include "terminal.h" #include "terminal.h"
#include "builtins.h"
#include <unistd.h> #include <unistd.h>
// KEYS keycode // KEYS keycode
@@ -19,15 +20,19 @@
// END \x1b[4~ || <esc>[8~ || <esc>[F || <esc>OF // END \x1b[4~ || <esc>[8~ || <esc>[F || <esc>OF
// DELETE \x1b[3~ // DELETE \x1b[3~
char *editorPrompt(struct editorConfig *E, char *prompt); char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
void editorMoveCursor(struct editorConfig *E, int key); const char *fileCompletion(const char *path);
int editorMoveCursor(int key);
int executeKeyBind(char *key_sequence);
/** /**
* \fn void editorProcessKeypress() * \fn void editorProcessKeypress()
* \brief Get the last key input and do the proper action. * \brief Get the last key input and do the proper action.
*/ */
void editorProcessKeypress(struct editorConfig *E); void editorProcessKeypress();
#endif // INPUT_H_ #endif // INPUT_H_
+3218
View File
File diff suppressed because it is too large Load Diff
+2177
View File
File diff suppressed because it is too large Load Diff
+4 -12
View File
@@ -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,16 +9,12 @@
* \brief Draws left rows of the editor. * \brief Draws left rows of the editor.
*/ */
void editorDrawRows(struct editorConfig *E, struct abuf *ab); void editorRefreshScreen();
void editorRefreshScreen(struct editorConfig *E); void editorScroll();
void editorScroll(struct editorConfig *E); void editorDrawStatusBar(struct abuf *ab);
void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab); void editorDrawMessageBar(struct abuf *ab);
void editorDrawMessageBar(struct editorConfig *E, struct abuf *ab);
void editorSetStatusMessage(struct editorConfig *E, const char *fmt, ...);
#endif // OUTPUT_H_ #endif // OUTPUT_H_
-13
View File
@@ -8,21 +8,8 @@
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
int editorRowCxToRx(erow *row, int cursor_x);
void editorUpdateRow(erow *row);
void editorInsertRow(struct editorConfig *E, int at, char *s, size_t len);
void editorFreeRow(erow *row);
void editorDelRow(struct editorConfig *E, int at);
void editorRowInsertChar(struct editorConfig *E, erow *row, int at, int c);
void editorRowAppendString(struct editorConfig *E, erow *row, char *s,
size_t len);
void editorRowDelchar(struct editorConfig *E, erow *row, int at);
#endif // ROW_OP_H_ #endif // ROW_OP_H_
+62
View File
@@ -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
+21
View File
@@ -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
+6 -4
View File
@@ -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>
@@ -21,9 +19,9 @@
void die(const char *s); void die(const char *s);
void disableRawMode(struct editorConfig *E); void disableRawMode();
void enableRawMode(struct editorConfig *E); void enableRawMode();
int editorReadKey(); int editorReadKey();
@@ -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
+16
View File
@@ -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
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
echo "--- Welcome to Beluga installer ---"
read -p "Do you want to start the installation ? (Y/n)" confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
# Check dependencies
if ! command -v "meson" &>/dev/null; then
echo "❌ Error: meson not found. Please install it first."
exit 1
fi
# Create config files
echo "Create config files ..."
mkdir -pv ~/.beluga/
mkdir -pv ~/.beluga/assets/
cp -rv ./assets/beluga.txt ~/.beluga/assets/
mkdir -pv ~/.beluga/packages/
mkdir -pv ~/.beluga/config/
read -p "Do you want to replace your config file or keep it (init.lisp.bak) / (init.lisp.new) ? (Y/n)" confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
mv ~/.beluga/config/init.lisp ~/.beluga/config/init.lisp.bak
cp -rv ./config/init.lisp ~/.beluga/config/
else
cp -rv ./config/init.lisp ~/.beluga/config/init.lisp.new
fi
# Compile the project
echo "Start compilation ..."
meson setup build/
meson compile -C build/
# Add to path
echo "Adding beluga to the path"
sudo cp -f ./build/beluga /usr/local/bin/
echo "Installation finish"
echo "Check ~/.beluga/config/init.lisp for customization"
+26 -10
View File
@@ -9,28 +9,44 @@
#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;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
struct editorConfig E; char * splash_screen = (char *) calloc(256, sizeof(char));
enableRawMode(&E); enableRawMode();
initEditor(&E); initEditor();
if (argc >= 2) { if (argc >= 2) {
editorOpen(&E, argv[1]); EditorPane *active = splitScreenGetActivePane();
} active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE);
} else {
strcat(splash_screen, getenv("HOME"));
strcat(splash_screen, "/.beluga/assets/beluga.txt");
appDebug("splash : %s\n", splash_screen);
EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(splash_screen, READ_ONLY);
}
free(splash_screen);
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
editorSetStatusMessage(&E, "HELP: Ctrl-S = save | Ctrl-Q = quit");
while (1) { while (1) {
editorRefreshScreen(&E); editorRefreshScreen();
editorProcessKeypress(&E); editorProcessKeypress();
} }
return 0; return 0;
} }
+35
View File
@@ -0,0 +1,35 @@
project('beluga', 'c',
version : '2.3',
default_options : [
'c_std=none',
]
)
cc = meson.get_compiler('c')
m = cc.find_library('m', required: false)
# Source files
src_files = files(
'main.c',
'src/append_buffer.c',
'src/editor_op.c',
'src/syntax_highlighter.c',
'src/file_io.c',
'src/init.c',
'src/input.c',
'src/output.c',
'src/row_op.c',
'src/terminal.c',
'src/builtins.c',
'src/buffer.c',
'src/split_screen.c',
'src/utf8.c'
)
# Executable
executable('beluga',
src_files,
dependencies: [m]
)
dependencies: [m]
)
+477
View File
@@ -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");
}
+561
View File
@@ -0,0 +1,561 @@
/**
* @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/buffer.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 <stdlib.h>
#include <string.h>
/**
* @brief Finds a prefix configuration by name
* @details Searches the prefix array for a prefix matching the given name.
* Returns the first prefix (default) if no match is found.
* @param prefix_name Name of the prefix to search for (max 64 chars)
* @return Matching prefix_t structure, or E.prefix[0] if not found
* @note Updates global editor state E
*/
struct prefix_t find_prefix(const char prefix_name[64]) {
int i = E.number_of_prefix + 1;
while (i--) {
if (!strcmp(prefix_name, E.prefix[i].prefix_name)) {
return E.prefix[i];
}
}
return E.prefix[0];
}
/**
* @brief Lisp function to bind a command to a key sequence
* @details Registers a keybinding with an associated Lisp command and optional
* prefix modifier. Dynamically extends the keybind array.
* @param args Lisp list of 3 arguments: (key-sequence command prefix-name)
* - key_sequence (string): The key or key combination to bind
* - command (Lisp): The Lisp command/function to execute
* - prefix_name (string): Prefix modifier name
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E (key_binds array)
*/
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
/*
* 3 arguments keybind command prefix
*/
const char *key_sequence = lisp_string(lisp_car(args));
args = lisp_cdr(args);
// second argument
const Lisp func = lisp_car(args);
E.key_binds = (struct keyBind_t *)realloc(
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
E.key_binds[E.number_of_keybinds - 1].key_sequence =
(char *)malloc(50 * sizeof(char));
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
E.key_binds[E.number_of_keybinds - 1].command = func;
// Third argument
args = lisp_cdr(args);
const char *prefix_name = lisp_string(lisp_car(args));
struct prefix_t prefix = find_prefix(prefix_name);
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
return lisp_null();
}
/**
* @brief Lisp function to move cursor in a specified direction
* @details Moves the editor cursor up, down, left, or right based on direction
* string.
* @param args Lisp list with one argument: direction string
* - "u": up, "d": down, "r": right, "l": left
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return Lisp boolean indicating whether movement was valid
* @note Updates global editor state E
* @see editorMoveCursor()
*/
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
const char *direction = lisp_string(lisp_car(args));
int is_in = 0;
switch (direction[0]) {
case 'u':
is_in = editorMoveCursor(ARROW_UP);
break;
case 'd':
is_in = editorMoveCursor(ARROW_DOWN);
break;
case 'r':
is_in = editorMoveCursor(ARROW_RIGHT);
break;
case 'l':
is_in = editorMoveCursor(ARROW_LEFT);
break;
}
appDebug("move lisp %d\n", is_in);
return lisp_make_bool(is_in);
}
/**
* @brief Frees all dynamically allocated editor structures
* @details Releases memory for prefix table, keybinds, filename, and all rows.
* Called during shutdown to prevent memory leaks.
* @note Updates global editor state E
*/
void free_structs(void) {
int i, 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) {
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
"another time to quit.");
--E.quit_times_buffer;
return lisp_null();
}
free_structs();
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode();
lisp_shutdown(E.ctx);
exit(0);
return lisp_null();
}
/**
* @brief Lisp function to save the current file
* @details Wrapper around editorSave() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @see editorSave()
*/
Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx) {
if (E.number_of_buffer >= 2) {
splitScreenVertical(E.buffers[0].buffer_id, E.buffers[1].buffer_id);
} else {
editorSetStatusMessage("Need at least 2 buffers open");
}
return lisp_null();
}
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
editorSave();
return lisp_null();
}
/**
* @brief Lisp function to insert a new line at cursor
* @details Wrapper around editorInsertNewLine() for use in Lisp keybindings.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @see editorInsertNewLine()
*/
Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
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;
while (--times) {
editorMoveCursor(ARROW_UP);
}
return lisp_null();
}
/**
* @brief Lisp function to move cursor down by one screen
* @details Scrolls down one full screen height, moving cursor to bottom of
* visible area.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
* @see editorMoveCursor()
*/
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
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;
while (--times) {
editorMoveCursor(ARROW_DOWN);
}
return lisp_null();
}
/**
* @brief Lisp function to open a file
* @details Prompts user for filename and opens the file for editing.
* @param args Lisp arguments (unused)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Updates global editor state E
* @see editorOpen()
* @see editorPrompt()
*/
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename) {
// editorOpen(filename);
EditorPane *active = splitScreenGetActivePane();
active->buffer_id = bufferCreate(filename, READ_AND_WRITE);
}
free(filename);
return lisp_null();
}
/**
* @brief Lisp function to insert a character
* @details Extracts a character from Lisp string argument and inserts it at
* cursor.
* @param args Lisp list with one string argument
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Uses first character of the string argument
*/
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
const char *src = lisp_string(lisp_car(args));
bufferInsertBytes(src, strlen(src));
return lisp_null();
}
/**
* @brief Lisp function to load and execute a package
* @details Loads a Lisp package from the user's packages directory
* (~/.beluga/packages/<package_name>/init.lisp) and evaluates it.
* @param args Lisp list with one argument: package name (string)
* @param e Error pointer for Lisp error handling
* @param ctx Lisp context
* @return lisp_null()
* @note Package files must be valid Lisp code
* @note Package directory defaults to ~/.beluga/packages/
*/
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
const char *package_name = lisp_string(lisp_car(args));
appDebug("%s\n", package_name);
char *package_dir = (char *)calloc(256, sizeof(char));
FILE *fd_package = NULL;
strcat(package_dir, getenv("HOME"));
strcat(package_dir, "/.beluga/packages/");
strcat(package_dir, package_name);
strcat(package_dir, "/init.lisp");
appDebug("%s\n", package_dir);
fd_package = fopen(package_dir, "r");
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx);
fclose(fd_package);
free(package_dir);
return lisp_null();
}
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) {
appDebug("switch buffer\n");
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();
}
+119 -33
View File
@@ -1,44 +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"
void editorInsertChar(struct editorConfig *E, int c) {
if (E->cursor_y == E->numrows) { extern struct editorConfig E;
editorInsertRow(E, E->numrows, "", 0);
}
editorRowInsertChar(E, &E->row[E->cursor_y], E->cursor_x, c); /**
E->cursor_x++; * @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);
} }
void editorInsertNewLine(struct editorConfig *E) { /**
erow *row; * @brief Moves the cursor based on arrow key input
if (!E->cursor_x) { * @details Updates cursor position (E.cursor_x, E.cursor_y) based on the given
editorInsertRow(E, E->cursor_y, "", 0); * key direction. Handles line wrapping and boundary conditions. Prevents cursor
} else { * from exceeding line lengths.
row = &E->row[E->cursor_y]; * @param key The arrow key code (ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT)
editorInsertRow(E, E->cursor_y + 1, &row->chars[E->cursor_x], * @return 1 if cursor movement was valid, 0 if cursor was constrained to line boundary
row->size - E->cursor_x); * @note Updates global editor state E
row = &E->row[E->cursor_y]; */
row->size = E->cursor_x; int editorMoveCursor(int key) {
row->chars[row->size] = '\0'; EditorPane *active = splitScreenGetActivePane();
editorUpdateRow(row); 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;
} }
++E->cursor_y; break;
E->cursor_x = 0; 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;
}
return 1;
} }
void editorDelChar(struct editorConfig *E) { char *editorGetClipboard(void) {
erow *row; FILE *pipe = popen(CLIPBOARD_PASTE_CMD, "r");
if (E->cursor_y == E->numrows || !(E->cursor_x || E->cursor_y)) { if (!pipe) return NULL;
return;
size_t cap = 4096;
size_t len = 0;
char *buf = malloc(cap);
int c;
while ((c = fgetc(pipe)) != EOF) {
if (len + 1 >= cap) {
cap *= 2;
buf = realloc(buf, cap);
} }
row = &E->row[E->cursor_y]; buf[len++] = (char)c;
if (E->cursor_x > 0) {
editorRowDelchar(E, row, E->cursor_x - 1);
--E->cursor_x;
} else {
E->cursor_x = E->row[E->cursor_y - 1].size;
editorRowAppendString(E, &E->row[E->cursor_y - 1], row->chars, row->size);
editorDelRow(E, E->cursor_y);
--E->cursor_y;
} }
buf[len] = '\0';
pclose(pipe);
return buf; // caller must free
}
void editorSetClipboard(const char *text, int len) {
FILE *pipe = popen(CLIPBOARD_COPY_CMD, "w");
if (!pipe) return;
fwrite(text, 1, len, pipe);
pclose(pipe);
}
int editorRowCxToByte(const row_t *row, int cursor_x) {
int i = 0, col = 0;
while (col < cursor_x && i < row->size) {
int sl = utf8Seqlen((unsigned char)row->chars[i]);
if (sl < 1)
sl = 1;
col++;
i += sl;
}
return i;
}
int editorRowCharCount(row_t *row, int x) {
int n = 0, i = 0;
while (i < x && i < row->size) {
int sl = utf8Seqlen((unsigned char)row->chars[i]);
if (sl < 1)
sl = 1;
n++;
i += sl;
}
return n;
} }
+86 -42
View File
@@ -1,86 +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>
char *editorRowsToString(struct editorConfig *E, int *buffer_len) { extern struct editorConfig E;
int tot_len = 0;
int j;
char *buf;
char *p;
for (j = 0; j < E->numrows; ++j) { /**
tot_len += E->row[j].size + 1; * @brief Closes the current file and resets editor state
* @details Clears all rows, resets cursor position, scroll offsets, and file
* metadata. Does not prompt to save unsaved changes.
* @note Updates global editor state E
*/
void editorCloseFile(void) {
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buf = bufferFindById(active->buffer_id);
active->cursor_x = 0;
active->cursor_y = 0;
active->x_offset = 0;
active->y_offset = 0;
for (int i = 0; i < buf->numrows; ++i) {
free(buf->row[i].chars);
} }
*buffer_len = tot_len; buf->numrows = 0;
buf = malloc(tot_len); free(buf->row);
p = buf; buf->row = NULL;
for (j = 0; j < E->numrows; ++j) { buf->dirty = 0;
memcpy(p, E->row[j].chars, E->row[j].size); free(buf->filename);
p += E->row[j].size; buf->filename = NULL;
*p = '\n'; E.status_msg[0] = '\0';
p++; E.status_msg_time = 0;
}
return buf;
} }
void editorOpen(struct editorConfig *E, char *filename) { /**
* @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; FILE *fp;
free(E->filename); fp = fopen(buffer->filename, "a+");
E->filename = strdup(filename);
fp = fopen(filename, "r");
if (!fp) if (!fp)
die("fopen"); die("fopen");
char *line = NULL; char *line = NULL;
size_t line_cap = 0; size_t line_cap;
ssize_t line_len; ssize_t line_len;
rewind(fp);
while ((line_len = getline(&line, &line_cap, fp)) != -1) { while ((line_len = getline(&line, &line_cap, fp)) != -1) {
while (line_len > 0 && while (line_len > 0 &&
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) { (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
--line_len; --line_len;
} }
editorInsertRow(E, E->numrows, line, line_len); appDebug("line %s\n", line);
bufferInsertRow(buffer, buffer->numrows, line, line_len);
free(line);
line = NULL;
} }
free(line); free(line);
fclose(fp); fclose(fp);
E->dirty = 0; E.dirty = 0;
} }
void editorSave(struct editorConfig *E) { /**
* @brief Saves the current file to disk
* @details Prompts for filename if not set, converts all rows to a buffer,
* writes to disk using open/ftruncate/write, and updates dirty flag.
* Displays status messages on success or failure.
* @note Updates global editor state E (dirty flag)
* @note If no filename is set, prompts user via editorPrompt()
* @note Uses O_RDWR | O_CREAT with mode 0644
* @see editorRowsToString()
*/
void editorSave() {
EditorPane *active = splitScreenGetActivePane();
struct buffer_t *buffer = bufferFindById(active->buffer_id);
int len; int len;
char *buf;
int fd; int fd;
if (E->filename == NULL) { if (buffer->filename == NULL) {
E->filename = editorPrompt(E, "Save as: %s (ESC to cancel)"); buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (E->filename == NULL) { if (buffer->filename == NULL) {
editorSetStatusMessage(E, "Save aborted"); editorSetStatusMessage("Save aborted");
return; return;
} }
} }
buf = editorRowsToString(E, &len); fd = open(buffer->filename, O_RDWR | O_CREAT, 0644);
fd = open(E->filename, O_RDWR | O_CREAT, 0644);
if (fd != -1) { if (fd != -1) {
if (ftruncate(fd, len) != -1) { for (int i = 0; i < buffer->numrows; ++i)
if (write(fd, buf, len) == len) { {
len = strlen(buffer->row[i].chars);
if (write(fd, buffer->row[i].chars, len) != len) {
close(fd); close(fd);
free(buf); editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
E->dirty = 0;
editorSetStatusMessage(E, "%d bytes written to disk", len);
return; return;
} }
write(fd, "\n", 1);
} }
buffer->dirty = 0;
close(fd); close(fd);
} }
free(buf); editorSetStatusMessage("File saved");
editorSetStatusMessage(E, "Can't save! I/O error: %s", strerror(errno));
} }
+146 -14
View File
@@ -1,19 +1,151 @@
#include "../include/init.h" #include "../include/init.h"
#include "../include/builtins.h"
#include "../include/color.h"
#include "../include/data.h"
#include "../include/terminal.h"
#include "../include/split_screen.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void initEditor(struct editorConfig *E) { #define LISP_IMPLEMENTATION
E->cursor_x = 0; #include "../include/lisp.h"
E->cursor_y = 0; #include "../include/lisp_lib.h"
E->rx = 0;
E->row_offset = 0; struct editorConfig;
E->col_offset = 0;
E->numrows = 0; void registerBuiltin(char *key_sequence, LispCFunc f) {
E->row = NULL; lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
E->dirty = 0; lisp_make_func(f), E.ctx);
E->filename = NULL;
E->status_msg[0] = '\0'; }
E->status_msg_time = 0;
if (getWindowSize(&E->screenrows, &E->screencols) == -1) { void initBuiltins() {
// Registering C functions as lisp macro
registerBuiltin("move-cursor", moveCursor);
registerBuiltin("map-key", mapKey);
registerBuiltin("editor-quit", editorQuit);
registerBuiltin("editor-save", l_editorSave);
registerBuiltin("editor-insert-new-line", l_editorInsertNewLine);
registerBuiltin("move-cursor-beg-line", moveCursorBeginLine);
registerBuiltin("move-cursor-end-line", moveCursorEndLine);
registerBuiltin("editor-delete-previous-char", deletePreviousChar);
registerBuiltin("move-cursor-page-up", editorMoveCursorPageUp);
registerBuiltin("move-cursor-page-down", editorMoveCursorPageDown);
registerBuiltin("editor-open-file", editorOpenFile);
registerBuiltin("editor-insert-char", editorPrintC);
registerBuiltin("add-package", addPackage);
registerBuiltin("buffer-find", bufferFind_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 initConfig() {
E.ctx = lisp_init();
E.ctx.p->err_port = fopen("log.err", "w");
E.env = lisp_env(E.ctx);
lisp_lib_load(E.ctx);
// Init builtins lisp functions
initBuiltins();
// Read config file
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
if (E.ctx_error != LISP_ERROR_NONE) {
die("init failed");
}
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
}
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"); die("getWindowSize");
} }
E->screenrows -= 2; 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
E.constantes.TAB_LENGTH =
(int)lisp_eval(lisp_read("TAB-LENGTH", &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx)
.val.int_val;
E.constantes.QUIT_TIMES =
(int)lisp_eval(lisp_read("QUIT-TIMES", &E.ctx_error, E.ctx), &E.ctx_error,
E.ctx)
.val.int_val;
E.quit_times_buffer = E.constantes.QUIT_TIMES;
} }
+176 -121
View File
@@ -1,43 +1,164 @@
#include "../include/input.h" #include "../include/input.h"
#include "../include/define.h"
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/file_io.h"
#include "../include/output.h" #include "../include/output.h"
#include "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 <sys/stat.h>
#include <unistd.h> #include <unistd.h>
/** #include "include/terminal.h"
* \fn char * editorPrompt(struct editorConfig *E, char *prompt) #include "include/utf8.h"
* \brief Return user input in a prompt when enter is hit. */
char *editorPrompt(struct editorConfig *E, char *prompt) { extern struct editorConfig E;
size_t buf_size = 128;
/**
* @file input.c
* @brief Input handling module for the Beluga text editor
* @details Manages user input processing, key bindings, cursor movement, and file path completion
*/
/**
* @brief Returns the first file completion match for the given path
* @details Searches the directory containing the given path prefix and returns
* the first file or directory entry that matches the filename prefix.
* Appends a trailing slash for directory entries.
* @param path The file path to complete (can be relative or absolute)
* @return Pointer to the completed file path (dynamically allocated), or NULL if:
* - path ends with '/' (already a directory)
* - no matching entries found
* - directory cannot be opened
* @note Caller is responsible for freeing the returned string
* @note Uses static buffer internally; may return stale pointers across calls
*/
const char *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(E, prompt, buf); editorSetStatusMessage(prompt, buf);
editorRefreshScreen(E); editorRefreshScreen();
c = editorReadKey(); c = editorReadKey();
if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
if (buf_len != 0) { if (buf_len != 0) {
buf[--buf_len] = '\0'; buf[--buf_len] = '\0';
} }
} else if (c == ESCAPE) { } else if (c == ESCAPE) {
fprintf(stderr, "escape"); editorSetStatusMessage("");
editorSetStatusMessage(E, "");
free(buf); free(buf);
return NULL; return NULL;
} else if (c == '\r') { } else if (c == '\r') {
if (buf_len != 0) { if (buf_len != 0) {
editorSetStatusMessage(E, ""); editorSetStatusMessage("");
return buf; return buf;
} }
} else if (!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);
@@ -48,121 +169,55 @@ char *editorPrompt(struct editorConfig *E, char *prompt) {
} }
} }
void editorMoveCursor(struct editorConfig *E, int key) {
erow *row = (E->cursor_y >= E->numrows) ? NULL : &E->row[E->cursor_y];
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) { * @brief Executes the command bound to a key sequence
E->cursor_x = row_len; * @details Searches the keybinding table for a matching key sequence and
* prefix state, then evaluates the associated Lisp command.
* @param key_sequence The string representation of the key sequence
* @return 1 if a matching keybinding was found and executed, 0 otherwise
* @note Updates global editor state E (prefix_state)
* @note Uses Lisp interpreter to evaluate bound commands
*/
int executeKeyBind(char *key_sequence) {
int i;
int previous_state = 0;
appDebug("pressed %s\n", key_sequence);
for (i = 0; i < E.number_of_keybinds; ++i) {
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
if (E.prefix_state != E.key_binds[i].prefix_id) {
continue;
} }
previous_state = E.prefix_state;
// It's a symbol, create a function call
lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx),
&E.ctx_error, E.ctx);
if (E.prefix_state == previous_state)
E.prefix_state = 0;
return 1;
}
}
return 0;
} }
void editorProcessKeypress(struct editorConfig *E) { /**
static int quit_times = QUIT_TIMES; * @brief Processes a single keypress from the user
* @details Reads a key, checks if it matches any registered keybinding,
* and either executes the bound command or inserts the character. Resets
* the quit buffer counter on successful key processing.
* @note Updates global editor state E
* @note Calls editorReadKey() to get input and editorInsertChar() for unbound keys
*/
void editorProcessKeypress() {
int c = editorReadKey(); int c = editorReadKey();
int times; char key_sequence[8];
switch (c) { if (executeKeyBind(keyToString(c))) {
case '\r':
editorInsertNewLine(E);
break;
case CTRL_KEY('q'):
if (E->dirty && quit_times > 0) {
editorSetStatusMessage(E,
"WARNING! Changes hasn't been saved. Press Ctrl-Q "
"another time to quit.");
--quit_times;
return; return;
} }
write(STDOUT_FILENO, "\x1b[2J", 4); int seq_len = utf8Encode(c, key_sequence);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); appDebug("key seq : %s\n", key_sequence);
disableRawMode(E); bufferInsertBytes(key_sequence, seq_len);
exit(0); E.quit_times_buffer = E.constantes.QUIT_TIMES;
break;
case CTRL_KEY('s'):
editorSave(E);
break;
case BEG_LINE:
E->cursor_x = 0;
break;
case END_LINE:
if (E->cursor_y < E->numrows) {
E->cursor_x = E->row[E->cursor_y].size;
}
break;
case BACKSPACE:
case CTRL_KEY('h'):
case DEL_KEY:
if (c == DEL_KEY) {
editorMoveCursor(E, ARROW_RIGHT);
}
editorDelChar(E);
break;
case PAGE_UP:
case PAGE_DOWN: {
if (c == PAGE_UP) {
E->cursor_y = E->row_offset;
} else if (c == PAGE_DOWN) {
E->cursor_y = E->row_offset + E->screenrows - 1;
if (E->cursor_y > E->numrows) {
E->cursor_y = E->numrows;
}
}
times = E->screenrows;
while (--times) {
editorMoveCursor(E, c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
}
} break;
case ARROW_UP:
case ARROW_DOWN:
case ARROW_LEFT:
case ARROW_RIGHT:
editorMoveCursor(E, c);
break;
case CTRL_KEY('l'):
case '\x1b':
break;
default:
editorInsertChar(E, c);
break;
}
quit_times = QUIT_TIMES;
} }
+322 -82
View File
@@ -1,135 +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>
void editorDrawRows(struct editorConfig *E, struct abuf *ab) { extern struct editorConfig E;
int y;
char welcome[80]; /**
int welcome_len; * @brief Renders a single pane with its buffer content
int padding; */
int len; static void editorDrawPane(struct abuf* ab, EditorPane* pane)
{
int file_row; int file_row;
for (y = 0; y < E->screenrows; ++y) { char pos_buf[32];
file_row = y + E->row_offset; int pos_len;
if (file_row >= E->numrows) { int byte_len_to_print;
if (E->numrows == 0 && y == E->screenrows / 3) { int bytes_to_print;
welcome_len = char* highlighted;
snprintf(welcome, sizeof(welcome),
"Beluga text editor -- version %s", BELUGA_VERSION); if (pane == NULL || pane->buffer_id < 0)
if (welcome_len > E->screencols) { return;
welcome_len = E->screencols;
} const struct buffer_t* buf = bufferFindById(pane->buffer_id);
padding = (E->screencols - welcome_len) / 2; if (buf == NULL)
if (padding) { 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); abAppend(ab, "~", 1);
--padding; for (int i = 0;
} i < pane->width - 1;
while (padding--) { ++i)
{
abAppend(ab, " ", 1); abAppend(ab, " ", 1);
} }
abAppend(ab, welcome, welcome_len);
} else {
abAppend(ab, "~", 1);
} }
} else { else
len = E->row[file_row].rsize - E->col_offset; {
if (len < 0)
len = 0;
if (len > E->screencols) if (buf->filename[strlen(buf->filename) - 1] == 'c' || buf->filename[strlen(buf->filename) - 1] == 'h')
len = E->screencols; {
abAppend(ab, &E->row[file_row].render[E->col_offset], len); // 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);
}
} }
abAppend(ab, ERASE_END_LINE, 3);
abAppend(ab, "\r\n", 2);
} }
} }
void editorScroll(struct editorConfig *E) { /**
E->rx = E->cursor_x; * @brief Renders all panes based on current split configuration
if (E->cursor_y < E->numrows) { */
E->rx = editorRowCxToRx(&E->row[E->cursor_y], E->cursor_x); static void editorDrawAllPanes(struct abuf* ab)
} {
const ScreenLayout* layout = splitScreenGetLayout();
if (E->cursor_y < E->row_offset) { if (layout->num_panes == 1)
E->row_offset = E->cursor_y; {
// 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
} }
if (E->cursor_y >= E->row_offset + E->screenrows) {
E->row_offset = E->cursor_y - E->screenrows + 1;
} }
if (E->rx < E->col_offset) {
E->col_offset = E->rx;
} }
if (E->rx >= E->col_offset + E->screencols) {
E->col_offset = E->rx - E->screencols + 1;
} }
} }
void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab) { /**
* @brief Updates scroll offsets to keep cursor visible on screen
* @details Adjusts E.row_offset and E.col_offset to ensure the cursor remains
* within the visible viewport. Also updates E.rx (rendered x-coordinate).
* @note Updates global editor state E
* @see editorRowCxToRx()
*/
void editorScroll()
{
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; int len, render_len;
char status[80], render_status[80]; 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 abAppend(ab, "\x1b[7m", 4); // inverting colors
len = snprintf(status, sizeof(status), "%.20s - %d lines%s",
E->filename ? E->filename : "[No Name]", E->numrows, const char* mode_str = "";
E->dirty ? "*" : ""); ScreenLayout* layout = splitScreenGetLayout();
render_len = snprintf(render_status, sizeof(render_status), "%d/%d", if (layout->mode == SPLIT_VERTICAL)
E->cursor_y + 1, E->numrows); mode_str = " [V-SPLIT]";
if (len > E->screencols) { else if (layout->mode == SPLIT_HORIZONTAL)
len = E->screencols; 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;
}
abAppend(ab, status, len); abAppend(ab, status, len);
while (len < E->screencols) { while (len < E.screencols)
if (E->screencols - len == render_len) { {
if (E.screencols - len == render_len + 1)
{
abAppend(ab, render_status, render_len); abAppend(ab, render_status, render_len);
break; break;
} else { }
else
{
abAppend(ab, " ", 1); abAppend(ab, " ", 1);
++len; ++len;
} }
} }
abAppend(ab, "\x1b[m", 3); // normal text mode abAppend(ab, "\x1b[m", 3); // normal text mode
abAppend(ab, "\r\n", 2); abAppend(ab, "\r\n", 2);
} }
void editorDrawMessageBar(struct editorConfig *E, struct abuf *ab) { /**
int msg_len = strlen(E->status_msg); * @brief Renders the message bar below the status bar
* @details Displays temporary status messages for a limited time (5 seconds).
* Only displays message if within time window and within screen width.
* @param ab Pointer to append buffer structure for accumulating output
* @note Messages are set by editorSetStatusMessage()
*/
void editorDrawMessageBar(struct abuf* ab)
{
int msg_len = (int) strlen(E.status_msg);
abAppend(ab, ERASE_END_LINE, 3); abAppend(ab, ERASE_END_LINE, 3);
if (msg_len > E->screencols) { if (msg_len > E.screencols)
msg_len = E->screencols; {
msg_len = E.screencols;
} }
if (msg_len && time(NULL) - E->status_msg_time < 5) { if (msg_len && time(NULL) - E.status_msg_time < 5)
abAppend(ab, E->status_msg, msg_len); {
abAppend(ab, E.status_msg, msg_len);
} }
} }
void editorRefreshScreen(struct editorConfig *E) {
editorScroll(E); /**
* @brief Performs complete screen refresh and buffer synchronization
* @details Clears screen, redraws all visible content (rows, status bar,
* message bar), positions cursor, and writes accumulated buffer to stdout. This
* is the main rendering function called each frame.
* @note Updates global editor state E (via editorScroll())
* @see editorDrawRows()
* @see editorDrawStatusBar()
* @see editorDrawMessageBar()
*/
void editorRefreshScreen()
{
struct abuf ab = ABUF_INIT; struct abuf ab = ABUF_INIT;
char buf[32]; char buf[32];
abAppend(&ab, HIDE_CURSOR, 6); abAppend(&ab, HIDE_CURSOR, 6);
abAppend(&ab, CURSOR_TOP_LEFT, 3); abAppend(&ab, CURSOR_TOP_LEFT, 3);
abAppend(&ab, E.theme.BACKGROUND_COLOR,
(int) strlen(E.theme.BACKGROUND_COLOR)); // RGB background is 12 bytes
editorDrawRows(E, &ab); // Draw all panes
editorDrawStatusBar(E, &ab); editorScroll();
editorDrawMessageBar(E, &ab); editorDrawAllPanes(&ab);
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E->cursor_y - E->row_offset) + 1, // Draw status bar and message bar
(E->rx - E->col_offset) + 1); editorDrawStatusBar(&ab);
abAppend(&ab, buf, strlen(buf)); editorDrawMessageBar(&ab);
// editorDrawContextBuffer(&ab);
// Position cursor in active pane
EditorPane* active = splitScreenGetActivePane();
if (active != NULL)
{
snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
active->cursor_y + active->origin_y + 1,
active->cursor_x + active->origin_x + 1);
abAppend(&ab, buf, (int) strlen(buf));
}
abAppend(&ab, SHOW_CURSOR, 6); abAppend(&ab, SHOW_CURSOR, 6);
write(STDOUT_FILENO, ab.b, ab.len); write(STDOUT_FILENO, ab.b, ab.len);
abFree(&ab); abFree(&ab);
} }
void editorSetStatusMessage(struct editorConfig *E, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(E->status_msg, sizeof(E->status_msg), fmt, ap);
va_end(ap);
E->status_msg_time = time(NULL);
}
+4 -123
View File
@@ -1,131 +1,12 @@
#include "../include/row_op.h" #include "../include/row_op.h"
#include "../include/data.h"
#include "../include/utf8.h"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
int editorRowCxToRx(erow *row, int cursor_x) { extern struct editorConfig E;
int render_x = 0;
int i;
for (i = 0; i < cursor_x; ++i) {
if (row->chars[i] == '\t') {
render_x += (TAB_LENGTH - 1) - (render_x % TAB_LENGTH);
}
render_x++;
}
return render_x;
}
/**
* \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 * (TAB_LENGTH - 1) +
1); /**< Tabs needs TAB_LENGTH chars so 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 % 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(struct editorConfig *E, int at, char *s, size_t len) {
if (at < 0 || at > E->numrows) {
return;
}
E->row = realloc(E->row, sizeof(erow) * (E->numrows + 1));
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(struct editorConfig *E, 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(struct editorConfig *E, erow *row, int at, int c) {
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(struct editorConfig *E, 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(struct editorConfig *E, 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;
}
+218
View File
@@ -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];
}
+248
View File
@@ -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;
}
+120 -27
View File
@@ -1,24 +1,37 @@
#include "../include/terminal.h" #include "../include/terminal.h"
#include <ctype.h>
#include "../include/data.h"
#include "../include/define.h"
#include <stdarg.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);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
lisp_shutdown(E.ctx);
perror(s); perror(s);
exit(1); exit(1);
} }
void disableRawMode(struct editorConfig *E) { void disableRawMode() {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E->orig_termios) == -1) { if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) {
die("tcsetattr"); die("tcsetattr");
} }
} }
void enableRawMode(struct editorConfig *E) { void enableRawMode() {
if (tcgetattr(STDIN_FILENO, &E->orig_termios) == -1) { if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) {
die("tcgetattr"); die("tcgetattr");
} }
struct termios raw = E->orig_termios; struct termios raw = E.orig_termios;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST); raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8); raw.c_cflag |= (CS8);
@@ -31,26 +44,88 @@ void enableRawMode(struct editorConfig *E) {
} }
} }
#include <ctype.h> /* isprint */
char *keyToString(int key) {
static char key_str[32];
if (key == '\r') {
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() { int editorReadKey() {
int nread;
char c; char c;
char seq[3]; /* read first byte — may be start of UTF-8 or escape */
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { while (read(STDIN_FILENO, &c, 1) != 1)
if (nread == -1 && errno != EAGAIN) { ;
die("read"); 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':
@@ -70,7 +145,6 @@ int editorReadKey() {
} }
} }
} else { } else {
switch (seq[1]) { switch (seq[1]) {
case 'A': case 'A':
return ARROW_UP; return ARROW_UP;
@@ -86,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) {
@@ -143,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
View File
@@ -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;
}