Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions tsd/apps/tools/tsdLua.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,20 @@ static void runInteractiveMode(tsd::scripting::LuaContext &ctx)
if (line == "exit" || line == "quit") {
break;
}

auto cmd = ctx.runRegisteredCommand(line);
if (cmd.handled) {
if (cmd.success)
printf("%s", cmd.output.c_str());
else
fprintf(stderr, "Error: %s\n", cmd.error.c_str());
continue;
}

// Fallback when no `help` command is registered (script pack absent): show
// the C++-owned default help describing the built-in exposure.
if (line == "help") {
printf("Available globals:\n");
printf(" scene - The current TSD scene\n");
printf(" tsd - The TSD Lua module\n");
printf("\n");
printf("TSD namespaces:\n");
printf(" tsd.io - Importers and procedural generators\n");
printf(" tsd.render - Rendering functions (loadDevice, "
"createRenderIndex, etc.)\n");
printf("\n");
printf("Example:\n");
printf(" tsd.io.generateRandomSpheres(scene)\n");
printf(" print(scene:numberOfObjects(tsd.GEOMETRY))\n");
printf("%s", ctx.consoleDefaultHelp().c_str());
continue;
}

Expand Down
24 changes: 24 additions & 0 deletions tsd/src/tsd/scripting/LuaBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ void registerAllBindings(sol::state &lua)
tsd["io"] = lua.create_table();
tsd["render"] = lua.create_table();

// Terminal command registry: scripts populate `tsd.terminal.commands` with
// { run = fn(args) -> string, summary = string } records; the interactive
// frontends dispatch a line's first token here before evaluating as Lua.
sol::table terminal = lua.create_table();
terminal["commands"] = lua.create_table();
// Default help describing the C++-provided exposure. Owned here so it is
// available even without the script pack; the fallback prints it and the
// script-registered `help` command embeds it in its overview.
terminal["defaultHelp"] =
"Available globals:\n"
" scene The current TSD scene\n"
" animationMgr Animation collection + time/frame control\n"
" tsd The TSD Lua module\n"
"\n"
"TSD namespaces:\n"
" tsd.io Importers and procedural generators\n"
" tsd.render Offline rendering (loadDevice, createRenderIndex, ...)\n"
" tsd.viewer Viewer integration (refresh, addMenuAction; viewer only)\n"
"\n"
"Example:\n"
" tsd.io.generateRandomSpheres(scene)\n"
" print(scene:numberOfObjects(tsd.GEOMETRY))\n";
tsd["terminal"] = terminal;

// Register bindings in order of dependency
registerMathBindings(lua);
registerContextBindings(lua);
Expand Down
72 changes: 72 additions & 0 deletions tsd/src/tsd/scripting/LuaContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// fmt
#include <fmt/format.h>
// std
#include <cctype>
#include <filesystem>

namespace tsd::scripting {
Expand Down Expand Up @@ -131,6 +132,77 @@ ExecutionResult LuaContext::executeString(const std::string &script)
return result;
}

ConsoleCommandResult LuaContext::runRegisteredCommand(const std::string &line)
{
ConsoleCommandResult result;

// Command dispatch is single-line; multiline input is always Lua.
if (line.find('\n') != std::string::npos)
return result;

std::vector<std::string> tokens;
for (size_t i = 0; i < line.size();) {
while (i < line.size() && std::isspace((unsigned char)line[i]))
i++;
size_t start = i;
while (i < line.size() && !std::isspace((unsigned char)line[i]))
i++;
if (i > start)
tokens.push_back(line.substr(start, i - start));
}
if (tokens.empty())
return result;

sol::table tsd = m_impl->lua["tsd"];
if (!tsd.valid())
return result;
sol::optional<sol::table> terminal = tsd["terminal"];
if (!terminal)
return result;
sol::optional<sol::table> commands = (*terminal)["commands"];
if (!commands)
return result;
sol::object cmd = (*commands)[tokens[0]];
if (!cmd.is<sol::table>())
return result;
sol::protected_function run = cmd.as<sol::table>()["run"];
if (!run.valid())
return result;

result.handled = true;

sol::table args = m_impl->lua.create_table();
for (size_t i = 1; i < tokens.size(); i++)
args[i] = tokens[i]; // 1-based: args[1] is the first argument

auto callResult = run(args);
if (!callResult.valid()) {
sol::error err = callResult;
result.error = err.what();
return result;
}

result.success = true;
if (callResult.return_count() > 0) {
sol::object ret = callResult[0];
if (ret.is<std::string>())
result.output = ret.as<std::string>();
}
return result;
}

std::string LuaContext::consoleDefaultHelp()
{
sol::table tsd = m_impl->lua["tsd"];
if (!tsd.valid())
return {};
sol::optional<sol::table> terminal = tsd["terminal"];
if (!terminal)
return {};
sol::optional<std::string> help = (*terminal)["defaultHelp"];
return help.value_or(std::string{});
}

std::vector<std::string> LuaContext::addScriptSearchPaths(
const std::vector<std::string> &paths)
{
Expand Down
22 changes: 22 additions & 0 deletions tsd/src/tsd/scripting/LuaContext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ struct ExecutionResult
std::string output;
};

// Result of attempting to dispatch a console line to a registered terminal
// command (the `tsd.terminal.commands` table). `handled` is false when the
// line's first token is not a registered command, in which case the caller
// should evaluate the line as Lua instead.
struct ConsoleCommandResult
{
bool handled{false};
bool success{false};
std::string output; // string returned by the command's run()
std::string error;
};

class LuaContext
{
public:
Expand All @@ -45,6 +57,16 @@ class LuaContext
ExecutionResult executeFile(const std::string &filepath);
ExecutionResult executeString(const std::string &script);

// If `line`'s first whitespace-delimited token names a registered command in
// `tsd.terminal.commands`, call its `run(args)` with the remaining tokens and
// return the result. Single-line input only; returns `handled == false`
// otherwise so the caller can evaluate `line` as Lua.
ConsoleCommandResult runRegisteredCommand(const std::string &line);

// The C++-owned default help text (`tsd.terminal.defaultHelp`), shown by the
// frontends when no `help` command is registered.
std::string consoleDefaultHelp();

// Scene is NOT owned by LuaContext
void bindScene(scene::Scene *scene, const std::string &varName = "scene");

Expand Down
30 changes: 13 additions & 17 deletions tsd/src/tsd/ui/imgui/windows/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,24 +135,20 @@ void Terminal::executeCommand(const std::string &command)
clear();
return;
}

auto cmd = m_luaContext->runRegisteredCommand(command);
if (cmd.handled) {
if (cmd.success)
addOutput(cmd.output);
else
addOutput("Error: " + cmd.error + "\n");
return;
}

// Fallback when no `help` command is registered (script pack absent): show
// the C++-owned default help describing the built-in exposure.
if (command == "help") {
addOutput(
"Available globals:\n"
" scene - The current TSD scene\n"
" tsd - The TSD Lua module\n"
"\n"
"TSD namespaces:\n"
" tsd.io - Importers and procedural generators\n"
" tsd.render - Rendering functions\n"
" tsd.viewer - Viewer integration (refresh, etc.)\n"
"\n"
"Built-in commands:\n"
" clear - Clear the terminal\n"
" help - Show this help\n"
"\n"
"Example:\n"
" tsd.io.generateRandomSpheres(scene)\n"
" tsd.viewer.refresh()\n");
addOutput(m_luaContext->consoleDefaultHelp());
return;
}

Expand Down
Loading