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/lua_bridge.c | 611 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 611 insertions(+) create mode 100644 src/core/lua_bridge.c (limited to 'src/core/lua_bridge.c') 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"); +} -- cgit v1.3.1