summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-05-11 15:07:55 -0500
committerCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-05-11 15:07:55 -0500
commitcebb280fe546190e55e071b6adbf37cd47950f34 (patch)
tree6e7a7605f8832b1c1bd7e467884a8792a5e1fab4 /src/core
initial commit
Diffstat (limited to 'src/core')
-rw-r--r--src/core/buffer.c296
-rw-r--r--src/core/buffer.h38
-rw-r--r--src/core/editor.c466
-rw-r--r--src/core/editor.h96
-rw-r--r--src/core/lua_bridge.c611
-rw-r--r--src/core/lua_bridge.h26
6 files changed, 1533 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <stdbool.h>
+#include <stddef.h>
+
+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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+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 <stdbool.h>
+#include <stdint.h>
+
+#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 <lauxlib.h>
+#include <lualib.h>
+
+#include <raylib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+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 <stdbool.h>
+
+#include <lua.h>
+
+#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