utf8 processing without struct

This commit is contained in:
2026-05-03 23:32:40 +02:00
parent eae85c32ca
commit 8e1b4d2f86
23 changed files with 637 additions and 906 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
void abAppend(struct abuf *ab, const unsigned char *s, int len); void abAppend(struct abuf *ab, const char *s, int len);
void abFree(struct abuf *ab); void abFree(struct abuf *ab);
-2
View File
@@ -31,8 +31,6 @@ Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx);
Lisp addPackage(Lisp args, LispError *e, LispContext ctx); Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx); Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx);
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx); Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx);
+8 -29
View File
@@ -8,23 +8,17 @@
#include "lisp.h" #include "lisp.h"
typedef struct{
unsigned char c[4];
char len;
} utf_8_char_t;
/** /**
* \struct erow * \struct row_t
* \brief Store one editor row * \brief Store one editor row
* \param * \param
* */ * */
typedef struct erow { typedef struct row {
int size; /**< Size of the line */ int size; /**< Size of the line */
int rsize; /**< Size of the render line */ int cap; /**< Size of the render line */
utf_8_char_t *chars; /**< Characters of the line */ char *chars; /**< Characters of the line */
utf_8_char_t *render; /**< The actual line we will print */ } row_t;
} erow;
enum editorStatus_e { enum editorStatus_e {
IDLE, IDLE,
@@ -57,24 +51,9 @@ typedef enum {
MOD_CTRL = 4 MOD_CTRL = 4
} KeyModifier; } 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 { struct keyBind_t {
KeyInfo *key_sequence; char *key_sequence;
Lisp command; Lisp command;
}; };
@@ -90,7 +69,7 @@ struct editorConfig {
int screenrows; /**< Terminal height*/ int screenrows; /**< Terminal height*/
int screencols; /**< Terminal width*/ int screencols; /**< Terminal width*/
int numrows; /**< Number of rows contained */ int numrows; /**< Number of rows contained */
erow *row; /**< Store all the rows printed */ row_t *rows; /**< Store all the rows printed */
int dirty; int dirty;
char *filename; char *filename;
enum editorStatus_e state; enum editorStatus_e state;
@@ -118,7 +97,7 @@ struct editorConfig {
* */ * */
struct abuf { struct abuf {
unsigned char *b; /**< Text that will be printed */ char *b; /**< Text that will be printed */
int len; /**< Length of the text */ int len; /**< Length of the text */
}; };
+12 -1
View File
@@ -11,7 +11,18 @@
#define TAB "\x09" #define TAB "\x09"
#define SPACE "\x20" #define SPACE "\x20"
enum editorKey_e {
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 ABUF_INIT {NULL, 0}
+1 -3
View File
@@ -2,9 +2,7 @@
#define EDITOR_OP_H_ #define EDITOR_OP_H_
#include "data.h" #include "data.h"
void editorInsertChar(utf_8_char_t *c); void editorInsertChar(int c);
void editorInsertNewLine(void);
void editorDelChar(); void editorDelChar();
-3
View File
@@ -8,9 +8,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
char *editorRowsToString(int *buffer_len);
void editorCloseFile(void); void editorCloseFile(void);
void editorOpen(char *filename); void editorOpen(char *filename);
+2 -4
View File
@@ -22,11 +22,9 @@
char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode); 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() * \fn void editorProcessKeypress()
+6 -12
View File
@@ -8,22 +8,16 @@
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
int editorRowCxToRx(erow *row, int cursor_x); void editorInsertRow(int at, char *s, int len);
int editorRowRxToCx(erow *row, int rx); void editorFreeRow(row_t *row);
void editorUpdateRow(erow *row); int editorRowCxToByte(const row_t *row, int cursor_x);
void editorInsertRow(int at, char *s, size_t len); int editorRowCharCount(row_t *row);
void editorFreeRow(erow *row); void editorRowInsertBytes(row_t *row, int at, const char *src, int len);
void editorDelRow(int at); void editorRowDelByte(row_t *row, int at, int n);
void editorRowInsertChar(erow *row, int at, utf_8_char_t c);
void editorRowAppendString(erow *row, char *s, size_t len);
void editorRowDelchar(erow *row, int at);
#endif // ROW_OP_H_ #endif // ROW_OP_H_
+3 -3
View File
@@ -25,12 +25,12 @@ void disableRawMode();
void enableRawMode(); void enableRawMode();
KeyInfo * editorReadKey(); int editorReadKey();
int getCursorPosition(int *rows, int *cols); int getCursorPosition(int *rows, int *cols);
KeyInfo *stringToCodepoint(const char *string);
int getWindowSize(int *rows, int *cols); int getWindowSize(int *rows, int *cols);
char *key_to_string(int key);
#endif #endif
+16
View File
@@ -0,0 +1,16 @@
//
// Created by Giorgio on 01/05/2026.
//
#ifndef BELUGA_UTF8_H
#define BELUGA_UTF8_H
#include <stdint.h>
uint32_t readUtf8Char(void);
int utf8Encode(uint32_t cp, char *buf);
int utf8Seqlen(unsigned char c);
int codepointWidth(uint32_t codepoint);
uint32_t utf8Decode(const char** s);
#endif //BELUGA_UTF8_H
Executable → Regular
View File
-3
View File
@@ -30,15 +30,12 @@ int main(int argc, char *argv[]) {
char * splash_screen = (char *) calloc(256, sizeof(char)); char * splash_screen = (char *) calloc(256, sizeof(char));
// Set support for utf-8
setlocale(LC_ALL, "");
// INIT // INIT
enableRawMode(); enableRawMode();
initEditor(); initEditor();
if (argc >= 2) { if (argc >= 2) {
E.state = READ_AND_WRITE;
editorOpen(argv[1]); editorOpen(argv[1]);
} else { } else {
strcat(splash_screen, getenv("HOME")); strcat(splash_screen, getenv("HOME"));
+1
View File
@@ -20,6 +20,7 @@ src_files = files(
'src/row_op.c', 'src/row_op.c',
'src/terminal.c', 'src/terminal.c',
'src/builtins.c', 'src/builtins.c',
'src/utf8.c'
) )
# Executable # Executable
+2 -4
View File
@@ -1,9 +1,7 @@
#include "../include/append_buffer.h" #include "../include/append_buffer.h"
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) { if (new == NULL) {
return; return;
+16 -41
View File
@@ -5,31 +5,22 @@
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/row_op.h" #include "../include/row_op.h"
#include "../include/data.h" #include "../include/data.h"
#include "../include/terminal.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.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) { Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
const char *key_string = lisp_string(lisp_car(args)); const char *key_sequence = lisp_string(lisp_car(args));
KeyInfo *key = stringToCodepoint(key_string);
args = lisp_cdr(args); args = lisp_cdr(args);
// second argument // second argument
Lisp func = lisp_car(args); Lisp func = lisp_car(args);
E.key_binds = E.key_binds =
(struct keyBind_t *)realloc(E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t)); (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)); E.key_binds[E.number_of_keybinds - 1].key_sequence = (char *) malloc(50 * sizeof(char));
memcpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key, sizeof(KeyInfo)); strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
E.key_binds[E.number_of_keybinds - 1].command = func; E.key_binds[E.number_of_keybinds - 1].command = func;
@@ -38,30 +29,26 @@ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) { Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
const char *direction = lisp_string(lisp_car(args)); const char *direction = lisp_string(lisp_car(args));
KeyInfo key;
key.type = KEY_ARROW;
switch (direction[0]) { switch (direction[0]) {
case 'u': case 'u':
key.data.arrow = 'A'; editorMoveCursor(ARROW_UP);
break; break;
case 'd': case 'd':
key.data.arrow = 'B'; editorMoveCursor(ARROW_DOWN);
break; break;
case 'r': case 'r':
key.data.arrow = 'C'; editorMoveCursor(ARROW_RIGHT);
break; break;
case 'l': case 'l':
key.data.arrow = 'D'; editorMoveCursor(ARROW_LEFT);
break; break;
} }
editorMoveCursor(&key);
return lisp_null(); return lisp_null();
} }
Lisp editorQuit(Lisp args, LispError* e, LispContext ctx) { Lisp editorQuit(Lisp args, LispError* e, LispContext ctx) {
fprintf(stderr, "quit\n");
if (E.dirty && E.quit_times_buffer > 0) { if (E.dirty && E.quit_times_buffer > 0) {
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q " editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
"another time to quit."); "another time to quit.");
@@ -90,7 +77,8 @@ Lisp l_editorSave(Lisp args, LispError* e, LispContext ctx) {
Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx) { Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx) {
// editorInsertNewLine(); editorInsertRow(E.numrows,"", 0);
editorMoveCursor(ARROW_DOWN);
return lisp_null(); return lisp_null();
@@ -104,7 +92,7 @@ Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
Lisp moveCursorEndLine(Lisp args, LispError* e, LispContext ctx) { Lisp moveCursorEndLine(Lisp args, LispError* e, LispContext ctx) {
if (E.cursor_y < E.numrows) { if (E.cursor_y < E.numrows) {
E.cursor_x = E.row[E.cursor_y].size; E.cursor_x = E.rows[E.cursor_y].size;
} }
return lisp_null(); return lisp_null();
} }
@@ -118,11 +106,8 @@ Lisp deletePreviousChar(Lisp args, LispError* e, LispContext ctx) {
Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) { Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx) {
E.cursor_y = E.row_offset; E.cursor_y = E.row_offset;
int times = E.screenrows; int times = E.screenrows;
KeyInfo key;
key.type = KEY_ARROW;
key.data.arrow = 'D';
while (--times) { while (--times) {
editorMoveCursor(&key); editorMoveCursor(ARROW_UP);
} }
return lisp_null(); return lisp_null();
} }
@@ -133,11 +118,8 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError* e, LispContext ctx) {
E.cursor_y = E.numrows; E.cursor_y = E.numrows;
} }
int times = E.screenrows; int times = E.screenrows;
KeyInfo key;
key.type = KEY_ARROW;
key.data.arrow = 'D';
while (--times) { while (--times) {
editorMoveCursor(&key); editorMoveCursor(ARROW_DOWN);
} }
return lisp_null(); return lisp_null();
@@ -153,9 +135,8 @@ Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) { Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
char *c = lisp_string(lisp_car(args)); char c = lisp_string(lisp_car(args))[0];
utf_8_char_t ch = make_utf8_char(c, 1); editorInsertChar(c);
editorInsertChar(&ch);
return lisp_null(); return lisp_null();
} }
@@ -177,19 +158,13 @@ Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
} }
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) { Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) {
editorFind(); editorFind();
return lisp_null(); return lisp_null();
} }
Lisp editorReadChar_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]); fprintf(stderr, "char read : %c\n", E.rows[E.cursor_y].chars[E.cursor_x]);
// return lisp_make_char(E.row[E.cursor_y].render[E.cursor_x]); return lisp_make_char(E.rows[E.cursor_y].chars[E.cursor_x]);
return lisp_null();
} }
+45 -67
View File
@@ -1,94 +1,72 @@
#include "../include/editor_op.h" #include "../include/editor_op.h"
#include "../include/row_op.h" #include "../include/row_op.h"
#include "include/data.h" #include "../include/data.h"
#include <stdio.h> #include <stdio.h>
#include "../include/utf8.h"
extern struct editorConfig E; extern struct editorConfig E;
void editorInsertChar(utf_8_char_t *c) { void editorInsertChar(int c) {
if (E.state == READ_ONLY) return; if (E.state == READ_ONLY) return;
fprintf(stderr, "Insert char %s %d\n", c->c, c->len); if (E.cursor_y == E.numrows)
// If cursor is past end of file, add empty rows
if (E.cursor_y == E.numrows) {
editorInsertRow(E.numrows, "", 0); editorInsertRow(E.numrows, "", 0);
row_t *row = &E.rows[E.cursor_y];
int byte = editorRowCxToByte(row, E.cursor_x);
char buf[4];
int n;
if (c < 0x80) {
buf[0] = c;
n = 1;
} else {
n = utf8Encode((uint32_t)c, buf);
} }
editorRowInsertBytes(row, byte, buf, n);
// Insert character at cursor position
editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, *c);
E.cursor_x++; E.cursor_x++;
E.dirty = 1;
} }
void editorInsertNewline(void) { void editorInsertNewline(const char* s, int len) {
if (E.state == READ_ONLY) return; if (E.state == READ_ONLY) return;
if (E.cursor_x == 0) { E.rows = realloc(E.rows, sizeof(row_t) * (E.numrows + 1));
// Insert blank line before current line row_t *r = &E.rows[E.numrows];
editorInsertRow(E.cursor_y, "", 0); r->cap = len + 1;
} else { r->chars = malloc(r->cap);
// Split current line at cursor memcpy(r->chars, s, len);
erow *row = &E.row[E.cursor_y]; r->size = len;
r->chars[len] = '\0';
// Calculate byte length of remaining part E.numrows++;
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;
} }
void editorRowAppendRow(erow *dest, erow *src) { void editorDelChar(void)
// 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.state == READ_ONLY) return;
if (E.cursor_y == E.numrows) return; if (E.cursor_y == E.numrows) return;
if (E.cursor_x == 0 && E.cursor_y == 0) return; if (E.cursor_x == 0 && E.cursor_y == 0) return;
erow *row = &E.row[E.cursor_y]; row_t *r = &E.rows[E.cursor_y];
if (E.cursor_x > 0) { if (E.cursor_x > 0) {
// Delete character before cursor /* find byte of previous char */
editorRowDelchar(row, E.cursor_x - 1); int byte = editorRowCxToByte(r, E.cursor_x);
/* step back one character */
int start = byte;
/* walk from beginning to find start of char at cx-1 */
start = editorRowCxToByte(r, E.cursor_x - 1);
editorRowDelByte(r, start, byte - start);
E.cursor_x--; E.cursor_x--;
E.dirty = 1;
} else { } else {
// At beginning of line - join with previous line /* merge with previous row */
E.cursor_x = E.row[E.cursor_y - 1].size; row_t *prev = &E.rows[E.cursor_y - 1];
editorRowAppendRow(&E.row[E.cursor_y - 1], row); E.cursor_x = editorRowCharCount(prev);
editorDelRow(E.cursor_y); editorRowInsertBytes(prev, prev->size, r->chars, r->size);
free(r->chars);
memmove(&E.rows[E.cursor_y], &E.rows[E.cursor_y + 1],
sizeof(row_t) * (E.numrows - E.cursor_y - 1));
E.numrows--;
E.cursor_y--; E.cursor_y--;
E.dirty = 1;
} }
} }
+54 -106
View File
@@ -9,50 +9,15 @@
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
extern char *strdup(const char *);
extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
FILE *restrict stream);
extern int ftruncate(int fd, off_t length);
extern struct editorConfig E; extern struct editorConfig E;
// Convert utf_8_char_t array to byte string
char *editorRowsToString(int *buffer_len) {
int tot_len = 0;
int j, i;
char *buf;
char *p;
// Calculate total byte length (not character count) void editorCloseFile(void)
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) {
// 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 // Free all rows
for (int i = 0; i < E.numrows; i++) { for (int i = 0; i < E.numrows; i++)
editorFreeRow(&E.row[i]); {
editorFreeRow(&E.rows[i]);
} }
E.cursor_x = 0; E.cursor_x = 0;
@@ -61,8 +26,8 @@ void editorCloseFile(void) {
E.row_offset = 0; E.row_offset = 0;
E.col_offset = 0; E.col_offset = 0;
E.numrows = 0; E.numrows = 0;
free(E.row); free(E.rows);
E.row = NULL; E.rows = NULL;
E.dirty = 0; E.dirty = 0;
free(E.filename); free(E.filename);
E.filename = NULL; E.filename = NULL;
@@ -70,11 +35,13 @@ void editorCloseFile(void) {
E.status_msg_time = 0; E.status_msg_time = 0;
} }
void editorOpen(char *filename) { void editorOpen(char* filename)
FILE *fp; {
FILE* fp;
// Test if a file is already open // Test if a file is already open
if (E.filename != NULL) { if (E.filename != NULL)
{
editorCloseFile(); editorCloseFile();
} }
E.state = READ_AND_WRITE; E.state = READ_AND_WRITE;
@@ -82,107 +49,87 @@ void editorOpen(char *filename) {
E.filename = strdup(filename); E.filename = strdup(filename);
fp = fopen(filename, "r"); fp = fopen(filename, "r");
if (!fp) { fprintf(stderr, "reading file %s\n", filename);
if (!fp)
{
// File doesn't exist - that's okay, we'll create it on save // File doesn't exist - that's okay, we'll create it on save
E.dirty = 0; E.dirty = 0;
return; return;
} }
char *line = NULL; char* line = NULL;
size_t line_cap = 0; size_t line_len;
ssize_t line_len;
while ((line_len = getline(&line, &line_cap, fp)) != -1) { while ((line_len = getline(&line, &line_len, fp)) != -1)
{
fprintf(stderr, "%s %d", line, line_len);
// Strip newline characters // Strip newline characters
while (line_len > 0 && while (line_len > 0 &&
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) { (line[line_len - 1] == '\n' || line[line_len - 1] == '\r'))
{
--line_len; --line_len;
} }
fprintf(stderr, "len %d\n", line_len);
// editorInsertRow will convert bytes to utf_8_char_t // editorInsertRow will convert bytes to utf_8_char_t
editorInsertRow(E.numrows, line, line_len); fprintf(stderr, "row number : %d\n", E.numrows);
editorInsertRow(E.numrows, line, (int) line_len);
} }
free(line); free(line);
fclose(fp); fclose(fp);
E.dirty = 0; E.dirty = 0;
} }
void editorSave() { void editorSave()
{
int len; int len;
char *buf; char* buf;
int fd; int fd;
if (E.filename == NULL) { if (E.filename == NULL)
{
E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1); E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
if (E.filename == NULL) { if (E.filename == NULL)
{
editorSetStatusMessage("Save aborted"); editorSetStatusMessage("Save aborted");
return; return;
} }
} }
buf = editorRowsToString(&len);
if (!buf) {
editorSetStatusMessage("Can't save! Memory error");
return;
}
fd = open(E.filename, O_RDWR | O_CREAT | O_TRUNC, 0644); fd = open(E.filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd != -1) { for (int i = 0; i < E.numrows; i++)
if (write(fd, buf, len) == len) { {
close(fd); write(fd, E.rows[i].chars, E.rows[i].size);
free(buf); write(fd, "\n", 1);
E.dirty = 0; // fputc('\n', fp);
editorSetStatusMessage("%d bytes written to disk", len);
return;
}
close(fd);
} }
close(fd);
free(buf); E.dirty = 0;
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); editorSetStatusMessage("%d bytes written to disk", len);
} }
// Helper to convert utf_8_char_t array to byte string for searching void editorFind()
static char *row_to_string(erow *row) { {
// Calculate byte length char* query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
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; if (query == NULL) return;
int saved_cursor_x = E.cursor_x; int saved_cursor_x = E.cursor_x;
int saved_cursor_y = E.cursor_y; int saved_cursor_y = E.cursor_y;
int saved_row_offset = E.row_offset; int saved_row_offset = E.row_offset;
int saved_col_offset = E.col_offset; int saved_col_offset = E.col_offset;
#if 0
// Search from current position forward // Search from current position forward
for (int i = E.cursor_y; i < E.numrows; i++) { for (int i = E.cursor_y; i < E.numrows; i++)
erow *row = &E.row[i]; {
row_t* row = &E.rows[i];
// Convert row to byte string for searching // Convert row to byte string for searching
char *render_str = row_to_string(row); char* render_str = row_to_string(row);
if (!render_str) continue; if (!render_str) continue;
char *match = strstr(render_str, query); char* match = strstr(render_str, query);
if (match) { if (match)
{
E.cursor_y = i; E.cursor_y = i;
// Find the character index from byte position // Find the character index from byte position
@@ -190,7 +137,8 @@ void editorFind() {
int char_idx = 0; int char_idx = 0;
int current_byte = 0; int current_byte = 0;
for (char_idx = 0; char_idx < row->rsize; char_idx++) { for (char_idx = 0; char_idx < row->rsize; char_idx++)
{
if (current_byte >= byte_pos) break; if (current_byte >= byte_pos) break;
current_byte += row->render[char_idx].len; current_byte += row->render[char_idx].len;
} }
@@ -211,7 +159,7 @@ void editorFind() {
E.cursor_y = saved_cursor_y; E.cursor_y = saved_cursor_y;
E.row_offset = saved_row_offset; E.row_offset = saved_row_offset;
E.col_offset = saved_col_offset; E.col_offset = saved_col_offset;
#endif
editorSetStatusMessage("Not found: %s", query); editorSetStatusMessage("Not found: %s", query);
free(query); free(query);
} }
+1 -2
View File
@@ -32,7 +32,6 @@ void initBuiltins() {
registerBuiltin("EDITOR-OPEN-FILE", editorOpenFile); registerBuiltin("EDITOR-OPEN-FILE", editorOpenFile);
registerBuiltin("EDITOR-INSERT-CHAR", editorPrintC); registerBuiltin("EDITOR-INSERT-CHAR", editorPrintC);
registerBuiltin("ADD-PACKAGE", addPackage); registerBuiltin("ADD-PACKAGE", addPackage);
registerBuiltin("EDITOR-DEL-ROW", editorDelRow_L);
registerBuiltin("EDITOR-FIND", editorFind_L); registerBuiltin("EDITOR-FIND", editorFind_L);
registerBuiltin("EDITOR-READ-CHAR", editorReadChar_L); registerBuiltin("EDITOR-READ-CHAR", editorReadChar_L);
} }
@@ -45,7 +44,7 @@ void initEditor() {
E.row_offset = 0; E.row_offset = 0;
E.col_offset = 0; E.col_offset = 0;
E.numrows = 0; E.numrows = 0;
E.row = NULL; E.rows = NULL;
E.dirty = 0; E.dirty = 0;
E.filename = NULL; E.filename = NULL;
E.state = READ_ONLY; E.state = READ_ONLY;
+128 -182
View File
@@ -13,236 +13,182 @@
#include <unistd.h> #include <unistd.h>
extern struct editorConfig E; extern struct editorConfig E;
char * file_completion(const char *path) {
char *file_completion(const char *path) { DIR * dir;
DIR *dir; struct dirent *entry;
struct dirent *entry; char directory[128];
char directory[128]; char predict[128];
char predict[128]; int predict_len = 0;
int predict_len = 0;
if (path[strlen(path) - 1] == '/') { if (path[strlen(path) - 1] == '/') {
return strdup(path); return path;
} }
// Find dir name // Find dir name
char *last_slash = strrchr(path, '/'); char * last_slash = strrchr(path, '/');
if (last_slash) { if (last_slash) {
size_t dir_len = last_slash - path + 1; size_t dir_len = last_slash - path + 1; // length of dir_path
strncpy(directory, path, dir_len); strncpy(directory, path, dir_len);
predict_len = strlen(path) - dir_len; predict_len = strlen(path) - dir_len - 1;
strncpy(predict, last_slash + 1, predict_len); strncpy(predict, last_slash + 1, predict_len);
directory[dir_len] = '\0'; directory[dir_len] = '\0';
predict[predict_len] = '\0'; predict[predict_len] = '\0';
} else { fprintf(stderr, "%s %s\n", directory, predict);
return NULL; } else {
} return NULL;
}
dir = opendir(directory); dir = opendir(directory);
if (!dir) 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; 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) * \fn char * editorPrompt(struct editorConfig *E, char *prompt, char bPathMode)
* \brief Return user input in a prompt when enter is hit. */ * \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; size_t buf_size = 128;
char *buf = malloc(buf_size); char *buf = malloc(buf_size);
size_t buf_len = 0; size_t buf_len = 0;
int c = 0;
buf[0] = '\0'; buf[0] = '\0';
strcpy(buf, placeHolder); strcpy(buf, placeHolder);
buf_len = strlen(placeHolder); buf_len = strlen(placeHolder);
while (1) { while (1) {
editorSetStatusMessage(prompt, buf); editorSetStatusMessage(prompt, buf);
editorRefreshScreen(); editorRefreshScreen();
c = editorReadKey();
KeyInfo *key = editorReadKey(); if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
// Handle backspace/delete
if (key->type == KEY_SPECIAL && (key->data.special == 127 || key->data.special == 8)) {
if (buf_len != 0) { if (buf_len != 0) {
buf[--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(""); editorSetStatusMessage("");
free(buf); free(buf);
return NULL; 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) { if (buf_len != 0) {
editorSetStatusMessage(""); editorSetStatusMessage("");
return buf; return buf;
} }
} } else if (bPathMode && c == '\t') {
// Handle Tab for path completion char path[128];
else if (bPathMode && key->type == KEY_SPECIAL && key->data.special == 9) { char * pwd;
char path[128]; if (buf[0] != '/') {
char *pwd; pwd = getenv("PWD");
if (buf[0] != '/') { fprintf(stderr, "%s\n", pwd);
pwd = getenv("PWD"); memcpy(path, pwd, strlen(pwd));
snprintf(path, sizeof(path), "%s/%s", pwd, buf); path[strlen(pwd)] = '/';
} else { strncat(path, buf, buf_len);
strcpy(path, buf); } else {
} strcpy(path, buf);
}
memset(buf, 0, 128);
buf_len = 0;
strcpy(buf, file_completion(path));
buf_len = strlen(buf);
buf[buf_len] = '\0';
char *completion = file_completion(path); } else if (!iscntrl(c) && c < 128) {
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) { if (buf_len == buf_size - 1) {
buf_size *= 2; buf_size *= 2;
buf = realloc(buf, buf_size); buf = realloc(buf, buf_size);
} }
buf[buf_len++] = (char)key->data.codepoint; buf[buf_len++] = c;
buf[buf_len] = '\0'; buf[buf_len] = '\0';
} }
} }
} }
void editorMoveCursor(KeyInfo *key) { void editorMoveCursor(int key)
if (key->type != KEY_ARROW) return; {
row_t *row = (E.cursor_y >= E.numrows) ? NULL : &E.rows[E.cursor_y];
int row_len;
switch (key) {
case ARROW_RIGHT:
if (row && E.cursor_x < row->size) {
++E.cursor_x;
} else if (row && E.cursor_x == row->size) {
E.cursor_y++;
E.cursor_x = 0;
}
break;
case ARROW_DOWN:
if (E.cursor_y < E.numrows) {
++E.cursor_y;
}
break;
case ARROW_UP:
if (E.cursor_y != 0) {
--E.cursor_y;
}
break;
case ARROW_LEFT:
if (E.cursor_x != 0) {
--E.cursor_x;
} else if (E.cursor_y > 0) {
--E.cursor_y;
E.cursor_x = E.rows[E.cursor_y].size;
}
break;
}
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y]; row = (E.cursor_y >= E.numrows) ? NULL : &E.rows[E.cursor_y];
int row_len; row_len = row ? row->size : 0;
if (E.cursor_x > row_len) {
switch (key->data.arrow) { E.cursor_x = row_len;
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 'B': // Down
if (E.cursor_y < E.numrows) {
++E.cursor_y;
}
break;
case 'A': // Up
if (E.cursor_y != 0) {
--E.cursor_y;
}
break;
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_len = row ? row->size : 0;
if (E.cursor_x > row_len) {
E.cursor_x = row_len;
}
} }
KeyInfo *stringToCodepoint(const char *string) { int executeKeyBind(char *key_sequence) {
KeyInfo *key = (KeyInfo *)malloc(sizeof(KeyInfo)); int i;
// test control key for (i = 0; i < E.number_of_keybinds; ++i) {
if (!strncmp("CTRL", string, 4)) { if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
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; 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),
static int key_match(KeyInfo *a, KeyInfo *b) { &E.ctx_error, E.ctx);
if (a->type != b->type) return 0; return 1;
if (a->modifiers != b->modifiers) return 0; }
}
switch (a->type) { return 0;
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() { void editorProcessKeypress() {
KeyInfo *key = editorReadKey(); int c = editorReadKey();
if (!key) char* key_sequence;
return;
key_sequence = key_to_string(c);
fprintf(stderr, "%s\n", key_sequence);
if (executeKeyBind(key_to_string(c))) {
return;
}
editorInsertChar(c);
E.quit_times_buffer = E.constantes.QUIT_TIMES;
if (executeKeyBind(key)) {
fprintf(stderr, "Keybinds found\n");
return;
}
editorInsertChar(&key->c);
E.quit_times_buffer = E.constantes.QUIT_TIMES;
} }
+43 -44
View File
@@ -4,18 +4,7 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
extern struct editorConfig E; #include "include/utf8.h"
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) { void editorDrawRows(struct abuf *ab) {
int y; int y;
@@ -24,6 +13,8 @@ void editorDrawRows(struct abuf *ab) {
int padding; int padding;
int len; int len;
int file_row; int file_row;
row_t *row;
for (y = 0; y < E.screenrows; ++y) { for (y = 0; y < E.screenrows; ++y) {
file_row = y + E.row_offset; file_row = y + E.row_offset;
if (file_row >= E.numrows) { if (file_row >= E.numrows) {
@@ -47,20 +38,22 @@ void editorDrawRows(struct abuf *ab) {
abAppend(ab, "~", 1); abAppend(ab, "~", 1);
} }
} else { } else {
len = E.row[file_row].rsize - E.col_offset; int rx = 0, i = 0;
if (len < 0) int rendered = 0;
len = 0; row_t *row = &E.rows[E.row_offset + y];
if (len > E.screencols) while (i < row->size && rendered < E.screencols) {
len = E.screencols; int sl = utf8Seqlen((unsigned char)row->chars[i]);
if (len > 0) { if (sl < 1) sl = 1;
unsigned char *display_buf = malloc(len * 4); // Max 4 bytes per char const char *p = row->chars + i;
int byte_len; uint32_t cp = utf8Decode(&p);
int w = codepointWidth(cp); if (w == 0) w = 1;
utf8_to_bytes(&E.row[file_row].render[E.col_offset], len, display_buf, if (rx >= E.col_offset) {
&byte_len); if (rendered + w > E.screencols) break;
abAppend(ab, display_buf, byte_len); abAppend(ab, row->chars + i, sl);
fprintf(stderr, "display buffer : %s %d\n", display_buf, byte_len); rendered += w;
free(display_buf); }
rx += w;
i += sl;
} }
} }
abAppend(ab, ERASE_END_LINE, 3); abAppend(ab, ERASE_END_LINE, 3);
@@ -68,24 +61,29 @@ void editorDrawRows(struct abuf *ab) {
} }
} }
void editorScroll() { int editorCxToRx(void) {
E.rx = E.cursor_x; if (E.cursor_y >= E.numrows) return E.cursor_x;
if (E.cursor_y < E.numrows) { row_t *row = &E.rows[E.cursor_y];
E.rx = editorRowCxToRx(&E.row[E.cursor_y], E.cursor_x); int rx = 0, i = 0, col = 0;
while (col < E.cursor_x && i < row->size) {
int sl = utf8Seqlen((unsigned char)row->chars[i]);
if (sl < 1) sl = 1;
const char *p = row->chars + i;
uint32_t cp = utf8Decode(&p);
int w = codepointWidth(cp);
if (w == 0) w = 1;
rx += w;
i += sl; col++;
} }
return rx;
}
if (E.cursor_y < E.row_offset) { void editorScroll() {
E.row_offset = E.cursor_y; E.rx = editorCxToRx();
} if (E.cursor_y < E.row_offset) E.row_offset = E.cursor_y;
if (E.cursor_y >= E.row_offset + E.screenrows) { if (E.cursor_y >= E.row_offset + E.screenrows) E.row_offset = E.cursor_y - E.screenrows + 1;
E.row_offset = E.cursor_y - E.screenrows + 1; if (E.rx < E.col_offset) E.col_offset = E.rx;
} if (E.rx >= E.col_offset + E.screencols) E.col_offset = E.rx - E.screencols + 1;
if (E.rx < E.col_offset) {
E.col_offset = E.rx;
}
if (E.rx >= E.col_offset + E.screencols) {
E.col_offset = E.rx - E.screencols + 1;
}
} }
void editorDrawStatusBar(struct abuf *ab) { void editorDrawStatusBar(struct abuf *ab) {
@@ -130,6 +128,7 @@ void editorRefreshScreen() {
editorScroll(); editorScroll();
struct abuf ab = ABUF_INIT; struct abuf ab = ABUF_INIT;
char buf[32]; char buf[32];
int len;
abAppend(&ab, HIDE_CURSOR, 6); abAppend(&ab, HIDE_CURSOR, 6);
abAppend(&ab, CURSOR_TOP_LEFT, 3); abAppend(&ab, CURSOR_TOP_LEFT, 3);
@@ -138,9 +137,9 @@ void editorRefreshScreen() {
editorDrawStatusBar(&ab); editorDrawStatusBar(&ab);
editorDrawMessageBar(&ab); editorDrawMessageBar(&ab);
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cursor_y - E.row_offset) + 1, len = snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cursor_y - E.row_offset) + 1,
(E.rx - E.col_offset) + 1); (E.rx - E.col_offset) + 1);
abAppend(&ab, buf, strlen(buf)); abAppend(&ab, buf, len);
abAppend(&ab, SHOW_CURSOR, 6); abAppend(&ab, SHOW_CURSOR, 6);
+43 -184
View File
@@ -6,213 +6,65 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
extern struct editorConfig E; #include "include/utf8.h"
static int is_tab(utf_8_char_t *ch) { void editorInsertRow(int at, char *s, int len) {
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 (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.
* */
void editorUpdateRow(erow *row) {
int i, i_render;
int tabs = 0;
// Count number of tabs
for (i = 0; i < row->size; ++i) {
if (is_tab(&row->chars[i])) {
tabs++;
}
}
free(row->render);
// 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;
}
i_render = 0;
for (i = 0; i < row->size; ++i) {
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->rsize = i_render;
}
void editorInsertRow(int at, char *s, size_t len) {
if (at < 0 || at > E.numrows) { if (at < 0 || at > E.numrows) {
return; return;
} }
erow *tmp = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1)); row_t *tmp = (row_t *)realloc(E.rows, sizeof(row_t) * (E.numrows + 1));
if (!tmp) { if (!tmp) {
return; return;
} }
E.row = tmp; E.rows = tmp;
memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at)); memmove(&E.rows[at + 1], &E.rows[at], sizeof(row_t) * (E.numrows - at));
// Initialize the new row // Initialize the new row
E.row[at].size = 0; E.rows[at].size = len;
E.row[at].chars = NULL; E.rows[at].chars = NULL;
E.row[at].rsize = 0;
E.row[at].render = NULL;
// Count UTF-8 characters first E.rows[at].cap = len + 1;
int char_count = 0; E.rows[at].chars = malloc(E.rows[at].cap);
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]);
memcpy(E.rows[at].chars, s, len);
E.rows[at].chars[len] = '\n';
++E.numrows; ++E.numrows;
++E.dirty; ++E.dirty;
} }
void editorFreeRow(erow *row) { void editorFreeRow(row_t *row) {
free(row->render);
free(row->chars); free(row->chars);
} }
void editorDelRow(int at) { int editorRowCxToByte(const row_t *row, int cursor_x) {
if (at < 0 || at >= E.numrows) { int i = 0, col = 0;
return; while (col < cursor_x && i < row->size) {
int sl = utf8Seqlen((unsigned char)row->chars[i]);
if (sl < 1) sl = 1;
col++;
i += sl;
} }
editorFreeRow(&E.row[at]); return i;
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) * \fn editorRowInsertChar(erow *row, int at, int c)
* \param at Index of where we want to insert the char */ * \param at Index of where we want to insert the char */
void editorRowInsertChar(erow *row, int at, utf_8_char_t c) { void editorRowInsertBytes(row_t *row, int at, const char *src, int n) {
if (E.state == READ_ONLY) if (E.state == READ_ONLY)
return; return;
if (at < 0 || at > row->size) { if (row->size + n + 1 > row->cap) {
at = row->size; row->cap = (row->size + n + 1) * 2;
row->chars = realloc(row->chars, row->cap);
} }
row->chars = realloc(row->chars, row->size + 1); memmove(row->chars + at + n, row->chars + at, row->size - at);
memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1); memcpy(row->chars + at, src, n);
++(row->size); row->size += n;
row->chars[at] = c; row->chars[row->size] = '\0';
fprintf(stderr, "Row insert : %s %d\n", c.c, c.len);
editorUpdateRow(row);
++E.dirty;
}
void editorRowAppendString(erow *row, char *s, size_t len) {
row->chars = realloc(row->chars, row->size + len + 1);
memcpy(&row->chars[row->size], s, len);
row->size += len;
editorUpdateRow(row);
++E.dirty; ++E.dirty;
} }
@@ -221,12 +73,19 @@ void editorRowAppendString(erow *row, char *s, size_t len) {
* \brief Delete the a char at the chosen position on the given row * \brief Delete the a char at the chosen position on the given row
* \param at Index of the char to delete * \param at Index of the char to delete
* \param row Row on operation is made */ * \param row Row on operation is made */
void editorRowDelchar(erow *row, int at) { void editorRowDelByte(row_t *row, int at, int n) {
if (at < 0 || at >= row->size) { memmove(row->chars + at, row->chars + at + n, row->size - at - n);
return; row->size -= n;
} row->chars[row->size] = '\0';
memmove(&row->chars[at], &row->chars[at + 1], row->size - at); }
--row->size;
editorUpdateRow(row); int editorRowCharCount(row_t *row)
++E.dirty; {
int n = 0, i = 0;
while (i < row->size) {
int sl = utf8Seqlen((unsigned char)row->chars[i]);
if (sl < 1) sl = 1;
n++; i += sl;
}
return n;
} }
+85 -193
View File
@@ -1,10 +1,16 @@
#include "../include/terminal.h" #include "../include/terminal.h"
#include <ctype.h>
#include "../include/data.h" #include "../include/data.h"
#include "../include/define.h"
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include "include/utf8.h"
void die(const char *s) { void die(const char *s) {
write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
@@ -37,211 +43,97 @@ void enableRawMode() {
} }
} }
int utf8_char_length(unsigned char first_byte) { #include <ctype.h> /* isprint */
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 char *key_to_string(int key) {
unsigned int utf8_to_codepoint(const unsigned char *bytes, int len) { static char key_str[32];
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) { if (key == '\r') {
memcpy(key->c.c, seq, len); strcpy(key_str, "ENTER");
key->c.len = len; } else if (key >= 1 && key <= 26) {
key->modifiers = MOD_NONE; snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
key->type = KEY_UNKNOWN; } else {
switch (key) {
// Control characters (Ctrl+A to Ctrl+Z) case ARROW_UP: strcpy(key_str, "ARROW-UP"); break;
if (len == 1 && seq[0] < 32 && seq[0] != 27 && seq[0] != 9 && seq[0] != 10 && case ARROW_DOWN: strcpy(key_str, "ARROW-DOWN"); break;
seq[0] != 13) { case ARROW_LEFT: strcpy(key_str, "ARROW-LEFT"); break;
key->type = KEY_CTRL; case ARROW_RIGHT: strcpy(key_str, "ARROW-RIGHT"); break;
key->data.ctrl_char = seq[0] + 64; case PAGE_UP: strcpy(key_str, "PAGE-UP"); break;
return; case PAGE_DOWN: strcpy(key_str, "PAGE-DOWN"); break;
} case DEL_KEY: strcpy(key_str, "DEL"); break;
case BACKSPACE: strcpy(key_str, "BACKSPACE"); break;
// Special single characters case BEG_LINE: strcpy(key_str, "HOME"); break;
if (len == 1) { case END_LINE: strcpy(key_str, "END"); break;
switch (seq[0]) { case '\x1b': strcpy(key_str, "ESCAPE"); break;
case 9: default:
case 10: if (key > 127) {
case 13: /* UTF-8 code point — re-encode into the buffer */
case 27: char buf[5] = {0};
case 127: int n = utf8Encode((uint32_t)key, buf);
key->type = KEY_SPECIAL; snprintf(key_str, sizeof(key_str), "%.*s", n, buf);
key->data.special = seq[0]; } else if (isprint(key)) {
return; snprintf(key_str, sizeof(key_str), "%c", key);
} else {
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
}
} }
} }
return key_str;
}
// Escape sequences int editorReadKey() {
if (len >= 2 && seq[0] == 27) { char c;
// Alt+key combinations /* read first byte — may be start of UTF-8 or escape */
if (len == 2 && seq[1] >= 32 && seq[1] < 127) { while (read(STDIN_FILENO, &c, 1) != 1);
key->type = KEY_ALT;
key->data.alt_char = seq[1];
return;
}
// CSI sequences (ESC [ ...) if (c == '\x1b') {
if (len >= 3 && seq[1] == '[') { char seq[6];
// Arrow keys /* try to read escape sequence */
if (len == 3) { if (read(STDIN_FILENO, &seq[0], 1) != 1) return '\x1b';
switch (seq[2]) { if (read(STDIN_FILENO, &seq[1], 1) != 1) return '\x1b';
case 'A': if (seq[0] == '[') {
case 'B': if (seq[1] >= '0' && seq[1] <= '9') {
case 'C': if (read(STDIN_FILENO, &seq[2], 1) != 1) return '\x1b';
case 'D': if (seq[2] == '~') {
key->type = KEY_ARROW; switch (seq[1]) {
key->data.arrow = seq[2]; case '1': return BEG_LINE;
return; case '3': return DEL_KEY;
case 'H': case '4': return END_LINE;
case 'F': case '5': return PAGE_UP;
key->type = KEY_NAVIGATION; case '6': return PAGE_DOWN;
key->data.special = seq[2]; case '7': return BEG_LINE;
return; case '8': return END_LINE;
} }
} }
} else {
// Modified keys (ESC [ 1 ; modifier letter) switch (seq[1]) {
if (len >= 6 && seq[2] == '1' && seq[3] == ';') { case 'A': return ARROW_UP;
int modifier = seq[4] - '0'; case 'B': return ARROW_DOWN;
char k = seq[5]; case 'C': return ARROW_RIGHT;
case 'D': return ARROW_LEFT;
if (modifier & 1) case 'H': return BEG_LINE;
key->modifiers |= MOD_SHIFT; case 'F': return END_LINE;
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;
} }
}
} }
} return '\x1b';
} }
// SS3 sequences (ESC O ...) /* multi-byte UTF-8: read remaining bytes */
if (len == 3 && seq[1] == 'O') { int seqlen = utf8Seqlen((unsigned char)c);
switch (seq[2]) { if (seqlen > 1) {
case 'P': /* pack into a pseudo-codepoint just to pass bytes through;
case 'Q': we handle encoding/decoding at the row level */
case 'R': char buf[4] = {c, 0, 0, 0};
case 'S': for (int i = 1; i < seqlen; i++)
key->type = KEY_FUNCTION; if (read(STDIN_FILENO, &buf[i], 1) != 1) break;
key->data.function_num = seq[2] - 'P' + 1; /* decode and return as uint32, but we need int — use high range */
return; const char *p = buf;
case 'H': uint32_t cp = utf8Decode(&p);
case 'F': return (int)cp; /* caller re-encodes when inserting */
key->type = KEY_NAVIGATION;
key->data.special = seq[2];
return;
}
} }
}
// UTF-8 character return (unsigned char)c;
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) { int getCursorPosition(int *rows, int *cols) {
+148
View File
@@ -0,0 +1,148 @@
/**
* @file utf8.c
*/
#include "../include/utf8.h"
#include "../include/data.h"
#include <stdint.h>
#include <unistd.h>
uint32_t readUtf8Char(void)
{
unsigned char buf[4];
read(STDIN_FILENO, &buf[0], 1);
int extra;
uint32_t cp;
if (buf[0] < 0x80)
{
cp = buf[0];
extra = 0;
}
else if (buf[0] < 0xC0) { return 0xFFFD; } // stray continuation
else if (buf[0] < 0xE0)
{
cp = buf[0] & 0x1F;
extra = 1;
}
else if (buf[0] < 0xF0)
{
cp = buf[0] & 0x0F;
extra = 2;
}
else
{
cp = buf[0] & 0x07;
extra = 3;
}
if (extra > 0)
{
read(STDIN_FILENO, &buf[1], extra); // read remaining bytes at once
for (int i = 0; i < extra; i++)
cp = (cp << 6) | (buf[1 + i] & 0x3F);
}
return cp;
}
uint32_t utf8Decode(const char** s)
{
unsigned char c = (unsigned char)**s;
uint32_t cp;
int extra;
if (c < 0x80)
{
cp = c;
extra = 0;
}
else if (c < 0xC0)
{
(*s)++;
return 0xFFFD;
}
else if (c < 0xE0)
{
cp = c & 0x1F;
extra = 1;
}
else if (c < 0xF0)
{
cp = c & 0x0F;
extra = 2;
}
else
{
cp = c & 0x07;
extra = 3;
}
(*s)++;
while (extra--)
{
c = (unsigned char)**s;
if ((c & 0xC0) != 0x80) return 0xFFFD;
cp = (cp << 6) | (c & 0x3F);
(*s)++;
}
return cp;
}
// buf must have at least 4 bytes; returns bytes written
int utf8Encode(uint32_t cp, char* buf)
{
if (cp < 0x80)
{
buf[0] = cp;
return 1;
}
if (cp < 0x800)
{
buf[0] = 0xC0 | (cp >> 6);
buf[1] = 0x80 | (cp & 0x3F);
return 2;
}
if (cp < 0x10000)
{
buf[0] = 0xE0 | (cp >> 12);
buf[1] = 0x80 | ((cp >> 6) & 0x3F);
buf[2] = 0x80 | (cp & 0x3F);
return 3;
}
buf[0] = 0xF0 | (cp >> 18);
buf[1] = 0x80 | ((cp >> 12) & 0x3F);
buf[2] = 0x80 | ((cp >> 6) & 0x3F);
buf[3] = 0x80 | (cp & 0x3F);
return 4;
}
int utf8Seqlen(unsigned char c)
{
if (c < 0x80) return 1;
if (c < 0xC0) return 0; /* continuation — shouldn't be leading */
if (c < 0xE0) return 2;
if (c < 0xF0) return 3;
return 4;
}
/**
* @param codepoint utf8 codepoint of a char
* @return length of the codepoint
*/
int codepointWidth(uint32_t codepoint)
{
if (codepoint < 0x20 || codepoint == 0x7F) return 0;
/* rough double-width ranges */
if ((codepoint >= 0x1100 && codepoint <= 0x115F) ||
(codepoint >= 0x2E80 && codepoint <= 0x303E) ||
(codepoint >= 0x3041 && codepoint <= 0x33BF) ||
(codepoint >= 0xAC00 && codepoint <= 0xD7AF) ||
(codepoint >= 0xF900 && codepoint <= 0xFAFF) ||
(codepoint >= 0xFF01 && codepoint <= 0xFF60) ||
(codepoint >= 0x1F300 && codepoint <= 0x1FAFF))
return 2;
return 1;
}