summaryrefslogtreecommitdiff
path: root/src/core/lua_bridge.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/lua_bridge.c')
-rw-r--r--src/core/lua_bridge.c611
1 files changed, 611 insertions, 0 deletions
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");
+}