Lua: Macro support

Allows users to use "%{Lua: ...}" to execute Lua code in their macros.
Adds "Macro" module with expand() and value() functions.

Change-Id: I6315b0646fbb503a77fd0cc631aed204abf3b699
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-07-30 10:30:11 +02:00
parent aed3eee06a
commit 61bb8d8e51
8 changed files with 191 additions and 13 deletions

View File

@@ -13,6 +13,7 @@ add_qtc_plugin(Lua
bindings/install.cpp
bindings/json.cpp
bindings/localsocket.cpp
bindings/macro.cpp
bindings/messagemanager.cpp
bindings/qtcprocess.cpp
bindings/settings.cpp
@@ -21,6 +22,7 @@ add_qtc_plugin(Lua
bindings/utils.cpp
luaengine.cpp
luaengine.h
luaexpander.cpp
luaplugin.cpp
luapluginspec.cpp
luapluginspec.h

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "luaengine.h"
#include <utils/macroexpander.h>
namespace Lua::Internal {
void setupMacroModule()
{
registerProvider("Macro", [](sol::state_view lua) {
sol::table module = lua.create_table();
module.set_function("expand", [](const QString &s) -> QString {
return Utils::globalMacroExpander()->expand(s);
});
module.set_function("value", [](const QString &s) -> QString {
return Utils::globalMacroExpander()->value(s.toUtf8());
});
return module;
});
}
} // namespace Lua::Internal

View File

@@ -20,6 +20,7 @@ QtcPlugin {
"lua_global.h",
"luaengine.cpp",
"luaengine.h",
"luaexpander.cpp",
"luaplugin.cpp",
"luapluginspec.cpp",
"luapluginspec.h",
@@ -45,6 +46,7 @@ QtcPlugin {
"install.cpp",
"json.cpp",
"localsocket.cpp",
"macro.cpp",
"messagemanager.cpp",
"qtcprocess.cpp",
"settings.cpp",

View File

@@ -54,13 +54,10 @@ public:
sol::state lua;
};
// Runs the gives script in a new Lua state. The returned Object manages the lifetime of the state.
std::unique_ptr<Utils::LuaState> runScript(
const QString &script, const QString &name, std::function<void(sol::state &)> customizeState)
void prepareLuaState(
sol::state &lua, const QString &name, const std::function<void(sol::state &)> &customizeState)
{
std::unique_ptr<LuaStateImpl> opaque = std::make_unique<LuaStateImpl>();
opaque->lua.open_libraries(
lua.open_libraries(
sol::lib::base,
sol::lib::bit32,
sol::lib::coroutine,
@@ -73,7 +70,7 @@ std::unique_ptr<Utils::LuaState> runScript(
sol::lib::table,
sol::lib::utf8);
opaque->lua["print"] = [prefix = name, printToOutputPane = true](sol::variadic_args va) {
lua["print"] = [prefix = name, printToOutputPane = true](sol::variadic_args va) {
const QString msg = variadicToStringList(va).join("\t");
qDebug().noquote() << "[" << prefix << "]" << msg;
@@ -84,23 +81,33 @@ std::unique_ptr<Utils::LuaState> runScript(
}
};
opaque->lua.new_usertype<ScriptPluginSpec>(
lua.new_usertype<ScriptPluginSpec>(
"PluginSpec", sol::no_constructor, "name", sol::property([](ScriptPluginSpec &self) {
return self.name;
}));
opaque->lua["PluginSpec"] = ScriptPluginSpec{name, {}, std::make_unique<QObject>()};
lua["PluginSpec"] = ScriptPluginSpec{name, {}, std::make_unique<QObject>()};
for (const auto &[name, func] : d->m_providers.asKeyValueRange()) {
opaque->lua["package"]["preload"][name.toStdString()] =
[func = func](const sol::this_state &s) { return func(s); };
lua["package"]["preload"][name.toStdString()] = [func = func](const sol::this_state &s) {
return func(s);
};
}
for (const auto &func : d->m_autoProviders)
func(opaque->lua);
func(lua);
if (customizeState)
customizeState(opaque->lua);
customizeState(lua);
}
// Runs the gives script in a new Lua state. The returned Object manages the lifetime of the state.
std::unique_ptr<Utils::LuaState> runScript(
const QString &script, const QString &name, std::function<void(sol::state &)> customizeState)
{
std::unique_ptr<LuaStateImpl> opaque = std::make_unique<LuaStateImpl>();
prepareLuaState(opaque->lua, name, customizeState);
auto result
= opaque->lua
@@ -116,6 +123,16 @@ std::unique_ptr<Utils::LuaState> runScript(
return opaque;
}
sol::protected_function_result runFunction(
sol::state &lua,
const QString &script,
const QString &name,
std::function<void(sol::state &)> customizeState)
{
prepareLuaState(lua, name, customizeState);
return lua.safe_script(script.toStdString(), sol::script_pass_on_error, name.toStdString());
}
void registerProvider(const QString &packageName, const PackageProvider &provider)
{
QTC_ASSERT(!d->m_providers.contains(packageName), return);

View File

@@ -101,6 +101,12 @@ LUA_EXPORT std::unique_ptr<Utils::LuaState> runScript(
const QString &name,
std::function<void(sol::state &)> customizeState = {});
sol::protected_function_result runFunction(
sol::state &lua,
const QString &script,
const QString &name,
std::function<void(sol::state &)> customizeState = {});
void setupLuaEngine(QObject *guard);
} // namespace Lua

View File

@@ -0,0 +1,104 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "luaengine.h"
#include "luatr.h"
#include <utils/macroexpander.h>
using namespace Utils;
namespace Lua::Internal {
void setNext(
MacroExpander *expander,
sol::state &lua,
auto &table,
const QByteArray &key,
QList<QByteArray>::const_iterator it,
QList<QByteArray>::const_iterator end)
{
if (it + 1 == end) {
if (expander->isPrefixVariable(key)) {
table.set_function(it->toStdString(), [key, expander](const QString &s) -> QString {
return expander->value(key + s.toUtf8());
});
} else {
table.set(it->toStdString(), expander->value(key));
}
} else {
auto existingTable = table.template get<sol::optional<sol::table>>(it->toStdString());
if (existingTable) {
setNext(expander, lua, *existingTable, key, it + 1, end);
} else {
sol::table newTable = lua.create_table();
setNext(expander, lua, newTable, key, it + 1, end);
table.set(it->toStdString(), newTable);
}
}
};
sol::protected_function_result run(sol::state &lua, QString statement, MacroExpander *expander)
{
return runFunction(lua, statement, "Statement", [expander](sol::state &lua) {
sol::global_table &t = lua.globals();
for (QByteArray key : expander->visibleVariables()) {
if (key != "Lua:<value>") {
if (key.endsWith(":<value>"))
key = key.chopped(7);
QList<QByteArray> parts = key.split(':');
parts.removeIf([](const QByteArray &part) { return part.isEmpty(); });
setNext(expander, lua, t, key, parts.cbegin(), parts.cend());
}
}
});
}
expected_str<QString> tryRun(const QString statement, MacroExpander *expander)
{
sol::state lua;
auto result = run(lua, statement, expander);
if (!result.valid()) {
sol::error err = result;
return make_unexpected(QString::fromStdString(err.what()));
}
QStringList results;
for (int i = 1; i <= result.return_count(); i++) {
size_t l;
const char *s = luaL_tolstring(result.lua_state(), int(i), &l);
results.append(QString::fromUtf8(s, int(l)));
}
return results.join(QLatin1Char(' '));
}
void setupLuaExpander(MacroExpander *expander)
{
expander->registerPrefix(
"Lua",
Tr::tr("Evaluate simple Lua statements.<br>"
"Literal '}' characters must be escaped as \"\\}\", "
"'\\' characters must be escaped as \"\\\\\", "
"'#' characters must be escaped as \"\\#\", "
"and \"%{\" must be escaped as \"%\\{\"."),
[expander](const QString &statement) -> QString {
if (statement.isEmpty())
return Tr::tr("No Lua statement to evaluate.");
expected_str<QString> result = tryRun("return " + statement, expander);
if (result)
return *result;
result = tryRun(statement, expander);
if (!result)
return result.error();
return *result;
});
}
} // namespace Lua::Internal

View File

@@ -15,6 +15,7 @@
#include <utils/algorithm.h>
#include <utils/layoutbuilder.h>
#include <utils/macroexpander.h>
#include <utils/qtcprocess.h>
#include <utils/theme/theme.h>
@@ -41,6 +42,7 @@ void setupHookModule();
void setupInstallModule();
void setupJsonModule();
void setupLocalSocketModule();
void setupMacroModule();
void setupMessageManagerModule();
void setupProcessModule();
void setupQtModule();
@@ -49,6 +51,8 @@ void setupTextEditorModule();
void setupTranslateModule();
void setupUtilsModule();
void setupLuaExpander(MacroExpander *expander);
class LuaJsExtension : public QObject
{
Q_OBJECT
@@ -256,6 +260,7 @@ public:
setupInstallModule();
setupJsonModule();
setupLocalSocketModule();
setupMacroModule();
setupMessageManagerModule();
setupProcessModule();
setupQtModule();
@@ -266,6 +271,8 @@ public:
Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); });
setupLuaExpander(globalMacroExpander());
pluginSpecsFromArchiveFactories().push_back([](const FilePath &path) {
QList<PluginSpec *> plugins;
auto dirs = path.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot);

View File

@@ -0,0 +1,14 @@
-- Copyright (C) 2024 The Qt Company Ltd.
-- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
---@meta Macro
local macro = {}
---Returns globalExpander():value(variableName).
function macro.value(variableName) end
---Returns globalExpander():expand(stringWithVariables).
function macro.expand(stringWithVariables) end
return macro