Files
text_editor/src/text_editor/lua_api.cpp
2024-08-14 15:08:27 +02:00

461 lines
14 KiB
C++

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