diff --git a/include/append_buffer.h b/include/append_buffer.h index 3f28992..dc266e4 100644 --- a/include/append_buffer.h +++ b/include/append_buffer.h @@ -5,7 +5,7 @@ #include #include -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); diff --git a/include/data.h b/include/data.h index 6eeaa4e..68b236a 100644 --- a/include/data.h +++ b/include/data.h @@ -7,6 +7,12 @@ #include "lisp.h" + +typedef struct{ + unsigned char c[4]; + char len; +} utf_8_char_t; + /** * \struct erow * \brief Store one editor row @@ -16,8 +22,8 @@ typedef struct erow { int size; /**< Size of the line */ int rsize; /**< Size of the render line */ - char *chars; /**< Characters of the line */ - char *render; /**< The actual line we will print */ + utf_8_char_t *chars; /**< Characters of the line */ + utf_8_char_t *render; /**< The actual line we will print */ } erow; enum editorStatus_e { @@ -31,10 +37,45 @@ struct const_t { 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 { - char *key_sequence; + KeyInfo *key_sequence; Lisp command; - }; /** @@ -77,7 +118,7 @@ struct editorConfig { * */ struct abuf { - char *b; /**< Text that will be printed */ + unsigned char *b; /**< Text that will be printed */ int len; /**< Length of the text */ }; diff --git a/include/define.h b/include/define.h index 3ff01e1..17c0ec9 100644 --- a/include/define.h +++ b/include/define.h @@ -8,19 +8,10 @@ #define HIDE_CURSOR "\x1b[?25l" #define SHOW_CURSOR "\x1b[?25h" #define ERASE_END_LINE "\x1b[K" +#define TAB "\x09" +#define SPACE "\x20" + -enum editorKey { - BACKSPACE = 127, - ARROW_LEFT = 1000, - ARROW_RIGHT, - ARROW_UP, - ARROW_DOWN, - DEL_KEY, - BEG_LINE, - END_LINE, - PAGE_UP, - PAGE_DOWN, -}; #define ABUF_INIT {NULL, 0} diff --git a/include/editor_op.h b/include/editor_op.h index 757b6c8..a67c7df 100644 --- a/include/editor_op.h +++ b/include/editor_op.h @@ -2,9 +2,9 @@ #define EDITOR_OP_H_ #include "data.h" -void editorInsertChar(int c); +void editorInsertChar(utf_8_char_t *c); -void editorInsertNewLine(); +void editorInsertNewLine(void); void editorDelChar(); diff --git a/include/input.h b/include/input.h index 2c031ef..8799904 100644 --- a/include/input.h +++ b/include/input.h @@ -24,9 +24,9 @@ char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode); char *key_to_string(int key); -void editorMoveCursor(int key); +void editorMoveCursor(KeyInfo * key); -int executeKeyBind(char *key_sequence); +int executeKeyBind(KeyInfo *key_sequence); /** * \fn void editorProcessKeypress() diff --git a/include/row_op.h b/include/row_op.h index 80973ad..fe350b9 100644 --- a/include/row_op.h +++ b/include/row_op.h @@ -20,7 +20,7 @@ void editorFreeRow(erow *row); void editorDelRow(int at); -void editorRowInsertChar(erow *row, int at, int c); +void editorRowInsertChar(erow *row, int at, utf_8_char_t c); void editorRowAppendString(erow *row, char *s, size_t len); diff --git a/include/terminal.h b/include/terminal.h index 6ab9108..d61a569 100644 --- a/include/terminal.h +++ b/include/terminal.h @@ -25,10 +25,12 @@ void disableRawMode(); void enableRawMode(); -int editorReadKey(); +KeyInfo * editorReadKey(); int getCursorPosition(int *rows, int *cols); +KeyInfo *stringToCodepoint(const char *string); + int getWindowSize(int *rows, int *cols); #endif diff --git a/install.sh b/install.sh index ddf59b0..3b869f0 100755 --- a/install.sh +++ b/install.sh @@ -17,6 +17,7 @@ fi 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 diff --git a/main.c b/main.c index 4dd8492..2cd2716 100644 --- a/main.c +++ b/main.c @@ -21,11 +21,19 @@ #include "include/output.h" #include "include/terminal.h" +#include +#include + struct editorConfig E; int main(int argc, char *argv[]) { char * splash_screen = (char *) calloc(256, sizeof(char)); + + // Set support for utf-8 + setlocale(LC_ALL, ""); + + // INIT enableRawMode(); initEditor(); diff --git a/src/append_buffer.c b/src/append_buffer.c index 00e14a3..6afb831 100644 --- a/src/append_buffer.c +++ b/src/append_buffer.c @@ -2,8 +2,8 @@ extern struct editorConfig E; -void abAppend(struct abuf *ab, const char *s, int len) { - char *new = realloc(ab->b, ab->len + len); +void abAppend(struct abuf *ab, const unsigned char *s, int len) { + unsigned char *new = realloc(ab->b, ab->len + len); if (new == NULL) { return; diff --git a/src/builtins.c b/src/builtins.c index 0a1c7a5..b580197 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -5,22 +5,31 @@ #include "../include/editor_op.h" #include "../include/row_op.h" #include "../include/data.h" +#include "../include/terminal.h" #include #include #include +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_sequence = lisp_string(lisp_car(args)); + const char *key_string = lisp_string(lisp_car(args)); + KeyInfo *key = stringToCodepoint(key_string); args = lisp_cdr(args); // second argument Lisp func = lisp_car(args); E.key_binds = (struct keyBind_t *)realloc(E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t)); - E.key_binds[E.number_of_keybinds - 1].key_sequence = (char *) malloc(50 * sizeof(char)); + E.key_binds[E.number_of_keybinds - 1].key_sequence = (KeyInfo *) malloc(sizeof(KeyInfo)); - strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50); + memcpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key, sizeof(KeyInfo)); E.key_binds[E.number_of_keybinds - 1].command = func; @@ -29,26 +38,30 @@ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) { 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': - editorMoveCursor(ARROW_UP); + key.data.arrow = 'A'; break; case 'd': - editorMoveCursor(ARROW_DOWN); + key.data.arrow = 'B'; break; case 'r': - editorMoveCursor(ARROW_RIGHT); + key.data.arrow = 'C'; break; case 'l': - editorMoveCursor(ARROW_LEFT); + 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."); @@ -77,7 +90,7 @@ Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx) { Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx) { - editorInsertNewLine(); + // editorInsertNewLine(); return lisp_null(); @@ -105,8 +118,11 @@ Lisp deletePreviousChar(Lisp args, LispError* e, LispContext ctx) { Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) { E.cursor_y = E.row_offset; int times = E.screenrows; + KeyInfo key; + key.type = KEY_ARROW; + key.data.arrow = 'D'; while (--times) { - editorMoveCursor(ARROW_UP); + editorMoveCursor(&key); } return lisp_null(); } @@ -117,8 +133,11 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx) { E.cursor_y = E.numrows; } int times = E.screenrows; + KeyInfo key; + key.type = KEY_ARROW; + key.data.arrow = 'D'; while (--times) { - editorMoveCursor(ARROW_DOWN); + editorMoveCursor(&key); } return lisp_null(); @@ -134,8 +153,9 @@ Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) { Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) { - char c = lisp_string(lisp_car(args))[0]; - editorInsertChar(c); + char *c = lisp_string(lisp_car(args)); + utf_8_char_t ch = make_utf8_char(c, 1); + editorInsertChar(&ch); return lisp_null(); } @@ -168,7 +188,8 @@ Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) { } 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]); + // 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(); } diff --git a/src/editor_op.c b/src/editor_op.c index 3506ffd..cbc5921 100644 --- a/src/editor_op.c +++ b/src/editor_op.c @@ -1,47 +1,94 @@ #include "../include/editor_op.h" #include "../include/row_op.h" +#include "include/data.h" +#include extern struct editorConfig E; -void editorInsertChar(int c) { - if (E.cursor_y == E.numrows) { - editorInsertRow(E.numrows, "", 0); - } - editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, c); - E.cursor_x++; +void editorInsertChar(utf_8_char_t *c) { + if (E.state == READ_ONLY) return; + fprintf(stderr, "Insert char %s %d\n", c->c, c->len); + // If cursor is past end of file, add empty rows + if (E.cursor_y == E.numrows) { + editorInsertRow(E.numrows, "", 0); + } + + // Insert character at cursor position + editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, *c); + E.cursor_x++; } -void editorInsertNewLine() { - erow *row; - if (!E.cursor_x) { +void editorInsertNewline(void) { + if (E.state == READ_ONLY) return; + + if (E.cursor_x == 0) { + // Insert blank line before current line editorInsertRow(E.cursor_y, "", 0); } else { - row = &E.row[E.cursor_y]; - editorInsertRow(E.cursor_y + 1, &row->chars[E.cursor_x], - row->size - E.cursor_x); - row = &E.row[E.cursor_y]; + // Split current line at cursor + erow *row = &E.row[E.cursor_y]; + + // Calculate byte length of remaining part + int remaining_chars = row->size - E.cursor_x; + + // Allocate buffer for remaining characters + char *buf = malloc(remaining_chars * 4); // Max 4 bytes per UTF-8 char + int buf_len = 0; + + // Convert utf_8_char_t to bytes + for (int i = E.cursor_x; i < row->size; i++) { + for (int j = 0; j < row->chars[i].len; j++) { + buf[buf_len++] = row->chars[i].c[j]; + } + } + + // Insert new row with remaining text + editorInsertRow(E.cursor_y + 1, buf, buf_len); + free(buf); + + // Truncate current row at cursor + row = &E.row[E.cursor_y]; // Refresh pointer after realloc row->size = E.cursor_x; - row->chars[row->size] = '\0'; editorUpdateRow(row); } - ++E.cursor_y; + + E.cursor_y++; E.cursor_x = 0; } -void editorDelChar() { - erow *row; - if (E.cursor_y == E.numrows || !(E.cursor_x || E.cursor_y)) { - return; - } - row = &E.row[E.cursor_y]; +void editorRowAppendRow(erow *dest, erow *src) { + // Allocate space for combined rows + utf_8_char_t *new_chars = realloc(dest->chars, + sizeof(utf_8_char_t) * (dest->size + src->size)); + if (!new_chars) return; + + dest->chars = new_chars; + + // Copy source row characters + memcpy(&dest->chars[dest->size], src->chars, sizeof(utf_8_char_t) * src->size); + dest->size += src->size; + + editorUpdateRow(dest); + ++E.dirty; +} + +void editorDelChar(void) { + if (E.state == READ_ONLY) return; + if (E.cursor_y == E.numrows) return; + if (E.cursor_x == 0 && E.cursor_y == 0) return; + + erow *row = &E.row[E.cursor_y]; + if (E.cursor_x > 0) { + // Delete character before cursor editorRowDelchar(row, E.cursor_x - 1); - --E.cursor_x; + E.cursor_x--; } else { + // At beginning of line - join with previous line E.cursor_x = E.row[E.cursor_y - 1].size; - editorRowAppendString(&E.row[E.cursor_y - 1], row->chars, row->size); + editorRowAppendRow(&E.row[E.cursor_y - 1], row); editorDelRow(E.cursor_y); - --E.cursor_y; + E.cursor_y--; } } diff --git a/src/file_io.c b/src/file_io.c index a0fb3a7..811d01a 100644 --- a/src/file_io.c +++ b/src/file_io.c @@ -7,6 +7,7 @@ #include #include #include +#include extern char *strdup(const char *); extern ssize_t getline(char **restrict lineptr, size_t *restrict n, @@ -14,37 +15,56 @@ extern ssize_t getline(char **restrict lineptr, size_t *restrict n, 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; + // Calculate total byte length (not character count) for (j = 0; j < E.numrows; ++j) { - tot_len += E.row[j].size + 1; + // Count actual bytes in each character + for (i = 0; i < E.row[j].size; i++) { + tot_len += E.row[j].chars[i].len; + } + tot_len++; // For newline } + *buffer_len = tot_len; buf = malloc(tot_len); + if (!buf) return NULL; + p = buf; for (j = 0; j < E.numrows; ++j) { - memcpy(p, E.row[j].chars, E.row[j].size); - p += E.row[j].size; - *p = '\n'; - p++; + // Copy each character's bytes + for (i = 0; i < E.row[j].size; i++) { + for (int k = 0; k < E.row[j].chars[i].len; k++) { + *p++ = E.row[j].chars[i].c[k]; + } + } + *p++ = '\n'; } return buf; } 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; @@ -56,25 +76,29 @@ void editorOpen(char *filename) { // Test if a file is already open if (E.filename != NULL) { editorCloseFile(); - E.state = READ_AND_WRITE; } + E.state = READ_AND_WRITE; - free(E.filename); E.filename = strdup(filename); - fp = fopen(filename, "a+"); - if (!fp) - die("fopen"); + fp = fopen(filename, "r"); + if (!fp) { + // File doesn't exist - that's okay, we'll create it on save + E.dirty = 0; + return; + } char *line = NULL; size_t line_cap = 0; ssize_t line_len; while ((line_len = getline(&line, &line_cap, fp)) != -1) { + // Strip newline characters while (line_len > 0 && (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) { --line_len; } + // editorInsertRow will convert bytes to utf_8_char_t editorInsertRow(E.numrows, line, line_len); } free(line); @@ -86,6 +110,7 @@ void editorSave() { int len; char *buf; int fd; + if (E.filename == NULL) { E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1); if (E.filename == NULL) { @@ -93,38 +118,100 @@ void editorSave() { return; } } + buf = editorRowsToString(&len); - fd = open(E.filename, O_RDWR | O_CREAT, 0644); + if (!buf) { + editorSetStatusMessage("Can't save! Memory error"); + return; + } + + fd = open(E.filename, O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd != -1) { - if (ftruncate(fd, len) != -1) { - if (write(fd, buf, len) == len) { - close(fd); - free(buf); - E.dirty = 0; - editorSetStatusMessage("%d bytes written to disk", len); - return; - } + if (write(fd, buf, len) == len) { + close(fd); + free(buf); + E.dirty = 0; + editorSetStatusMessage("%d bytes written to disk", len); + return; } close(fd); } + free(buf); editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); } -void editorFind() { - char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0); - if (query == NULL) return; - int i; - for (i = E.cursor_y + 1; i < E.numrows; i++) { - erow *row = &E.row[i]; - char *match = strstr(row->render, query); - if (match) { - E.cursor_y = i; - E.cursor_x = editorRowRxToCx(row, match - row->render); - E.row_offset = E.numrows; - break; +// Helper to convert utf_8_char_t array to byte string for searching +static char *row_to_string(erow *row) { + // Calculate byte length + int byte_len = 0; + for (int i = 0; i < row->rsize; i++) { + byte_len += row->render[i].len; } - } - free(query); + + char *str = malloc(byte_len + 1); + if (!str) return NULL; + + // Convert to bytes + int pos = 0; + for (int i = 0; i < row->rsize; i++) { + for (int j = 0; j < row->render[i].len; j++) { + str[pos++] = row->render[i].c[j]; + } + } + str[pos] = '\0'; + + return str; } +void editorFind() { + char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0); + if (query == NULL) return; + + int saved_cursor_x = E.cursor_x; + int saved_cursor_y = E.cursor_y; + int saved_row_offset = E.row_offset; + int saved_col_offset = E.col_offset; + + // Search from current position forward + for (int i = E.cursor_y; i < E.numrows; i++) { + erow *row = &E.row[i]; + + // Convert row to byte string for searching + char *render_str = row_to_string(row); + if (!render_str) continue; + + char *match = strstr(render_str, query); + if (match) { + E.cursor_y = i; + + // Find the character index from byte position + int byte_pos = match - render_str; + int char_idx = 0; + int current_byte = 0; + + for (char_idx = 0; char_idx < row->rsize; char_idx++) { + if (current_byte >= byte_pos) break; + current_byte += row->render[char_idx].len; + } + + E.cursor_x = editorRowRxToCx(row, char_idx); + E.row_offset = E.numrows; // Force scroll + + free(render_str); + free(query); + return; + } + + free(render_str); + } + + // Not found - restore cursor position + E.cursor_x = saved_cursor_x; + E.cursor_y = saved_cursor_y; + E.row_offset = saved_row_offset; + E.col_offset = saved_col_offset; + + editorSetStatusMessage("Not found: %s", query); + free(query); +} diff --git a/src/init.c b/src/init.c index 00df766..a0e5ac0 100644 --- a/src/init.c +++ b/src/init.c @@ -86,6 +86,6 @@ void initEditor() { (int)lisp_eval(lisp_read("QUIT-TIMES", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx) .val.int_val; - + fprintf(stderr, "Tab %d\n", E.constantes.QUIT_TIMES); E.quit_times_buffer = E.constantes.QUIT_TIMES; } diff --git a/src/input.c b/src/input.c index b1bdbca..0632fff 100644 --- a/src/input.c +++ b/src/input.c @@ -1,200 +1,147 @@ #include "../include/input.h" +#include "../include/define.h" #include "../include/editor_op.h" #include "../include/output.h" -#include "../include/define.h" +#include "include/data.h" #include -#include #include #include #include #include #include +#include #include 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; +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 path; + return strdup(path); } - // Find dir name - char * last_slash = strrchr(path, '/'); - if (last_slash) { - size_t dir_len = last_slash - path + 1; // length of dir_path - strncpy(directory, path, dir_len); - predict_len = strlen(path) - dir_len - 1; - strncpy(predict, last_slash + 1, predict_len); - directory[dir_len] = '\0'; - predict[predict_len] = '\0'; - fprintf(stderr, "%s %s\n", directory, predict); - } else { - return NULL; - } - + // 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, "/"); // add slash for directories - } - - return strdup(full_path); - } - } - - // Cleanup when no more entries - closedir(dir); - dir = NULL; 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, char bPathMode) * \brief Return user input in a prompt when enter is hit. */ -char *editorPrompt(char *prompt, char * placeHolder, char bPathMode) { +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); + strcpy(buf, placeHolder); + buf_len = strlen(placeHolder); while (1) { - editorSetStatusMessage(prompt, buf); + editorSetStatusMessage(prompt, buf); editorRefreshScreen(); - c = editorReadKey(); - if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { + + KeyInfo *key = editorReadKey(); + + // Handle backspace/delete + if (key->type == KEY_SPECIAL && (key->data.special == 127 || key->data.special == 8)) { if (buf_len != 0) { buf[--buf_len] = '\0'; } - } else if (c == ESCAPE) { + } + // Handle Ctrl+H (backspace) + else if (key->type == KEY_CTRL && key->data.ctrl_char == 'H') { + if (buf_len != 0) { + buf[--buf_len] = '\0'; + } + } + // Handle ESC + else if (key->type == KEY_SPECIAL && key->data.special == 27) { editorSetStatusMessage(""); free(buf); return NULL; - } else if (c == '\r') { + } + // Handle Enter + else if (key->type == KEY_SPECIAL && (key->data.special == 13 || key->data.special == 10)) { if (buf_len != 0) { editorSetStatusMessage(""); return buf; } - } else if (bPathMode && c == '\t') { - char path[128]; - char * pwd; - if (buf[0] != '/') { - pwd = getenv("PWD"); - fprintf(stderr, "%s\n", pwd); - memcpy(path, pwd, strlen(pwd)); - path[strlen(pwd)] = '/'; - strncat(path, buf, buf_len); - } else { - strcpy(path, buf); - } - memset(buf, 0, 128); - buf_len = 0; - strcpy(buf, file_completion(path)); - buf_len = strlen(buf); - buf[buf_len] = '\0'; - - } 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'; } } } -char *key_to_string(int key) { - static char key_str[32]; - - char tmp[10]; - sprintf(tmp, "%d", key); - - - // First test enter key - - if (key == '\r') { - strcpy(key_str, "ENTER"); - } else if (key >= 1 && key <= 26) { // CTRL keys - snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1); - } else { - switch (key) { - case ARROW_UP: - strcpy(key_str, "ARROW-UP"); - break; - case ARROW_DOWN: - strcpy(key_str, "ARROW-DOWN"); - break; - case ARROW_LEFT: - strcpy(key_str, "ARROW-LEFT"); - break; - case ARROW_RIGHT: - strcpy(key_str, "ARROW-RIGHT"); - break; - case PAGE_UP: - strcpy(key_str, "PAGE-UP"); - fprintf(stderr, "pagr up\n"); - break; - case PAGE_DOWN: - strcpy(key_str, "PAGE-DOWN"); - break; - case DEL_KEY: - fprintf(stderr, "delete key\n"); - strcpy(key_str, "DEL"); - - break; - case BACKSPACE: - strcpy(key_str, "BACKSPACE"); - break; - case '\r': - strcpy(key_str, "ENTER"); - break; - case '\x1b': - strcpy(key_str, "ESCAPE"); - break; - case BEG_LINE: - strcpy(key_str, "HOME"); - break; - case END_LINE: - strcpy(key_str, "END"); - break; - default: - // For regular characters - if (isprint(key)) { - snprintf(key_str, sizeof(key_str), "%c", key); - } else { - snprintf(key_str, sizeof(key_str), "KEY-%d", key); - } - } - } - return key_str; -} - - -void editorMoveCursor(int key) { +void editorMoveCursor(KeyInfo *key) { + if (key->type != KEY_ARROW) return; + erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y]; int row_len; - switch (key) { - case ARROW_RIGHT: + + switch (key->data.arrow) { + case 'C': // Right if (row && E.cursor_x < row->size) { ++E.cursor_x; } else if (row && E.cursor_x == row->size) { @@ -202,17 +149,17 @@ void editorMoveCursor(int key) { E.cursor_x = 0; } break; - case ARROW_DOWN: + case 'B': // Down if (E.cursor_y < E.numrows) { ++E.cursor_y; } break; - case ARROW_UP: + case 'A': // Up if (E.cursor_y != 0) { --E.cursor_y; } break; - case ARROW_LEFT: + case 'D': // Left if (E.cursor_x != 0) { --E.cursor_x; } else if (E.cursor_y > 0) { @@ -229,28 +176,73 @@ void editorMoveCursor(int key) { } } -int executeKeyBind(char *key_sequence) { - int i; - for (i = 0; i < E.number_of_keybinds; ++i) { - if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) { +KeyInfo *stringToCodepoint(const char *string) { + KeyInfo *key = (KeyInfo *)malloc(sizeof(KeyInfo)); + // test control key + if (!strncmp("CTRL", string, 4)) { + key->type = KEY_CTRL; + key->data.ctrl_char = toupper(string[6]) + 64; + } else if (!strncmp("ARROW", string, 5)) { + key->type = KEY_ARROW; + if (!strcmp("UP", string + 7)) { + key->data.arrow = 'A'; + } else if (!strcmp("DOWN", string + 7)) { + key->data.arrow = 'B'; + } else if (!strcmp("RIGHT", string + 7)) { + key->data.arrow = 'C'; + } else if (!strcmp("LEFT", string + 7)) { + key->data.arrow = 'D'; + } + } + + return key; +} + +static int key_match(KeyInfo *a, KeyInfo *b) { + if (a->type != b->type) return 0; + if (a->modifiers != b->modifiers) return 0; - fprintf(stderr, "lisp function %s\n", key_sequence); - // 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); - return 1; + 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() { - int c = editorReadKey(); + KeyInfo *key = editorReadKey(); + if (!key) + return; - if (executeKeyBind(key_to_string(c))) { + if (executeKeyBind(key)) { + fprintf(stderr, "Keybinds found\n"); return; } - editorInsertChar(c); + editorInsertChar(&key->c); E.quit_times_buffer = E.constantes.QUIT_TIMES; - } diff --git a/src/output.c b/src/output.c index a8a896a..b337628 100644 --- a/src/output.c +++ b/src/output.c @@ -6,6 +6,17 @@ 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]; @@ -41,7 +52,16 @@ void editorDrawRows(struct abuf *ab) { len = 0; if (len > E.screencols) len = E.screencols; - abAppend(ab, &E.row[file_row].render[E.col_offset], len); + 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); diff --git a/src/row_op.c b/src/row_op.c index 29bd22d..1650f68 100644 --- a/src/row_op.c +++ b/src/row_op.c @@ -1,4 +1,6 @@ #include "../include/row_op.h" +#include "include/data.h" +#include "include/define.h" #include #include #include @@ -6,11 +8,29 @@ extern struct editorConfig E; +static int is_tab(utf_8_char_t *ch) { + return ch->len == 1 && ch->c[0] == '\t'; +} + +// Helper function to check if two utf_8_char_t are equal +static int utf8_char_equal(utf_8_char_t *a, utf_8_char_t *b) { + if (a->len != b->len) return 0; + return memcmp(a->c, b->c, a->len) == 0; +} + +// Helper function to create a space character +static utf_8_char_t make_space() { + utf_8_char_t space; + space.c[0] = ' '; + space.len = 1; + return space; +} + int editorRowCxToRx(erow *row, int cursor_x) { int render_x = 0; int i; for (i = 0; i < cursor_x; ++i) { - if (row->chars[i] == '\t') { + if (is_tab(&row->chars[i])) { render_x += (E.constantes.TAB_LENGTH - 1) - (render_x % E.constantes.TAB_LENGTH); } render_x++; @@ -22,7 +42,7 @@ int editorRowRxToCx(erow *row, int rx) { int cur_rx = 0; int cx; for (cx = 0; cx < row->size; cx++) { - if (row->chars[cx] == '\t') + if (is_tab(&row->chars[cx])) cur_rx += (E.constantes.TAB_LENGTH - 1) - (cur_rx % E.constantes.TAB_LENGTH); cur_rx++; if (cur_rx > rx) return cx; @@ -39,40 +59,42 @@ void editorUpdateRow(erow *row) { int i, i_render; int tabs = 0; - // counting number of tabs - + // Count number of tabs for (i = 0; i < row->size; ++i) { - tabs += - (row->chars[i] == '\t'); /**< increment tabs of 1 if chars[i] is one. */ + if (is_tab(&row->chars[i])) { + tabs++; + } } free(row->render); - row->render = malloc(row->size + tabs * (E.constantes.TAB_LENGTH - 1) + - 1); /**< Tabs needs E.constantes.TAB_LENGTH chars so E.constantes.TAB_LENGTH - 1 - more than the first already counted. */ + // Allocate space for utf_8_char_t array + row->render = malloc(sizeof(utf_8_char_t) * (row->size + tabs * (E.constantes.TAB_LENGTH - 1))); + + if (!row->render) { + row->rsize = 0; + return; + } - // end of counting i_render = 0; for (i = 0; i < row->size; ++i) { - if (row->chars[i] == '\t') { - row->render[i_render++] = ' '; + if (is_tab(&row->chars[i])) { + // Replace tab with spaces + row->render[i_render++] = make_space(); while (i_render % E.constantes.TAB_LENGTH) { - row->render[i_render++] = - ' '; /**< Addind the right amount of spaces for tabs */ + row->render[i_render++] = make_space(); } } else { row->render[i_render++] = row->chars[i]; } } - row->render[i_render] = '\0'; // Don't forget the end of string character. row->rsize = i_render; } void editorInsertRow(int at, char *s, size_t len) { if (at < 0 || at > E.numrows) { - return; } + erow *tmp = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1)); if (!tmp) { return; @@ -80,19 +102,78 @@ void editorInsertRow(int at, char *s, size_t len) { E.row = tmp; memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at)); - E.row[at].size = len; - E.row[at].chars = malloc(len + 1); - memcpy(E.row[at].chars, s, len); - E.row[at].chars[len] = '\0'; - + // Initialize the new row + E.row[at].size = 0; + E.row[at].chars = NULL; E.row[at].rsize = 0; E.row[at].render = NULL; + + // Count UTF-8 characters first + int char_count = 0; + int i = 0; + while (i < len) { + unsigned char first = (unsigned char)s[i]; + int char_len; + + if ((first & 0x80) == 0) { + char_len = 1; + } else if ((first & 0xE0) == 0xC0) { + char_len = 2; + } else if ((first & 0xF0) == 0xE0) { + char_len = 3; + } else if ((first & 0xF8) == 0xF0) { + char_len = 4; + } else { + char_len = 1; // Invalid, treat as single byte + } + + i += char_len; + char_count++; + } + + // Allocate for the actual number of characters + if (char_count > 0) { + E.row[at].chars = malloc(sizeof(utf_8_char_t) * char_count); + if (!E.row[at].chars) { + return; + } + } + + // Now convert to utf_8_char_t array + i = 0; + E.row[at].size = 0; + while (i < len && E.row[at].size < char_count) { + utf_8_char_t ch; + + unsigned char first = (unsigned char)s[i]; + if ((first & 0x80) == 0) { + ch.len = 1; + } else if ((first & 0xE0) == 0xC0) { + ch.len = 2; + } else if ((first & 0xF0) == 0xE0) { + ch.len = 3; + } else if ((first & 0xF8) == 0xF0) { + ch.len = 4; + } else { + ch.len = 1; + } + + // Copy bytes + for (int j = 0; j < ch.len && i < len; j++) { + ch.c[j] = s[i++]; + } + + E.row[at].chars[E.row[at].size++] = ch; + } + editorUpdateRow(&E.row[at]); + ++E.numrows; ++E.dirty; } + void editorFreeRow(erow *row) { free(row->render); free(row->chars); @@ -112,16 +193,17 @@ void editorDelRow(int at) { * \fn editorRowInsertChar(erow *row, int at, int c) * \param at Index of where we want to insert the char */ -void editorRowInsertChar(erow *row, int at, int c) { +void editorRowInsertChar(erow *row, int at, utf_8_char_t c) { if (E.state == READ_ONLY) return; if (at < 0 || at > row->size) { at = row->size; } - row->chars = realloc(row->chars, row->size + 2); + row->chars = realloc(row->chars, row->size + 1); memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1); - ++row->size; + ++(row->size); row->chars[at] = c; + fprintf(stderr, "Row insert : %s %d\n", c.c, c.len); editorUpdateRow(row); ++E.dirty; } @@ -130,7 +212,6 @@ void editorRowAppendString(erow *row, char *s, size_t len) { row->chars = realloc(row->chars, row->size + len + 1); memcpy(&row->chars[row->size], s, len); row->size += len; - row->chars[row->size] = '\0'; editorUpdateRow(row); ++E.dirty; } diff --git a/src/terminal.c b/src/terminal.c index 521958a..29626ba 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -2,6 +2,8 @@ #include "../include/data.h" #include +#include +#include void die(const char *s) { write(STDOUT_FILENO, "\x1b[2J", 4); @@ -35,73 +37,211 @@ void enableRawMode() { } } -int editorReadKey() { - int nread; - char c; - char seq[3]; - while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { - if (nread == -1 && errno != EAGAIN) { - die("read"); +int utf8_char_length(unsigned char first_byte) { + if ((first_byte & 0x80) == 0) + return 1; // 0xxxxxxx - ASCII + if ((first_byte & 0xE0) == 0xC0) + return 2; // 110xxxxx - 2 bytes + if ((first_byte & 0xF0) == 0xE0) + return 3; // 1110xxxx - 3 bytes + if ((first_byte & 0xF8) == 0xF0) + return 4; // 11110xxx - 4 bytes + return 1; // Invalid, treat as single byte +} + +// Convert UTF-8 to Unicode code point +unsigned int utf8_to_codepoint(const unsigned char *bytes, int len) { + if (len == 1) + return bytes[0]; + if (len == 2) + return ((bytes[0] & 0x1F) << 6) | (bytes[1] & 0x3F); + if (len == 3) + return ((bytes[0] & 0x0F) << 12) | ((bytes[1] & 0x3F) << 6) | + (bytes[2] & 0x3F); + if (len == 4) + return ((bytes[0] & 0x07) << 18) | ((bytes[1] & 0x3F) << 12) | + ((bytes[2] & 0x3F) << 6) | (bytes[3] & 0x3F); + return 0; +} + +void parse_key(unsigned char *seq, int len, KeyInfo *key) { + memcpy(key->c.c, seq, len); + key->c.len = len; + key->modifiers = MOD_NONE; + key->type = KEY_UNKNOWN; + + // Control characters (Ctrl+A to Ctrl+Z) + if (len == 1 && seq[0] < 32 && seq[0] != 27 && seq[0] != 9 && seq[0] != 10 && + seq[0] != 13) { + key->type = KEY_CTRL; + key->data.ctrl_char = seq[0] + 64; + return; + } + + // Special single characters + if (len == 1) { + switch (seq[0]) { + case 9: + case 10: + case 13: + case 27: + case 127: + key->type = KEY_SPECIAL; + key->data.special = seq[0]; + return; } } - if (c == '\x1b') { - if (read(STDIN_FILENO, &seq[0], 1) != 1 || - read(STDIN_FILENO, &seq[1], 1) != 1) { - return '\x1b'; + // Escape sequences + if (len >= 2 && seq[0] == 27) { + // Alt+key combinations + if (len == 2 && seq[1] >= 32 && seq[1] < 127) { + key->type = KEY_ALT; + key->data.alt_char = seq[1]; + return; } - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - if (read(STDIN_FILENO, &seq[2], 1) != 1) { - return '\x1b'; + + // CSI sequences (ESC [ ...) + if (len >= 3 && seq[1] == '[') { + // Arrow keys + if (len == 3) { + switch (seq[2]) { + case 'A': + case 'B': + case 'C': + case 'D': + key->type = KEY_ARROW; + key->data.arrow = seq[2]; + return; + case 'H': + case 'F': + key->type = KEY_NAVIGATION; + key->data.special = seq[2]; + return; } - if (seq[2] == '~') { - switch (seq[1]) { - case '1': - return BEG_LINE; - case '3': - return DEL_KEY; - case '4': - return END_LINE; - case '5': - return PAGE_UP; - case '6': - return PAGE_DOWN; - case '7': - return BEG_LINE; - case '8': - return END_LINE; + } + + // Modified keys (ESC [ 1 ; modifier letter) + if (len >= 6 && seq[2] == '1' && seq[3] == ';') { + int modifier = seq[4] - '0'; + char k = seq[5]; + + if (modifier & 1) + key->modifiers |= MOD_SHIFT; + if (modifier & 2) + key->modifiers |= MOD_ALT; + if (modifier & 4) + key->modifiers |= MOD_CTRL; + + switch (k) { + case 'A': + case 'B': + case 'C': + case 'D': + key->type = KEY_ARROW; + key->data.arrow = k; + return; + case 'H': + case 'F': + key->type = KEY_NAVIGATION; + key->data.special = k; + return; + } + } + + // Function keys and navigation + if (len == 4 && seq[3] == '~') { + int num = seq[2] - '0'; + if (num >= 1 && num <= 6) { + key->type = KEY_NAVIGATION; + key->data.special = seq[2]; + return; + } + } + + if (len == 5 && seq[4] == '~') { + int num = (seq[2] - '0') * 10 + (seq[3] - '0'); + if (num >= 15 && num <= 24) { + key->type = KEY_FUNCTION; + // Map to F5-F12 + int f_map[] = {15, 17, 18, 19, 20, 21, 23, 24}; + for (int i = 0; i < 8; i++) { + if (f_map[i] == num) { + key->data.function_num = i + 5; + return; + } } } - } else { - - switch (seq[1]) { - case 'A': - return ARROW_UP; - case 'B': - return ARROW_DOWN; - case 'C': - return ARROW_RIGHT; - case 'D': - return ARROW_LEFT; - case 'H': - return BEG_LINE; - case 'F': - return END_LINE; - } - } - } else if (seq[0] == 'O') { - switch (seq[1]) { - case 'H': - return BEG_LINE; - case 'F': - return END_LINE; } } - return '\x1b'; - } else { - return c; + + // SS3 sequences (ESC O ...) + if (len == 3 && seq[1] == 'O') { + switch (seq[2]) { + case 'P': + case 'Q': + case 'R': + case 'S': + key->type = KEY_FUNCTION; + key->data.function_num = seq[2] - 'P' + 1; + return; + case 'H': + case 'F': + key->type = KEY_NAVIGATION; + key->data.special = seq[2]; + return; + } + } } + + // UTF-8 character + if (seq[0] >= 32 || (seq[0] & 0x80)) { + int char_len = utf8_char_length(seq[0]); + fprintf(stderr, "char length : %d\n", char_len); + if (char_len <= len) { + key->type = KEY_CHAR; + memcpy(key->c.c, seq, len); + key->c.len = len; + return; + } + } +} + +KeyInfo *editorReadKey() { + fd_set fds; + int timeout_ms = 10; + struct timeval tv; + int total = 0; + KeyInfo *key = (KeyInfo *)malloc(sizeof(KeyInfo)); + int len; + unsigned char buffer[20]; + + if (read(STDIN_FILENO, &buffer[0], 1) <= 0) + return 0; + + while (total < 20) { + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + tv.tv_sec = 0; + tv.tv_usec = timeout_ms * 1000; + + int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); + if (ret <= 0) + break; + + if (read(STDIN_FILENO, &buffer[total], 1) <= 0) + break; + total++; + } + total++; + + parse_key(buffer, total, key); + + // DEBUG + + fprintf(stderr, "%s %d %d %s %d\n", buffer, buffer[0], buffer[1], key->c.c, key->c.len); + + return key; } int getCursorPosition(int *rows, int *cols) {