forked from qt-creator/qt-creator
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:
@@ -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
|
||||
|
26
src/plugins/lua/bindings/macro.cpp
Normal file
26
src/plugins/lua/bindings/macro.cpp
Normal 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
|
@@ -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",
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
104
src/plugins/lua/luaexpander.cpp
Normal file
104
src/plugins/lua/luaexpander.cpp
Normal 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
|
@@ -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);
|
||||
|
14
src/plugins/lua/meta/macro.lua
Normal file
14
src/plugins/lua/meta/macro.lua
Normal 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
|
Reference in New Issue
Block a user