From cebb280fe546190e55e071b6adbf37cd47950f34 Mon Sep 17 00:00:00 2001 From: Collin Williams <96917990+bluedragon1221@users.noreply.github.com> Date: Mon, 11 May 2026 15:07:55 -0500 Subject: initial commit --- src/core/buffer.c | 296 ++++++++++++++++++++++++ src/core/buffer.h | 38 ++++ src/core/editor.c | 466 ++++++++++++++++++++++++++++++++++++++ src/core/editor.h | 96 ++++++++ src/core/lua_bridge.c | 611 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/core/lua_bridge.h | 26 +++ 6 files changed, 1533 insertions(+) create mode 100644 src/core/buffer.c create mode 100644 src/core/buffer.h create mode 100644 src/core/editor.c create mode 100644 src/core/editor.h create mode 100644 src/core/lua_bridge.c create mode 100644 src/core/lua_bridge.h (limited to 'src/core') diff --git a/src/core/buffer.c b/src/core/buffer.c new file mode 100644 index 0000000..16a9dfe --- /dev/null +++ b/src/core/buffer.c @@ -0,0 +1,296 @@ +#include "core/buffer.h" + +#include +#include +#include + +#define INITIAL_CAPACITY 1024 + +static size_t gap_size(const Buffer *buffer) { + return buffer->gap_end - buffer->gap_start; +} + +static size_t physical_index(const Buffer *buffer, size_t logical) { + if (logical < buffer->gap_start) { + return logical; + } + return logical + gap_size(buffer); +} + +static bool ensure_gap(Buffer *buffer, size_t needed) { + if (gap_size(buffer) >= needed) { + return true; + } + + size_t old_gap = gap_size(buffer); + size_t old_capacity = buffer->capacity; + size_t new_capacity = old_capacity; + while ((new_capacity - buffer->gap_start - (old_capacity - buffer->gap_end)) < needed) { + new_capacity *= 2; + } + + char *new_data = (char *)malloc(new_capacity); + if (!new_data) { + return false; + } + + memcpy(new_data, buffer->data, buffer->gap_start); + size_t tail_size = old_capacity - buffer->gap_end; + size_t new_gap_end = new_capacity - tail_size; + memcpy(new_data + new_gap_end, buffer->data + buffer->gap_end, tail_size); + + free(buffer->data); + buffer->data = new_data; + buffer->capacity = new_capacity; + buffer->gap_end = new_gap_end; + + (void)old_gap; + return true; +} + +static void move_gap(Buffer *buffer, size_t target) { + if (target == buffer->gap_start) { + return; + } + + if (target < buffer->gap_start) { + size_t move = buffer->gap_start - target; + memmove(buffer->data + buffer->gap_end - move, buffer->data + target, move); + buffer->gap_start = target; + buffer->gap_end -= move; + return; + } + + size_t move = target - buffer->gap_start; + memmove(buffer->data + buffer->gap_start, buffer->data + buffer->gap_end, move); + buffer->gap_start += move; + buffer->gap_end += move; +} + +bool buffer_init(Buffer *buffer) { + buffer->data = (char *)malloc(INITIAL_CAPACITY); + if (!buffer->data) { + return false; + } + buffer->capacity = INITIAL_CAPACITY; + buffer->gap_start = 0; + buffer->gap_end = INITIAL_CAPACITY; + buffer->path[0] = '\0'; + buffer->modified = false; + return true; +} + +void buffer_free(Buffer *buffer) { + free(buffer->data); + buffer->data = NULL; + buffer->capacity = 0; + buffer->gap_start = 0; + buffer->gap_end = 0; +} + +size_t buffer_length(const Buffer *buffer) { + return buffer->capacity - gap_size(buffer); +} + +size_t buffer_point(const Buffer *buffer) { + return buffer->gap_start; +} + +void buffer_set_point(Buffer *buffer, size_t point) { + size_t len = buffer_length(buffer); + if (point > len) { + point = len; + } + move_gap(buffer, point); +} + +bool buffer_insert_char(Buffer *buffer, char c) { + if (!ensure_gap(buffer, 1)) { + return false; + } + buffer->data[buffer->gap_start++] = c; + buffer->modified = true; + return true; +} + +bool buffer_insert_cstr(Buffer *buffer, const char *text) { + size_t len = strlen(text); + if (!ensure_gap(buffer, len)) { + return false; + } + memcpy(buffer->data + buffer->gap_start, text, len); + buffer->gap_start += len; + if (len > 0) { + buffer->modified = true; + } + return true; +} + +bool buffer_backspace(Buffer *buffer) { + if (buffer->gap_start == 0) { + return false; + } + buffer->gap_start--; + buffer->modified = true; + return true; +} + +bool buffer_delete_forward(Buffer *buffer) { + if (buffer->gap_end >= buffer->capacity) { + return false; + } + buffer->gap_end++; + buffer->modified = true; + return true; +} + +char buffer_char_at(const Buffer *buffer, size_t index) { + size_t len = buffer_length(buffer); + if (index >= len) { + return '\0'; + } + return buffer->data[physical_index(buffer, index)]; +} + +bool buffer_load_file(Buffer *buffer, const char *path) { + FILE *fp = fopen(path, "rb"); + if (!fp) { + return false; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + + long file_size = ftell(fp); + if (file_size < 0) { + fclose(fp); + return false; + } + rewind(fp); + + buffer->gap_start = 0; + buffer->gap_end = buffer->capacity; + + if (!ensure_gap(buffer, (size_t)file_size + 1)) { + fclose(fp); + return false; + } + + size_t read = fread(buffer->data, 1, (size_t)file_size, fp); + fclose(fp); + if (read != (size_t)file_size) { + return false; + } + + buffer->gap_start = (size_t)file_size; + buffer->gap_end = buffer->capacity; + + strncpy(buffer->path, path, sizeof(buffer->path) - 1); + buffer->path[sizeof(buffer->path) - 1] = '\0'; + buffer->modified = false; + return true; +} + +bool buffer_save_file(Buffer *buffer, const char *path) { + const char *out_path = path; + if (!out_path || out_path[0] == '\0') { + out_path = buffer->path; + } + + if (!out_path || out_path[0] == '\0') { + return false; + } + + FILE *fp = fopen(out_path, "wb"); + if (!fp) { + return false; + } + + size_t before = buffer->gap_start; + size_t after = buffer->capacity - buffer->gap_end; + + if (before > 0 && fwrite(buffer->data, 1, before, fp) != before) { + fclose(fp); + return false; + } + + if (after > 0 && fwrite(buffer->data + buffer->gap_end, 1, after, fp) != after) { + fclose(fp); + return false; + } + + fclose(fp); + + if (path && path[0] != '\0') { + strncpy(buffer->path, path, sizeof(buffer->path) - 1); + buffer->path[sizeof(buffer->path) - 1] = '\0'; + } + + buffer->modified = false; + + return true; +} + +const char *buffer_path(const Buffer *buffer) { + return buffer->path; +} + +bool buffer_modified(const Buffer *buffer) { + return buffer->modified; +} + +void buffer_index_to_line_col(const Buffer *buffer, size_t index, int *line, int *col) { + size_t len = buffer_length(buffer); + if (index > len) { + index = len; + } + + int l = 0; + int c = 0; + for (size_t i = 0; i < index; ++i) { + char ch = buffer_char_at(buffer, i); + if (ch == '\n') { + l++; + c = 0; + } else { + c++; + } + } + + *line = l; + *col = c; +} + +size_t buffer_line_col_to_index(const Buffer *buffer, int target_line, int target_col) { + if (target_line < 0) { + target_line = 0; + } + if (target_col < 0) { + target_col = 0; + } + + size_t len = buffer_length(buffer); + int line = 0; + int col = 0; + + for (size_t i = 0; i < len; ++i) { + if (line == target_line && col == target_col) { + return i; + } + + char ch = buffer_char_at(buffer, i); + if (ch == '\n') { + if (line == target_line) { + return i; + } + line++; + col = 0; + } else { + col++; + } + } + + return len; +} diff --git a/src/core/buffer.h b/src/core/buffer.h new file mode 100644 index 0000000..8fac8ab --- /dev/null +++ b/src/core/buffer.h @@ -0,0 +1,38 @@ +#ifndef LEMACS_BUFFER_H +#define LEMACS_BUFFER_H + +#include +#include + +typedef struct { + char *data; + size_t capacity; + size_t gap_start; + size_t gap_end; + char path[1024]; + bool modified; +} Buffer; + +bool buffer_init(Buffer *buffer); +void buffer_free(Buffer *buffer); + +size_t buffer_length(const Buffer *buffer); +size_t buffer_point(const Buffer *buffer); +void buffer_set_point(Buffer *buffer, size_t point); + +bool buffer_insert_char(Buffer *buffer, char c); +bool buffer_insert_cstr(Buffer *buffer, const char *text); +bool buffer_backspace(Buffer *buffer); +bool buffer_delete_forward(Buffer *buffer); + +char buffer_char_at(const Buffer *buffer, size_t index); + +bool buffer_load_file(Buffer *buffer, const char *path); +bool buffer_save_file(Buffer *buffer, const char *path); +const char *buffer_path(const Buffer *buffer); +bool buffer_modified(const Buffer *buffer); + +void buffer_index_to_line_col(const Buffer *buffer, size_t index, int *line, int *col); +size_t buffer_line_col_to_index(const Buffer *buffer, int target_line, int target_col); + +#endif diff --git a/src/core/editor.c b/src/core/editor.c new file mode 100644 index 0000000..43c28c7 --- /dev/null +++ b/src/core/editor.c @@ -0,0 +1,466 @@ +#include "core/editor.h" + +#include +#include +#include + +static EditorColor make_color(int r, int g, int b, int a) { + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + if (a < 0) a = 0; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + if (a > 255) a = 255; + + EditorColor c; + c.r = (uint8_t)r; + c.g = (uint8_t)g; + c.b = (uint8_t)b; + c.a = (uint8_t)a; + return c; +} + +static Buffer *active_buffer(Editor *editor) { + if (editor->current_buffer < 0 || editor->current_buffer >= editor->buffer_count) { + return NULL; + } + return &editor->buffers[editor->current_buffer]; +} + +static const Buffer *active_buffer_const(const Editor *editor) { + if (editor->current_buffer < 0 || editor->current_buffer >= editor->buffer_count) { + return NULL; + } + return &editor->buffers[editor->current_buffer]; +} + +static void clamp_point(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + size_t len = buffer_length(buffer); + if (point > len) { + buffer_set_point(buffer, len); + } +} + +static bool valid_aux_index(const Editor *editor, int aux_index) { + return aux_index >= 0 && aux_index < editor->aux_buffer_count; +} + +static bool valid_buffer_index(const Editor *editor, int index) { + return index >= 0 && index < editor->buffer_count; +} + +bool editor_init(Editor *editor) { + editor->buffer_count = 0; + editor->current_buffer = 0; + + if (!buffer_init(&editor->buffers[0])) { + return false; + } + + editor->buffer_count = 1; + editor->aux_buffer_count = 0; + editor->desired_col = -1; + editor->scroll_line = 0; + snprintf(editor->font_query, sizeof(editor->font_query), "%s", "monospace:size=14"); + editor->font_dirty = true; + editor->bg_top = make_color(248, 248, 248, 255); + editor->bg_bottom = make_color(238, 238, 238, 255); + editor->text = make_color(56, 60, 67, 255); + editor->cursor = make_color(176, 65, 11, 255); + editor->cursor_shape = LEMACS_CURSOR_BAR; + editor->cursor_thickness = 2; + editor->view_count = 0; + editor->should_quit = false; + return true; +} + +void editor_free(Editor *editor) { + for (int i = 0; i < editor->buffer_count; ++i) { + buffer_free(&editor->buffers[i]); + } + + for (int i = 0; i < editor->aux_buffer_count; ++i) { + buffer_free(&editor->aux_buffers[i]); + } + + editor->buffer_count = 0; + editor->current_buffer = 0; + editor->aux_buffer_count = 0; + editor->view_count = 0; +} + +void editor_set_font_query(Editor *editor, const char *query) { + snprintf(editor->font_query, sizeof(editor->font_query), "%s", (query && query[0] != '\0') ? query : "monospace:size=14"); + editor->font_dirty = true; +} + +const char *editor_font_query(const Editor *editor) { + return editor->font_query; +} + +bool editor_consume_font_dirty(Editor *editor) { + bool dirty = editor->font_dirty; + editor->font_dirty = false; + return dirty; +} + +bool editor_set_theme_color(Editor *editor, const char *name, int r, int g, int b, int a) { + EditorColor c = make_color(r, g, b, a); + + if (strcmp(name, "bg_top") == 0) { + editor->bg_top = c; + return true; + } + if (strcmp(name, "bg_bottom") == 0) { + editor->bg_bottom = c; + return true; + } + if (strcmp(name, "text") == 0) { + editor->text = c; + return true; + } + if (strcmp(name, "cursor") == 0) { + editor->cursor = c; + return true; + } + if (strcmp(name, "accent") == 0) { + editor->cursor = c; + return true; + } + + return false; +} + +bool editor_set_cursor_shape(Editor *editor, int shape) { + if (shape < LEMACS_CURSOR_BAR || shape > LEMACS_CURSOR_UNDERLINE) { + return false; + } + editor->cursor_shape = shape; + return true; +} + +void editor_set_cursor_thickness(Editor *editor, int thickness) { + if (thickness < 1) { + thickness = 1; + } + if (thickness > 24) { + thickness = 24; + } + editor->cursor_thickness = thickness; +} + +int editor_cursor_shape(const Editor *editor) { + return editor->cursor_shape; +} + +int editor_cursor_thickness(const Editor *editor) { + return editor->cursor_thickness; +} + +int editor_aux_buffer_new(Editor *editor) { + if (editor->aux_buffer_count >= LEMACS_MAX_AUX_BUFFERS) { + return -1; + } + + int index = editor->aux_buffer_count; + if (!buffer_init(&editor->aux_buffers[index])) { + return -1; + } + + editor->aux_buffer_count++; + return index; +} + +bool editor_aux_buffer_set_text(Editor *editor, int aux_index, const char *text) { + if (!valid_aux_index(editor, aux_index)) { + return false; + } + + Buffer *buffer = &editor->aux_buffers[aux_index]; + buffer->gap_start = 0; + buffer->gap_end = buffer->capacity; + buffer->path[0] = '\0'; + buffer->modified = false; + + if (!text || text[0] == '\0') { + return true; + } + + return buffer_insert_cstr(buffer, text); +} + +Buffer *editor_aux_buffer_get(Editor *editor, int aux_index) { + if (!valid_aux_index(editor, aux_index)) { + return NULL; + } + return &editor->aux_buffers[aux_index]; +} + +void editor_layout_reset(Editor *editor) { + editor->view_count = 0; +} + +bool editor_layout_add_view(Editor *editor, int source_type, int source_index, int x, int y, int width, int height, bool show_cursor, EditorColor bg, EditorColor fg) { + if (editor->view_count >= LEMACS_MAX_VIEWS) { + return false; + } + + if (width <= 0 || height <= 0) { + return false; + } + + if (source_type == 1 && !valid_aux_index(editor, source_index)) { + return false; + } + + if (source_type != 0 && source_type != 1) { + return false; + } + + EditorView *view = &editor->views[editor->view_count++]; + view->x = x; + view->y = y; + view->width = width; + view->height = height; + view->source_type = source_type; + view->source_index = source_index; + view->show_cursor = show_cursor; + view->bg = bg; + view->fg = fg; + return true; +} + +void editor_move_left(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + if (point > 0) { + buffer_set_point(buffer, point - 1); + } + editor->desired_col = -1; +} + +void editor_move_right(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + size_t len = buffer_length(buffer); + if (point < len) { + buffer_set_point(buffer, point + 1); + } + editor->desired_col = -1; +} + +void editor_move_up(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + int line = 0; + int col = 0; + buffer_index_to_line_col(buffer, point, &line, &col); + if (editor->desired_col < 0) { + editor->desired_col = col; + } + if (line > 0) { + size_t new_point = buffer_line_col_to_index(buffer, line - 1, editor->desired_col); + buffer_set_point(buffer, new_point); + } + clamp_point(editor); +} + +void editor_move_down(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + int line = 0; + int col = 0; + buffer_index_to_line_col(buffer, point, &line, &col); + if (editor->desired_col < 0) { + editor->desired_col = col; + } + + size_t len = buffer_length(buffer); + int last_line = 0; + int last_col = 0; + buffer_index_to_line_col(buffer, len, &last_line, &last_col); + if (line < last_line) { + size_t new_point = buffer_line_col_to_index(buffer, line + 1, editor->desired_col); + buffer_set_point(buffer, new_point); + } + clamp_point(editor); +} + +void editor_move_beginning_of_line(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + while (point > 0) { + char ch = buffer_char_at(buffer, point - 1); + if (ch == '\n') { + break; + } + point--; + } + buffer_set_point(buffer, point); + editor->desired_col = -1; +} + +void editor_move_end_of_line(Editor *editor) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + size_t point = buffer_point(buffer); + size_t len = buffer_length(buffer); + while (point < len) { + char ch = buffer_char_at(buffer, point); + if (ch == '\n') { + break; + } + point++; + } + buffer_set_point(buffer, point); + editor->desired_col = -1; +} + +void editor_set_point(Editor *editor, size_t point) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return; + } + + buffer_set_point(buffer, point); + editor->desired_col = -1; +} + +char editor_char_at(const Editor *editor, size_t index) { + const Buffer *buffer = active_buffer_const(editor); + if (!buffer) { + return '\0'; + } + return buffer_char_at(buffer, index); +} + +bool editor_open_file(Editor *editor, const char *path) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return false; + } + + if (!path || path[0] == '\0') { + return false; + } + + if (!buffer_load_file(buffer, path)) { + if (errno != ENOENT) { + return false; + } + + buffer->gap_start = 0; + buffer->gap_end = buffer->capacity; + buffer->modified = false; + strncpy(buffer->path, path, sizeof(buffer->path) - 1); + buffer->path[sizeof(buffer->path) - 1] = '\0'; + } + + buffer_set_point(buffer, 0); + editor->desired_col = -1; + editor->scroll_line = 0; + + return true; +} + +bool editor_save_file(Editor *editor, const char *path) { + Buffer *buffer = active_buffer(editor); + if (!buffer) { + return false; + } + return buffer_save_file(buffer, path); +} + +Buffer *editor_current_buffer(Editor *editor) { + return active_buffer(editor); +} + +const Buffer *editor_current_buffer_const(const Editor *editor) { + return active_buffer_const(editor); +} + +int editor_buffer_count(const Editor *editor) { + return editor->buffer_count; +} + +int editor_current_buffer_index(const Editor *editor) { + return editor->current_buffer; +} + +bool editor_switch_buffer(Editor *editor, int index) { + if (!valid_buffer_index(editor, index)) { + return false; + } + + editor->current_buffer = index; + editor->desired_col = -1; + editor->scroll_line = 0; + return true; +} + +int editor_new_buffer(Editor *editor) { + if (editor->buffer_count >= LEMACS_MAX_BUFFERS) { + return -1; + } + + int index = editor->buffer_count; + if (!buffer_init(&editor->buffers[index])) { + return -1; + } + + editor->buffer_count++; + editor->current_buffer = index; + editor->desired_col = -1; + editor->scroll_line = 0; + return index; +} + +const char *editor_buffer_path_at(const Editor *editor, int index) { + if (!valid_buffer_index(editor, index)) { + return NULL; + } + + const char *path = buffer_path(&editor->buffers[index]); + if (!path || path[0] == '\0') { + return NULL; + } + return path; +} + +bool editor_buffer_modified_at(const Editor *editor, int index) { + if (!valid_buffer_index(editor, index)) { + return false; + } + return buffer_modified(&editor->buffers[index]); +} diff --git a/src/core/editor.h b/src/core/editor.h new file mode 100644 index 0000000..5388197 --- /dev/null +++ b/src/core/editor.h @@ -0,0 +1,96 @@ +#ifndef LEMACS_EDITOR_H +#define LEMACS_EDITOR_H + +#include +#include + +#include "core/buffer.h" + +#define LEMACS_MAX_AUX_BUFFERS 8 +#define LEMACS_MAX_VIEWS 16 +#define LEMACS_MAX_BUFFERS 32 + +#define LEMACS_CURSOR_BAR 0 +#define LEMACS_CURSOR_BLOCK 1 +#define LEMACS_CURSOR_UNDERLINE 2 + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} EditorColor; + +typedef struct { + int x; + int y; + int width; + int height; + int source_type; + int source_index; + bool show_cursor; + EditorColor bg; + EditorColor fg; +} EditorView; + +typedef struct { + Buffer buffers[LEMACS_MAX_BUFFERS]; + int buffer_count; + int current_buffer; + Buffer aux_buffers[LEMACS_MAX_AUX_BUFFERS]; + int aux_buffer_count; + int desired_col; + int scroll_line; + char font_query[256]; + bool font_dirty; + EditorView views[LEMACS_MAX_VIEWS]; + int view_count; + EditorColor bg_top; + EditorColor bg_bottom; + EditorColor text; + EditorColor cursor; + int cursor_shape; + int cursor_thickness; + bool should_quit; +} Editor; + +bool editor_init(Editor *editor); +void editor_free(Editor *editor); + +void editor_set_font_query(Editor *editor, const char *query); +const char *editor_font_query(const Editor *editor); +bool editor_consume_font_dirty(Editor *editor); +bool editor_set_theme_color(Editor *editor, const char *name, int r, int g, int b, int a); +bool editor_set_cursor_shape(Editor *editor, int shape); +void editor_set_cursor_thickness(Editor *editor, int thickness); +int editor_cursor_shape(const Editor *editor); +int editor_cursor_thickness(const Editor *editor); + +int editor_aux_buffer_new(Editor *editor); +bool editor_aux_buffer_set_text(Editor *editor, int aux_index, const char *text); +Buffer *editor_aux_buffer_get(Editor *editor, int aux_index); + +void editor_layout_reset(Editor *editor); +bool editor_layout_add_view(Editor *editor, int source_type, int source_index, int x, int y, int width, int height, bool show_cursor, EditorColor bg, EditorColor fg); + +void editor_move_left(Editor *editor); +void editor_move_right(Editor *editor); +void editor_move_up(Editor *editor); +void editor_move_down(Editor *editor); +void editor_move_beginning_of_line(Editor *editor); +void editor_move_end_of_line(Editor *editor); +void editor_set_point(Editor *editor, size_t point); +char editor_char_at(const Editor *editor, size_t index); + +bool editor_open_file(Editor *editor, const char *path); +bool editor_save_file(Editor *editor, const char *path); +Buffer *editor_current_buffer(Editor *editor); +const Buffer *editor_current_buffer_const(const Editor *editor); +int editor_buffer_count(const Editor *editor); +int editor_current_buffer_index(const Editor *editor); +bool editor_switch_buffer(Editor *editor, int index); +int editor_new_buffer(Editor *editor); +const char *editor_buffer_path_at(const Editor *editor, int index); +bool editor_buffer_modified_at(const Editor *editor, int index); + +#endif diff --git a/src/core/lua_bridge.c b/src/core/lua_bridge.c new file mode 100644 index 0000000..f1bf16c --- /dev/null +++ b/src/core/lua_bridge.c @@ -0,0 +1,611 @@ +#include "core/lua_bridge.h" + +#include +#include + +#include + +#include +#include +#include + +static const char *EDITOR_REG_KEY = "lemacs.editor"; + +static Editor *get_editor(lua_State *L) { + lua_getfield(L, LUA_REGISTRYINDEX, EDITOR_REG_KEY); + Editor *editor = (Editor *)lua_touserdata(L, -1); + lua_pop(L, 1); + return editor; +} + +static int l_insert(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + if (!buffer) { + lua_pushboolean(L, 0); + return 1; + } + + size_t len = 0; + const char *text = luaL_checklstring(L, 1, &len); + for (size_t i = 0; i < len; ++i) { + if (!buffer_insert_char(buffer, text[i])) { + lua_pushboolean(L, 0); + return 1; + } + } + lua_pushboolean(L, 1); + return 1; +} + +static int l_backspace(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushboolean(L, buffer ? buffer_backspace(buffer) : false); + return 1; +} + +static int l_delete_forward(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushboolean(L, buffer ? buffer_delete_forward(buffer) : false); + return 1; +} + +static int l_move_left(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_left(editor); + return 0; +} + +static int l_move_right(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_right(editor); + return 0; +} + +static int l_move_up(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_up(editor); + return 0; +} + +static int l_move_down(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_down(editor); + return 0; +} + +static int l_move_beginning_of_line(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_beginning_of_line(editor); + return 0; +} + +static int l_move_end_of_line(lua_State *L) { + Editor *editor = get_editor(L); + editor_move_end_of_line(editor); + return 0; +} + +static int l_open_file(lua_State *L) { + Editor *editor = get_editor(L); + const char *path = luaL_checkstring(L, 1); + lua_pushboolean(L, editor_open_file(editor, path)); + return 1; +} + +static int l_save_file(lua_State *L) { + Editor *editor = get_editor(L); + const char *path = NULL; + if (!lua_isnoneornil(L, 1)) { + path = luaL_checkstring(L, 1); + } + lua_pushboolean(L, editor_save_file(editor, path)); + return 1; +} + +static int l_get_point(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushinteger(L, (lua_Integer)(buffer ? buffer_point(buffer) : 0)); + return 1; +} + +static int l_set_point(lua_State *L) { + Editor *editor = get_editor(L); + lua_Integer point = luaL_checkinteger(L, 1); + if (point < 0) { + point = 0; + } + editor_set_point(editor, (size_t)point); + return 0; +} + +static int l_char_at(lua_State *L) { + Editor *editor = get_editor(L); + lua_Integer idx = luaL_checkinteger(L, 1); + if (idx < 0) { + lua_pushstring(L, ""); + return 1; + } + + char ch = editor_char_at(editor, (size_t)idx); + if (ch == '\0') { + lua_pushstring(L, ""); + return 1; + } + + char text[2] = {ch, '\0'}; + lua_pushstring(L, text); + return 1; +} + +static int l_set_font(lua_State *L) { + Editor *editor = get_editor(L); + const char *query = luaL_checkstring(L, 1); + editor_set_font_query(editor, query); + return 0; +} + +static int l_set_theme_color(lua_State *L) { + Editor *editor = get_editor(L); + const char *name = luaL_checkstring(L, 1); + int r = (int)luaL_checkinteger(L, 2); + int g = (int)luaL_checkinteger(L, 3); + int b = (int)luaL_checkinteger(L, 4); + int a = 255; + if (!lua_isnoneornil(L, 5)) { + a = (int)luaL_checkinteger(L, 5); + } + + lua_pushboolean(L, editor_set_theme_color(editor, name, r, g, b, a)); + return 1; +} + +static int l_set_cursor_shape(lua_State *L) { + Editor *editor = get_editor(L); + const char *shape = luaL_checkstring(L, 1); + + int cursor_shape = -1; + if (strcmp(shape, "bar") == 0) { + cursor_shape = LEMACS_CURSOR_BAR; + } else if (strcmp(shape, "block") == 0) { + cursor_shape = LEMACS_CURSOR_BLOCK; + } else if (strcmp(shape, "underline") == 0) { + cursor_shape = LEMACS_CURSOR_UNDERLINE; + } else { + lua_pushboolean(L, 0); + return 1; + } + + lua_pushboolean(L, editor_set_cursor_shape(editor, cursor_shape)); + return 1; +} + +static int l_set_cursor_thickness(lua_State *L) { + Editor *editor = get_editor(L); + int thickness = (int)luaL_checkinteger(L, 1); + editor_set_cursor_thickness(editor, thickness); + return 0; +} + +static int l_aux_buffer_new(lua_State *L) { + Editor *editor = get_editor(L); + int index = editor_aux_buffer_new(editor); + if (index < 0) { + lua_pushnil(L); + return 1; + } + lua_pushinteger(L, index); + return 1; +} + +static int l_aux_buffer_set_text(lua_State *L) { + Editor *editor = get_editor(L); + int aux_index = (int)luaL_checkinteger(L, 1); + const char *text = luaL_checkstring(L, 2); + lua_pushboolean(L, editor_aux_buffer_set_text(editor, aux_index, text)); + return 1; +} + +static int l_layout_reset(lua_State *L) { + Editor *editor = get_editor(L); + editor_layout_reset(editor); + return 0; +} + +static int l_layout_add_view(lua_State *L) { + Editor *editor = get_editor(L); + int source_type = (int)luaL_checkinteger(L, 1); + int source_index = (int)luaL_checkinteger(L, 2); + int x = (int)luaL_checkinteger(L, 3); + int y = (int)luaL_checkinteger(L, 4); + int width = (int)luaL_checkinteger(L, 5); + int height = (int)luaL_checkinteger(L, 6); + bool show_cursor = lua_toboolean(L, 7) != 0; + int bg_r = (int)luaL_checkinteger(L, 8); + int bg_g = (int)luaL_checkinteger(L, 9); + int bg_b = (int)luaL_checkinteger(L, 10); + int bg_a = (int)luaL_optinteger(L, 11, 255); + int fg_r = (int)luaL_checkinteger(L, 12); + int fg_g = (int)luaL_checkinteger(L, 13); + int fg_b = (int)luaL_checkinteger(L, 14); + int fg_a = (int)luaL_optinteger(L, 15, 255); + + EditorColor bg = {(uint8_t)bg_r, (uint8_t)bg_g, (uint8_t)bg_b, (uint8_t)bg_a}; + EditorColor fg = {(uint8_t)fg_r, (uint8_t)fg_g, (uint8_t)fg_b, (uint8_t)fg_a}; + + lua_pushboolean(L, editor_layout_add_view(editor, source_type, source_index, x, y, width, height, show_cursor, bg, fg)); + return 1; +} + +static int l_screen_width(lua_State *L) { + lua_pushinteger(L, GetScreenWidth()); + return 1; +} + +static int l_screen_height(lua_State *L) { + lua_pushinteger(L, GetScreenHeight()); + return 1; +} + +static int l_buffer_length(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushinteger(L, (lua_Integer)(buffer ? buffer_length(buffer) : 0)); + return 1; +} + +static int l_request_quit(lua_State *L) { + Editor *editor = get_editor(L); + editor->should_quit = true; + return 0; +} + +static int l_buffer_path(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + const char *path = buffer ? buffer_path(buffer) : NULL; + if (path && path[0] != '\0') { + lua_pushstring(L, path); + } else { + lua_pushnil(L); + } + return 1; +} + +static int l_buffer_modified(lua_State *L) { + Editor *editor = get_editor(L); + Buffer *buffer = editor_current_buffer(editor); + lua_pushboolean(L, buffer ? buffer_modified(buffer) : false); + return 1; +} + +static int l_buffer_count(lua_State *L) { + Editor *editor = get_editor(L); + lua_pushinteger(L, (lua_Integer)editor_buffer_count(editor)); + return 1; +} + +static int l_current_buffer_index(lua_State *L) { + Editor *editor = get_editor(L); + lua_pushinteger(L, (lua_Integer)editor_current_buffer_index(editor)); + return 1; +} + +static int l_switch_buffer(lua_State *L) { + Editor *editor = get_editor(L); + int index = (int)luaL_checkinteger(L, 1); + lua_pushboolean(L, editor_switch_buffer(editor, index)); + return 1; +} + +static int l_new_buffer(lua_State *L) { + Editor *editor = get_editor(L); + int index = editor_new_buffer(editor); + if (index < 0) { + lua_pushnil(L); + } else { + lua_pushinteger(L, index); + } + return 1; +} + +static int l_buffer_path_at(lua_State *L) { + Editor *editor = get_editor(L); + int index = (int)luaL_checkinteger(L, 1); + const char *path = editor_buffer_path_at(editor, index); + if (!path) { + lua_pushnil(L); + } else { + lua_pushstring(L, path); + } + return 1; +} + +static int l_buffer_modified_at(lua_State *L) { + Editor *editor = get_editor(L); + int index = (int)luaL_checkinteger(L, 1); + lua_pushboolean(L, editor_buffer_modified_at(editor, index)); + return 1; +} + +static void register_editor_api(lua_State *L) { + lua_newtable(L); + + lua_pushcfunction(L, l_insert); + lua_setfield(L, -2, "insert"); + + lua_pushcfunction(L, l_backspace); + lua_setfield(L, -2, "backspace"); + + lua_pushcfunction(L, l_delete_forward); + lua_setfield(L, -2, "delete_forward"); + + lua_pushcfunction(L, l_move_left); + lua_setfield(L, -2, "move_left"); + + lua_pushcfunction(L, l_move_right); + lua_setfield(L, -2, "move_right"); + + lua_pushcfunction(L, l_move_up); + lua_setfield(L, -2, "move_up"); + + lua_pushcfunction(L, l_move_down); + lua_setfield(L, -2, "move_down"); + + lua_pushcfunction(L, l_move_beginning_of_line); + lua_setfield(L, -2, "move_beginning_of_line"); + + lua_pushcfunction(L, l_move_end_of_line); + lua_setfield(L, -2, "move_end_of_line"); + + lua_pushcfunction(L, l_open_file); + lua_setfield(L, -2, "open_file"); + + lua_pushcfunction(L, l_save_file); + lua_setfield(L, -2, "save_file"); + + lua_pushcfunction(L, l_aux_buffer_new); + lua_setfield(L, -2, "aux_buffer_new"); + + lua_pushcfunction(L, l_aux_buffer_set_text); + lua_setfield(L, -2, "aux_buffer_set_text"); + + lua_pushcfunction(L, l_layout_reset); + lua_setfield(L, -2, "layout_reset"); + + lua_pushcfunction(L, l_layout_add_view); + lua_setfield(L, -2, "layout_add_view"); + + lua_pushcfunction(L, l_screen_width); + lua_setfield(L, -2, "screen_width"); + + lua_pushcfunction(L, l_screen_height); + lua_setfield(L, -2, "screen_height"); + + lua_pushcfunction(L, l_get_point); + lua_setfield(L, -2, "point"); + + lua_pushcfunction(L, l_set_point); + lua_setfield(L, -2, "set_point"); + + lua_pushcfunction(L, l_char_at); + lua_setfield(L, -2, "char_at"); + + lua_pushcfunction(L, l_set_font); + lua_setfield(L, -2, "set_font"); + + lua_pushcfunction(L, l_set_theme_color); + lua_setfield(L, -2, "set_theme_color"); + + lua_pushcfunction(L, l_set_cursor_shape); + lua_setfield(L, -2, "set_cursor_shape"); + + lua_pushcfunction(L, l_set_cursor_thickness); + lua_setfield(L, -2, "set_cursor_thickness"); + + lua_pushcfunction(L, l_buffer_length); + lua_setfield(L, -2, "buffer_length"); + + lua_pushcfunction(L, l_buffer_path); + lua_setfield(L, -2, "buffer_path"); + + lua_pushcfunction(L, l_buffer_modified); + lua_setfield(L, -2, "buffer_modified"); + + lua_pushcfunction(L, l_buffer_count); + lua_setfield(L, -2, "buffer_count"); + + lua_pushcfunction(L, l_current_buffer_index); + lua_setfield(L, -2, "current_buffer_index"); + + lua_pushcfunction(L, l_switch_buffer); + lua_setfield(L, -2, "switch_buffer"); + + lua_pushcfunction(L, l_new_buffer); + lua_setfield(L, -2, "new_buffer"); + + lua_pushcfunction(L, l_buffer_path_at); + lua_setfield(L, -2, "buffer_path_at"); + + lua_pushcfunction(L, l_buffer_modified_at); + lua_setfield(L, -2, "buffer_modified_at"); + + lua_pushcfunction(L, l_request_quit); + lua_setfield(L, -2, "request_quit"); + + lua_setglobal(L, "editor"); +} + +static bool call_global_with_string(lua_State *L, const char *name, const char *arg) { + lua_getglobal(L, name); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return false; + } + + lua_pushstring(L, arg); + if (lua_pcall(L, 1, 1, 0) != LUA_OK) { + fprintf(stderr, "lua error in %s: %s\n", name, lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + + bool handled = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + return handled; +} + +static bool call_global_noargs(lua_State *L, const char *name) { + lua_getglobal(L, name); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return false; + } + + if (lua_pcall(L, 0, 0, 0) != LUA_OK) { + fprintf(stderr, "lua error in %s: %s\n", name, lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + + return true; +} + +static void configure_lua_package_path(lua_State *L, const char *init_script_path) { + char runtime_lua_path[2048] = {0}; + snprintf(runtime_lua_path, sizeof(runtime_lua_path), "%s", init_script_path ? init_script_path : "runtime/init.lua"); + + char *last_slash = strrchr(runtime_lua_path, '/'); + if (!last_slash) { + return; + } + *last_slash = '\0'; + + char add_path[2300] = {0}; + snprintf(add_path, sizeof(add_path), "%s/lua/?.lua;%s/lua/?/init.lua", runtime_lua_path, runtime_lua_path); + + lua_getglobal(L, "package"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return; + } + + lua_getfield(L, -1, "path"); + const char *current = lua_tostring(L, -1); + if (!current) { + current = ""; + } + lua_pop(L, 1); + + char merged[4096] = {0}; + if (current[0] != '\0') { + snprintf(merged, sizeof(merged), "%s;%s", add_path, current); + } else { + snprintf(merged, sizeof(merged), "%s", add_path); + } + + lua_pushstring(L, merged); + lua_setfield(L, -2, "path"); + lua_pop(L, 1); +} + +bool lua_bridge_init(LuaBridge *bridge, Editor *editor) { + bridge->editor = editor; + bridge->runtime_init_path[0] = '\0'; + bridge->L = luaL_newstate(); + if (!bridge->L) { + return false; + } + + luaL_openlibs(bridge->L); + + lua_pushlightuserdata(bridge->L, editor); + lua_setfield(bridge->L, LUA_REGISTRYINDEX, EDITOR_REG_KEY); + + register_editor_api(bridge->L); + return true; +} + +void lua_bridge_free(LuaBridge *bridge) { + if (bridge->L) { + lua_close(bridge->L); + bridge->L = NULL; + } +} + +bool lua_bridge_load_runtime(LuaBridge *bridge, const char *init_script_path) { + configure_lua_package_path(bridge->L, init_script_path); + snprintf(bridge->runtime_init_path, sizeof(bridge->runtime_init_path), "%s", init_script_path ? init_script_path : ""); + + if (luaL_dofile(bridge->L, init_script_path) != LUA_OK) { + const char *err = lua_tostring(bridge->L, -1); + fprintf(stderr, "failed to load %s: %s\n", init_script_path, err ? err : "unknown"); + lua_pop(bridge->L, 1); + return false; + } + return true; +} + +bool lua_bridge_load_optional_init(LuaBridge *bridge, const char *script_path) { + if (!script_path || script_path[0] == '\0') { + return false; + } + + lua_getglobal(bridge->L, "dofile"); + if (!lua_isfunction(bridge->L, -1)) { + lua_pop(bridge->L, 1); + return false; + } + + lua_pushstring(bridge->L, script_path); + if (lua_pcall(bridge->L, 1, 0, 0) != LUA_OK) { + fprintf(stderr, "optional init load error (%s): %s\n", script_path, lua_tostring(bridge->L, -1)); + lua_pop(bridge->L, 1); + return false; + } + + return true; +} + +static bool call_global_with_optional_string(lua_State *L, const char *name, const char *arg) { + lua_getglobal(L, name); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return false; + } + + if (arg && arg[0] != '\0') { + lua_pushstring(L, arg); + } else { + lua_pushnil(L); + } + + if (lua_pcall(L, 1, 1, 0) != LUA_OK) { + fprintf(stderr, "lua error in %s: %s\n", name, lua_tostring(L, -1)); + lua_pop(L, 1); + return false; + } + + bool ok = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + return ok; +} + +bool lua_bridge_call_open_file(LuaBridge *bridge, const char *path) { + return call_global_with_optional_string(bridge->L, "on_open_file", path); +} + +bool lua_bridge_call_text(LuaBridge *bridge, const char *text) { + return call_global_with_string(bridge->L, "on_text", text ? text : ""); +} + +bool lua_bridge_call_key(LuaBridge *bridge, const char *key_name) { + return call_global_with_string(bridge->L, "on_key", key_name); +} + +bool lua_bridge_call_tick(LuaBridge *bridge) { + return call_global_noargs(bridge->L, "on_tick"); +} diff --git a/src/core/lua_bridge.h b/src/core/lua_bridge.h new file mode 100644 index 0000000..9ff9123 --- /dev/null +++ b/src/core/lua_bridge.h @@ -0,0 +1,26 @@ +#ifndef LEMACS_LUA_BRIDGE_H +#define LEMACS_LUA_BRIDGE_H + +#include + +#include + +#include "core/editor.h" + +typedef struct { + lua_State *L; + Editor *editor; + char runtime_init_path[1024]; +} LuaBridge; + +bool lua_bridge_init(LuaBridge *bridge, Editor *editor); +void lua_bridge_free(LuaBridge *bridge); + +bool lua_bridge_load_runtime(LuaBridge *bridge, const char *init_script_path); +bool lua_bridge_load_optional_init(LuaBridge *bridge, const char *script_path); +bool lua_bridge_call_key(LuaBridge *bridge, const char *key_name); +bool lua_bridge_call_tick(LuaBridge *bridge); +bool lua_bridge_call_open_file(LuaBridge *bridge, const char *path); +bool lua_bridge_call_text(LuaBridge *bridge, const char *text); + +#endif -- cgit v1.3.1