Files
text_editor/src/text_editor/lua_api.cpp
2025-05-05 16:49:28 +02:00

631 lines
18 KiB
C++

lua_State *LuaState = NULL;
String16 LuaCommandResult = {};
extern luaL_Reg LuaFunctions[];
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 Command_JumpNew(BSet *set) {
CheckpointBeforeGoto(set->window);
String current_dir = ChopLastSlash(set->buffer->name);
String buffer_name = GetUniqueBufferName(current_dir, "temp");
set->view = WindowOpenBufferView(set->window, buffer_name);
set->buffer = GetBuffer(set->view->active_buffer);
set->buffer->garbage = true;
}
void Command_ExecInNewBuffer(BSet set, String cmd, String working_dir) {
BSet main = GetActiveMainSet();
Command_JumpNew(&main);
Exec(main.view->id, true, cmd, working_dir);
ActiveWindow = set.window->id;
}
View *Command_ExecHidden(String buffer_name, String cmd, String working_dir) {
View *view = OpenBufferView(buffer_name);
Buffer *buffer = GetBuffer(view->active_buffer);
// buffer->garbage = true;
Exec(view->id, true, cmd, working_dir);
return view;
}
void Command_BeginJump(BSet *set, BufferID buffer_id = NullBufferID) {
CheckpointBeforeGoto(set->window);
set->buffer = GetBuffer(buffer_id);
set->view = WindowOpenBufferView(set->window, set->buffer->name);
}
void Command_EndJump(BSet set) {
Int pos = XYToPos(set.buffer, {0, set.buffer->line_starts.len - 1});
set.view->carets[0] = MakeCaret(pos);
UpdateScroll(set.window, true);
}
void Command_ListBuffers() {
BSet main = GetActiveMainSet();
ActiveWindow = main.window->id;
Command_JumpNew(&main);
for (Buffer *it = FirstBuffer; it; it = it->next) {
Command_Appendf(main.view, "%.*s\n", FmtString(it->name));
}
}
BSet Command_Exec(String cmd, String working_dir) {
BSet set = GetActiveMainSet();
ActiveWindow = set.window->id;
Command_JumpNew(&set);
Exec(set.view->id, true, cmd, working_dir);
return set;
}
BSet Command_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 {};
}
BSet main = GetActiveMainSet();
main.window->active_goto_list = main.view->id;
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);
ActiveWindow = main.window->id;
if (IsDir(file_path)) {
Command_JumpNew(&main);
Command_Appendf(main.view, "%.*s/..\n", FmtString(file_path));
for (FileIter it = IterateFiles(scratch, file_path); IsValid(it); Advance(&it)) {
Command_Appendf(main.view, "%.*s\n", FmtString(it.absolute_path));
}
} else {
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);
} else if (FieldString(LuaState, "kind") == "exec") {
String cmd = FieldString(LuaState, "cmd");
String working_dir = FieldString(LuaState, "working_dir");
Command_Exec(cmd, Command_GetDir());
} else if (FieldString(LuaState, "kind") == "exec_console") {
// this shouldn't change the focus/window/view
String cmd = FieldString(LuaState, "cmd");
String working_dir = FieldString(LuaState, "working_dir");
Exec(NullViewID, true, cmd, working_dir);
} else {
ReportWarningf("Failed to match any of ApplyRules results!");
}
return main;
}
void Command_Open(String16 path) {
Scratch scratch;
String string = ToString(scratch, path);
Command_Open(string);
}
int Lua_C(lua_State *L) {
String string = lua_tostring(L, 1);
lua_pop(L, 1);
Command_Exec(string, Command_GetDir());
return 0;
}
void Command_Eval(String string) {
if (luaL_dostring(LuaState, string.data) != LUA_OK) {
const char *error_message = lua_tostring(LuaState, -1);
ReportWarningf("Execution error! %s", error_message);
lua_pop(LuaState, 1);
}
}
void Command_Eval(String16 string) {
Scratch scratch;
Command_Eval(ToString(scratch, string));
}
int Lua_Eval(lua_State *L) {
String string = lua_tostring(L, 1);
lua_pop(L, 1);
Command_Eval(string);
return 0;
}
int Lua_NewBufferCMD(lua_State *L) {
String string = lua_tostring(L, 1);
lua_pop(L, 1);
String working_dir = Command_GetDir();
BSet set = GetActiveMainSet();
Command_ExecInNewBuffer(set, string, working_dir);
return 0;
}
int Lua_Cmd(lua_State *L) {
if (!lua_istable(L, -1)) luaL_error(L, "expected a table as argument");
defer { lua_pop(L, 1); };
lua_getfield(L, -1, "working_dir");
if (!lua_isstring(L, -1)) luaL_error(L, "expected a string for working_dir param");
String working_dir = lua_tostring(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "cmd");
if (!lua_isstring(L, -1)) luaL_error(L, "expected a string for cmd param");
String cmd = lua_tostring(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "destination");
String destination = lua_tostring(L, -1);
lua_pop(L, 1);
if (destination == "console") {
BSet set = GetConsoleSet();
Command_BeginJump(&set);
Exec(set.view->id, true, cmd, working_dir);
Command_EndJump(set);
} else {
BSet set = GetActiveMainSet();
Command_JumpNew(&set);
Exec(set.view->id, true, cmd, working_dir);
ActiveWindow = set.window->id;
}
return 0;
}
int Lua_Print(lua_State *L) {
Scratch scratch;
int nargs = lua_gettop(L);
View *null_view = GetView(NullViewID);
for (int i = 1; i <= nargs; i += 1) {
String string = lua_tostring(L, i);
Command_Appendf(null_view, "%.*s ", FmtString(string));
}
Command_Appendf(null_view, "\n");
lua_pop(L, nargs);
return 0;
}
int Lua_Kill(lua_State *L) {
BSet main = GetActiveMainSet();
KillProcess(main.view);
return 0;
}
int Lua_GetLoadWord(lua_State *L) {
BSet active = GetActiveSet();
Range range = active.view->carets[0].range;
if (GetSize(range) == 0) {
range = EncloseLoadWord(active.buffer, range.min);
}
Scratch scratch;
String string = AllocCharString(scratch, active.buffer, range);
lua_pushlstring(L, string.data, string.len);
return 1;
}
int Lua_New(lua_State *L) {
Scratch scratch;
String name = luaL_checkstring(L, 1);
lua_pop(L, 1);
BSet main = GetActiveMainSet();
if (!IsAbsolute(name)) {
String dir = GetDir(main.buffer);
name = Format(scratch, "%.*s/%.*s", FmtString(dir), FmtString(name));
}
WindowOpenBufferView(main.window, name);
return 0;
}
int Lua_Open(lua_State *L) {
Scratch scratch;
String path = luaL_checkstring(L, 1);
lua_pop(L, 1);
Command_Open(path);
return 0;
}
int Lua_SetProjectFile(lua_State *L) {
BSet set = GetActiveMainSet();
LuaProjectBuffer = set.buffer;
LuaProjectBuffer->user_change_id = -1;
return 0;
}
int Lua_ListCommands(lua_State *L) {
BSet main = GetActiveMainSet();
Command_BeginJump(&main);
for (int i = 0; LuaFunctions[i].name != NULL; i += 1) {
Command_Appendf(main.view, "%20s() ", LuaFunctions[i].name);
if (((i + 1) % 6) == 0) {
Command_Appendf(main.view, "\n");
}
}
Command_EndJump(main);
ActiveWindow = main.window->id;
return 0;
}
int Lua_Reopen(lua_State *L) {
ReopenBuffer(GetActiveMainSet());
return 0;
}
int Lua_ToggleFullscreen(lua_State *L) {
ToggleFullscreen();
return 0;
}
int Lua_ListBuffers(lua_State *L) {
Command_ListBuffers();
return 0;
}
int Lua_GetBufferList(lua_State *L) {
lua_createtable(L, 0, (int)BufferCount);
int i = 1;
for (Buffer *it = FirstBuffer; it; it = it->next) {
lua_pushinteger(L, i++);
lua_pushlstring(L, it->name.data, it->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_BufferExists(lua_State *L) {
String string = lua_tostring(L, 1);
lua_pop(L, 1);
Buffer *buffer = GetBuffer(string);
lua_pushboolean(L, buffer != NULL);
return 1;
}
int Lua_GetCFiles(lua_State *L) {
Command_GetCFiles();
return 0;
}
int Lua_Rename(lua_State *L) {
String string = lua_tostring(L, 1);
lua_pop(L, 1);
return 0;
}
int Lua_GetSelection(lua_State *L) {
Scratch scratch;
BSet main = GetActiveMainSet();
String16 string16 = GetString(main.buffer, main.view->carets[0].range);
String string = ToString(scratch, string16);
lua_pushlstring(L, string.data, string.len);
return 1;
}
int Lua_GetEntireBuffer(lua_State *L) {
Scratch scratch;
BSet main = GetActiveMainSet();
String16 string16 = GetString(main.buffer);
String string = ToString(scratch, string16);
lua_pushlstring(L, string.data, string.len);
return 1;
}
int Lua_GetClipboard(lua_State *L) {
Scratch scratch;
BSet main = GetActiveMainSet();
String string = ToString(scratch, SavedClipboardString);
lua_pushlstring(L, string.data, string.len);
return 1;
}
int Lua_GetFilename(lua_State *L) {
BSet main = GetActiveMainSet();
lua_pushlstring(L, main.buffer->name.data, main.buffer->name.len);
return 1;
}
int Lua_GetProjectPath(lua_State *L) {
if (LuaProjectBuffer) {
String path = ChopLastSlash(LuaProjectBuffer->name);
lua_pushlstring(L, path.data, path.len);
} else {
lua_pushlstring(L, "no config", 9);
}
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_GetDir(lua_State *L) {
String name = Command_GetDir();
lua_pushlstring(L, name.data, name.len);
return 1;
}
int Lua_SplitSize(lua_State *L) {
lua_Number num = lua_tonumber(L, 1);
lua_pop(L, 1);
BSet set = GetActiveMainSet();
WindowSplit *split = set.window->split_ref;
split->parent->value = num;
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");
}
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;
}
double GetFloat(lua_State *L, const char *name) {
lua_getfield(L, -1, name);
double num = lua_tonumber(L, -1);
lua_pop(L, 1);
return 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;
}
void PushEvent(lua_State *L, Event *event) {
lua_createtable(L, 0, EVENT_FIELD_COUNT);
#define lua_pushInt lua_pushinteger
#define lua_pushString lua_pushstring
#define lua_pushFloat lua_pushnumber
#define X(TYPE, KIND, NAME) \
lua_push##KIND(L, event->NAME); \
lua_setfield(L, -2, #NAME);
EVENT_FIELDS
#undef X
}
// :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 = {};
#define X(TYPE, KIND, NAME) event.NAME = (TYPE)Get##KIND(L, #NAME);
EVENT_FIELDS
#undef X
Add(&EventPlayback, event);
}
return 0;
}
void ReloadStyle();
extern String BaseLuaConfig;
void LoadLuaBuffer(Buffer *lua_buffer) {
if (!lua_buffer) return;
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;
}
void ReloadLuaConfigs() {
bool reload = false;
if (LuaConfigBuffer && !LuaConfigBuffer->dirty && LuaConfigBuffer->change_id != LuaConfigBuffer->user_change_id) {
reload = true;
}
if (LuaProjectBuffer && !LuaProjectBuffer->dirty && LuaProjectBuffer->change_id != LuaProjectBuffer->user_change_id) {
reload = true;
}
if (reload == false) {
return;
}
LoadLuaBuffer(LuaConfigBuffer);
LoadLuaBuffer(LuaProjectBuffer);
ReloadStyle();
ReloadFont();
for (Window *it = FirstWindow; it; it = it->next) {
if (!it->visible || it->absolute_position || it->is_title_bar || it->is_search_bar) {
continue;
}
it->draw_scrollbar = StyleDrawScrollbar;
it->draw_line_numbers = StyleDrawLineNumbers;
}
}
void CallOnCommand(Event *event) {
lua_getglobal(LuaState, "OnCommand");
PushEvent(LuaState, event);
if (lua_pcall(LuaState, 1, 0, 0) != 0) {
const char *error_message = lua_tostring(LuaState, -1);
ReportWarningf("Failed the call to OnCommand! %s", error_message);
lua_pop(LuaState, 1);
return;
}
}
void CallLuaOnUpdate(Event *event) {
lua_getglobal(LuaState, "OnUpdate");
PushEvent(LuaState, event);
if (lua_pcall(LuaState, 1, 0, 0) != 0) {
const char *error_message = lua_tostring(LuaState, -1);
ReportWarningf("Failed the call to OnUpdate! %s", error_message);
lua_pop(LuaState, 1);
return;
}
}
void CallLuaOnInit() {
if (luaL_dostring(LuaState, "OnInit()") != LUA_OK) {
const char *error_message = lua_tostring(LuaState, -1);
ReportErrorf("Error in OnInit! %s", error_message);
lua_pop(LuaState, 1);
}
}
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);
RawReplaceText(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;
ReloadLuaConfigs();
CallLuaOnInit();
}