#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"); }