45 Commits

Author SHA1 Message Date
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
31 changed files with 6973 additions and 476 deletions
+35
View File
@@ -0,0 +1,35 @@
name: Build project
on:
push:
branches: ["main"]
jobs:
build:
runs-on: ubuntu-latest
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/*
bin/*
build/*
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
+17 -10
View File
@@ -1,26 +1,33 @@
# 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.
It's abviously only working for **Linux**.
## 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
### From source
Here is the installation line for development version:
Here is the installation line :
```git clone https://homelinuxserver.ddns.net/git/arthur/beluga.git ~/.beluga && 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
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 :
- Ctrl-Q : leave the editor
- Ctrl-S : Save a file
Here is some few command that you will need :
| keybind| command |
|--------|------------------|
| Ctrl-Q | leave the editor |
| Ctrl-S | Save a file |
| Ctrl-O | open file |
+43
View File
@@ -0,0 +1,43 @@
**#%#*****###%**
*##+--------------------=##*
#*=----------------------------=*#*
#*------------------------------------*#
%+----------------------------------------=#*
#+---------------------------------------------##
*#-------------------------------------------------=##
*#----------------------------------------------------:-##
#----------------------------------------------------------##
#=--------------------------------------------------------------##
+--------------------------+@#-%*-----------------------------------#*
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 1.1
*-=-------------------------#@@*---------------------------------------=%
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
*%%=-=--------------------------------------------------------------------=# CTRL-q leave
%=--------------------------------------------------------------------------#* CTRL-s save
%-----------------------------------------------------------------------------** CTRL-o open-file
*+--=---===----=---------------=*-----------------------------------------------**
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
*%**=-----------==== ==---------------------------------=+#+-----------------=#
*%=----------------------------------------------------------=#*---------------#
##=----------------------------------=------------------------+%=------------+#
#%+---------------------------------=*------------------------+%------------#
*#%*=-------------=-----------------#-------------------------#+----------#
**#%#*******+=======-------------#=------------------------#----------#
#===#*=======------------------#*----=-----------=--=##*-----------#
-====##=------------------------*%+------------=*#+=====----------#
--=====+#*=----------------------=-=+*#####***+=======-----------=*
%------=====*%*=-------------------------========-----------------+*
*-=--------====%%###+=--------------------------=-----------------#
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
%=-------#* #%*=-----------------+#
*%+--=## ##=-----------------=#*
** #+----=-------------------#*
%+----------------------------#*
*%-------------==----------------+#
##--------------==------------------#
*#--------------===%-----------------=%
##---------------=-##*-----------------+#
*#---------------==#+=#%-----------------%
*%---------------+# %*---------------#*
*#------------=+#* #%*=-----------#*
#****##****** *#%%##+=----%
+34
View File
@@ -0,0 +1,34 @@
;; MACROS
(define TAB-LENGTH 4)
(define QUIT-TIMES 1)
;; FUNCTIONS
(define editor-delete-next-char (lambda () (
(move-cursor "right")
(editor-delete-previous-char)
)
)
)
;; KEY MAPPING
(map-key "CTRL-q" editor-quit)
(map-key "CTRL-d" editor-save)
(map-key "ARROW-UP" '(move-cursor "up"))
(map-key "ARROW-DOWN" '(move-cursor "down"))
(map-key "ARROW-RIGHT" '(move-cursor "right"))
(map-key "ARROW-LEFT" '(move-cursor "left"))
(map-key "ENTER" editor-insert-new-line)
(map-key "CTRL-a" move-cursor-beg-line)
(map-key "CTRL-e" move-cursor-end-line)
(map-key "BACKSPACE" editor-delete-previous-char)
(map-key "DEL" editor-delete-next-char)
(map-key "PAGE-UP" move-cursor-page-up)
(map-key "PAGE-DOWN" move-cursor-page-down)
(map-key "CTRL-o" editor-open-file)
(map-key "CTRL-k" editor-del-row)
(map-key "CTRL-s" editor-find)
+1 -1
View File
@@ -5,7 +5,7 @@
#include <stdlib.h>
#include <string.h>
void abAppend(struct abuf *ab, const char *s, int len);
void abAppend(struct abuf *ab, const unsigned char *s, int len);
void abFree(struct abuf *ab);
+40
View File
@@ -0,0 +1,40 @@
#ifndef BUILTINS_H_
#define BUILTINS_H_
#include "lisp.h"
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx);
Lisp mapKey(Lisp args, LispError *e, LispContext ctx);
void registerBuiltin(char * key_sequence, LispCFunc f);
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx);
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx);
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx);
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx);
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx);
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx);
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx);
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx);
Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx);
#endif
+81 -3
View File
@@ -1,9 +1,18 @@
#ifndef DATA_H_
#define DATA_H_
#include <stdio.h>
#include <termios.h>
#include <time.h>
#include "lisp.h"
typedef struct{
unsigned char c[4];
char len;
} utf_8_char_t;
/**
* \struct erow
* \brief Store one editor row
@@ -13,10 +22,62 @@
typedef struct erow {
int size; /**< Size of the line */
int rsize; /**< Size of the render line */
char *chars; /**< Characters of the line */
char *render; /**< The actual line we will print */
utf_8_char_t *chars; /**< Characters of the line */
utf_8_char_t *render; /**< The actual line we will print */
} erow;
enum editorStatus_e {
IDLE,
READ_ONLY,
READ_AND_WRITE,
};
struct const_t {
int TAB_LENGTH;
int QUIT_TIMES;
};
// Key types
typedef enum {
KEY_CHAR, // Regular character or UTF-8
KEY_CTRL, // Ctrl+letter
KEY_ALT, // Alt+letter
KEY_ARROW, // Arrow keys
KEY_FUNCTION, // Function keys
KEY_SPECIAL, // Tab, Enter, ESC, Backspace, etc.
KEY_NAVIGATION, // Home, End, PgUp, PgDn, Insert, Delete
KEY_UNKNOWN
} KeyType;
// Modifiers
typedef enum {
MOD_NONE = 0,
MOD_SHIFT = 1,
MOD_ALT = 2,
MOD_CTRL = 4
} KeyModifier;
// Key information structure
typedef struct {
KeyType type;
int modifiers; // Bitmask of KeyModifier
union {
unsigned int codepoint; // For KEY_CHAR
char ctrl_char; // For KEY_CTRL (A-Z)
char alt_char; // For KEY_ALT
char arrow; // For KEY_ARROW (U/D/L/R)
int function_num; // For KEY_FUNCTION (1-12)
char special; // For KEY_SPECIAL and KEY_NAVIGATION
} data;
utf_8_char_t c; // Raw bytes
} KeyInfo;
struct keyBind_t {
KeyInfo *key_sequence;
Lisp command;
};
/**
* \struct editorConfig
* \brief Containing our editor state.
@@ -32,9 +93,23 @@ struct editorConfig {
erow *row; /**< Store all the rows printed */
int dirty;
char *filename;
enum editorStatus_e state;
char status_msg[80];
time_t status_msg_time;
struct termios orig_termios; /**< Terminal communication interface */
struct const_t constantes;
int quit_times_buffer;
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;
};
/**
@@ -43,8 +118,11 @@ struct editorConfig {
* */
struct abuf {
char *b; /**< Text that will be printed */
unsigned char *b; /**< Text that will be printed */
int len; /**< Length of the text */
};
extern struct editorConfig E;
#endif
+4 -15
View File
@@ -8,24 +8,13 @@
#define HIDE_CURSOR "\x1b[?25l"
#define SHOW_CURSOR "\x1b[?25h"
#define ERASE_END_LINE "\x1b[K"
#define TAB "\x09"
#define SPACE "\x20"
enum editorKey {
BACKSPACE = 127,
ARROW_LEFT = 1000,
ARROW_RIGHT,
ARROW_UP,
ARROW_DOWN,
DEL_KEY,
BEG_LINE,
END_LINE,
PAGE_UP,
PAGE_DOWN,
};
#define ABUF_INIT {NULL, 0}
#define BELUGA_VERSION "1.0"
#define TAB_LENGTH 4
#define QUIT_TIMES 1
#define BELUGA_VERSION "1.1"
#endif // DEFINE_H_
+3 -3
View File
@@ -2,10 +2,10 @@
#define EDITOR_OP_H_
#include "data.h"
void editorInsertChar(struct editorConfig *E, int c);
void editorInsertChar(utf_8_char_t *c);
void editorInsertNewLine(struct editorConfig *E);
void editorInsertNewLine(void);
void editorDelChar(struct editorConfig *E);
void editorDelChar();
#endif // EDITOR_OP_H_
+8 -3
View File
@@ -8,10 +8,15 @@
#include <stdlib.h>
#include <sys/types.h>
char *editorRowsToString(struct editorConfig *E, int *buffer_len);
char *editorRowsToString(int *buffer_len);
void editorOpen(struct editorConfig *E, char *filename);
void editorSave(struct editorConfig *E);
void editorCloseFile(void);
void editorOpen(char *filename);
void editorSave();
void editorFind();
#endif // FILE_IO_H_
+4 -1
View File
@@ -1,6 +1,7 @@
#ifndef INIT_H_
#define INIT_H_
#include "builtins.h"
#include "data.h"
#include "terminal.h"
#include <stdio.h>
@@ -10,6 +11,8 @@
* \brief Job's function is to initialize all the fields of editorConfig.
* */
void initEditor(struct editorConfig *E);
void initBuiltins();
void initEditor();
#endif // INIT_H_
+8 -3
View File
@@ -5,6 +5,7 @@
#include "define.h"
#include "output.h"
#include "terminal.h"
#include "builtins.h"
#include <unistd.h>
// KEYS keycode
@@ -19,15 +20,19 @@
// END \x1b[4~ || <esc>[8~ || <esc>[F || <esc>OF
// DELETE \x1b[3~
char *editorPrompt(struct editorConfig *E, char *prompt);
char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
void editorMoveCursor(struct editorConfig *E, int key);
char *key_to_string(int key);
void editorMoveCursor(KeyInfo * key);
int executeKeyBind(KeyInfo *key_sequence);
/**
* \fn void editorProcessKeypress()
* \brief Get the last key input and do the proper action.
*/
void editorProcessKeypress(struct editorConfig *E);
void editorProcessKeypress();
#endif // INPUT_H_
+3215
View File
File diff suppressed because it is too large Load Diff
+2180
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -13,16 +13,16 @@
* \brief Draws left rows of the editor.
*/
void editorDrawRows(struct editorConfig *E, struct abuf *ab);
void editorDrawRows(struct abuf *ab);
void editorRefreshScreen(struct editorConfig *E);
void editorRefreshScreen();
void editorScroll(struct editorConfig *E);
void editorScroll();
void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab);
void editorDrawStatusBar(struct abuf *ab);
void editorDrawMessageBar(struct editorConfig *E, struct abuf *ab);
void editorDrawMessageBar(struct abuf *ab);
void editorSetStatusMessage(struct editorConfig *E, const char *fmt, ...);
void editorSetStatusMessage(const char *fmt, ...);
#endif // OUTPUT_H_
+7 -6
View File
@@ -10,19 +10,20 @@
int editorRowCxToRx(erow *row, int cursor_x);
int editorRowRxToCx(erow *row, int rx);
void editorUpdateRow(erow *row);
void editorInsertRow(struct editorConfig *E, int at, char *s, size_t len);
void editorInsertRow(int at, char *s, size_t len);
void editorFreeRow(erow *row);
void editorDelRow(struct editorConfig *E, int at);
void editorDelRow(int at);
void editorRowInsertChar(struct editorConfig *E, erow *row, int at, int c);
void editorRowInsertChar(erow *row, int at, utf_8_char_t c);
void editorRowAppendString(struct editorConfig *E, erow *row, char *s,
size_t len);
void editorRowAppendString(erow *row, char *s, size_t len);
void editorRowDelchar(struct editorConfig *E, erow *row, int at);
void editorRowDelchar(erow *row, int at);
#endif // ROW_OP_H_
+5 -3
View File
@@ -21,14 +21,16 @@
void die(const char *s);
void disableRawMode(struct editorConfig *E);
void disableRawMode();
void enableRawMode(struct editorConfig *E);
void enableRawMode();
int editorReadKey();
KeyInfo * editorReadKey();
int getCursorPosition(int *rows, int *cols);
KeyInfo *stringToCodepoint(const char *string);
int getWindowSize(int *rows, int *cols);
#endif
Executable
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
echo "--- Welcome to Beluga installer ---"
read -p "Do you want to start the installation ? (Y/n)" confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
# Check dependencies
if ! command -v "meson" &>/dev/null; then
echo "❌ Error: meson not found. Please install it first."
exit 1
fi
# Create config files
echo "Create config files ..."
mkdir -pv ~/.beluga/
cp -rv ./assets/ ~/.beluga/
mkdir -pv ~/.beluga/config/
mkdir -pv ~/.beluga/packages/
read -p "Do you want to replace your config file or keep it (init.lisp.bak) / (init.lisp.new) ? (Y/n)" confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
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"
+32 -9
View File
@@ -5,6 +5,11 @@
* interactions. \version 0.1 \date 21 septembre 2024
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _GNU_SOURCE
@@ -16,21 +21,39 @@
#include "include/output.h"
#include "include/terminal.h"
int main(int argc, char *argv[]) {
#include <locale.h>
#include <wchar.h>
struct editorConfig E;
enableRawMode(&E);
initEditor(&E);
if (argc >= 2) {
editorOpen(&E, argv[1]);
}
int main(int argc, char *argv[]) {
char * splash_screen = (char *) calloc(256, sizeof(char));
// Set support for utf-8
setlocale(LC_ALL, "");
// INIT
enableRawMode();
initEditor();
if (argc >= 2) {
E.state = READ_AND_WRITE;
editorOpen(argv[1]);
} else {
strcat(splash_screen, getenv("HOME"));
strcat(splash_screen, "/.beluga/assets/beluga.txt");
fprintf(stderr, "%s\n", splash_screen);
editorOpen(splash_screen);
}
free(splash_screen);
editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit");
editorSetStatusMessage(&E, "HELP: Ctrl-S = save | Ctrl-Q = quit");
while (1) {
editorRefreshScreen(&E);
editorProcessKeypress(&E);
editorRefreshScreen();
editorProcessKeypress();
}
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
project('beluga', 'c',
version : '1.1',
default_options : [
'c_std=none',
]
)
cc = meson.get_compiler('c')
m = cc.find_library('m', required: false)
# Source files
src_files = files(
'main.c',
'src/append_buffer.c',
'src/editor_op.c',
'src/file_io.c',
'src/init.c',
'src/input.c',
'src/output.c',
'src/row_op.c',
'src/terminal.c',
'src/builtins.c',
)
# Executable
executable('beluga',
src_files,
dependencies: [m]
)
+4 -2
View File
@@ -1,7 +1,9 @@
#include "../include/append_buffer.h"
void abAppend(struct abuf *ab, const char *s, int len) {
char *new = realloc(ab->b, ab->len + len);
extern struct editorConfig E;
void abAppend(struct abuf *ab, const unsigned char *s, int len) {
unsigned char *new = realloc(ab->b, ab->len + len);
if (new == NULL) {
return;
+195
View File
@@ -0,0 +1,195 @@
#include "../include/builtins.h"
#include "../include/define.h"
#include "../include/input.h"
#include "../include/file_io.h"
#include "../include/editor_op.h"
#include "../include/row_op.h"
#include "../include/data.h"
#include "../include/terminal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
utf_8_char_t make_utf8_char(const char *bytes, int len) {
utf_8_char_t ch;
ch.len = len;
memcpy(ch.c, bytes, len);
return ch;
}
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
const char *key_string = lisp_string(lisp_car(args));
KeyInfo *key = stringToCodepoint(key_string);
args = lisp_cdr(args);
// second argument
Lisp func = lisp_car(args);
E.key_binds =
(struct keyBind_t *)realloc(E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
E.key_binds[E.number_of_keybinds - 1].key_sequence = (KeyInfo *) malloc(sizeof(KeyInfo));
memcpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key, sizeof(KeyInfo));
E.key_binds[E.number_of_keybinds - 1].command = func;
return lisp_null();
}
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
const char *direction = lisp_string(lisp_car(args));
KeyInfo key;
key.type = KEY_ARROW;
switch (direction[0]) {
case 'u':
key.data.arrow = 'A';
break;
case 'd':
key.data.arrow = 'B';
break;
case 'r':
key.data.arrow = 'C';
break;
case 'l':
key.data.arrow = 'D';
break;
}
editorMoveCursor(&key);
return lisp_null();
}
Lisp editorQuit(Lisp args, LispError* e, LispContext ctx) {
fprintf(stderr, "quit\n");
if (E.dirty && E.quit_times_buffer > 0) {
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
"another time to quit.");
--E.quit_times_buffer;
return lisp_null();
}
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode();
exit(0);
return lisp_null();
}
Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx) {
editorSave();
return lisp_null();
}
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx) {
// editorInsertNewLine();
return lisp_null();
}
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
E.cursor_x = 0;
return lisp_null();
}
Lisp moveCursorEndLine(Lisp args, LispError* e, LispContext ctx) {
if (E.cursor_y < E.numrows) {
E.cursor_x = E.row[E.cursor_y].size;
}
return lisp_null();
}
Lisp deletePreviousChar(Lisp args, LispError* e, LispContext ctx) {
editorDelChar();
return lisp_null();
}
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) {
E.cursor_y = E.row_offset;
int times = E.screenrows;
KeyInfo key;
key.type = KEY_ARROW;
key.data.arrow = 'D';
while (--times) {
editorMoveCursor(&key);
}
return lisp_null();
}
Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx) {
E.cursor_y = E.row_offset + E.screenrows - 1;
if (E.cursor_y > E.numrows) {
E.cursor_y = E.numrows;
}
int times = E.screenrows;
KeyInfo key;
key.type = KEY_ARROW;
key.data.arrow = 'D';
while (--times) {
editorMoveCursor(&key);
}
return lisp_null();
}
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
if (filename)
editorOpen(filename);
return lisp_null();
}
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
char *c = lisp_string(lisp_car(args));
utf_8_char_t ch = make_utf8_char(c, 1);
editorInsertChar(&ch);
return lisp_null();
}
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
const char *package_name = lisp_string(lisp_car(args));
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");
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 editorDelRow_L(Lisp args, LispError *e, LispContext ctx) {
editorDelRow(E.cursor_y);
return lisp_null();
}
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) {
editorFind();
return lisp_null();
}
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
// fprintf(stderr, "char read : %c\n", E.row[E.cursor_y].render[E.cursor_x]);
// return lisp_make_char(E.row[E.cursor_y].render[E.cursor_x]);
return lisp_null();
}
+80 -30
View File
@@ -1,44 +1,94 @@
#include "../include/editor_op.h"
#include "../include/row_op.h"
#include "include/data.h"
#include <stdio.h>
void editorInsertChar(struct editorConfig *E, int c) {
if (E->cursor_y == E->numrows) {
editorInsertRow(E, E->numrows, "", 0);
}
editorRowInsertChar(E, &E->row[E->cursor_y], E->cursor_x, c);
E->cursor_x++;
extern struct editorConfig E;
void editorInsertChar(utf_8_char_t *c) {
if (E.state == READ_ONLY) return;
fprintf(stderr, "Insert char %s %d\n", c->c, c->len);
// If cursor is past end of file, add empty rows
if (E.cursor_y == E.numrows) {
editorInsertRow(E.numrows, "", 0);
}
void editorInsertNewLine(struct editorConfig *E) {
erow *row;
if (!E->cursor_x) {
editorInsertRow(E, E->cursor_y, "", 0);
// Insert character at cursor position
editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, *c);
E.cursor_x++;
}
void editorInsertNewline(void) {
if (E.state == READ_ONLY) return;
if (E.cursor_x == 0) {
// Insert blank line before current line
editorInsertRow(E.cursor_y, "", 0);
} else {
row = &E->row[E->cursor_y];
editorInsertRow(E, E->cursor_y + 1, &row->chars[E->cursor_x],
row->size - E->cursor_x);
row = &E->row[E->cursor_y];
row->size = E->cursor_x;
row->chars[row->size] = '\0';
// Split current line at cursor
erow *row = &E.row[E.cursor_y];
// Calculate byte length of remaining part
int remaining_chars = row->size - E.cursor_x;
// Allocate buffer for remaining characters
char *buf = malloc(remaining_chars * 4); // Max 4 bytes per UTF-8 char
int buf_len = 0;
// Convert utf_8_char_t to bytes
for (int i = E.cursor_x; i < row->size; i++) {
for (int j = 0; j < row->chars[i].len; j++) {
buf[buf_len++] = row->chars[i].c[j];
}
}
// Insert new row with remaining text
editorInsertRow(E.cursor_y + 1, buf, buf_len);
free(buf);
// Truncate current row at cursor
row = &E.row[E.cursor_y]; // Refresh pointer after realloc
row->size = E.cursor_x;
editorUpdateRow(row);
}
++E->cursor_y;
E->cursor_x = 0;
E.cursor_y++;
E.cursor_x = 0;
}
void editorDelChar(struct editorConfig *E) {
erow *row;
if (E->cursor_y == E->numrows || !(E->cursor_x || E->cursor_y)) {
return;
void editorRowAppendRow(erow *dest, erow *src) {
// Allocate space for combined rows
utf_8_char_t *new_chars = realloc(dest->chars,
sizeof(utf_8_char_t) * (dest->size + src->size));
if (!new_chars) return;
dest->chars = new_chars;
// Copy source row characters
memcpy(&dest->chars[dest->size], src->chars, sizeof(utf_8_char_t) * src->size);
dest->size += src->size;
editorUpdateRow(dest);
++E.dirty;
}
row = &E->row[E->cursor_y];
if (E->cursor_x > 0) {
editorRowDelchar(E, row, E->cursor_x - 1);
--E->cursor_x;
void editorDelChar(void) {
if (E.state == READ_ONLY) return;
if (E.cursor_y == E.numrows) return;
if (E.cursor_x == 0 && E.cursor_y == 0) return;
erow *row = &E.row[E.cursor_y];
if (E.cursor_x > 0) {
// Delete character before cursor
editorRowDelchar(row, E.cursor_x - 1);
E.cursor_x--;
} 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;
// At beginning of line - join with previous line
E.cursor_x = E.row[E.cursor_y - 1].size;
editorRowAppendRow(&E.row[E.cursor_y - 1], row);
editorDelRow(E.cursor_y);
E.cursor_y--;
}
}
+159 -28
View File
@@ -7,80 +7,211 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
char *editorRowsToString(struct editorConfig *E, int *buffer_len) {
extern char *strdup(const char *);
extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
FILE *restrict stream);
extern int ftruncate(int fd, off_t length);
extern struct editorConfig E;
// Convert utf_8_char_t array to byte string
char *editorRowsToString(int *buffer_len) {
int tot_len = 0;
int j;
int j, i;
char *buf;
char *p;
for (j = 0; j < E->numrows; ++j) {
tot_len += E->row[j].size + 1;
// Calculate total byte length (not character count)
for (j = 0; j < E.numrows; ++j) {
// Count actual bytes in each character
for (i = 0; i < E.row[j].size; i++) {
tot_len += E.row[j].chars[i].len;
}
tot_len++; // For newline
}
*buffer_len = tot_len;
buf = malloc(tot_len);
if (!buf) return NULL;
p = buf;
for (j = 0; j < E->numrows; ++j) {
memcpy(p, E->row[j].chars, E->row[j].size);
p += E->row[j].size;
*p = '\n';
p++;
for (j = 0; j < E.numrows; ++j) {
// Copy each character's bytes
for (i = 0; i < E.row[j].size; i++) {
for (int k = 0; k < E.row[j].chars[i].len; k++) {
*p++ = E.row[j].chars[i].c[k];
}
}
*p++ = '\n';
}
return buf;
}
void editorOpen(struct editorConfig *E, char *filename) {
void editorCloseFile(void) {
// Free all rows
for (int i = 0; i < E.numrows; i++) {
editorFreeRow(&E.row[i]);
}
E.cursor_x = 0;
E.cursor_y = 0;
E.rx = 0;
E.row_offset = 0;
E.col_offset = 0;
E.numrows = 0;
free(E.row);
E.row = NULL;
E.dirty = 0;
free(E.filename);
E.filename = NULL;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
}
void editorOpen(char *filename) {
FILE *fp;
free(E->filename);
E->filename = strdup(filename);
// Test if a file is already open
if (E.filename != NULL) {
editorCloseFile();
}
E.state = READ_AND_WRITE;
E.filename = strdup(filename);
fp = fopen(filename, "r");
if (!fp)
die("fopen");
if (!fp) {
// File doesn't exist - that's okay, we'll create it on save
E.dirty = 0;
return;
}
char *line = NULL;
size_t line_cap = 0;
ssize_t line_len;
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
// Strip newline characters
while (line_len > 0 &&
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
--line_len;
}
editorInsertRow(E, E->numrows, line, line_len);
// editorInsertRow will convert bytes to utf_8_char_t
editorInsertRow(E.numrows, line, line_len);
}
free(line);
fclose(fp);
E->dirty = 0;
E.dirty = 0;
}
void editorSave(struct editorConfig *E) {
void editorSave() {
int len;
char *buf;
int fd;
if (E->filename == NULL) {
E->filename = editorPrompt(E, "Save as: %s (ESC to cancel)");
if (E->filename == NULL) {
editorSetStatusMessage(E, "Save aborted");
if (E.filename == NULL) {
E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (E.filename == NULL) {
editorSetStatusMessage("Save aborted");
return;
}
}
buf = editorRowsToString(E, &len);
fd = open(E->filename, O_RDWR | O_CREAT, 0644);
buf = editorRowsToString(&len);
if (!buf) {
editorSetStatusMessage("Can't save! Memory error");
return;
}
fd = open(E.filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd != -1) {
if (ftruncate(fd, len) != -1) {
if (write(fd, buf, len) == len) {
close(fd);
free(buf);
E->dirty = 0;
editorSetStatusMessage(E, "%d bytes written to disk", len);
E.dirty = 0;
editorSetStatusMessage("%d bytes written to disk", len);
return;
}
}
close(fd);
}
free(buf);
editorSetStatusMessage(E, "Can't save! I/O error: %s", strerror(errno));
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
}
// Helper to convert utf_8_char_t array to byte string for searching
static char *row_to_string(erow *row) {
// Calculate byte length
int byte_len = 0;
for (int i = 0; i < row->rsize; i++) {
byte_len += row->render[i].len;
}
char *str = malloc(byte_len + 1);
if (!str) return NULL;
// Convert to bytes
int pos = 0;
for (int i = 0; i < row->rsize; i++) {
for (int j = 0; j < row->render[i].len; j++) {
str[pos++] = row->render[i].c[j];
}
}
str[pos] = '\0';
return str;
}
void editorFind() {
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
if (query == NULL) return;
int saved_cursor_x = E.cursor_x;
int saved_cursor_y = E.cursor_y;
int saved_row_offset = E.row_offset;
int saved_col_offset = E.col_offset;
// Search from current position forward
for (int i = E.cursor_y; i < E.numrows; i++) {
erow *row = &E.row[i];
// Convert row to byte string for searching
char *render_str = row_to_string(row);
if (!render_str) continue;
char *match = strstr(render_str, query);
if (match) {
E.cursor_y = i;
// Find the character index from byte position
int byte_pos = match - render_str;
int char_idx = 0;
int current_byte = 0;
for (char_idx = 0; char_idx < row->rsize; char_idx++) {
if (current_byte >= byte_pos) break;
current_byte += row->render[char_idx].len;
}
E.cursor_x = editorRowRxToCx(row, char_idx);
E.row_offset = E.numrows; // Force scroll
free(render_str);
free(query);
return;
}
free(render_str);
}
// Not found - restore cursor position
E.cursor_x = saved_cursor_x;
E.cursor_y = saved_cursor_y;
E.row_offset = saved_row_offset;
E.col_offset = saved_col_offset;
editorSetStatusMessage("Not found: %s", query);
free(query);
}
+86 -14
View File
@@ -1,19 +1,91 @@
#include "../include/init.h"
#include "../include/data.h"
#include "../include/terminal.h"
#include "../include/builtins.h"
#include <stdio.h>
void initEditor(struct editorConfig *E) {
E->cursor_x = 0;
E->cursor_y = 0;
E->rx = 0;
E->row_offset = 0;
E->col_offset = 0;
E->numrows = 0;
E->row = NULL;
E->dirty = 0;
E->filename = NULL;
E->status_msg[0] = '\0';
E->status_msg_time = 0;
if (getWindowSize(&E->screenrows, &E->screencols) == -1) {
#define LISP_IMPLEMENTATION
#include "../include/lisp.h"
#include "../include/lisp_lib.h"
extern struct editorConfig;
void registerBuiltin(char *key_sequence, LispCFunc f) {
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
lisp_make_func(f), E.ctx);
}
void initBuiltins() {
// move cursor
registerBuiltin("MOVE-CURSOR", moveCursor);
registerBuiltin("MAP-KEY", mapKey);
registerBuiltin("EDITOR-QUIT", editorQuit);
registerBuiltin("EDITOR-SAVE", l_editorSave);
registerBuiltin("EDITOR-INSERT-NEW-LINE", l_editorInsertNewLine);
registerBuiltin("MOVE-CURSOR-BEG-LINE", moveCursorBeginLine);
registerBuiltin("MOVE-CURSOR-END-LINE", moveCursorEndLine);
registerBuiltin("EDITOR-DELETE-PREVIOUS-CHAR", deletePreviousChar);
registerBuiltin("MOVE-CURSOR-PAGE-UP", editorMoveCursorPageUp);
registerBuiltin("MOVE-CURSOR-PAGE-DOWN", editorMoveCursorPageDown);
registerBuiltin("EDITOR-OPEN-FILE", editorOpenFile);
registerBuiltin("EDITOR-INSERT-CHAR", editorPrintC);
registerBuiltin("ADD-PACKAGE", addPackage);
registerBuiltin("EDITOR-DEL-ROW", editorDelRow_L);
registerBuiltin("EDITOR-FIND", editorFind_L);
registerBuiltin("EDITOR-READ-CHAR", editorReadChar_L);
}
void initEditor() {
char * init_file_path = (char *) calloc(256, sizeof(char));
E.cursor_x = 0;
E.cursor_y = 0;
E.rx = 0;
E.row_offset = 0;
E.col_offset = 0;
E.numrows = 0;
E.row = NULL;
E.dirty = 0;
E.filename = NULL;
E.state = READ_ONLY;
E.status_msg[0] = '\0';
E.status_msg_time = 0;
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
die("getWindowSize");
}
E->screenrows -= 2;
E.screenrows -= 2;
E.number_of_keybinds = 0;
strcat(init_file_path, getenv("HOME"));
strcat(init_file_path, "/.beluga/config/init.lisp");
printf("%s\n", init_file_path);
E.fd_init_file = fopen(init_file_path, "r");
E.ctx = lisp_init();
E.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);
free(init_file_path);
if (E.ctx_error != LISP_ERROR_NONE) {
die("init failed");
}
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
// To modify
E.constantes.TAB_LENGTH =
(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;
fprintf(stderr, "Tab %d\n", E.constantes.QUIT_TIMES);
E.quit_times_buffer = E.constantes.QUIT_TIMES;
}
+193 -113
View File
@@ -1,168 +1,248 @@
#include "../include/input.h"
#include "../include/define.h"
#include "../include/editor_op.h"
#include "../include/file_io.h"
#include "../include/output.h"
#include "include/data.h"
#include <ctype.h>
#include <dirent.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
extern struct editorConfig E;
char *file_completion(const char *path) {
DIR *dir;
struct dirent *entry;
char directory[128];
char predict[128];
int predict_len = 0;
if (path[strlen(path) - 1] == '/') {
return strdup(path);
}
// Find dir name
char *last_slash = strrchr(path, '/');
if (last_slash) {
size_t dir_len = last_slash - path + 1;
strncpy(directory, path, dir_len);
predict_len = strlen(path) - dir_len;
strncpy(predict, last_slash + 1, predict_len);
directory[dir_len] = '\0';
predict[predict_len] = '\0';
} else {
return NULL;
}
dir = opendir(directory);
if (!dir)
return NULL;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, predict, predict_len) == 0) {
static char full_path[128];
snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
strcat(full_path, "/");
}
closedir(dir);
return strdup(full_path);
}
}
closedir(dir);
return NULL;
}
/**
* \fn char * editorPrompt(struct editorConfig *E, char *prompt)
* \fn char * editorPrompt(struct editorConfig *E, char *prompt, char bPathMode)
* \brief Return user input in a prompt when enter is hit. */
char *editorPrompt(struct editorConfig *E, char *prompt) {
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
size_t buf_size = 128;
char *buf = malloc(buf_size);
size_t buf_len = 0;
int c = 0;
buf[0] = '\0';
strcpy(buf, placeHolder);
buf_len = strlen(placeHolder);
while (1) {
editorSetStatusMessage(E, prompt, buf);
editorRefreshScreen(E);
c = editorReadKey();
if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
editorSetStatusMessage(prompt, buf);
editorRefreshScreen();
KeyInfo *key = editorReadKey();
// Handle backspace/delete
if (key->type == KEY_SPECIAL && (key->data.special == 127 || key->data.special == 8)) {
if (buf_len != 0) {
buf[--buf_len] = '\0';
}
} else if (c == ESCAPE) {
fprintf(stderr, "escape");
editorSetStatusMessage(E, "");
}
// Handle Ctrl+H (backspace)
else if (key->type == KEY_CTRL && key->data.ctrl_char == 'H') {
if (buf_len != 0) {
buf[--buf_len] = '\0';
}
}
// Handle ESC
else if (key->type == KEY_SPECIAL && key->data.special == 27) {
editorSetStatusMessage("");
free(buf);
return NULL;
} else if (c == '\r') {
}
// Handle Enter
else if (key->type == KEY_SPECIAL && (key->data.special == 13 || key->data.special == 10)) {
if (buf_len != 0) {
editorSetStatusMessage(E, "");
editorSetStatusMessage("");
return buf;
}
} else if (!iscntrl(c) && c < 128) {
}
// Handle Tab for path completion
else if (bPathMode && key->type == KEY_SPECIAL && key->data.special == 9) {
char path[128];
char *pwd;
if (buf[0] != '/') {
pwd = getenv("PWD");
snprintf(path, sizeof(path), "%s/%s", pwd, buf);
} else {
strcpy(path, buf);
}
char *completion = file_completion(path);
if (completion) {
memset(buf, 0, buf_size);
strcpy(buf, completion);
buf_len = strlen(buf);
free(completion);
}
}
// Handle regular characters (ASCII only for prompts)
else if (key->type == KEY_CHAR && key->data.codepoint < 128) {
if (buf_len == buf_size - 1) {
buf_size *= 2;
buf = realloc(buf, buf_size);
}
buf[buf_len++] = c;
buf[buf_len++] = (char)key->data.codepoint;
buf[buf_len] = '\0';
}
}
}
void editorMoveCursor(struct editorConfig *E, int key) {
erow *row = (E->cursor_y >= E->numrows) ? NULL : &E->row[E->cursor_y];
void editorMoveCursor(KeyInfo *key) {
if (key->type != KEY_ARROW) return;
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
int row_len;
switch (key) {
case ARROW_RIGHT:
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;
switch (key->data.arrow) {
case 'C': // Right
if (row && E.cursor_x < row->size) {
++E.cursor_x;
} else if (row && E.cursor_x == row->size) {
E.cursor_y++;
E.cursor_x = 0;
}
break;
case ARROW_DOWN:
if (E->cursor_y < E->numrows) {
++E->cursor_y;
case 'B': // Down
if (E.cursor_y < E.numrows) {
++E.cursor_y;
}
break;
case ARROW_UP:
if (E->cursor_y != 0) {
--E->cursor_y;
case 'A': // 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;
case 'D': // 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 = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
row_len = row ? row->size : 0;
if (E->cursor_x > row_len) {
E->cursor_x = row_len;
if (E.cursor_x > row_len) {
E.cursor_x = row_len;
}
}
void editorProcessKeypress(struct editorConfig *E) {
static int quit_times = QUIT_TIMES;
int c = editorReadKey();
int times;
KeyInfo *stringToCodepoint(const char *string) {
KeyInfo *key = (KeyInfo *)malloc(sizeof(KeyInfo));
// test control key
if (!strncmp("CTRL", string, 4)) {
key->type = KEY_CTRL;
key->data.ctrl_char = toupper(string[6]) + 64;
} else if (!strncmp("ARROW", string, 5)) {
key->type = KEY_ARROW;
if (!strcmp("UP", string + 7)) {
key->data.arrow = 'A';
} else if (!strcmp("DOWN", string + 7)) {
key->data.arrow = 'B';
} else if (!strcmp("RIGHT", string + 7)) {
key->data.arrow = 'C';
} else if (!strcmp("LEFT", string + 7)) {
key->data.arrow = 'D';
}
}
switch (c) {
return key;
}
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;
static int key_match(KeyInfo *a, KeyInfo *b) {
if (a->type != b->type) return 0;
if (a->modifiers != b->modifiers) return 0;
switch (a->type) {
case KEY_CTRL:
return toupper(a->data.ctrl_char) == toupper(b->data.ctrl_char);
case KEY_ALT:
return a->data.alt_char == b->data.alt_char;
case KEY_ARROW:
return a->data.arrow == b->data.arrow;
case KEY_FUNCTION:
return a->data.function_num == b->data.function_num;
case KEY_CHAR:
return a->data.codepoint == b->data.codepoint;
case KEY_SPECIAL:
case KEY_NAVIGATION:
return a->data.special == b->data.special;
default:
return 0;
}
}
int executeKeyBind(KeyInfo *key_sequence) {
for (int i = 0; i < E.number_of_keybinds; ++i) {
fprintf(stderr, "Keybind found\n");
if (key_match(key_sequence, E.key_binds[i].key_sequence)) {
// Execute the lisp command
lisp_eval(lisp_cons(E.key_binds[i].command, lisp_null(), E.ctx),
&E.ctx_error, E.ctx);
return 1;
}
}
return 0;
}
void editorProcessKeypress() {
KeyInfo *key = editorReadKey();
if (!key)
return;
if (executeKeyBind(key)) {
fprintf(stderr, "Keybinds found\n");
return;
}
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
disableRawMode(E);
exit(0);
break;
case CTRL_KEY('s'):
editorSave(E);
break;
case BEG_LINE:
E->cursor_x = 0;
break;
case END_LINE:
if (E->cursor_y < E->numrows) {
E->cursor_x = E->row[E->cursor_y].size;
}
break;
case BACKSPACE:
case CTRL_KEY('h'):
case DEL_KEY:
if (c == DEL_KEY) {
editorMoveCursor(E, ARROW_RIGHT);
}
editorDelChar(E);
break;
case PAGE_UP:
case PAGE_DOWN: {
if (c == PAGE_UP) {
E->cursor_y = E->row_offset;
} else if (c == PAGE_DOWN) {
E->cursor_y = E->row_offset + E->screenrows - 1;
if (E->cursor_y > E->numrows) {
E->cursor_y = E->numrows;
}
}
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;
editorInsertChar(&key->c);
E.quit_times_buffer = E.constantes.QUIT_TIMES;
}
+70 -48
View File
@@ -4,24 +4,37 @@
#include <string.h>
#include <time.h>
void editorDrawRows(struct editorConfig *E, struct abuf *ab) {
extern struct editorConfig E;
static void utf8_to_bytes(utf_8_char_t *chars, int count, unsigned char *output, int *output_len) {
int pos = 0;
for (int i = 0; i < count; i++) {
for (int j = 0; j < chars[i].len; j++) {
output[pos++] = chars[i].c[j];
}
fprintf(stderr, "bytes length : %s %d\n", chars[i].c, pos);
}
*output_len = pos;
}
void editorDrawRows(struct abuf *ab) {
int y;
char welcome[80];
int welcome_len;
int padding;
int len;
int file_row;
for (y = 0; y < E->screenrows; ++y) {
file_row = y + E->row_offset;
if (file_row >= E->numrows) {
if (E->numrows == 0 && y == E->screenrows / 3) {
for (y = 0; y < E.screenrows; ++y) {
file_row = y + E.row_offset;
if (file_row >= E.numrows) {
if (E.numrows == 0 && y == E.screenrows / 3) {
welcome_len =
snprintf(welcome, sizeof(welcome),
"Beluga text editor -- version %s", BELUGA_VERSION);
if (welcome_len > E->screencols) {
welcome_len = E->screencols;
if (welcome_len > E.screencols) {
welcome_len = E.screencols;
}
padding = (E->screencols - welcome_len) / 2;
padding = (E.screencols - welcome_len) / 2;
if (padding) {
abAppend(ab, "~", 1);
--padding;
@@ -34,54 +47,63 @@ void editorDrawRows(struct editorConfig *E, struct abuf *ab) {
abAppend(ab, "~", 1);
}
} else {
len = E->row[file_row].rsize - E->col_offset;
len = E.row[file_row].rsize - E.col_offset;
if (len < 0)
len = 0;
if (len > E->screencols)
len = E->screencols;
abAppend(ab, &E->row[file_row].render[E->col_offset], len);
if (len > E.screencols)
len = E.screencols;
if (len > 0) {
unsigned char *display_buf = malloc(len * 4); // Max 4 bytes per char
int byte_len;
utf8_to_bytes(&E.row[file_row].render[E.col_offset], len, display_buf,
&byte_len);
abAppend(ab, display_buf, byte_len);
fprintf(stderr, "display buffer : %s %d\n", display_buf, byte_len);
free(display_buf);
}
}
abAppend(ab, ERASE_END_LINE, 3);
abAppend(ab, "\r\n", 2);
}
}
void editorScroll(struct editorConfig *E) {
E->rx = E->cursor_x;
if (E->cursor_y < E->numrows) {
E->rx = editorRowCxToRx(&E->row[E->cursor_y], E->cursor_x);
void editorScroll() {
E.rx = E.cursor_x;
if (E.cursor_y < E.numrows) {
E.rx = editorRowCxToRx(&E.row[E.cursor_y], E.cursor_x);
}
if (E->cursor_y < E->row_offset) {
E->row_offset = E->cursor_y;
if (E.cursor_y < E.row_offset) {
E.row_offset = E.cursor_y;
}
if (E->cursor_y >= E->row_offset + E->screenrows) {
E->row_offset = E->cursor_y - E->screenrows + 1;
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.col_offset = E.rx;
}
if (E->rx >= E->col_offset + E->screencols) {
E->col_offset = E->rx - E->screencols + 1;
if (E.rx >= E.col_offset + E.screencols) {
E.col_offset = E.rx - E.screencols + 1;
}
}
void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab) {
void editorDrawStatusBar(struct abuf *ab) {
int len, render_len;
char status[80], render_status[80];
abAppend(ab, "\x1b[7m", 4); // inverting colors
len = snprintf(status, sizeof(status), "%.20s - %d lines%s",
E->filename ? E->filename : "[No Name]", E->numrows,
E->dirty ? "*" : "");
E.filename ? E.filename : "[No Name]", E.numrows,
E.dirty ? "*" : "");
render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
E->cursor_y + 1, E->numrows);
if (len > E->screencols) {
len = E->screencols;
E.cursor_y + 1, E.numrows);
if (len > E.screencols) {
len = E.screencols;
}
abAppend(ab, status, len);
while (len < E->screencols) {
if (E->screencols - len == render_len) {
while (len < E.screencols) {
if (E.screencols - len == render_len) {
abAppend(ab, render_status, render_len);
break;
} else {
@@ -93,31 +115,31 @@ void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab) {
abAppend(ab, "\r\n", 2);
}
void editorDrawMessageBar(struct editorConfig *E, struct abuf *ab) {
int msg_len = strlen(E->status_msg);
void editorDrawMessageBar(struct abuf *ab) {
int msg_len = strlen(E.status_msg);
abAppend(ab, ERASE_END_LINE, 3);
if (msg_len > E->screencols) {
msg_len = E->screencols;
if (msg_len > E.screencols) {
msg_len = E.screencols;
}
if (msg_len && time(NULL) - E->status_msg_time < 5) {
abAppend(ab, E->status_msg, msg_len);
if (msg_len && time(NULL) - E.status_msg_time < 5) {
abAppend(ab, E.status_msg, msg_len);
}
}
void editorRefreshScreen(struct editorConfig *E) {
editorScroll(E);
void editorRefreshScreen() {
editorScroll();
struct abuf ab = ABUF_INIT;
char buf[32];
abAppend(&ab, HIDE_CURSOR, 6);
abAppend(&ab, CURSOR_TOP_LEFT, 3);
editorDrawRows(E, &ab);
editorDrawStatusBar(E, &ab);
editorDrawMessageBar(E, &ab);
editorDrawRows(&ab);
editorDrawStatusBar(&ab);
editorDrawMessageBar(&ab);
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E->cursor_y - E->row_offset) + 1,
(E->rx - E->col_offset) + 1);
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cursor_y - E.row_offset) + 1,
(E.rx - E.col_offset) + 1);
abAppend(&ab, buf, strlen(buf));
abAppend(&ab, SHOW_CURSOR, 6);
@@ -126,10 +148,10 @@ void editorRefreshScreen(struct editorConfig *E) {
abFree(&ab);
}
void editorSetStatusMessage(struct editorConfig *E, const char *fmt, ...) {
void editorSetStatusMessage(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(E->status_msg, sizeof(E->status_msg), fmt, ap);
vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
va_end(ap);
E->status_msg_time = time(NULL);
E.status_msg_time = time(NULL);
}
+149 -48
View File
@@ -1,20 +1,55 @@
#include "../include/row_op.h"
#include "include/data.h"
#include "include/define.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
extern struct editorConfig E;
static int is_tab(utf_8_char_t *ch) {
return ch->len == 1 && ch->c[0] == '\t';
}
// Helper function to check if two utf_8_char_t are equal
static int utf8_char_equal(utf_8_char_t *a, utf_8_char_t *b) {
if (a->len != b->len) return 0;
return memcmp(a->c, b->c, a->len) == 0;
}
// Helper function to create a space character
static utf_8_char_t make_space() {
utf_8_char_t space;
space.c[0] = ' ';
space.len = 1;
return space;
}
int editorRowCxToRx(erow *row, int cursor_x) {
int render_x = 0;
int i;
for (i = 0; i < cursor_x; ++i) {
if (row->chars[i] == '\t') {
render_x += (TAB_LENGTH - 1) - (render_x % TAB_LENGTH);
if (is_tab(&row->chars[i])) {
render_x += (E.constantes.TAB_LENGTH - 1) - (render_x % E.constantes.TAB_LENGTH);
}
render_x++;
}
return render_x;
}
int editorRowRxToCx(erow *row, int rx) {
int cur_rx = 0;
int cx;
for (cx = 0; cx < row->size; cx++) {
if (is_tab(&row->chars[cx]))
cur_rx += (E.constantes.TAB_LENGTH - 1) - (cur_rx % E.constantes.TAB_LENGTH);
cur_rx++;
if (cur_rx > rx) return cx;
}
return cx;
}
/**
* \fn editorUpdateRow(erow *row)
* \brief Copy content of \p row in \p row->render.
@@ -24,95 +59,161 @@ void editorUpdateRow(erow *row) {
int i, i_render;
int tabs = 0;
// counting number of tabs
// Count number of tabs
for (i = 0; i < row->size; ++i) {
tabs +=
(row->chars[i] == '\t'); /**< increment tabs of 1 if chars[i] is one. */
if (is_tab(&row->chars[i])) {
tabs++;
}
}
free(row->render);
row->render = malloc(row->size + tabs * (TAB_LENGTH - 1) +
1); /**< Tabs needs TAB_LENGTH chars so TAB_LENGTH - 1
more than the first already counted. */
// Allocate space for utf_8_char_t array
row->render = malloc(sizeof(utf_8_char_t) * (row->size + tabs * (E.constantes.TAB_LENGTH - 1)));
if (!row->render) {
row->rsize = 0;
return;
}
// end of counting
i_render = 0;
for (i = 0; i < row->size; ++i) {
if (row->chars[i] == '\t') {
row->render[i_render++] = ' ';
while (i_render % TAB_LENGTH) {
row->render[i_render++] =
' '; /**< Addind the right amount of spaces for tabs */
if (is_tab(&row->chars[i])) {
// Replace tab with spaces
row->render[i_render++] = make_space();
while (i_render % E.constantes.TAB_LENGTH) {
row->render[i_render++] = make_space();
}
} else {
row->render[i_render++] = row->chars[i];
}
}
row->render[i_render] = '\0'; // Don't forget the end of string character.
row->rsize = i_render;
}
void editorInsertRow(struct editorConfig *E, int at, char *s, size_t len) {
if (at < 0 || at > E->numrows) {
void editorInsertRow(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;
erow *tmp = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1));
if (!tmp) {
return;
}
E.row = tmp;
memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));
// Initialize the new row
E.row[at].size = 0;
E.row[at].chars = NULL;
E.row[at].rsize = 0;
E.row[at].render = NULL;
// Count UTF-8 characters first
int char_count = 0;
int i = 0;
while (i < len) {
unsigned char first = (unsigned char)s[i];
int char_len;
if ((first & 0x80) == 0) {
char_len = 1;
} else if ((first & 0xE0) == 0xC0) {
char_len = 2;
} else if ((first & 0xF0) == 0xE0) {
char_len = 3;
} else if ((first & 0xF8) == 0xF0) {
char_len = 4;
} else {
char_len = 1; // Invalid, treat as single byte
}
i += char_len;
char_count++;
}
// Allocate for the actual number of characters
if (char_count > 0) {
E.row[at].chars = malloc(sizeof(utf_8_char_t) * char_count);
if (!E.row[at].chars) {
return;
}
}
// Now convert to utf_8_char_t array
i = 0;
E.row[at].size = 0;
while (i < len && E.row[at].size < char_count) {
utf_8_char_t ch;
unsigned char first = (unsigned char)s[i];
if ((first & 0x80) == 0) {
ch.len = 1;
} else if ((first & 0xE0) == 0xC0) {
ch.len = 2;
} else if ((first & 0xF0) == 0xE0) {
ch.len = 3;
} else if ((first & 0xF8) == 0xF0) {
ch.len = 4;
} else {
ch.len = 1;
}
// Copy bytes
for (int j = 0; j < ch.len && i < len; j++) {
ch.c[j] = s[i++];
}
E.row[at].chars[E.row[at].size++] = ch;
}
editorUpdateRow(&E.row[at]);
++E.numrows;
++E.dirty;
}
void editorFreeRow(erow *row) {
free(row->render);
free(row->chars);
}
void editorDelRow(struct editorConfig *E, int at) {
if (at < 0 || at >= E->numrows) {
void editorDelRow(int at) {
if (at < 0 || at >= E.numrows) {
return;
}
editorFreeRow(&E->row[at]);
memmove(&E->row[at], &E->row[at + 1], sizeof(erow) * (E->numrows - at - 1));
--E->numrows;
++E->dirty;
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) {
void editorRowInsertChar(erow *row, int at, utf_8_char_t c) {
if (E.state == READ_ONLY)
return;
if (at < 0 || at > row->size) {
at = row->size;
}
row->chars = realloc(row->chars, row->size + 2);
row->chars = realloc(row->chars, row->size + 1);
memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
++row->size;
++(row->size);
row->chars[at] = c;
fprintf(stderr, "Row insert : %s %d\n", c.c, c.len);
editorUpdateRow(row);
++E->dirty;
++E.dirty;
}
void editorRowAppendString(struct editorConfig *E, erow *row, char *s,
size_t len) {
void editorRowAppendString(erow *row, char *s, size_t len) {
row->chars = realloc(row->chars, row->size + len + 1);
memcpy(&row->chars[row->size], s, len);
row->size += len;
row->chars[row->size] = '\0';
editorUpdateRow(row);
++E->dirty;
++E.dirty;
}
/**
@@ -120,12 +221,12 @@ void editorRowAppendString(struct editorConfig *E, erow *row, char *s,
* \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) {
void editorRowDelchar(erow *row, int at) {
if (at < 0 || at >= row->size) {
return;
}
memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
--row->size;
editorUpdateRow(row);
++E->dirty;
++E.dirty;
}
+198 -54
View File
@@ -1,24 +1,30 @@
#include "../include/terminal.h"
#include "../include/data.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void die(const char *s) {
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
lisp_shutdown(E.ctx);
perror(s);
exit(1);
}
void disableRawMode(struct editorConfig *E) {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E->orig_termios) == -1) {
void disableRawMode() {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) {
die("tcsetattr");
}
}
void enableRawMode(struct editorConfig *E) {
if (tcgetattr(STDIN_FILENO, &E->orig_termios) == -1) {
void enableRawMode() {
if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) {
die("tcgetattr");
}
struct termios raw = E->orig_termios;
struct termios raw = E.orig_termios;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
@@ -31,75 +37,213 @@ void enableRawMode(struct editorConfig *E) {
}
}
int editorReadKey() {
int nread;
char c;
char seq[3];
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
if (nread == -1 && errno != EAGAIN) {
die("read");
int utf8_char_length(unsigned char first_byte) {
if ((first_byte & 0x80) == 0)
return 1; // 0xxxxxxx - ASCII
if ((first_byte & 0xE0) == 0xC0)
return 2; // 110xxxxx - 2 bytes
if ((first_byte & 0xF0) == 0xE0)
return 3; // 1110xxxx - 3 bytes
if ((first_byte & 0xF8) == 0xF0)
return 4; // 11110xxx - 4 bytes
return 1; // Invalid, treat as single byte
}
// Convert UTF-8 to Unicode code point
unsigned int utf8_to_codepoint(const unsigned char *bytes, int len) {
if (len == 1)
return bytes[0];
if (len == 2)
return ((bytes[0] & 0x1F) << 6) | (bytes[1] & 0x3F);
if (len == 3)
return ((bytes[0] & 0x0F) << 12) | ((bytes[1] & 0x3F) << 6) |
(bytes[2] & 0x3F);
if (len == 4)
return ((bytes[0] & 0x07) << 18) | ((bytes[1] & 0x3F) << 12) |
((bytes[2] & 0x3F) << 6) | (bytes[3] & 0x3F);
return 0;
}
void parse_key(unsigned char *seq, int len, KeyInfo *key) {
memcpy(key->c.c, seq, len);
key->c.len = len;
key->modifiers = MOD_NONE;
key->type = KEY_UNKNOWN;
// Control characters (Ctrl+A to Ctrl+Z)
if (len == 1 && seq[0] < 32 && seq[0] != 27 && seq[0] != 9 && seq[0] != 10 &&
seq[0] != 13) {
key->type = KEY_CTRL;
key->data.ctrl_char = seq[0] + 64;
return;
}
// Special single characters
if (len == 1) {
switch (seq[0]) {
case 9:
case 10:
case 13:
case 27:
case 127:
key->type = KEY_SPECIAL;
key->data.special = seq[0];
return;
}
}
if (c == '\x1b') {
if (read(STDIN_FILENO, &seq[0], 1) != 1 ||
read(STDIN_FILENO, &seq[1], 1) != 1) {
return '\x1b';
// Escape sequences
if (len >= 2 && seq[0] == 27) {
// Alt+key combinations
if (len == 2 && seq[1] >= 32 && seq[1] < 127) {
key->type = KEY_ALT;
key->data.alt_char = seq[1];
return;
}
if (seq[0] == '[') {
if (seq[1] >= '0' && seq[1] <= '9') {
if (read(STDIN_FILENO, &seq[2], 1) != 1) {
return '\x1b';
}
if (seq[2] == '~') {
switch (seq[1]) {
case '1':
return BEG_LINE;
case '3':
return DEL_KEY;
case '4':
return END_LINE;
case '5':
return PAGE_UP;
case '6':
return PAGE_DOWN;
case '7':
return BEG_LINE;
case '8':
return END_LINE;
}
}
} else {
switch (seq[1]) {
// CSI sequences (ESC [ ...)
if (len >= 3 && seq[1] == '[') {
// Arrow keys
if (len == 3) {
switch (seq[2]) {
case 'A':
return ARROW_UP;
case 'B':
return ARROW_DOWN;
case 'C':
return ARROW_RIGHT;
case 'D':
return ARROW_LEFT;
key->type = KEY_ARROW;
key->data.arrow = seq[2];
return;
case 'H':
return BEG_LINE;
case 'F':
return END_LINE;
key->type = KEY_NAVIGATION;
key->data.special = seq[2];
return;
}
}
} else if (seq[0] == 'O') {
switch (seq[1]) {
// Modified keys (ESC [ 1 ; modifier letter)
if (len >= 6 && seq[2] == '1' && seq[3] == ';') {
int modifier = seq[4] - '0';
char k = seq[5];
if (modifier & 1)
key->modifiers |= MOD_SHIFT;
if (modifier & 2)
key->modifiers |= MOD_ALT;
if (modifier & 4)
key->modifiers |= MOD_CTRL;
switch (k) {
case 'A':
case 'B':
case 'C':
case 'D':
key->type = KEY_ARROW;
key->data.arrow = k;
return;
case 'H':
return BEG_LINE;
case 'F':
return END_LINE;
key->type = KEY_NAVIGATION;
key->data.special = k;
return;
}
}
return '\x1b';
} else {
return c;
// Function keys and navigation
if (len == 4 && seq[3] == '~') {
int num = seq[2] - '0';
if (num >= 1 && num <= 6) {
key->type = KEY_NAVIGATION;
key->data.special = seq[2];
return;
}
}
if (len == 5 && seq[4] == '~') {
int num = (seq[2] - '0') * 10 + (seq[3] - '0');
if (num >= 15 && num <= 24) {
key->type = KEY_FUNCTION;
// Map to F5-F12
int f_map[] = {15, 17, 18, 19, 20, 21, 23, 24};
for (int i = 0; i < 8; i++) {
if (f_map[i] == num) {
key->data.function_num = i + 5;
return;
}
}
}
}
}
// SS3 sequences (ESC O ...)
if (len == 3 && seq[1] == 'O') {
switch (seq[2]) {
case 'P':
case 'Q':
case 'R':
case 'S':
key->type = KEY_FUNCTION;
key->data.function_num = seq[2] - 'P' + 1;
return;
case 'H':
case 'F':
key->type = KEY_NAVIGATION;
key->data.special = seq[2];
return;
}
}
}
// UTF-8 character
if (seq[0] >= 32 || (seq[0] & 0x80)) {
int char_len = utf8_char_length(seq[0]);
fprintf(stderr, "char length : %d\n", char_len);
if (char_len <= len) {
key->type = KEY_CHAR;
memcpy(key->c.c, seq, len);
key->c.len = len;
return;
}
}
}
KeyInfo *editorReadKey() {
fd_set fds;
int timeout_ms = 10;
struct timeval tv;
int total = 0;
KeyInfo *key = (KeyInfo *)malloc(sizeof(KeyInfo));
int len;
unsigned char buffer[20];
if (read(STDIN_FILENO, &buffer[0], 1) <= 0)
return 0;
while (total < 20) {
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
tv.tv_sec = 0;
tv.tv_usec = timeout_ms * 1000;
int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
if (ret <= 0)
break;
if (read(STDIN_FILENO, &buffer[total], 1) <= 0)
break;
total++;
}
total++;
parse_key(buffer, total, key);
// DEBUG
fprintf(stderr, "%s %d %d %s %d\n", buffer, buffer[0], buffer[1], key->c.c, key->c.len);
return key;
}
int getCursorPosition(int *rows, int *cols) {
char buf[32];
unsigned int i = 0;