Skip to content

Commit 3ddc2ac

Browse files
committed
-
1 parent fbf115c commit 3ddc2ac

16 files changed

Lines changed: 603 additions & 139 deletions

File tree

3rdparty/imguipack

Submodule imguipack updated 155 files

apis/LtgPluginApi.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ struct ScriptingError {
9292
};
9393
typedef std::vector<ScriptingError> ErrorContainer;
9494

95+
// One member of an object/namespace surfaced by the plugin's introspection for autocompletion.
96+
// `type` is the Lua type name reported by lua_typename ("function", "table", "number", "string", ...);
97+
// the host uses it to drive the popup's icon/color and ordering.
98+
struct CompletionEntry {
99+
std::string name;
100+
std::string type;
101+
};
102+
95103
// chunk name passed to the scripting runtime when compiling the in-memory project script.
96104
// the host's CodePane uses the SAME string as the sheet id, so a ScriptingError's `file` field
97105
// can be routed back to the matching sheet. keep both sides in sync.
@@ -144,6 +152,10 @@ struct ScriptingModule : public PluginModule, public IScriptDebugger {
144152
virtual void setRowIndex(int32_t vRowIndex) = 0;
145153
// will set the row count
146154
virtual void setRowCount(int32_t vRowCount) = 0;
155+
// introspects the plugin's live scripting state and returns the members of `aTarget` (a global
156+
// table or sol2 usertype). default no-op so non-Lua plugins compile unchanged. consumed by the
157+
// host's autocompletion popup; called from the UI thread, fast (one Lua table walk).
158+
virtual void getCompletionEntries(const std::string& /*aTarget*/, std::vector<CompletionEntry>& /*aoEntries*/) {}
147159
};
148160

149161
typedef std::shared_ptr<ScriptingModule> ScriptingModulePtr;

cmake/imguipack.cmake

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ set(USE_IMPLOT ON CACHE BOOL "" FORCE) ## graph panes need ImPlot
1717
set(USE_IM_GUIZMO OFF CACHE BOOL "" FORCE)
1818
set(USE_IMGUI_MARKDOW OFF CACHE BOOL "" FORCE)
1919
set(USE_IM_GRADIENT_HDR OFF CACHE BOOL "" FORCE)
20-
set(USE_IMGUI_NODE_EDITOR OFF CACHE BOOL "" FORCE)
2120

2221
add_subdirectory(${IMGUIPACK_SOURCE_DIR})
2322

plugins/LuaScripting/src/modules/Module.cpp

Lines changed: 113 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,24 @@ void Module::unload() {
208208
bool Module::compileScript(const Ltg::ScriptFilePathName& vFilePathName, Ltg::ErrorContainer& vOutErrors) {
209209
try {
210210
m_luaPtr->script_file(vFilePathName);
211+
// validate the required entry points exist — DO NOT call them here (see compileScriptCode
212+
// for the rationale: ScriptingEngine's loop calls them at the right time per source file).
211213
bool res = true;
212214
sol::function parse = (*m_luaPtr)["parse"];
213215
if (!parse.valid()) {
214216
LogVarLightError("Lua: %s", "the lua function parse(buffer) is missing");
215217
res = false;
216218
}
217-
res &= callScriptStart(vOutErrors);
218-
res &= callScriptEnd(vOutErrors);
219+
sol::function startFile = (*m_luaPtr)["startFile"];
220+
if (!startFile.valid()) {
221+
LogVarLightError("Lua: %s", "the lua function startFile() is missing");
222+
res = false;
223+
}
224+
sol::function endFile = (*m_luaPtr)["endFile"];
225+
if (!endFile.valid()) {
226+
LogVarLightError("Lua: %s", "the lua function endFile() is missing");
227+
res = false;
228+
}
219229
return res;
220230
} catch (const sol::error& ex) {
221231
LogVarError("Lua: Error in the Lua script : %s", ex.what());
@@ -232,14 +242,25 @@ bool Module::compileScriptCode(const std::string& aCode, Ltg::ErrorContainer& vO
232242
// pass an explicit chunk name so sol2's error messages identify our project script
233243
// (otherwise sol2 uses the first line of code as the chunk name, which breaks routing).
234244
m_luaPtr->script(aCode, Ltg::sc_PROJECT_SCRIPT_CHUNK); // compile + run the chunk (defines parse/startFile/endFile)
245+
// validate the required entry points exist — DO NOT call them here. ScriptingEngine's main
246+
// loop is responsible for calling startFile/parse/endFile at the proper times; calling them
247+
// here would double-execute user code (duplicate logs / state init).
235248
bool res = true;
236249
sol::function parse = (*m_luaPtr)["parse"];
237250
if (!parse.valid()) {
238251
LogVarLightError("Lua: %s", "the lua function parse(buffer) is missing");
239252
res = false;
240253
}
241-
res &= callScriptStart(vOutErrors);
242-
res &= callScriptEnd(vOutErrors);
254+
sol::function startFile = (*m_luaPtr)["startFile"];
255+
if (!startFile.valid()) {
256+
LogVarLightError("Lua: %s", "the lua function startFile() is missing");
257+
res = false;
258+
}
259+
sol::function endFile = (*m_luaPtr)["endFile"];
260+
if (!endFile.valid()) {
261+
LogVarLightError("Lua: %s", "the lua function endFile() is missing");
262+
res = false;
263+
}
243264
return res;
244265
} catch (const sol::error& ex) {
245266
Ltg::ScriptingError err;
@@ -261,6 +282,94 @@ bool Module::compileScriptCode(const std::string& aCode, Ltg::ErrorContainer& vO
261282
return false;
262283
}
263284

285+
void Module::m_ensureCompletionState() {
286+
if (m_completionLuaPtr != nullptr) {
287+
return;
288+
}
289+
// dedicated state that mirrors the analysis state's bindings but never executes user code.
290+
// we instantiate LuaDatasModel directly (skipping the create() factory which would null it out
291+
// when no IDatasModel is bound) because we only need the usertype methods to be REGISTERED in the
292+
// metatable for introspection — none of them is ever actually called from the completion state.
293+
m_completionLuaPtr = std::unique_ptr<sol::state>(new sol::state());
294+
m_completionLuaPtr->open_libraries(sol::lib::base);
295+
m_completionLuaPtr->open_libraries(sol::lib::package);
296+
m_completionLuaPtr->open_libraries(sol::lib::coroutine);
297+
m_completionLuaPtr->open_libraries(sol::lib::string);
298+
m_completionLuaPtr->open_libraries(sol::lib::os);
299+
m_completionLuaPtr->open_libraries(sol::lib::math);
300+
m_completionLuaPtr->open_libraries(sol::lib::table);
301+
m_completionLuaPtr->open_libraries(sol::lib::debug);
302+
m_completionLuaPtr->open_libraries(sol::lib::bit32);
303+
m_completionLuaPtr->open_libraries(sol::lib::io);
304+
m_completionLuaPtr->open_libraries(sol::lib::ffi);
305+
m_completionLuaPtr->open_libraries(sol::lib::jit);
306+
307+
// clang-format off
308+
m_completionLuaPtr->new_usertype<LuaDatasModel>(
309+
"LuaDatasModel", sol::constructors<std::shared_ptr<LuaDatasModel>()>(),
310+
"stringToEpoch", &LuaDatasModel::luaModuleStringToEpoch,
311+
"epochToString", &LuaDatasModel::luaModuleEpochToString,
312+
"addSignalTag", &LuaDatasModel::luaModuleAddSignalTag,
313+
"addSignalStatus", &LuaDatasModel::luaModuleAddSignalStatus,
314+
"addSignalValue", sol::overload(
315+
&LuaDatasModel::luaModuleAddSignalValue,
316+
&LuaDatasModel::luaModuleAddSignalValueWithDesc),
317+
"addSignalStartZone", &LuaDatasModel::luaModuleAddSignalStartZone,
318+
"addSignalEndZone", &LuaDatasModel::luaModuleAddSignalEndZone,
319+
"logInfo", &LuaDatasModel::luaModuleLogInfo,
320+
"logWarning", &LuaDatasModel::luaModuleLogWarning,
321+
"logError", &LuaDatasModel::luaModuleLogError,
322+
"logDebug", &LuaDatasModel::luaModuleLogDebug,
323+
"getRowIndex", &LuaDatasModel::luaModuleGetRowIndex,
324+
"getRowCount", &LuaDatasModel::luaModuleGetRowCount);
325+
// clang-format on
326+
327+
// empty stub instance: ctor is public, no IDatasModel is required for introspection.
328+
m_completionDatasModelPtr = std::make_shared<LuaDatasModel>();
329+
(*m_completionLuaPtr)["ltg"] = m_completionDatasModelPtr;
330+
}
331+
332+
void Module::m_iterateLuaTable(lua_State* apLua, int aTableIndex, std::vector<Ltg::CompletionEntry>& aoEntries) {
333+
// aTableIndex must be an absolute stack index (lua_next manipulates the stack, breaking relative refs).
334+
lua_pushnil(apLua);
335+
while (lua_next(apLua, aTableIndex) != 0) {
336+
// -2 is the key, -1 is the value
337+
if (lua_type(apLua, -2) == LUA_TSTRING) {
338+
Ltg::CompletionEntry entry;
339+
entry.name = lua_tostring(apLua, -2);
340+
entry.type = lua_typename(apLua, lua_type(apLua, -1));
341+
aoEntries.push_back(entry);
342+
}
343+
lua_pop(apLua, 1); // pop value, keep key for next iter
344+
}
345+
}
346+
347+
void Module::getCompletionEntries(const std::string& aTarget, std::vector<Ltg::CompletionEntry>& aoEntries) {
348+
if (aTarget.empty()) {
349+
return;
350+
}
351+
m_ensureCompletionState();
352+
lua_State* L = m_completionLuaPtr->lua_state();
353+
const int topBefore = lua_gettop(L);
354+
355+
lua_getglobal(L, aTarget.c_str());
356+
const int targetType = lua_type(L, -1);
357+
if (targetType == LUA_TTABLE) {
358+
// plain table (Lua stdlib namespace, user table) — iterate directly
359+
m_iterateLuaTable(L, lua_gettop(L), aoEntries);
360+
} else if (targetType == LUA_TUSERDATA) {
361+
// sol2 usertype — methods are stored in the metatable's __index field, hop through it
362+
if (lua_getmetatable(L, -1) != 0) {
363+
lua_getfield(L, -1, "__index");
364+
if (lua_type(L, -1) == LUA_TTABLE) {
365+
m_iterateLuaTable(L, lua_gettop(L), aoEntries);
366+
}
367+
lua_pop(L, 2); // __index + metatable
368+
}
369+
}
370+
lua_settop(L, topBefore);
371+
}
372+
264373
bool Module::callScriptStart(Ltg::ErrorContainer& vOutErrors) {
265374
sol::protected_function startFile = (*m_luaPtr)["startFile"];
266375
if (!startFile.valid()) {

plugins/LuaScripting/src/modules/Module.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class Module : public Ltg::ScriptingModule {
2727
std::unique_ptr<sol::state> m_luaPtr = nullptr;
2828
Ltg::IDatasModelWeak m_datasModel;
2929
LuaDatasModelPtr m_luaDatasModelPtr = nullptr;
30+
// dedicated, permanent sol::state that only holds the bindings + stdlib for autocompletion
31+
// introspection. lives independently of the analysis state (which is created/destroyed per run).
32+
std::unique_ptr<sol::state> m_completionLuaPtr = nullptr;
33+
LuaDatasModelPtr m_completionDatasModelPtr = nullptr;
3034

3135
private: // debug session state
3236
Ltg::IScriptDebugHost* m_debugHostPtr = nullptr; // borrowed, non-owning
@@ -54,6 +58,8 @@ class Module : public Ltg::ScriptingModule {
5458
void setRowIndex(int32_t vRowIndex) final;
5559
void setRowCount(int32_t vRowCount) final;
5660

61+
void getCompletionEntries(const std::string& aTarget, std::vector<Ltg::CompletionEntry>& aoEntries) final;
62+
5763
// IScriptDebugger — driven by the host
5864
void enableDebug(Ltg::IScriptDebugHost* apHost) final;
5965
void disableDebug() final;
@@ -71,4 +77,7 @@ class Module : public Ltg::ScriptingModule {
7177
std::vector<Ltg::DebugVar> m_expandRef(lua_State* apLua, int32_t aRef);
7278
Ltg::EvalResult m_evalExpression(lua_State* apLua, lua_Debug* apDebug, const std::string& aExpression);
7379
void m_releaseDebugRefs(lua_State* apLua);
80+
81+
void m_ensureCompletionState(); // lazy init of m_completionLuaPtr on first getCompletionEntries call
82+
static void m_iterateLuaTable(lua_State* apLua, int aTableIndex, std::vector<Ltg::CompletionEntry>& aoEntries);
7483
};

0 commit comments

Comments
 (0)