diff --git a/src/plugins/lua/bindings/qtcprocess.cpp b/src/plugins/lua/bindings/qtcprocess.cpp index e52c3f3b299..01e48b4a7c9 100644 --- a/src/plugins/lua/bindings/qtcprocess.cpp +++ b/src/plugins/lua/bindings/qtcprocess.cpp @@ -10,10 +10,24 @@ using namespace Utils; namespace Lua::Internal { +FilePath toFilePath(std::variant &&v) +{ + return std::visit( + [](auto &&arg) -> FilePath { + using T = std::decay_t; + if constexpr (std::is_same_v) + return FilePath::fromUserInput(arg); + else + return arg; + }, + v); +} + void setupProcessModule() { registerProvider("Process", [](sol::state_view lua) -> sol::object { const ScriptPluginSpec *pluginSpec = lua.get("PluginSpec"); + QObject *guard = pluginSpec->connectionGuard.get(); sol::table async = lua.script("return require('async')", "_process_").get(); sol::function wrap = async["wrap"]; @@ -46,6 +60,100 @@ void setupProcessModule() process["runInTerminal"] = wrap(process["runInTerminal_cb"]); process["commandOutput"] = wrap(process["commandOutput_cb"]); + process["create"] = [](const sol::table ¶meter) { + const auto cmd = toFilePath(parameter.get>("command")); + + const QStringList arguments + = parameter.get_or("arguments", {}); + const std::optional workingDirectory = parameter.get>( + "workingDirectory"); + + const auto stdOut = parameter.get>("stdout"); + const auto stdErr = parameter.get>("stderr"); + const auto stdIn = parameter.get>("stdin"); + + auto p = std::make_unique(); + + p->setCommand({cmd, arguments}); + if (workingDirectory) + p->setWorkingDirectory(*workingDirectory); + if (stdIn) + p->setWriteData(stdIn->toUtf8()); + if (stdOut) { + // clang-format off + QObject::connect(p.get(), &Process::readyReadStandardOutput, + p.get(), + [p = p.get(), cb = *stdOut]() { + void_safe_call(cb, p->readAllStandardOutput()); + }); + // clang-format on + } + if (stdErr) { + // clang-format off + QObject::connect(p.get(), &Process::readyReadStandardError, + p.get(), + [p = p.get(), cb = *stdErr]() { + void_safe_call(cb, p->readAllStandardError()); + }); + // clang-format on + } + return p; + }; + + process.new_usertype( + "Process", + sol::no_constructor, + "start_cb", + [guard](Process *process, sol::function callback) { + if (process->state() != QProcess::NotRunning) + callback(false, "Process is already running"); + + struct Connections + { + QMetaObject::Connection startedConnection; + QMetaObject::Connection doneConnection; + }; + std::shared_ptr connections = std::make_shared(); + + // clang-format off + connections->startedConnection + = QObject::connect(process, &Process::started, + guard, [callback, process, connections]() { + process->disconnect(connections->doneConnection); + callback(true); + }, + Qt::SingleShotConnection); + connections->doneConnection + = QObject::connect(process, &Process::done, + guard, [callback, process, connections]() { + process->disconnect(connections->startedConnection); + callback(false, process->errorString()); + }, + Qt::SingleShotConnection); + // clang-format on + process->start(); + }, + "stop_cb", + [](Process *process, sol::function callback) { + if (process->state() != QProcess::Running) + callback(false, "Process is not running"); + + // clang-format off + QObject::connect(process, &Process::done, + process, [callback, process]() { + callback(true); + process->disconnect(); + }, + Qt::SingleShotConnection); + // clang-format on + process->stop(); + }, + "isRunning", + &Process::isRunning); + + process["Process"]["start"] = wrap(process["Process"]["start_cb"]); + process["Process"]["stop"] = wrap(process["Process"]["stop_cb"]); + return process; }); } diff --git a/src/plugins/lua/meta/process.lua b/src/plugins/lua/meta/process.lua index e15d71996ae..017524fc7cd 100644 --- a/src/plugins/lua/meta/process.lua +++ b/src/plugins/lua/meta/process.lua @@ -14,4 +14,38 @@ function process.runInTerminal(cmd) end ---@return string output The output of the command. function process.commandOutput(cmd) end +---@class Process +process.Process = {} + +---@class ProcessParameters +---@field command FilePath The command to run. +---@field arguments? string[] The arguments to pass to the command. +---@field workingDirectory? FilePath The working directory for the command. +---@field stdin? string The input to write to stdin. +---@field stdout? function The callback to call when the process writes to stdout. +---@field stderr? function The callback to call when the process writes to stderr. +process.ProcessParameters = {} + +---Creates a new process object. +---@param parameters ProcessParameters +---@return Process +function process.create(parameters) end + +---Start the process. +---@async +---@return boolean isRunning Whether the process was started successfully. +function process.Process:start() end + +---Stop the process. +---@async +---@return boolean didStop Whether the process was stopped successfully. +---@return number exitCode The exit code of the process. +---@return string error The error message if the process could not be stopped. +function process.Process:stop() end + +---Returns whether the process is running. +---@return boolean isRunning Whether the process is running. +function process.Process:isRunning() end + +--- return process