lua_State *LuaState = NULL; String16 LuaCommandResult = {}; String FieldString(lua_State *L, String name) { String result = {}; if (lua_istable(L, -1)) { lua_pushlstring(L, name.data, name.len); lua_gettable(L, -2); defer { lua_pop(L, 1); }; if (lua_isstring(L, -1)) { result = lua_tostring(L, -1); } } return result; } void ExecInNewBuffer(String cmd, String working_dir) { BSet main = GetActiveMainSet(); CheckpointBeforeGoto(main.window); Scratch scratch; String buffer_name = GetUniqueBufferName(scratch, working_dir, "+cmd-"); View *view = WindowOpenBufferView(main.window, buffer_name); Buffer *buffer = GetBuffer(view->active_buffer); buffer->gc = true; Command_SelectRangeOneCursor(view, Rng(0)); Exec(view->id, false, cmd, working_dir); ActiveWindow = main.window->id; } void Open(String path) { Scratch scratch; lua_getglobal(LuaState, "ApplyRules"); lua_pushlstring(LuaState, path.data, path.len); if (lua_pcall(LuaState, 1, 1, 0) != 0) { const char *error_message = lua_tostring(LuaState, -1); ReportWarningf("Failed the call to ApplyRules! %s", error_message); lua_pop(LuaState, 1); return; } if (FieldString(LuaState, "kind") == "text") { String file_path = FieldString(LuaState, "file_path"); String line_string = FieldString(LuaState, "line"); Int line = strtoll(line_string.data, NULL, 10); String col_string = FieldString(LuaState, "col"); Int col = strtoll(col_string.data, NULL, 10); BSet main = GetActiveMainSet(); CheckpointBeforeGoto(main.window); View *view = WindowOpenBufferView(main.window, file_path); Buffer *buffer = GetBuffer(view->active_buffer); if (line != -1 && col != -1) { Int pos = XYToPos(buffer, {col - 1, line - 1}); view->carets[0] = MakeCaret(pos); } UpdateScroll(main.window, true); ActiveWindow = main.window->id; } else if (FieldString(LuaState, "kind") == "exec") { String cmd = FieldString(LuaState, "cmd"); String working_dir = FieldString(LuaState, "working_dir"); ExecInNewBuffer(cmd, working_dir); } else { ReportWarningf("Failed to match any of ApplyRules results!"); } } void Open(String16 path) { Scratch scratch; String string = ToString(scratch, path); Open(string); } int Lua_FuzzySort(lua_State *L) { String string = lua_tostring(L, 1); lua_pop(L, 1); Scratch scratch; String16 string16 = ToString16(scratch, string); BSet main = GetActiveMainSet(); Command_FuzzySort(main.view, string16); return 0; } int Lua_AppendCmd(lua_State *L) { String string = lua_tostring(L, 1); lua_pop(L, 1); String working_dir = GetActiveMainWindowBufferDir(); Exec(NullViewID, true, string, working_dir); return 0; } int Lua_C(lua_State *L) { String string = lua_tostring(L, 1); lua_pop(L, 1); String working_dir = GetActiveMainWindowBufferDir(); ExecInNewBuffer(string, working_dir); return 0; } int Lua_Kill(lua_State *L) { BSet main = GetActiveMainSet(); KillProcess(main.view); return 0; } int Lua_Open(lua_State *L) { Scratch scratch; String path = luaL_checkstring(L, 1); lua_pop(L, 1); Open(path); return 0; } int Lua_Reopen(lua_State *L) { BSet main = GetActiveMainSet(); ReopenBuffer(main.view, main.buffer); return 0; } int Lua_Print(lua_State *L) { Scratch scratch; String string = lua_tostring(L, 1); lua_pop(L, 1); View *null_view = GetView(NullViewID); Command_Append(null_view, string, true); return 0; } int Lua_ListBuffers(lua_State *L) { Command_ListBuffers(); return 0; } int Lua_GetBufferList(lua_State *L) { lua_createtable(L, 0, (int)Buffers.len); int i = 1; For(Buffers) { if (StartsWith(it.o->name, "+titlebar")) continue; lua_pushinteger(L, i++); lua_pushlstring(L, it.o->name.data, it.o->name.len); lua_settable(L, -3); /* 3rd element from the stack top */ } /* We still have table left on top of the Lua stack. */ return 1; } int Lua_FileExists(lua_State *L) { String path = luaL_checkstring(L, 1); lua_pop(L, 1); bool exists = FileExists(path); lua_pushboolean(L, exists); return 1; } int Lua_GetWorkingDir(lua_State *L) { lua_pushlstring(L, WorkingDir.data, WorkingDir.len); return 1; } int Lua_GetActiveMainWindowBufferName(lua_State *L) { String name = GetActiveMainWindowBufferName(); lua_pushlstring(L, name.data, name.len); return 1; } int Lua_GetActiveMainWindowBufferDir(lua_State *L) { String name = GetActiveMainWindowBufferDir(); lua_pushlstring(L, name.data, name.len); return 1; } void ListFilesRecursive(Buffer *buffer, String filename) { Scratch scratch(buffer->line_starts.allocator); for (FileIter it = IterateFiles(scratch, filename); IsValid(it); Advance(&it)) { if (it.filename == ".git") continue; if (it.is_directory) { ListFilesRecursive(buffer, it.absolute_path); } else { IKnowWhatImDoing_Appendf(buffer, "%.*s\n", FmtString(it.absolute_path)); } } } int Lua_Ls(lua_State *L) { BSet main = GetActiveMainSet(); Scratch scratch; String buffer_name = GetUniqueBufferName(scratch, GetDir(main.buffer), "+ls-"); Buffer *buffer = CreateBuffer(GetSystemAllocator(), buffer_name, 4096 * 4); ListFilesRecursive(buffer, GetActiveMainWindowBufferDir()); WindowOpenBufferView(main.window, buffer_name); return 0; } int Lua_Search(lua_State *L) { BSet main = GetActiveMainSet(); main.window->search_string = lua_tostring(L, 1); lua_pop(L, 1); Scratch scratch; Command_Find(main.view, ToString16(scratch, main.window->search_string), true); return 0; } int Lua_SearchB(lua_State *L) { BSet main = GetActiveMainSet(); main.window->search_string = lua_tostring(L, 1); lua_pop(L, 1); Scratch scratch; Command_Find(main.view, ToString16(scratch, main.window->search_string), false); return 0; } int Lua_Rename(lua_State *L) { String string = lua_tostring(L, 1); lua_pop(L, 1); return 0; } static void HookLuaForceExit(lua_State *L, lua_Debug *debug) { SDL_PumpEvents(); int numkeys = 0; const uint8_t *keys = SDL_GetKeyboardState(&numkeys); if (keys[SDL_SCANCODE_F9] > 0) luaL_error(L, "lua execution got interrupted"); } extern luaL_Reg LuaFunctions[]; Int GetStyleInt(String name, Int default_int) { Int result = default_int; lua_getglobal(LuaState, "Style"); defer { lua_pop(LuaState, 1); }; if (lua_istable(LuaState, -1)) { lua_pushlstring(LuaState, name.data, name.len); lua_gettable(LuaState, -2); defer { lua_pop(LuaState, 1); }; if (lua_isnumber(LuaState, -1) || lua_isboolean(LuaState, -1) || lua_isinteger(LuaState, -1)) { lua_Integer num = lua_tointeger(LuaState, -1); result = (Int)num; } } return result; } String GetStyleString(String name, String default_string) { String result = default_string; lua_getglobal(LuaState, "Style"); defer { lua_pop(LuaState, 1); }; if (lua_istable(LuaState, -1)) { lua_pushlstring(LuaState, name.data, name.len); lua_gettable(LuaState, -2); defer { lua_pop(LuaState, 1); }; if (lua_isstring(LuaState, -1)) { const char *string = lua_tostring(LuaState, -1); result = Intern(&GlobalInternTable, string); } } return result; } Color GetColor(String name, Color default_color) { Color result = default_color; lua_getglobal(LuaState, "Color"); defer { lua_pop(LuaState, 1); }; if (lua_istable(LuaState, -1)) { lua_pushlstring(LuaState, name.data, name.len); lua_gettable(LuaState, -2); defer { lua_pop(LuaState, 1); }; if (lua_isnumber(LuaState, -1)) { lua_Integer num = lua_tointeger(LuaState, -1); result.r = (uint8_t)((0xFF000000 & num) >> 24); result.g = (uint8_t)((0x00FF0000 & num) >> 16); result.b = (uint8_t)((0x0000FF00 & num) >> 8); result.a = (uint8_t)((0x000000FF & num) >> 0); } } return result; } Int GetInt(lua_State *L, const char *name) { lua_getfield(L, -1, name); lua_Integer num = lua_tointeger(L, -1); lua_pop(L, 1); return (Int)num; } const char *GetString(lua_State *L, const char *name) { lua_getfield(L, -1, name); const char *result = lua_tostring(L, -1); lua_pop(L, 1); return result; } Vec2 GetVec2(lua_State *L, const char *name) { Vec2 result = {}; lua_getfield(L, -1, name); defer { lua_pop(L, 1); }; { lua_getfield(L, -1, "1"); defer { lua_pop(L, 1); }; result.x = (float)lua_tonumber(L, -1); } { lua_getfield(L, -1, "2"); defer { lua_pop(L, 1); }; result.y = (float)lua_tonumber(L, -1); } return result; } // :Event int Lua_Play(lua_State *L) { if (!lua_istable(L, -1)) luaL_error(L, "expected a table of events"); defer { lua_pop(L, 1); }; int size = (int)lua_rawlen(L, -1); for (int i = 0; i < size; i += 1) { lua_geti(L, -1, i + 1); if (!lua_istable(L, -1)) luaL_error(L, "expected a table of events"); defer { lua_pop(L, 1); }; Event event = {}; event.kind = (EventKind)GetInt(L, "kind"); event.key = (SDL_Keycode)GetInt(L, "key"); event.xmouse = (int16_t)GetInt(L, "xmouse"); event.ymouse = (int16_t)GetInt(L, "ymouse"); event.xwindow = (int16_t)GetInt(L, "xwindow"); event.ywindow = (int16_t)GetInt(L, "ywindow"); event.clicks = (uint8_t)GetInt(L, "clicks"); event.flags = (uint8_t)GetInt(L, "flags"); event.text = (char *)GetString(L, "text"); event.wheel = GetVec2(L, "wheel"); Add(&EventPlayback, event); } return 0; } void ReloadStyle(); extern String BaseLuaConfig; int LoadLuaBuffer(Buffer *lua_buffer) { if (!lua_buffer || lua_buffer->dirty || lua_buffer->change_id == lua_buffer->user_change_id) return false; ReportConsolef("reloading config: %.*s", FmtString(lua_buffer->name)); Scratch scratch; String string = AllocCharString(scratch, lua_buffer); if (luaL_dostring(LuaState, string.data) == LUA_OK) { if (lua_isstring(LuaState, -1)) { const char *text = lua_tostring(LuaState, -1); ReportConsolef(text); lua_pop(LuaState, 1); } } else { const char *error_message = lua_tostring(LuaState, -1); ReportWarningf("Failed to load user config! %s", error_message); lua_pop(LuaState, 1); } lua_buffer->user_change_id = lua_buffer->change_id; return true; } void UpdateLua() { int base_config_reload = LoadLuaBuffer(LuaConfigBuffer); int project_config_reload = LoadLuaBuffer(LuaProjectBuffer); if (base_config_reload || project_config_reload) { ReportConsolef("reloading style variables because config changed"); ReloadStyle(); ReloadFont(); // @todo: maybe don't reload font if nothing changed For(Windows) { Window *window = it.o; if (!window->visible || window->absolute_position || window->is_title_bar) continue; window->draw_scrollbar = StyleDrawScrollbar; window->draw_line_numbers = StyleDrawLineNumbers; } } if (luaL_dostring(LuaState, "OnUpdate()") != 0) { const char *error_message = lua_tostring(LuaState, -1); ReportWarningf("OnUpdate failed! %s", error_message); lua_pop(LuaState, 1); return; } } void InitLuaConfig() { LuaState = luaL_newstate(); luaL_openlibs(LuaState); lua_sethook(LuaState, HookLuaForceExit, LUA_MASKCOUNT, 100000000); for (int i = 0; LuaFunctions[i].name; i += 1) { lua_pushcfunction(LuaState, LuaFunctions[i].func); lua_setglobal(LuaState, LuaFunctions[i].name); } // Init base config, test that it works and initialize the lua stuff ReportConsolef("load base lua config"); if (!luaL_dostring(LuaState, BaseLuaConfig.data) == LUA_OK) { const char *error_message = lua_tostring(LuaState, -1); ReportErrorf("Failed to load base lua config! %s", error_message); lua_pop(LuaState, 1); Assert(!"Invalid codepath"); } // Init user config Scratch scratch; String lua_config_path = Format(scratch, "%.*s/init.lua", FmtString(ConfigDir)); #if DEBUG_BUILD // WARNING! Delete config to make sure we are running this code more frequently SDL_RemovePath(lua_config_path.data); ReportConsolef("deleting config for debug purposes!"); #endif Buffer *lua_buffer = BufferOpenFile(lua_config_path); if (lua_buffer->len == 0) { String16 string16 = ToString16(scratch, BaseLuaConfig); IKnowWhatImDoing_ReplaceText(lua_buffer, {}, string16); ReportConsolef("no config at: %.*s - creating config buffer", FmtString(lua_config_path)); } lua_buffer->user_change_id = -1; // if we loaded from file this should force to read LuaConfigBuffer = lua_buffer; UpdateLua(); } // bool EvalString(Allocator allocator, String16 string16) { Scratch scratch((Arena *)allocator.object); String string = ToString(scratch, string16); if (luaL_dostring(LuaState, string.data) != LUA_OK) { const char *error_message = lua_tostring(LuaState, -1); ReportConsolef("Execution error! %s", error_message); lua_pop(LuaState, 1); return false; } return true; } bool Command_EvalLua(View *view, String16 string) { Scratch scratch; Buffer *buffer = GetBuffer(view->active_buffer); return EvalString(scratch, string); }