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/install.cpp
|
||||||
bindings/json.cpp
|
bindings/json.cpp
|
||||||
bindings/localsocket.cpp
|
bindings/localsocket.cpp
|
||||||
|
bindings/macro.cpp
|
||||||
bindings/messagemanager.cpp
|
bindings/messagemanager.cpp
|
||||||
bindings/qtcprocess.cpp
|
bindings/qtcprocess.cpp
|
||||||
bindings/settings.cpp
|
bindings/settings.cpp
|
||||||
@@ -21,6 +22,7 @@ add_qtc_plugin(Lua
|
|||||||
bindings/utils.cpp
|
bindings/utils.cpp
|
||||||
luaengine.cpp
|
luaengine.cpp
|
||||||
luaengine.h
|
luaengine.h
|
||||||
|
luaexpander.cpp
|
||||||
luaplugin.cpp
|
luaplugin.cpp
|
||||||
luapluginspec.cpp
|
luapluginspec.cpp
|
||||||
luapluginspec.h
|
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",
|
"lua_global.h",
|
||||||
"luaengine.cpp",
|
"luaengine.cpp",
|
||||||
"luaengine.h",
|
"luaengine.h",
|
||||||
|
"luaexpander.cpp",
|
||||||
"luaplugin.cpp",
|
"luaplugin.cpp",
|
||||||
"luapluginspec.cpp",
|
"luapluginspec.cpp",
|
||||||
"luapluginspec.h",
|
"luapluginspec.h",
|
||||||
@@ -45,6 +46,7 @@ QtcPlugin {
|
|||||||
"install.cpp",
|
"install.cpp",
|
||||||
"json.cpp",
|
"json.cpp",
|
||||||
"localsocket.cpp",
|
"localsocket.cpp",
|
||||||
|
"macro.cpp",
|
||||||
"messagemanager.cpp",
|
"messagemanager.cpp",
|
||||||
"qtcprocess.cpp",
|
"qtcprocess.cpp",
|
||||||
"settings.cpp",
|
"settings.cpp",
|
||||||
|
@@ -54,13 +54,10 @@ public:
|
|||||||
sol::state lua;
|
sol::state lua;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Runs the gives script in a new Lua state. The returned Object manages the lifetime of the state.
|
void prepareLuaState(
|
||||||
std::unique_ptr<Utils::LuaState> runScript(
|
sol::state &lua, const QString &name, const std::function<void(sol::state &)> &customizeState)
|
||||||
const QString &script, const QString &name, std::function<void(sol::state &)> customizeState)
|
|
||||||
{
|
{
|
||||||
std::unique_ptr<LuaStateImpl> opaque = std::make_unique<LuaStateImpl>();
|
lua.open_libraries(
|
||||||
|
|
||||||
opaque->lua.open_libraries(
|
|
||||||
sol::lib::base,
|
sol::lib::base,
|
||||||
sol::lib::bit32,
|
sol::lib::bit32,
|
||||||
sol::lib::coroutine,
|
sol::lib::coroutine,
|
||||||
@@ -73,7 +70,7 @@ std::unique_ptr<Utils::LuaState> runScript(
|
|||||||
sol::lib::table,
|
sol::lib::table,
|
||||||
sol::lib::utf8);
|
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");
|
const QString msg = variadicToStringList(va).join("\t");
|
||||||
|
|
||||||
qDebug().noquote() << "[" << prefix << "]" << msg;
|
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) {
|
"PluginSpec", sol::no_constructor, "name", sol::property([](ScriptPluginSpec &self) {
|
||||||
return self.name;
|
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()) {
|
for (const auto &[name, func] : d->m_providers.asKeyValueRange()) {
|
||||||
opaque->lua["package"]["preload"][name.toStdString()] =
|
lua["package"]["preload"][name.toStdString()] = [func = func](const sol::this_state &s) {
|
||||||
[func = func](const sol::this_state &s) { return func(s); };
|
return func(s);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &func : d->m_autoProviders)
|
for (const auto &func : d->m_autoProviders)
|
||||||
func(opaque->lua);
|
func(lua);
|
||||||
|
|
||||||
if (customizeState)
|
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
|
auto result
|
||||||
= opaque->lua
|
= opaque->lua
|
||||||
@@ -116,6 +123,16 @@ std::unique_ptr<Utils::LuaState> runScript(
|
|||||||
return opaque;
|
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)
|
void registerProvider(const QString &packageName, const PackageProvider &provider)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(!d->m_providers.contains(packageName), return);
|
QTC_ASSERT(!d->m_providers.contains(packageName), return);
|
||||||
|
@@ -101,6 +101,12 @@ LUA_EXPORT std::unique_ptr<Utils::LuaState> runScript(
|
|||||||
const QString &name,
|
const QString &name,
|
||||||
std::function<void(sol::state &)> customizeState = {});
|
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);
|
void setupLuaEngine(QObject *guard);
|
||||||
|
|
||||||
} // namespace Lua
|
} // 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/algorithm.h>
|
||||||
#include <utils/layoutbuilder.h>
|
#include <utils/layoutbuilder.h>
|
||||||
|
#include <utils/macroexpander.h>
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ void setupHookModule();
|
|||||||
void setupInstallModule();
|
void setupInstallModule();
|
||||||
void setupJsonModule();
|
void setupJsonModule();
|
||||||
void setupLocalSocketModule();
|
void setupLocalSocketModule();
|
||||||
|
void setupMacroModule();
|
||||||
void setupMessageManagerModule();
|
void setupMessageManagerModule();
|
||||||
void setupProcessModule();
|
void setupProcessModule();
|
||||||
void setupQtModule();
|
void setupQtModule();
|
||||||
@@ -49,6 +51,8 @@ void setupTextEditorModule();
|
|||||||
void setupTranslateModule();
|
void setupTranslateModule();
|
||||||
void setupUtilsModule();
|
void setupUtilsModule();
|
||||||
|
|
||||||
|
void setupLuaExpander(MacroExpander *expander);
|
||||||
|
|
||||||
class LuaJsExtension : public QObject
|
class LuaJsExtension : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -256,6 +260,7 @@ public:
|
|||||||
setupInstallModule();
|
setupInstallModule();
|
||||||
setupJsonModule();
|
setupJsonModule();
|
||||||
setupLocalSocketModule();
|
setupLocalSocketModule();
|
||||||
|
setupMacroModule();
|
||||||
setupMessageManagerModule();
|
setupMessageManagerModule();
|
||||||
setupProcessModule();
|
setupProcessModule();
|
||||||
setupQtModule();
|
setupQtModule();
|
||||||
@@ -266,6 +271,8 @@ public:
|
|||||||
|
|
||||||
Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); });
|
Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); });
|
||||||
|
|
||||||
|
setupLuaExpander(globalMacroExpander());
|
||||||
|
|
||||||
pluginSpecsFromArchiveFactories().push_back([](const FilePath &path) {
|
pluginSpecsFromArchiveFactories().push_back([](const FilePath &path) {
|
||||||
QList<PluginSpec *> plugins;
|
QList<PluginSpec *> plugins;
|
||||||
auto dirs = path.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot);
|
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