diff --git a/src/plugins/lua/CMakeLists.txt b/src/plugins/lua/CMakeLists.txt index fba3459805c..846e9cdcf2d 100644 --- a/src/plugins/lua/CMakeLists.txt +++ b/src/plugins/lua/CMakeLists.txt @@ -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 diff --git a/src/plugins/lua/bindings/macro.cpp b/src/plugins/lua/bindings/macro.cpp new file mode 100644 index 00000000000..4a800d7fbcf --- /dev/null +++ b/src/plugins/lua/bindings/macro.cpp @@ -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 + +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 diff --git a/src/plugins/lua/lua.qbs b/src/plugins/lua/lua.qbs index 9216b9d6705..e09257bd54e 100644 --- a/src/plugins/lua/lua.qbs +++ b/src/plugins/lua/lua.qbs @@ -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", diff --git a/src/plugins/lua/luaengine.cpp b/src/plugins/lua/luaengine.cpp index ccd72cc9669..359f68309ad 100644 --- a/src/plugins/lua/luaengine.cpp +++ b/src/plugins/lua/luaengine.cpp @@ -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 runScript( - const QString &script, const QString &name, std::function customizeState) +void prepareLuaState( + sol::state &lua, const QString &name, const std::function &customizeState) { - std::unique_ptr opaque = std::make_unique(); - - opaque->lua.open_libraries( + lua.open_libraries( sol::lib::base, sol::lib::bit32, sol::lib::coroutine, @@ -73,7 +70,7 @@ std::unique_ptr 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 runScript( } }; - opaque->lua.new_usertype( + lua.new_usertype( "PluginSpec", sol::no_constructor, "name", sol::property([](ScriptPluginSpec &self) { return self.name; })); - opaque->lua["PluginSpec"] = ScriptPluginSpec{name, {}, std::make_unique()}; + lua["PluginSpec"] = ScriptPluginSpec{name, {}, std::make_unique()}; 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 runScript( + const QString &script, const QString &name, std::function customizeState) +{ + std::unique_ptr opaque = std::make_unique(); + + prepareLuaState(opaque->lua, name, customizeState); auto result = opaque->lua @@ -116,6 +123,16 @@ std::unique_ptr runScript( return opaque; } +sol::protected_function_result runFunction( + sol::state &lua, + const QString &script, + const QString &name, + std::function 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); diff --git a/src/plugins/lua/luaengine.h b/src/plugins/lua/luaengine.h index 1da32f0baba..2be08f5ed0e 100644 --- a/src/plugins/lua/luaengine.h +++ b/src/plugins/lua/luaengine.h @@ -101,6 +101,12 @@ LUA_EXPORT std::unique_ptr runScript( const QString &name, std::function customizeState = {}); +sol::protected_function_result runFunction( + sol::state &lua, + const QString &script, + const QString &name, + std::function customizeState = {}); + void setupLuaEngine(QObject *guard); } // namespace Lua diff --git a/src/plugins/lua/luaexpander.cpp b/src/plugins/lua/luaexpander.cpp new file mode 100644 index 00000000000..5d0e5989e20 --- /dev/null +++ b/src/plugins/lua/luaexpander.cpp @@ -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 + +using namespace Utils; + +namespace Lua::Internal { + +void setNext( + MacroExpander *expander, + sol::state &lua, + auto &table, + const QByteArray &key, + QList::const_iterator it, + QList::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>(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:") { + if (key.endsWith(":")) + key = key.chopped(7); + + QList parts = key.split(':'); + parts.removeIf([](const QByteArray &part) { return part.isEmpty(); }); + setNext(expander, lua, t, key, parts.cbegin(), parts.cend()); + } + } + }); +} + +expected_str 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.
" + "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 result = tryRun("return " + statement, expander); + if (result) + return *result; + + result = tryRun(statement, expander); + if (!result) + return result.error(); + + return *result; + }); +} + +} // namespace Lua::Internal diff --git a/src/plugins/lua/luaplugin.cpp b/src/plugins/lua/luaplugin.cpp index 99dded6124f..6fd86aab187 100644 --- a/src/plugins/lua/luaplugin.cpp +++ b/src/plugins/lua/luaplugin.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -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 plugins; auto dirs = path.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot); diff --git a/src/plugins/lua/meta/macro.lua b/src/plugins/lua/meta/macro.lua new file mode 100644 index 00000000000..b99877cd3de --- /dev/null +++ b/src/plugins/lua/meta/macro.lua @@ -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