Skip to content

Commit 8b06705

Browse files
committed
-
1 parent 89c4506 commit 8b06705

23 files changed

Lines changed: 2643 additions & 53 deletions

apis/LtgPluginApi.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ struct CompletionEntry {
100100
std::string type;
101101
};
102102

103+
// One argument descriptor inside a SignatureInfo. `type` is a free-form display string
104+
// ("number", "string", "table", "any", ...) — informational, no runtime semantics.
105+
struct SignatureArg {
106+
std::string name;
107+
std::string type;
108+
};
109+
110+
// One function/method signature surfaced by the plugin for the signature-help tooltip (the IDE
111+
// hint that appears when the user types `(`, and that highlights the arg currently being typed).
112+
// `label` is what the tooltip displays before the parens (e.g. "ltg:addSignalTag"); `args` is
113+
// the ordered list of parameter descriptors. The plugin owns a curated catalog — Lua/LuaJIT
114+
// cannot introspect C-binding param names, and Lua function param names are not portable across
115+
// 5.1/5.2 / LuaJIT.
116+
struct SignatureInfo {
117+
std::string label;
118+
std::vector<SignatureArg> args;
119+
};
120+
103121
// chunk name passed to the scripting runtime when compiling the in-memory project script.
104122
// the host's CodePane uses the SAME string as the sheet id, so a ScriptingError's `file` field
105123
// can be routed back to the matching sheet. keep both sides in sync.
@@ -156,6 +174,11 @@ struct ScriptingModule : public PluginModule, public IScriptDebugger {
156174
// table or sol2 usertype). default no-op so non-Lua plugins compile unchanged. consumed by the
157175
// host's autocompletion popup; called from the UI thread, fast (one Lua table walk).
158176
virtual void getCompletionEntries(const std::string& /*aTarget*/, std::vector<CompletionEntry>& /*aoEntries*/) {}
177+
// returns the signature info for `aTarget:aFunctionName` (or `aTarget.aFunctionName`, or just
178+
// `aFunctionName` when aTarget is empty) — used by the host signature-help tooltip when the
179+
// user types `(`. default no-op so non-Lua plugins compile unchanged. An empty aoSignature
180+
// (no label, no args) means "no signature known" — host then doesn't open the tooltip.
181+
virtual void getSignatureInfo(const std::string& /*aTarget*/, const std::string& /*aFunctionName*/, SignatureInfo& /*aoSignature*/) {}
159182
};
160183

161184
typedef std::shared_ptr<ScriptingModule> ScriptingModulePtr;

plugins/LuaScripting/src/modules/Module.cpp

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <ezlibs/ezFile.hpp>
66
#include <ezlibs/ezTime.hpp>
77
#include <ezlibs/ezLog.hpp>
8+
#include <algorithm>
89
#include <exception>
910
#include <chrono>
1011
#include <ctime>
@@ -329,16 +330,36 @@ void Module::m_ensureCompletionState() {
329330
(*m_completionLuaPtr)["ltg"] = m_completionDatasModelPtr;
330331
}
331332

333+
// Filter out keys that are Lua-internal metamethods (`__name`, `__index`, `__gc`, `__eq`,
334+
// `__pairs`, `__newindex`, `__type`, ...) and sol2-internal helpers (`class_check`, `class_cast`,
335+
// `new`). These appear in the metatable iteration but are noise from the user's POV — they
336+
// don't help write Lua, they expose implementation details.
337+
static bool s_isCompletionNoise(const std::string& aName) {
338+
if (aName.size() >= 2 && aName[0] == '_' && aName[1] == '_') {
339+
return true; // any `__*` metamethod
340+
}
341+
if (aName.compare(0, 6, "class_") == 0) {
342+
return true; // sol2 inheritance bookkeeping (class_check, class_cast)
343+
}
344+
if (aName == "new") {
345+
return true; // sol2 default constructor binding — never useful through `:method()` autocompletion
346+
}
347+
return false;
348+
}
349+
332350
void Module::m_iterateLuaTable(lua_State* apLua, int aTableIndex, std::vector<Ltg::CompletionEntry>& aoEntries) {
333351
// aTableIndex must be an absolute stack index (lua_next manipulates the stack, breaking relative refs).
334352
lua_pushnil(apLua);
335353
while (lua_next(apLua, aTableIndex) != 0) {
336354
// -2 is the key, -1 is the value
337355
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);
356+
std::string keyName = lua_tostring(apLua, -2);
357+
if (!s_isCompletionNoise(keyName)) {
358+
Ltg::CompletionEntry entry;
359+
entry.name = std::move(keyName);
360+
entry.type = lua_typename(apLua, lua_type(apLua, -1));
361+
aoEntries.push_back(std::move(entry));
362+
}
342363
}
343364
lua_pop(apLua, 1); // pop value, keep key for next iter
344365
}
@@ -368,6 +389,72 @@ void Module::getCompletionEntries(const std::string& aTarget, std::vector<Ltg::C
368389
}
369390
}
370391
lua_settop(L, topBefore);
392+
393+
// alphabetic order — lua_next returns hash-table order which has no stable meaning to the user.
394+
std::sort(aoEntries.begin(), aoEntries.end(), [](const Ltg::CompletionEntry& aLhs, const Ltg::CompletionEntry& aRhs) {
395+
return aLhs.name < aRhs.name;
396+
});
397+
}
398+
399+
namespace {
400+
401+
// Hand-maintained signature catalog for the Lua plugin. Must be kept in sync with the bindings
402+
// in Module::load (and m_ensureCompletionState). For overloaded methods we list the widest
403+
// variant — the host shows a single line in v1, no overload picker yet. v1 covers `ltg:`
404+
// methods only; `math.*` / `string.*` curated entries will land next.
405+
struct CatalogArg {
406+
const char* name;
407+
const char* type;
408+
};
409+
struct SignatureEntry {
410+
const char* target; // "" for globals
411+
const char* functionName;
412+
std::vector<CatalogArg> args;
413+
};
414+
415+
const std::vector<SignatureEntry>& s_signatureCatalog() {
416+
static const std::vector<SignatureEntry> catalog = {
417+
// ltg: usertype methods (cf. new_usertype<LuaDatasModel> in Module::load)
418+
{"ltg", "stringToEpoch", {{"dateTime","string"}, {"hourOffset","number"}}},
419+
{"ltg", "epochToString", {{"epochTime","number"}, {"hourOffset","number"}}},
420+
{"ltg", "addSignalTag", {{"epoch","number"}, {"r","number"}, {"g","number"}, {"b","number"}, {"a","number"}, {"name","string"}, {"help","string"}}},
421+
{"ltg", "addSignalStatus", {{"category","string"}, {"name","string"}, {"epoch","number"}, {"status","string"}}},
422+
{"ltg", "addSignalValue", {{"category","string"}, {"name","string"}, {"epoch","number"}, {"value","number"}, {"desc","string"}}},
423+
{"ltg", "addSignalStartZone", {{"category","string"}, {"name","string"}, {"epoch","number"}, {"startMsg","string"}}},
424+
{"ltg", "addSignalEndZone", {{"category","string"}, {"name","string"}, {"epoch","number"}, {"endMsg","string"}}},
425+
{"ltg", "logInfo", {{"message","string"}}},
426+
{"ltg", "logWarning", {{"message","string"}}},
427+
{"ltg", "logError", {{"message","string"}}},
428+
{"ltg", "logDebug", {{"message","string"}}},
429+
{"ltg", "getRowIndex", {}},
430+
{"ltg", "getRowCount", {}},
431+
};
432+
return catalog;
433+
}
434+
435+
} // namespace
436+
437+
void Module::getSignatureInfo(const std::string& aTarget, const std::string& aFunctionName, Ltg::SignatureInfo& aoSignature) {
438+
if (aFunctionName.empty()) {
439+
return;
440+
}
441+
for (const auto& entry : s_signatureCatalog()) {
442+
if (aTarget == entry.target && aFunctionName == entry.functionName) {
443+
aoSignature.label = entry.target[0] != '\0'
444+
? std::string(entry.target) + ":" + entry.functionName
445+
: entry.functionName;
446+
aoSignature.args.clear();
447+
aoSignature.args.reserve(entry.args.size());
448+
for (const auto& catArg : entry.args) {
449+
Ltg::SignatureArg arg;
450+
arg.name = catArg.name;
451+
arg.type = catArg.type;
452+
aoSignature.args.push_back(std::move(arg));
453+
}
454+
return;
455+
}
456+
}
457+
// not in the catalog — leave aoSignature empty, the host won't open the tooltip
371458
}
372459

373460
bool Module::callScriptStart(Ltg::ErrorContainer& vOutErrors) {

plugins/LuaScripting/src/modules/Module.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class Module : public Ltg::ScriptingModule {
5959
void setRowCount(int32_t vRowCount) final;
6060

6161
void getCompletionEntries(const std::string& aTarget, std::vector<Ltg::CompletionEntry>& aoEntries) final;
62+
void getSignatureInfo(const std::string& aTarget, const std::string& aFunctionName, Ltg::SignatureInfo& aoSignature) final;
6263

6364
// IScriptDebugger — driven by the host
6465
void enableDebug(Ltg::IScriptDebugHost* apHost) final;

src/backend/MainBackend.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@
3636
#include <systems/AppSettings.h>
3737

3838
// we include the cpp just for embedded fonts
39-
#include <res/fontIcons.cpp>
40-
#include <res/Roboto_Medium.cpp>
39+
#include <fonts/fontIcons.cpp>
40+
#include <fonts/Roboto_Medium.cpp>
41+
#include <fonts/DejaVuSansMono-Bold.h>
4142

4243
#include <filesystem>
4344
namespace fs = std::filesystem;
@@ -244,9 +245,15 @@ void MainBackend::m_MainLoop() {
244245
{
245246
// idle wait — runtime toggle + timeout from AppSettings (cf. SettingsDialog "app/general").
246247
// gated by IsJoinable so the threading progress bar keeps animating during analyse.
247-
if (AppSettings::ref()->isWaitEventsEnabled() && !ScriptingEngine::ref()->IsJoinable()) {
248+
const bool waitEventsActive = AppSettings::ref()->isWaitEventsEnabled() && !ScriptingEngine::ref()->IsJoinable();
249+
if (waitEventsActive) {
248250
glfwWaitEventsTimeout(AppSettings::ref()->getWaitEventsTimeoutSec());
249251
}
252+
// disable text-caret blink when frames are gated by wait-events: there isn't a steady
253+
// frame stream to animate the blink timer with, so the caret would freeze in whatever
254+
// half of the cycle it last landed (looks like a bug). With blink off, the caret stays
255+
// fixed visible — still hidden by TextEditor when the editor loses focus.
256+
ImGui::GetIO().ConfigInputTextCursorBlink = !waitEventsActive;
250257
IAGPNewFrame("GPU Frame", "GPU Frame"); // a main Zone is always needed
251258

252259
ProjectFile::ref()->NewFrame();
@@ -421,6 +428,11 @@ bool MainBackend::m_InitImGui() {
421428
assert(0); // failed to load font
422429
}
423430
}
431+
{ // dev font
432+
if (ImGui::GetIO().Fonts->AddFontFromMemoryCompressedBase85TTF(DVSMB_compressed_data_base85, 15.0f) == nullptr) {
433+
assert(0); // failed to load font
434+
}
435+
}
424436
}
425437

426438
// Setup Platform/Renderer bindings

0 commit comments

Comments
 (0)