aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/lua
diff options
context:
space:
mode:
authorMarcus Tillmanns <marcus.tillmanns@qt.io>2024-04-12 14:36:37 +0200
committerMarcus Tillmanns <marcus.tillmanns@qt.io>2024-04-16 13:11:18 +0000
commit6e3aab5f1b614672aa6f2fe913fd70316aeafd32 (patch)
tree58d4079f7dd2f9a80e7438cfc83f15587cb66877 /src/plugins/lua
parenta296157f58a5fa31861daa359b2ef8998d06c8a6 (diff)
Lua: Add Lua plugin support
Adds basic support for writing Plugins using the lua scripting language. Lua Plugins are registered just as native plugins are and can be enabled or disabled via the plugin dialog. see src/plugins/lua/README.md for further details. Change-Id: I9f4d15e9632c46e1c6c132bcd0bbcdd70b150640 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
Diffstat (limited to 'src/plugins/lua')
-rw-r--r--src/plugins/lua/CMakeLists.txt37
-rw-r--r--src/plugins/lua/Lua.json.in21
-rw-r--r--src/plugins/lua/README.md115
-rw-r--r--src/plugins/lua/bindings/ASYNC-LICENSE.txt22
-rw-r--r--src/plugins/lua/bindings/action.cpp68
-rw-r--r--src/plugins/lua/bindings/async.cpp91
-rw-r--r--src/plugins/lua/bindings/core.cpp46
-rw-r--r--src/plugins/lua/bindings/fetch.cpp135
-rw-r--r--src/plugins/lua/bindings/hook.cpp65
-rw-r--r--src/plugins/lua/bindings/inheritance.h126
-rw-r--r--src/plugins/lua/bindings/layout.cpp177
-rw-r--r--src/plugins/lua/bindings/messagemanager.cpp57
-rw-r--r--src/plugins/lua/bindings/qtcprocess.cpp49
-rw-r--r--src/plugins/lua/bindings/settings.cpp528
-rw-r--r--src/plugins/lua/bindings/utils.cpp113
-rw-r--r--src/plugins/lua/generateqtbindings.cpp401
-rw-r--r--src/plugins/lua/lua.qbs26
-rw-r--r--src/plugins/lua/lua_global.h14
-rw-r--r--src/plugins/lua/luaengine.cpp249
-rw-r--r--src/plugins/lua/luaengine.h99
-rw-r--r--src/plugins/lua/luaplugin.cpp68
-rw-r--r--src/plugins/lua/luapluginloader.cpp64
-rw-r--r--src/plugins/lua/luapluginloader.h30
-rw-r--r--src/plugins/lua/luapluginspec.cpp140
-rw-r--r--src/plugins/lua/luapluginspec.h57
-rw-r--r--src/plugins/lua/luaqttypes.cpp314
-rw-r--r--src/plugins/lua/luaqttypes.h39
-rw-r--r--src/plugins/lua/luatr.h15
-rw-r--r--src/plugins/lua/luauibindings.cpp141
-rw-r--r--src/plugins/lua/meta/action.lua34
-rw-r--r--src/plugins/lua/meta/async.lua62
-rw-r--r--src/plugins/lua/meta/core.lua26
-rw-r--r--src/plugins/lua/meta/fetch.lua30
-rw-r--r--src/plugins/lua/meta/layout.lua186
-rw-r--r--src/plugins/lua/meta/lsp.lua34
-rw-r--r--src/plugins/lua/meta/messagemanager.lua17
-rw-r--r--src/plugins/lua/meta/process.lua11
-rw-r--r--src/plugins/lua/meta/qt.lua66
-rw-r--r--src/plugins/lua/meta/qtc.lua40
-rw-r--r--src/plugins/lua/meta/settings.lua182
-rw-r--r--src/plugins/lua/meta/simpletypes.lua36
-rw-r--r--src/plugins/lua/meta/utils.lua62
-rw-r--r--src/plugins/lua/meta/widgets.lua111
-rw-r--r--src/plugins/lua/meta/wizard.lua64
-rw-r--r--src/plugins/lua/metabackup/qobject.lua6
45 files changed, 4274 insertions, 0 deletions
diff --git a/src/plugins/lua/CMakeLists.txt b/src/plugins/lua/CMakeLists.txt
new file mode 100644
index 00000000000..7f0d440319a
--- /dev/null
+++ b/src/plugins/lua/CMakeLists.txt
@@ -0,0 +1,37 @@
+
+add_qtc_plugin(Lua
+ PLUGIN_DEPENDS Core
+ PUBLIC_DEPENDS lua546 sol2
+ PUBLIC_DEFINES LUA_AVAILABLE
+ SOURCES
+ bindings/inheritance.h
+ bindings/async.cpp
+ bindings/action.cpp
+ bindings/hook.cpp
+ bindings/core.cpp
+ bindings/fetch.cpp
+ bindings/layout.cpp
+ bindings/messagemanager.cpp
+ bindings/qtcprocess.cpp
+ bindings/settings.cpp
+ bindings/utils.cpp
+ luaengine.cpp
+ luaengine.h
+ luaplugin.cpp
+ luapluginloader.cpp
+ luapluginloader.h
+ luapluginspec.cpp
+ luapluginspec.h
+ luaqttypes.cpp
+ luaqttypes.h
+ luatr.h
+ # generateqtbindings.cpp # Use this if you need to generate some code.
+)
+
+
+set_source_files_properties(luauibindings.cpp PROPERTY SKIP_AUTOMOC ON PROPERTY SKIP_AUTOGEN ON)
+
+if (MSVC)
+ # Prevent fatal error C1128
+ set_property(SOURCE bindings/settings.cpp PROPERTY COMPILE_FLAGS /bigobj)
+endif()
diff --git a/src/plugins/lua/Lua.json.in b/src/plugins/lua/Lua.json.in
new file mode 100644
index 00000000000..7007945934c
--- /dev/null
+++ b/src/plugins/lua/Lua.json.in
@@ -0,0 +1,21 @@
+{
+ "Name" : "Lua",
+ "Version" : "${IDE_VERSION}",
+ "CompatVersion" : "${IDE_VERSION_COMPAT}",
+ "DisabledByDefault" : true,
+ "SoftLoadable" : true,
+ "Vendor" : "The Qt Company Ltd",
+ "Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
+ "License" : [ "Commercial Usage",
+ "",
+ "Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.",
+ "",
+ "GNU General Public License Usage",
+ "",
+ "Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
+ ],
+ "Category" : "Scripting",
+ "Description" : "Support for Lua based plugins.",
+ "Url" : "http://www.qt.io",
+ ${IDE_PLUGIN_DEPENDENCIES}
+}
diff --git a/src/plugins/lua/README.md b/src/plugins/lua/README.md
new file mode 100644
index 00000000000..108420735ed
--- /dev/null
+++ b/src/plugins/lua/README.md
@@ -0,0 +1,115 @@
+# Lua Plugin
+
+## Introduction
+
+The Lua plugin provides support for writing plugins using the Lua scripting language.
+
+## Usage
+
+The plugin scans the folder `lua-plugins` folder inside the normal plugin folder of Qt Creator
+`ExtensionSystem::PluginManager::pluginPaths()`. It loads scripts from any folder that contains
+a .lua script named the same as the folder.
+Whether or not the script is enabled is determined by the `disabledByDefault` field in the plugin
+table and the settings configured via the "About Plugins" dialog in Qt Creator.
+
+## Basic Lua plugin
+
+A Lua script needs to provide the following table to be considered a plugin:
+
+```lua
+-- lua-plugins/myluaplugin/myluaplugin.lua
+return {
+ name = "MyLuaPlugin",
+ version = "1.0.0",
+ compatVersion = "1.0.0",
+ vendor = "The Qt Company",
+ category = "Language Client",
+ setup = function() print("Hello World!") end,
+
+ --- The following fields are optional
+ description = "My first lua plugin",
+ longDescription = [[
+A long description.
+Can contain newlines.
+ ]],
+ url = "https://www.qt.io",
+ license = "MIT",
+ revision = "rev1",
+ copyright = "2024",
+ experimental = true,
+ disabledByDefault = false,
+
+ dependencies = {
+ { name="Core", version = "12.0.0" }
+ },
+} --[[@as QtcPlugin]]
+```
+
+Your base file needs to be named the same as the folder its contained in.
+It must only return the plugin specification table and not execute or require any other code.
+Use `require` to load other files from within the setup function.
+
+```lua
+-- lua-plugins/myluaplugin/myluaplugin.lua
+return {
+ -- ... required fields omitted ..
+ setup = function() require 'init'.setup() end,
+} --[[@as QtcPlugin]]
+
+-- lua-plugins/myluaplugin/init.lua
+local function setup()
+ print("Hello from Lua!")
+end
+
+return {
+ setup = setup,
+}
+```
+
+The `require` function will search for files as such:
+
+```
+my-lua-plugin/?.lua
+```
+
+## Lua <=> C++ bindings
+
+The Lua plugin provides the [sol2](https://github.com/ThePhD/sol2) library to bind C++ code to Lua.
+sol2 is well [documented here](https://sol2.rtfd.io).
+
+## Lua Language Server
+
+To make developing plugins easier, we provide a meta file [qtc.lua](meta/qtc.lua) that describes
+what functions and classes are available in the Lua plugin. If you add bindings yourself
+please add them to this file. The [.luarc.json](../../.luarc.json) file contains the configuration
+for the [Lua Language Server](https://luals.github.io/) and will automatically load the `qtc.lua` file.
+
+## Coroutines
+
+A lot of Qt Creator functions will take some time to complete. To not block the main thread during
+that time, we make heavy use of lua coroutines. Functions that need a coroutine to work are documented
+as such, lets take for example `qtc.waitms(ms)`. This function will wait for `ms` milliseconds and
+then return. To use it you need to call it using the async module from a running coroutine:
+
+```lua
+local a = require 'async'
+local function myFunction()
+ a.wait(qtc.waitms(1000))
+ print("Hello from Lua!")
+end
+
+local function setup()
+ a.sync(myFunction)()
+end
+```
+
+The waitms function will immediately yield, which will suspend the execution of `myFunction` **AND**
+make the `a.sync(myFunction)()` return.
+
+Once the internal timer is triggered, the C++ code will resume `myFunction` and it will continue to
+print the message. `myFunction` will then return and the coroutine will be "dead", meaning it cannot
+be resumed again. You can of course create a new coroutine and call `myFunction` again.
+
+## Contributing
+
+Contributions to the Lua plugin are welcome. Please read the contributing guide for more information.
diff --git a/src/plugins/lua/bindings/ASYNC-LICENSE.txt b/src/plugins/lua/bindings/ASYNC-LICENSE.txt
new file mode 100644
index 00000000000..609bb2e52b1
--- /dev/null
+++ b/src/plugins/lua/bindings/ASYNC-LICENSE.txt
@@ -0,0 +1,22 @@
+
+MIT License
+
+Copyright (c) 2020 whocares
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/plugins/lua/bindings/action.cpp b/src/plugins/lua/bindings/action.cpp
new file mode 100644
index 00000000000..93ae6f51883
--- /dev/null
+++ b/src/plugins/lua/bindings/action.cpp
@@ -0,0 +1,68 @@
+// 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 <coreplugin/actionmanager/actionmanager.h>
+
+using namespace Utils;
+
+namespace Lua::Internal {
+
+void addActionModule()
+{
+ LuaEngine::registerProvider("Action", [](sol::state_view lua) -> sol::object {
+ sol::table result = lua.create_table();
+
+ result.new_enum("CommandAttribute",
+ "CA_Hide",
+ Core::Command::CA_Hide,
+ "CA_UpdateText",
+ Core::Command::CA_UpdateText,
+ "CA_UpdateIcon",
+ Core::Command::CA_UpdateIcon,
+ "CA_NonConfigurable",
+ Core::Command::CA_NonConfigurable);
+
+ result["create"] = [](const std::string &actionId, sol::table options) {
+ Core::ActionBuilder b(nullptr, Id::fromString(QString::fromStdString(actionId)));
+
+ for (const auto &[k, v] : options) {
+ QString key = k.as<QString>();
+
+ if (key == "context")
+ b.setContext(Id::fromString(v.as<QString>()));
+ else if (key == "onTrigger")
+ b.addOnTriggered([f = v.as<sol::function>()]() {
+ auto res = Lua::LuaEngine::void_safe_call(f);
+ QTC_CHECK_EXPECTED(res);
+ });
+ else if (key == "text")
+ b.setText(v.as<QString>());
+ else if (key == "iconText")
+ b.setIconText(v.as<QString>());
+ else if (key == "toolTip")
+ b.setToolTip(v.as<QString>());
+ else if (key == "commandAttributes")
+ b.setCommandAttribute((Core::Command::CommandAttribute) v.as<int>());
+ else if (key == "commandDescription")
+ b.setCommandDescription(v.as<QString>());
+ else if (key == "defaultKeySequence")
+ b.setDefaultKeySequence(QKeySequence(v.as<QString>()));
+ else if (key == "defaultKeySequences") {
+ sol::table t = v.as<sol::table>();
+ QList<QKeySequence> sequences;
+ sequences.reserve(t.size());
+ for (const auto &[_, v] : t)
+ sequences.push_back(QKeySequence(v.as<QString>()));
+ b.setDefaultKeySequences(sequences);
+ } else
+ throw std::runtime_error("Unknown key: " + key.toStdString());
+ }
+ };
+
+ return result;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/async.cpp b/src/plugins/lua/bindings/async.cpp
new file mode 100644
index 00000000000..b9408002ecb
--- /dev/null
+++ b/src/plugins/lua/bindings/async.cpp
@@ -0,0 +1,91 @@
+// 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"
+
+namespace Lua::Internal {
+
+static const char *async_source = R"(
+-- From: https://github.com/ms-jpq/lua-async-await
+-- Licensed under MIT
+local co = coroutine
+-- use with wrap
+local pong = function(func, callback)
+ assert(type(func) == "function", "type error :: expected func")
+ local thread = co.create(func)
+ local step = nil
+ step = function(...)
+ local stat, ret = co.resume(thread, ...)
+ assert(stat, ret)
+ if co.status(thread) == "dead" then
+ (callback or function() end)(ret)
+ else
+ assert(type(ret) == "function", "type error :: expected func")
+ ret(step)
+ end
+ end
+ step()
+end
+-- use with pong, creates thunk factory
+local wrap = function(func)
+ assert(type(func) == "function", "type error :: expected func")
+ local factory = function(...)
+ local params = { ... }
+ local thunk = function(step)
+ table.insert(params, step)
+ return func(table.unpack(params))
+ end
+ return thunk
+ end
+ return factory
+end
+-- many thunks -> single thunk
+local join = function(thunks)
+ local len = #thunks
+ local done = 0
+ local acc = {}
+
+ local thunk = function(step)
+ if len == 0 then
+ return step()
+ end
+ for i, tk in ipairs(thunks) do
+ assert(type(tk) == "function", "thunk must be function")
+ local callback = function(...)
+ acc[i] = ...
+ done = done + 1
+ if done == len then
+ step(acc)
+ end
+ end
+ tk(callback)
+ end
+ end
+ return thunk
+end
+-- sugar over coroutine
+local await = function(defer)
+ assert(type(defer) == "function", "type error :: expected func")
+ return co.yield(defer)
+end
+local await_all = function(defer)
+ assert(type(defer) == "table", "type error :: expected table")
+ return co.yield(join(defer))
+end
+return {
+ sync = wrap(pong),
+ wait = await,
+ wait_all = await_all,
+ wrap = wrap,
+}
+)";
+
+void addAsyncModule()
+{
+ LuaEngine::registerProvider("async", [](sol::state_view lua) -> sol::object {
+ sol::protected_function_result res = lua.script(async_source, "async.cpp");
+ return res.get<sol::table>(0);
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/core.cpp b/src/plugins/lua/bindings/core.cpp
new file mode 100644
index 00000000000..18bafa7b313
--- /dev/null
+++ b/src/plugins/lua/bindings/core.cpp
@@ -0,0 +1,46 @@
+// 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 <coreplugin/generatedfile.h>
+
+using namespace Core;
+
+namespace Lua::Internal {
+
+void addCoreModule()
+{
+ LuaEngine::registerProvider("Core", [](sol::state_view lua) -> sol::object {
+ sol::table core = lua.create_table();
+
+ auto generatedFileType = core.new_usertype<GeneratedFile>(
+ "GeneratedFile",
+ "filePath",
+ sol::property(&GeneratedFile::filePath, &GeneratedFile::setFilePath),
+ "contents",
+ sol::property(&GeneratedFile::contents, &GeneratedFile::setContents),
+ "attributes",
+ sol::property([](GeneratedFile *f) -> int { return f->attributes().toInt(); },
+ [](GeneratedFile *f, int flags) {
+ f->setAttributes(GeneratedFile::Attributes::fromInt(flags));
+ }),
+ "isBinary",
+ sol::property(&GeneratedFile::isBinary, &GeneratedFile::setBinary));
+
+ // clang-format off
+ generatedFileType["Attribute"] = lua.create_table_with(
+ "OpenEditorAttribute", GeneratedFile::OpenEditorAttribute,
+ "OpenProjectAttribute", GeneratedFile::OpenProjectAttribute,
+ "CustomGeneratorAttribute", GeneratedFile::CustomGeneratorAttribute,
+ "KeepExistingFileAttribute", GeneratedFile::KeepExistingFileAttribute,
+ "ForceOverwrite", GeneratedFile::ForceOverwrite,
+ "TemporaryFile", GeneratedFile::TemporaryFile
+ );
+ // clang-format on
+
+ return core;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/fetch.cpp b/src/plugins/lua/bindings/fetch.cpp
new file mode 100644
index 00000000000..85809573501
--- /dev/null
+++ b/src/plugins/lua/bindings/fetch.cpp
@@ -0,0 +1,135 @@
+// 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 "../luaqttypes.h"
+
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+
+namespace Lua::Internal {
+
+static QString opToString(QNetworkAccessManager::Operation op)
+{
+ switch (op) {
+ case QNetworkAccessManager::HeadOperation:
+ return "HEAD";
+ case QNetworkAccessManager::GetOperation:
+ return "GET";
+ case QNetworkAccessManager::PutOperation:
+ return "PUT";
+ case QNetworkAccessManager::PostOperation:
+ return "POST";
+
+ case QNetworkAccessManager::DeleteOperation:
+ return "DELETE";
+ case QNetworkAccessManager::CustomOperation:
+ return "CUSTOM";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+void addFetchModule()
+{
+ LuaEngine::registerProvider("__fetch", [](sol::state_view lua) -> sol::object {
+ sol::table fetch = lua.create_table();
+
+ auto networkReplyType = lua.new_usertype<QNetworkReply>(
+ "QNetworkReply",
+ "error",
+ sol::property([](QNetworkReply *self) -> int { return self->error(); }),
+ "readAll",
+ [](QNetworkReply *r) { return r->readAll().toStdString(); },
+ "__tostring",
+ [](QNetworkReply *r) {
+ return QString("QNetworkReply(%1 \"%2\") => %3")
+ .arg(opToString(r->operation()))
+ .arg(r->url().toString())
+ .arg(r->error());
+ });
+
+ static QNetworkAccessManager networkAccessManager;
+
+ fetch["fetch_cb"] = [](sol::table options, sol::function callback, sol::this_state s) {
+ auto url = options.get<QString>("url");
+
+ auto method = (options.get_or<QString>("method", "GET")).toLower();
+ auto headers = options.get_or<sol::table>("headers", {});
+ auto data = options.get_or<QString>("body", {});
+ bool convertToTable = options.get<std::optional<bool>>("convertToTable").value_or(false);
+
+ QNetworkRequest request((QUrl(url)));
+ if (headers && !headers.empty()) {
+ for (const auto &[k, v] : headers) {
+ request.setRawHeader(k.as<QString>().toUtf8(), v.as<QString>().toUtf8());
+ }
+ }
+
+ QNetworkReply *reply = nullptr;
+ if (method == "get")
+ reply = networkAccessManager.get(request);
+ else if (method == "post")
+ reply = networkAccessManager.post(request, data.toUtf8());
+ else
+ throw std::runtime_error("Unknown method: " + method.toStdString());
+
+ if (convertToTable) {
+ QObject::connect(reply, &QNetworkReply::finished, reply, [reply, s, callback]() {
+ reply->deleteLater();
+
+ if (reply->error() != QNetworkReply::NoError) {
+ callback(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
+ return;
+ }
+
+ QByteArray data = reply->readAll();
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &error);
+ if (error.error != QJsonParseError::NoError) {
+ callback(error.errorString());
+ return;
+ }
+ if (doc.isObject()) {
+ callback(LuaEngine::toTable(s, doc.object()));
+ } else if (doc.isArray()) {
+ callback(LuaEngine::toTable(s, doc.array()));
+ } else {
+ sol::state_view lua(s);
+ callback(lua.create_table());
+ }
+ });
+
+ } else {
+ QObject::connect(reply, &QNetworkReply::finished, reply, [reply, callback]() {
+ // We don't want the network reply to be deleted by the manager, but
+ // by the Lua GC
+ reply->setParent(nullptr);
+ callback(std::unique_ptr<QNetworkReply>(reply));
+ });
+ }
+ };
+
+ return fetch;
+ });
+
+ LuaEngine::registerProvider("Fetch", [](sol::state_view lua) -> sol::object {
+ return lua
+ .script(
+ R"(
+local f = require("__fetch")
+local a = require("async")
+
+return {
+ fetch_cb = f.fetch_cb,
+ fetch = a.wrap(f.fetch_cb)
+}
+)",
+ "_fetch_")
+ .get<sol::table>();
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/hook.cpp b/src/plugins/lua/bindings/hook.cpp
new file mode 100644
index 00000000000..cb94e54582e
--- /dev/null
+++ b/src/plugins/lua/bindings/hook.cpp
@@ -0,0 +1,65 @@
+// 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 <coreplugin/editormanager/editormanager.h>
+
+namespace Lua {
+
+class Hook : public QObject
+{
+ Q_OBJECT
+
+public:
+ Hook(QObject *source);
+
+signals:
+ void trigger(sol::table &args);
+};
+
+Hook::Hook(QObject *source)
+ : QObject(source)
+{}
+
+namespace Internal {
+
+void addHookModule()
+{
+ LuaEngine::autoRegister([](sol::state_view lua) {
+ auto connection = lua.new_usertype<QMetaObject::Connection>("QMetaConnection",
+ sol::no_constructor);
+
+ auto hook = lua.new_usertype<Hook>(
+ "Hook",
+ sol::no_constructor,
+ "connect",
+ [](Hook *hook, sol::function func) -> QMetaObject::Connection {
+ QMetaObject::Connection con
+ = QObject::connect(hook, &Hook::trigger, [func](sol::table args) {
+ auto res = LuaEngine::void_safe_call(func, args);
+ QTC_CHECK_EXPECTED(res);
+ });
+ return con;
+ },
+ "disconnect",
+ [](Hook *, QMetaObject::Connection con) { QObject::disconnect(con); });
+ });
+
+ LuaEngine::registerHook("editors.documentOpened", [](sol::function func) {
+ QObject::connect(Core::EditorManager::instance(),
+ &Core::EditorManager::documentOpened,
+ [func](Core::IDocument *document) { func(document); });
+ });
+ LuaEngine::registerHook("editors.documentClosed", [](sol::function func) {
+ QObject::connect(Core::EditorManager::instance(),
+ &Core::EditorManager::documentClosed,
+ [func](Core::IDocument *document) { func(document); });
+ });
+}
+
+} // namespace Internal
+
+} // namespace Lua
+
+#include "hook.moc"
diff --git a/src/plugins/lua/bindings/inheritance.h b/src/plugins/lua/bindings/inheritance.h
new file mode 100644
index 00000000000..6336ff4d2d6
--- /dev/null
+++ b/src/plugins/lua/bindings/inheritance.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <sol/forward.hpp>
+
+namespace Lua::Internal {
+class LuaAspectContainer;
+}
+
+namespace Utils {
+class AspectContainer;
+class BoolAspect;
+class ColorAspect;
+class SelectionAspect;
+class MultiSelectionAspect;
+class StringAspect;
+class FilePathAspect;
+class IntegerAspect;
+class DoubleAspect;
+class StringListAspect;
+class FilePathListAspect;
+class IntegersAspect;
+class StringSelectionAspect;
+class ToggleAspect;
+class TriStateAspect;
+class TextDisplay;
+class AspectList;
+class BaseAspect;
+} // namespace Utils
+
+SOL_BASE_CLASSES(::Lua::Internal::LuaAspectContainer, Utils::AspectContainer, Utils::BaseAspect);
+
+SOL_BASE_CLASSES(Utils::BoolAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::ColorAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::SelectionAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::MultiSelectionAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::StringAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::FilePathAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::IntegerAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::DoubleAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::StringListAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::FilePathListAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::IntegersAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::StringSelectionAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::ToggleAspect, Utils::BoolAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::TriStateAspect, Utils::SelectionAspect, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::TextDisplay, Utils::BaseAspect);
+SOL_BASE_CLASSES(Utils::AspectList, Utils::BaseAspect);
+
+SOL_DERIVED_CLASSES(Utils::AspectContainer, Lua::Internal::LuaAspectContainer);
+
+SOL_DERIVED_CLASSES(
+ Utils::BaseAspect,
+ Utils::AspectContainer,
+ Utils::BoolAspect,
+ Utils::ColorAspect,
+ Utils::SelectionAspect,
+ Utils::MultiSelectionAspect,
+ Utils::StringAspect,
+ Utils::FilePathAspect,
+ Utils::IntegerAspect,
+ Utils::DoubleAspect,
+ Utils::StringListAspect,
+ Utils::FilePathListAspect,
+ Utils::IntegersAspect,
+ Utils::StringSelectionAspect,
+ Utils::ToggleAspect,
+ Utils::TriStateAspect,
+ Utils::TextDisplay,
+ Utils::AspectList);
+
+namespace Layouting {
+class LayoutItem;
+class Column;
+class Row;
+class Flow;
+class Grid;
+class Form;
+class Widget;
+class Stack;
+class Tab;
+class Group;
+class TextEdit;
+class PushButton;
+class SpinBox;
+class Splitter;
+class ToolBar;
+class TabWidget;
+class Group;
+} // namespace Layouting
+
+SOL_BASE_CLASSES(Layouting::Column, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Row, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Flow, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Grid, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Form, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Widget, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Stack, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Tab, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Group, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::TextEdit, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::PushButton, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::SpinBox, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::Splitter, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::ToolBar, Layouting::LayoutItem);
+SOL_BASE_CLASSES(Layouting::TabWidget, Layouting::LayoutItem);
+
+SOL_DERIVED_CLASSES(
+ Layouting::LayoutItem,
+ Layouting::Column,
+ Layouting::Row,
+ Layouting::Flow,
+ Layouting::Grid,
+ Layouting::Form,
+ Layouting::Widget,
+ Layouting::Stack,
+ Layouting::Tab,
+ Layouting::Group,
+ Layouting::TextEdit,
+ Layouting::PushButton,
+ Layouting::SpinBox,
+ Layouting::Splitter,
+ Layouting::ToolBar,
+ Layouting::TabWidget);
diff --git a/src/plugins/lua/bindings/layout.cpp b/src/plugins/lua/bindings/layout.cpp
new file mode 100644
index 00000000000..3d396161c7b
--- /dev/null
+++ b/src/plugins/lua/bindings/layout.cpp
@@ -0,0 +1,177 @@
+// 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 "inheritance.h"
+
+#include <utils/aspects.h>
+#include <utils/layoutbuilder.h>
+
+using namespace Layouting;
+using namespace Utils;
+
+namespace Lua::Internal {
+
+static void processChildren(LayoutItem *item, sol::table children)
+{
+ for (size_t i = 1; i <= children.size(); ++i) {
+ sol::object v = children[i];
+
+ if (v.is<LayoutItem *>()) {
+ item->addItem(*v.as<LayoutItem *>());
+ } else if (v.is<BaseAspect>()) {
+ v.as<BaseAspect *>()->addToLayout(*item);
+ } else if (v.is<QString>()) {
+ item->addItem(v.as<QString>());
+ } else if (v.is<sol::function>()) {
+ sol::function f = v.as<sol::function>();
+ auto res = LuaEngine::safe_call<LayoutItem *>(f);
+ QTC_ASSERT_EXPECTED(res, continue);
+ item->addItem(**res);
+ } else {
+ qWarning() << "Incompatible object added to layout item: " << (int) v.get_type()
+ << " (expected LayoutItem, Aspect or function returning LayoutItem)";
+ }
+ }
+}
+
+template<class T, typename... Args>
+static std::unique_ptr<T> construct(Args &&...args, sol::table children)
+{
+ std::unique_ptr<T> item(new T(std::forward<Args>(args)..., {}));
+
+ processChildren(item.get(), children);
+
+ return item;
+}
+
+void addLayoutModule()
+{
+ LuaEngine::registerProvider("Layout", [](sol::state_view l) -> sol::object {
+ sol::table layout = l.create_table();
+
+ layout.new_usertype<LayoutItem>("LayoutItem", "attachTo", &LayoutItem::attachTo);
+
+ layout["Span"] = [](int span, LayoutItem *item) {
+ return createItem(item, Span(span, *item));
+ };
+ layout["Space"] = [](int space) { return createItem(nullptr, Space(space)); };
+ layout["Stretch"] = [](int stretch) { return createItem(nullptr, Stretch(stretch)); };
+
+ layout.new_usertype<Column>("Column",
+ sol::call_constructor,
+ sol::factories(&construct<Column>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<Row>("Row",
+ sol::call_constructor,
+ sol::factories(&construct<Row>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<Flow>("Flow",
+ sol::call_constructor,
+ sol::factories(&construct<Flow>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<Grid>("Grid",
+ sol::call_constructor,
+ sol::factories(&construct<Grid>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<Form>("Form",
+ sol::call_constructor,
+ sol::factories(&construct<Form>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<Widget>("Widget",
+ sol::call_constructor,
+ sol::factories(&construct<Widget>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<Stack>("Stack",
+ sol::call_constructor,
+ sol::factories(&construct<Stack>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<Tab>(
+ "Tab",
+ sol::call_constructor,
+ sol::factories(&construct<Tab, QString>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<TextEdit>("TextEdit",
+ sol::call_constructor,
+ sol::factories(&construct<TextEdit>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<PushButton>("PushButton",
+ sol::call_constructor,
+ sol::factories(&construct<PushButton>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<SpinBox>("SpinBox",
+ sol::call_constructor,
+ sol::factories(&construct<SpinBox>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<Splitter>("Splitter",
+ sol::call_constructor,
+ sol::factories(&construct<Splitter>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<ToolBar>("ToolBar",
+ sol::call_constructor,
+ sol::factories(&construct<ToolBar>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+ layout.new_usertype<TabWidget>("TabWidget",
+ sol::call_constructor,
+ sol::factories(&construct<TabWidget>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+
+ layout.new_usertype<Group>("Group",
+ sol::call_constructor,
+ sol::factories(&construct<Group>),
+ sol::base_classes,
+ sol::bases<LayoutItem>());
+
+ layout["br"] = &br;
+ layout["st"] = &st;
+ layout["empty"] = &empty;
+ layout["hr"] = &hr;
+ layout["noMargin"] = &noMargin;
+ layout["normalMargin"] = &normalMargin;
+ layout["customMargin"] = [](int left, int top, int right, int bottom) {
+ return customMargin(QMargins(left, top, right, bottom));
+ };
+ layout["withFormAlignment"] = &withFormAlignment;
+ layout["title"] = &title;
+ layout["text"] = &text;
+ layout["tooltip"] = &tooltip;
+ layout["resize"] = &resize;
+ layout["columnStretch"] = &columnStretch;
+ layout["spacing"] = &spacing;
+ layout["windowTitle"] = &windowTitle;
+ layout["fieldGrowthPolicy"] = &fieldGrowthPolicy;
+ layout["id"] = &id;
+ layout["setText"] = &setText;
+ layout["onClicked"] = [](sol::function f) {
+ return onClicked([f]() {
+ auto res = LuaEngine::void_safe_call(f);
+ QTC_CHECK_EXPECTED(res);
+ });
+ };
+ layout["onTextChanged"] = [](sol::function f) {
+ return onTextChanged([f](const QString &text) {
+ auto res = LuaEngine::void_safe_call(f, text);
+ QTC_CHECK_EXPECTED(res);
+ });
+ };
+
+ return layout;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/messagemanager.cpp b/src/plugins/lua/bindings/messagemanager.cpp
new file mode 100644
index 00000000000..4e284c0b50a
--- /dev/null
+++ b/src/plugins/lua/bindings/messagemanager.cpp
@@ -0,0 +1,57 @@
+// 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 <coreplugin/messagemanager.h>
+
+namespace Lua::Internal {
+
+static QString variadicToString(sol::state_view lua, sol::variadic_args vargs)
+{
+ sol::function tostring = lua["tostring"];
+ QStringList msg;
+ for (auto v : vargs) {
+ if (v.get_type() != sol::type::string) {
+ lua_getglobal(lua.lua_state(), "tostring");
+ v.push();
+ if (lua_pcall(lua.lua_state(), 1, 1, 0) != LUA_OK) {
+ msg.append("<invalid>");
+ continue;
+ }
+ if (lua_isstring(lua.lua_state(), -1) != 1) {
+ msg.append("<invalid>");
+ continue;
+ }
+ auto str = sol::stack::pop<QString>(lua.lua_state());
+ msg.append(str);
+ } else {
+ msg.append(v.get<QString>());
+ }
+ }
+ return msg.join("");
+}
+
+void addMessageManagerModule()
+{
+ LuaEngine::registerProvider("MessageManager", [](sol::state_view lua) -> sol::object {
+ sol::table mm = lua.create_table();
+
+ mm.set_function("writeFlashing", [](sol::variadic_args vargs, sol::this_state s) {
+ sol::state_view lua(s);
+ Core::MessageManager::writeFlashing(variadicToString(lua, vargs));
+ });
+ mm.set_function("writeDisrupting", [](sol::variadic_args vargs, sol::this_state s) {
+ sol::state_view lua(s);
+ Core::MessageManager::writeDisrupting(variadicToString(lua, vargs));
+ });
+ mm.set_function("writeSilently", [](sol::variadic_args vargs, sol::this_state s) {
+ sol::state_view lua(s);
+ Core::MessageManager::writeSilently(variadicToString(lua, vargs));
+ });
+
+ return mm;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/qtcprocess.cpp b/src/plugins/lua/bindings/qtcprocess.cpp
new file mode 100644
index 00000000000..f40f22f4156
--- /dev/null
+++ b/src/plugins/lua/bindings/qtcprocess.cpp
@@ -0,0 +1,49 @@
+// 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/environment.h>
+#include <utils/qtcprocess.h>
+
+using namespace Utils;
+
+namespace Lua::Internal {
+
+void addProcessModule()
+{
+ LuaEngine::registerProvider("__process", [](sol::state_view lua) -> sol::object {
+ sol::table process = lua.create_table();
+
+ process["runInTerminal_cb"] = [](const QString &cmdline, sol::function cb) {
+ Process *p = new Process;
+ p->setTerminalMode(TerminalMode::Run);
+ p->setCommand(CommandLine::fromUserInput((cmdline)));
+ p->setEnvironment(Environment::systemEnvironment());
+
+ QObject::connect(p, &Process::done, [p, cb]() { cb(p->exitCode()); });
+
+ p->start();
+ };
+
+ return process;
+ });
+
+ LuaEngine::registerProvider("Process", [](sol::state_view lua) -> sol::object {
+ return lua
+ .script(
+ R"(
+local p = require("__process")
+local a = require("async")
+
+return {
+ runInTerminal_cb = p.runInTerminal_cb,
+ runInTerminal = a.wrap(p.runInTerminal_cb)
+}
+)",
+ "_process_")
+ .get<sol::table>();
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/settings.cpp b/src/plugins/lua/bindings/settings.cpp
new file mode 100644
index 00000000000..d0234def048
--- /dev/null
+++ b/src/plugins/lua/bindings/settings.cpp
@@ -0,0 +1,528 @@
+// 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/aspects.h>
+#include <utils/environment.h>
+#include <utils/layoutbuilder.h>
+
+#include <coreplugin/dialogs/ioptionspage.h>
+
+using namespace Utils;
+
+namespace Lua::Internal {
+
+class LuaAspectContainer : public AspectContainer
+{
+public:
+ using AspectContainer::AspectContainer;
+
+ sol::object dynamic_get(const std::string &key)
+ {
+ auto it = m_entries.find(key);
+ if (it == m_entries.cend()) {
+ return sol::lua_nil;
+ }
+ return it->second;
+ }
+
+ void dynamic_set(const std::string &key, sol::stack_object value)
+ {
+ if (!value.is<BaseAspect>())
+ throw std::runtime_error("AspectContainer can only contain BaseAspect instances");
+
+ registerAspect(value.as<BaseAspect *>(), false);
+
+ auto it = m_entries.find(key);
+ if (it == m_entries.cend()) {
+ m_entries.insert(it, {std::move(key), std::move(value)});
+ } else {
+ std::pair<const std::string, sol::object> &kvp = *it;
+ sol::object &entry = kvp.second;
+ entry = sol::object(std::move(value));
+ }
+ }
+
+ size_t size() const { return m_entries.size(); }
+
+public:
+ std::unordered_map<std::string, sol::object> m_entries;
+};
+
+std::unique_ptr<LuaAspectContainer> aspectContainerCreate(sol::table options)
+{
+ auto container = std::make_unique<LuaAspectContainer>();
+
+ for (const auto &[k, v] : options) {
+ if (k.is<std::string>()) {
+ std::string key = k.as<std::string>();
+ if (key == "autoApply") {
+ container->setAutoApply(v.as<bool>());
+ } else if (key == "layouter") {
+ if (v.is<sol::function>())
+ container->setLayouter(
+ [func = v.as<sol::function>()]() -> Layouting::LayoutItem {
+ auto res = Lua::LuaEngine::safe_call<Layouting::LayoutItem>(func);
+ QTC_ASSERT_EXPECTED(res, return {});
+ return *res;
+ });
+ } else {
+ container->m_entries[key] = v;
+ if (v.is<BaseAspect>()) {
+ container->registerAspect(v.as<BaseAspect *>());
+ }
+ }
+ }
+ }
+
+ container->readSettings();
+
+ return container;
+}
+
+void baseAspectCreate(BaseAspect *aspect, const std::string &key, const sol::object &value)
+{
+ if (key == "settingsKey")
+ aspect->setSettingsKey(keyFromString(value.as<QString>()));
+ else if (key == "displayName")
+ aspect->setDisplayName(value.as<QString>());
+ else if (key == "labelText")
+ aspect->setLabelText(value.as<QString>());
+ else if (key == "toolTip")
+ aspect->setToolTip(value.as<QString>());
+ else if (key == "onValueChanged") {
+ QObject::connect(aspect, &BaseAspect::changed, aspect, [func = value.as<sol::function>()]() {
+ Lua::LuaEngine::void_safe_call(func);
+ });
+ } else if (key == "onVolatileValueChanged") {
+ QObject::connect(aspect,
+ &BaseAspect::volatileValueChanged,
+ aspect,
+ [func = value.as<sol::function>()]() {
+ Lua::LuaEngine::void_safe_call(func);
+ });
+ } else if (key == "enabler")
+ aspect->setEnabler(value.as<BoolAspect *>());
+ else
+ qWarning() << "Unknown key:" << key.c_str();
+}
+
+template<class T>
+void typedAspectCreate(T *aspect, const std::string &key, const sol::object &value)
+{
+ if (key == "defaultValue")
+ aspect->setDefaultValue(value.as<typename T::valueType>());
+ else if (key == "value")
+ aspect->setValue(value.as<typename T::valueType>());
+ else
+ baseAspectCreate(aspect, key, value);
+}
+
+template<>
+void typedAspectCreate(StringAspect *aspect, const std::string &key, const sol::object &value)
+{
+ if (key == "displayStyle")
+ aspect->setDisplayStyle((StringAspect::DisplayStyle) value.as<int>());
+ else if (key == "historyId")
+ aspect->setHistoryCompleter(value.as<QString>().toLocal8Bit());
+ else if (key == "valueAcceptor")
+ aspect->setValueAcceptor([func = value.as<sol::function>()](const QString &oldValue,
+ const QString &newValue)
+ -> std::optional<QString> {
+ auto res = Lua::LuaEngine::safe_call<std::optional<QString>>(func, oldValue, newValue);
+ QTC_ASSERT_EXPECTED(res, return std::nullopt);
+ return *res;
+ });
+ else if (key == "showToolTipOnLabel")
+ aspect->setShowToolTipOnLabel(value.as<bool>());
+ else if (key == "displayFilter")
+ aspect->setDisplayFilter([func = value.as<sol::function>()](const QString &value) {
+ auto res = Lua::LuaEngine::safe_call<QString>(func, value);
+ QTC_ASSERT_EXPECTED(res, return value);
+ return *res;
+ });
+ else if (key == "placeHolderText")
+ aspect->setPlaceHolderText(value.as<QString>());
+ else if (key == "acceptRichText")
+ aspect->setAcceptRichText(value.as<bool>());
+ else if (key == "autoApplyOnEditingFinished")
+ aspect->setAutoApplyOnEditingFinished(value.as<bool>());
+ else if (key == "elideMode")
+ aspect->setElideMode((Qt::TextElideMode) value.as<int>());
+ else
+ typedAspectCreate(static_cast<TypedAspect<QString> *>(aspect), key, value);
+}
+
+template<>
+void typedAspectCreate(FilePathAspect *aspect, const std::string &key, const sol::object &value)
+{
+ if (key == "defaultPath")
+ aspect->setDefaultPathValue(value.as<FilePath>());
+ else if (key == "historyId")
+ aspect->setHistoryCompleter(value.as<QString>().toLocal8Bit());
+ else if (key == "promptDialogFilter")
+ aspect->setPromptDialogFilter(value.as<QString>());
+ else if (key == "promptDialogTitle")
+ aspect->setPromptDialogTitle(value.as<QString>());
+ else if (key == "commandVersionArguments")
+ aspect->setCommandVersionArguments(value.as<QStringList>());
+ else if (key == "allowPathFromDevice")
+ aspect->setAllowPathFromDevice(value.as<bool>());
+ else if (key == "validatePlaceHolder")
+ aspect->setValidatePlaceHolder(value.as<bool>());
+ else if (key == "openTerminalHandler")
+ aspect->setOpenTerminalHandler([func = value.as<sol::function>()]() {
+ auto res = Lua::LuaEngine::void_safe_call(func);
+ QTC_CHECK_EXPECTED(res);
+ });
+ else if (key == "expectedKind")
+ aspect->setExpectedKind((PathChooser::Kind) value.as<int>());
+ else if (key == "environment")
+ aspect->setEnvironment(value.as<Environment>());
+ else if (key == "baseFileName")
+ aspect->setBaseFileName(value.as<FilePath>());
+ else if (key == "valueAcceptor")
+ aspect->setValueAcceptor([func = value.as<sol::function>()](const QString &oldValue,
+ const QString &newValue)
+ -> std::optional<QString> {
+ auto res = Lua::LuaEngine::safe_call<std::optional<QString>>(func, oldValue, newValue);
+ QTC_ASSERT_EXPECTED(res, return std::nullopt);
+ return *res;
+ });
+ else if (key == "showToolTipOnLabel")
+ aspect->setShowToolTipOnLabel(value.as<bool>());
+ else if (key == "autoApplyOnEditingFinished")
+ aspect->setAutoApplyOnEditingFinished(value.as<bool>());
+ /*else if (key == "validationFunction")
+ aspect->setValidationFunction(
+ [func = value.as<sol::function>()](const QString &path) {
+ return func.call<std::optional<QString>>(path);
+ });
+ */
+ else if (key == "displayFilter")
+ aspect->setDisplayFilter([func = value.as<sol::function>()](const QString &path) {
+ auto res = Lua::LuaEngine::safe_call<QString>(func, path);
+ QTC_ASSERT_EXPECTED(res, return path);
+ return *res;
+ });
+ else if (key == "placeHolderText")
+ aspect->setPlaceHolderText(value.as<QString>());
+ else
+ typedAspectCreate(static_cast<TypedAspect<QString> *>(aspect), key, value);
+}
+
+template<>
+void typedAspectCreate(BoolAspect *aspect, const std::string &key, const sol::object &value)
+{
+ if (key == "labelPlacement") {
+ aspect->setLabelPlacement((BoolAspect::LabelPlacement) value.as<int>());
+ } else {
+ typedAspectCreate(static_cast<TypedAspect<bool> *>(aspect), key, value);
+ }
+}
+
+template<class T>
+std::unique_ptr<T> createAspectFromTable(
+ sol::table options, const std::function<void(T *, const std::string &, sol::object)> &f)
+{
+ auto aspect = std::make_unique<T>();
+
+ for (const auto &[k, v] : options) {
+ if (k.template is<std::string>()) {
+ f(aspect.get(), k.template as<std::string>(), v);
+ }
+ }
+
+ return aspect;
+}
+
+template<class T>
+void addTypedAspectBaseBindings(sol::table &lua)
+{
+ lua.new_usertype<TypedAspect<T>>("TypedAspect<bool>",
+ "value",
+ sol::property(&TypedAspect<T>::value,
+ [](TypedAspect<T> *a, const T &v) {
+ a->setValue(v);
+ }),
+ "volatileValue",
+ sol::property(&TypedAspect<T>::volatileValue,
+ [](TypedAspect<T> *a, const T &v) {
+ a->setVolatileValue(v);
+ }),
+ "defaultValue",
+ sol::property(&TypedAspect<T>::defaultValue),
+ sol::base_classes,
+ sol::bases<BaseAspect>());
+}
+
+template<class T>
+sol::usertype<T> addTypedAspect(sol::table &lua, const QString &name)
+{
+ addTypedAspectBaseBindings<typename T::valueType>(lua);
+
+ return lua.new_usertype<T>(
+ name,
+ "create",
+ [](sol::table options) { return createAspectFromTable<T>(options, &typedAspectCreate<T>); },
+ sol::base_classes,
+ sol::bases<TypedAspect<typename T::valueType>, BaseAspect>());
+}
+
+void addSettingsModule()
+{
+ LuaEngine::registerProvider("Settings", [](sol::state_view l) -> sol::object {
+ sol::table settings = l.create_table();
+
+ settings.new_usertype<BaseAspect>("Aspect", "apply", &BaseAspect::apply);
+
+ settings.new_usertype<LuaAspectContainer>("AspectContainer",
+ "create",
+ &aspectContainerCreate,
+ "apply",
+ &LuaAspectContainer::apply,
+ sol::meta_function::index,
+ &LuaAspectContainer::dynamic_get,
+ sol::meta_function::new_index,
+ &LuaAspectContainer::dynamic_set,
+ sol::meta_function::length,
+ &LuaAspectContainer::size,
+ sol::base_classes,
+ sol::bases<AspectContainer, BaseAspect>());
+
+ addTypedAspect<BoolAspect>(settings, "BoolAspect");
+ addTypedAspect<ColorAspect>(settings, "ColorAspect");
+ addTypedAspect<SelectionAspect>(settings, "SelectionAspect");
+ addTypedAspect<MultiSelectionAspect>(settings, "MultiSelectionAspect");
+ addTypedAspect<StringAspect>(settings, "StringAspect");
+
+ auto filePathAspectType = addTypedAspect<FilePathAspect>(settings, "FilePathAspect");
+ filePathAspectType.set("expandedValue", sol::property(&FilePathAspect::expandedValue));
+
+ addTypedAspect<IntegerAspect>(settings, "IntegerAspect");
+ addTypedAspect<DoubleAspect>(settings, "DoubleAspect");
+ addTypedAspect<StringListAspect>(settings, "StringListAspect");
+ addTypedAspect<FilePathListAspect>(settings, "FilePathListAspect");
+ addTypedAspect<IntegersAspect>(settings, "IntegersAspect");
+ addTypedAspect<StringSelectionAspect>(settings, "StringSelectionAspect");
+
+ settings.new_usertype<ToggleAspect>(
+ "ToggleAspect",
+ "create",
+ [](sol::table options) {
+ return createAspectFromTable<ToggleAspect>(
+ options,
+ [](ToggleAspect *aspect, const std::string &key, const sol::object &value) {
+ if (key == "offIcon")
+ aspect->setOffIcon(QIcon(value.as<QString>()));
+ else if (key == "offTooltip")
+ aspect->setOffTooltip(value.as<QString>());
+ else if (key == "onIcon")
+ aspect->setOnIcon(QIcon(value.as<QString>()));
+ else if (key == "onTooltip")
+ aspect->setOnTooltip(value.as<QString>());
+ else if (key == "onText")
+ aspect->setOnText(value.as<QString>());
+ else if (key == "offText")
+ aspect->setOffText(value.as<QString>());
+ else
+ typedAspectCreate(aspect, key, value);
+ });
+ },
+ "action",
+ &ToggleAspect::action,
+ sol::base_classes,
+ sol::bases<BoolAspect, TypedAspect<bool>, BaseAspect>());
+
+ static auto triStateFromString = [](const QString &str) -> TriState {
+ const QString l = str.toLower();
+ if (l == "enabled")
+ return TriState::Enabled;
+ else if (l == "disabled")
+ return TriState::Disabled;
+ else if (l == "default")
+ return TriState::Default;
+ else
+ return TriState::Default;
+ };
+
+ static auto triStateToString = [](TriState state) -> QString {
+ if (state == TriState::Enabled)
+ return "enabled";
+ else if (state == TriState::Disabled)
+ return "disabled";
+ return "default";
+ };
+
+ settings.new_usertype<TriStateAspect>(
+ "TriStateAspect",
+ "create",
+ [](sol::table options) {
+ return createAspectFromTable<TriStateAspect>(
+ options,
+ [](TriStateAspect *aspect, const std::string &key, const sol::object &value) {
+ if (key == "defaultValue")
+ aspect->setDefaultValue(triStateFromString(value.as<QString>()));
+ else if (key == "value")
+ aspect->setValue(triStateFromString(value.as<QString>()));
+ else
+ baseAspectCreate(aspect, key, value);
+ });
+ },
+ "value",
+ sol::property([](TriStateAspect *a) { return triStateToString(a->value()); },
+ [](TriStateAspect *a, const QString &v) {
+ a->setValue(triStateFromString(v));
+ }),
+ "volatileValue",
+ sol::property(
+ [](TriStateAspect *a) {
+ return triStateToString(TriState::fromInt(a->volatileValue()));
+ },
+ [](TriStateAspect *a, const QString &v) {
+ a->setVolatileValue(triStateFromString(v).toInt());
+ }),
+ "defaultValue",
+ sol::property([](TriStateAspect *a) { return triStateToString(a->defaultValue()); }),
+ sol::base_classes,
+ sol::bases<SelectionAspect, TypedAspect<int>, BaseAspect>());
+
+ settings.new_usertype<TextDisplay>(
+ "TextDisplay",
+ "create",
+ [](sol::table options) {
+ return createAspectFromTable<TextDisplay>(
+ options,
+ [](TextDisplay *aspect, const std::string &key, const sol::object &value) {
+ if (key == "text") {
+ aspect->setText(value.as<QString>());
+ } else if (key == "iconType") {
+ const QString type = value.as<QString>().toLower();
+
+ if (type.isEmpty() || type == "None")
+ aspect->setIconType(Utils::InfoLabel::InfoType::None);
+ else if (type == "information")
+ aspect->setIconType(Utils::InfoLabel::InfoType::Information);
+ else if (type == "warning")
+ aspect->setIconType(Utils::InfoLabel::InfoType::Warning);
+ else if (type == "error")
+ aspect->setIconType(Utils::InfoLabel::InfoType::Error);
+ else if (type == "ok")
+ aspect->setIconType(Utils::InfoLabel::InfoType::Ok);
+ else if (type == "notok")
+ aspect->setIconType(Utils::InfoLabel::InfoType::NotOk);
+ else
+ aspect->setIconType(Utils::InfoLabel::InfoType::None);
+ } else {
+ baseAspectCreate(aspect, key, value);
+ }
+ });
+ },
+ sol::base_classes,
+ sol::bases<BaseAspect>());
+
+ settings.new_usertype<AspectList>(
+ "AspectList",
+ "create",
+ [](sol::table options) {
+ return createAspectFromTable<AspectList>(
+ options,
+ [](AspectList *aspect, const std::string &key, const sol::object &value) {
+ if (key == "createItemFunction") {
+ aspect->setCreateItemFunction([func = value.as<sol::function>()]()
+ -> std::shared_ptr<BaseAspect> {
+ auto res = Lua::LuaEngine::safe_call<std::shared_ptr<BaseAspect>>(
+ func);
+ QTC_ASSERT_EXPECTED(res, return nullptr);
+ return *res;
+ });
+ } else if (key == "onItemAdded") {
+ aspect->setItemAddedCallback([func = value.as<sol::function>()](
+ std::shared_ptr<BaseAspect> item) {
+ auto res = Lua::LuaEngine::void_safe_call(func, item);
+ QTC_CHECK_EXPECTED(res);
+ });
+ } else if (key == "onItemRemoved") {
+ aspect->setItemRemovedCallback([func = value.as<sol::function>()](
+ std::shared_ptr<BaseAspect> item) {
+ auto res = Lua::LuaEngine::void_safe_call(func, item);
+ QTC_CHECK_EXPECTED(res);
+ });
+ } else {
+ baseAspectCreate(aspect, key, value);
+ }
+ });
+ },
+ "createAndAddItem",
+ &AspectList::createAndAddItem,
+ "foreach",
+ [](AspectList *a, sol::function clbk) {
+ a->forEachItem<BaseAspect>([clbk](std::shared_ptr<BaseAspect> item) {
+ auto res = Lua::LuaEngine::void_safe_call(clbk, item);
+ QTC_CHECK_EXPECTED(res);
+ });
+ },
+ "enumerate",
+ [](AspectList *a, sol::function clbk) {
+ a->forEachItem<BaseAspect>([clbk](std::shared_ptr<BaseAspect> item, int idx) {
+ auto res = Lua::LuaEngine::void_safe_call(clbk, item, idx);
+ QTC_CHECK_EXPECTED(res);
+ });
+ },
+ sol::base_classes,
+ sol::bases<BaseAspect>());
+
+ class OptionsPage : public Core::IOptionsPage
+ {
+ public:
+ OptionsPage(const sol::table &options)
+ {
+ setId(Id::fromString(options.get<QString>("id")));
+ setDisplayName(options.get<QString>("displayName"));
+ setCategory(Id::fromString(options.get<QString>("categoryId")));
+ setDisplayCategory(options.get<QString>("displayCategory"));
+ setCategoryIconPath(
+ FilePath::fromUserInput(options.get<QString>("categoryIconPath")));
+ AspectContainer *container = options.get<AspectContainer *>("aspectContainer");
+ setSettingsProvider([container]() { return container; });
+ }
+ };
+
+ settings.new_usertype<OptionsPage>("OptionsPage", "create", [](sol::table options) {
+ return std::make_unique<OptionsPage>(options);
+ });
+
+ // clang-format off
+ settings["StringDisplayStyle"] = l.create_table_with(
+ "Label", StringAspect::DisplayStyle::LabelDisplay,
+ "LineEdit", StringAspect::DisplayStyle::LineEditDisplay,
+ "TextEdit", StringAspect::DisplayStyle::TextEditDisplay,
+ "PasswordLineEdit", StringAspect::DisplayStyle::PasswordLineEditDisplay
+ );
+
+ settings["CheckBoxPlacement"] = l.create_table_with(
+ "Top", CheckBoxPlacement::Top,
+ "Right", CheckBoxPlacement::Right
+ );
+ settings["Kind"] = l.create_table_with(
+ "ExistingDirectory", PathChooser::Kind::ExistingDirectory,
+ "Directory", PathChooser::Kind::Directory,
+ "File", PathChooser::Kind::File,
+ "SaveFile", PathChooser::Kind::SaveFile,
+ "ExistingCommand", PathChooser::Kind::ExistingCommand,
+ "Command", PathChooser::Kind::Command,
+ "Any", PathChooser::Kind::Any
+ );
+ settings["LabelPlacement"] = l.create_table_with(
+ "AtCheckBox", BoolAspect::LabelPlacement::AtCheckBox,
+ "Compact", BoolAspect::LabelPlacement::Compact,
+ "InExtraLabel", BoolAspect::LabelPlacement::InExtraLabel
+ );
+ // clang-format on
+
+ return settings;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/bindings/utils.cpp b/src/plugins/lua/bindings/utils.cpp
new file mode 100644
index 00000000000..d73564c7ae9
--- /dev/null
+++ b/src/plugins/lua/bindings/utils.cpp
@@ -0,0 +1,113 @@
+// 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 "../luaqttypes.h"
+
+#include <utils/hostosinfo.h>
+
+#include <QTimer>
+
+using namespace Utils;
+
+namespace Lua::Internal {
+
+void addUtilsModule()
+{
+ LuaEngine::registerProvider("__utils", [](sol::state_view lua) -> sol::object {
+ sol::table utils = lua.create_table();
+
+ utils.set_function("waitms_cb", [](int ms, sol::function cb) {
+ QTimer *timer = new QTimer();
+ timer->setSingleShot(true);
+ timer->setInterval(ms);
+ QObject::connect(timer, &QTimer::timeout, timer, [cb, timer]() {
+ cb();
+ timer->deleteLater();
+ });
+ timer->start();
+ });
+
+ return utils;
+ });
+
+ LuaEngine::registerProvider("Utils", [](sol::state_view lua) -> sol::object {
+ sol::table utils = lua.script(
+ R"(
+local u = require("__utils")
+local a = require("async")
+
+return {
+ waitms_cb = u.waitms_cb,
+ waitms = a.wrap(u.waitms_cb)
+}
+)",
+ "_utils_")
+ .get<sol::table>();
+
+ auto hostOsInfoType = utils.new_usertype<HostOsInfo>("HostOsInfo");
+ hostOsInfoType["isWindowsHost"] = &HostOsInfo::isWindowsHost;
+ hostOsInfoType["isMacHost"] = &HostOsInfo::isMacHost;
+ hostOsInfoType["isLinuxHost"] = &HostOsInfo::isLinuxHost;
+ hostOsInfoType["os"] = sol::var([]() {
+ if (HostOsInfo::isMacHost())
+ return "mac";
+ else if (HostOsInfo::isLinuxHost())
+ return "linux";
+ else if (HostOsInfo::isWindowsHost())
+ return "windows";
+ else
+ return "unknown";
+ }());
+
+ auto filePathType = utils.new_usertype<FilePath>(
+ "FilePath",
+ sol::call_constructor,
+ sol::constructors<FilePath()>(),
+ "fromUserInput",
+ &FilePath::fromUserInput,
+ "searchInPath",
+ [](const FilePath &self) { return self.searchInPath(); },
+ "exists",
+ &FilePath::exists,
+ "dirEntries",
+ [](sol::this_state s, const FilePath &p, sol::table options) -> sol::table {
+ sol::state_view lua(s);
+ sol::table result = lua.create_table();
+ const QStringList nameFilters = options.get_or<QStringList>("nameFilters", {});
+ QDir::Filters fileFilters
+ = (QDir::Filters) options.get_or<int>("fileFilters", QDir::NoFilter);
+ QDirIterator::IteratorFlags flags
+ = (QDirIterator::IteratorFlags)
+ options.get_or<int>("flags", QDirIterator::NoIteratorFlags);
+
+ FileFilter filter(nameFilters);
+ p.iterateDirectory(
+ [&result](const FilePath &item) {
+ result.add(item);
+ return IterationPolicy::Continue;
+ },
+ FileFilter(nameFilters, fileFilters, flags));
+
+ return result;
+ },
+ "nativePath",
+ &FilePath::nativePath,
+ "toUserOutput",
+ &FilePath::toUserOutput,
+ "fileName",
+ &FilePath::fileName,
+ "currentWorkingPath",
+ &FilePath::currentWorkingPath,
+ "parentDir",
+ &FilePath::parentDir,
+ "resolvePath",
+ sol::overload(
+ [](const FilePath &p, const QString &path) { return p.resolvePath(path); },
+ [](const FilePath &p, const FilePath &path) { return p.resolvePath(path); }));
+
+ return utils;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/generateqtbindings.cpp b/src/plugins/lua/generateqtbindings.cpp
new file mode 100644
index 00000000000..c7ed25dfd8e
--- /dev/null
+++ b/src/plugins/lua/generateqtbindings.cpp
@@ -0,0 +1,401 @@
+// 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 "lua_global.h"
+
+#include "luaapiregistry.h"
+#include "luaengine.h"
+
+#include <utils/layoutbuilder.h>
+
+#include <QAbstractButton>
+#include <QAbstractSlider>
+#include <QAbstractSpinBox>
+#include <QCalendarWidget>
+#include <QComboBox>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QDockWidget>
+#include <QElapsedTimer>
+#include <QFocusFrame>
+#include <QFrame>
+#include <QGroupBox>
+#include <QKeySequenceEdit>
+#include <QLineEdit>
+#include <QMainWindow>
+#include <QMdiSubWindow>
+#include <QMenu>
+#include <QMenuBar>
+#include <QMetaProperty>
+#include <QProgressBar>
+#include <QRubberBand>
+#include <QSizeGrip>
+#include <QSplashScreen>
+#include <QSplitterHandle>
+#include <QStatusBar>
+#include <QTabBar>
+#include <QTabWidget>
+#include <QToolBar>
+#include <QWizardPage>
+
+#include <fstream>
+#include <iostream>
+#include <valarray>
+
+namespace Lua::Internal {
+
+QStringList baseClasses(const QMetaObject *metaObject)
+{
+ QStringList bases;
+ const QMetaObject *base = metaObject;
+ while ((base = base->superClass())) {
+ bases << QString::fromLocal8Bit(base->className());
+ }
+ return bases;
+}
+
+bool isBaseClassProperty(QString name, const QMetaObject *metaObject)
+{
+ const QMetaObject *base = metaObject;
+ while ((base = base->superClass())) {
+ for (int i = 0; i < base->propertyCount(); ++i) {
+ QMetaProperty p = base->property(i);
+ if (QString::fromLocal8Bit(p.name()) == name)
+ return true;
+ }
+ }
+ return false;
+}
+
+template<class T>
+QString createQObjectRegisterCode()
+{
+ // Add new types here when you've added "sol_lua_check, sol_lua_get and sol_lua_push" for them.
+ // clang-format off
+ static const QStringList whiteListedTypes = {
+ "bool", "int", "double", "float",
+ "QString", "QRect"};
+ // clang-format on
+
+ auto &metaObject = T::staticMetaObject;
+ auto className = QString::fromLocal8Bit(metaObject.className());
+
+ QStringList parts = {};
+
+ // properties
+ for (int i = 0; i < metaObject.propertyCount(); ++i) {
+ QMetaProperty p = metaObject.property(i);
+ QMetaType t = p.metaType();
+ QString typeName = QString::fromLocal8Bit(t.name());
+ QString propName = QString::fromLocal8Bit(p.name());
+ if (isBaseClassProperty(propName, &metaObject))
+ continue;
+
+ if (!p.isEnumType() && !whiteListedTypes.contains(typeName)) {
+ qDebug() << "Skipping" << p.name() << "of type" << typeName
+ << "as it is not whitelisted";
+ continue;
+ }
+
+ QString propTemplate
+ = QString(" \"%1\",\nsol::property(").arg(QString::fromLocal8Bit(p.name()));
+
+ if (p.isReadable()) {
+ QString readTemplate;
+
+ if (p.isEnumType()) {
+ readTemplate = QString(R"([](const %1 *obj) -> const char * {
+ auto p = %1::staticMetaObject.property(%2);
+ int v = p.read(obj).toInt();
+ return p.enumerator().valueToKey(v);
+ })")
+ .arg(className)
+ .arg(i);
+
+ } else {
+ readTemplate = QString(R"([](const %1 *obj) -> %2 {
+ return qvariant_cast<%2>(
+ %1::staticMetaObject.property(%3).read(obj));
+ })")
+ .arg(className)
+ .arg(typeName)
+ .arg(i);
+ }
+
+ propTemplate += readTemplate;
+ }
+
+ if (p.isWritable()) {
+ QString writeTemplate = ",\n";
+
+ if (p.isEnumType()) {
+ writeTemplate += QString(R"([](%1 *obj, const char *v) {
+ auto p = %1::staticMetaObject.property(%2);
+ int i = p.enumerator().keyToValue(v);
+ p.write(obj, i);
+ })")
+ .arg(className)
+ .arg(i);
+
+ } else {
+ writeTemplate += QString(R"([](%1 *obj, %2 v) {
+ %1::staticMetaObject.property(%3).write(obj, QVariant::fromValue(v));
+ })")
+ .arg(className)
+ .arg(typeName)
+ .arg(i);
+ }
+
+ propTemplate += writeTemplate;
+ }
+
+ propTemplate += ")";
+
+ parts << propTemplate;
+ }
+
+ // Methods
+
+ /*for (int i = 0; i < metaObject.methodCount(); i++) {
+ QMetaMethod m = metaObject.method(i);
+ QString methodTemplate;
+ if (m.methodType() == QMetaMethod::Signal) {
+ QString name = QString::fromLocal8Bit(m.name());
+ name[0] = name[0].toUpper();
+ name = "on" + name;
+
+ methodTemplate = QString(" \"%1\",\n").arg(name);
+
+
+
+ } else if (m.methodType() == QMetaMethod::Slot) {
+ } else if (m.methodType() == QMetaMethod::Method) {
+ } else if (m.methodType() == QMetaMethod::Constructor) {
+ } else {
+ qDebug() << "Unknown method type" << m.methodType();
+ }
+
+ templateString += methodTemplate;
+ }*/
+
+ // Generate base classes
+ if (metaObject.superClass()) {
+ parts << QString("sol::base_classes,\nsol::bases<%1>()")
+ .arg(baseClasses(&metaObject).join(','));
+ }
+
+ const QString registerFunctionTemplate = QString(R"(
+static void register%1Bindings(sol::state &lua) {
+ lua.new_usertype<%1>("%1",
+ %2
+ );
+}
+ )")
+ .arg(className)
+ .arg(parts.join(",\n"));
+
+ return registerFunctionTemplate;
+}
+
+template<class... Classes>
+QStringList createQObjectRegisterCodes()
+{
+ QStringList result;
+ ((result << createQObjectRegisterCode<Classes>()), ...);
+ return result;
+}
+
+template<class... Classes>
+QStringList createRegisterCalls()
+{
+ QStringList result;
+ ((result << QString("register%1Bindings(lua);")
+ .arg(QString::fromLocal8Bit(Classes::staticMetaObject.className()))),
+ ...);
+ return result;
+}
+
+template<class... Classes>
+QStringList createIncludeCalls()
+{
+ QStringList result;
+ ((result << QString("#include <%1>")
+ .arg(QString::fromLocal8Bit(Classes::staticMetaObject.className()))),
+ ...);
+ return result;
+}
+
+template<class... Classes>
+QString createBindings()
+{
+ QStringList registerFunctions = createQObjectRegisterCodes<Classes...>();
+
+ QString finalFunction = QString(R"(
+void registerUiBindings() {
+ auto &lua = LuaEngine::instance().lua();
+ %1
+}
+ )")
+ .arg(createRegisterCalls<Classes...>().join('\n'));
+
+ return QString(R"(
+// 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 "lua_global.h"
+#include "luaengine.h"
+
+#include <QMetaProperty>
+
+%1
+
+namespace Lua::Internal
+{
+ %2
+
+ %3
+}
+ )")
+ .arg(createIncludeCalls<Classes...>().join('\n'))
+ .arg(registerFunctions.join('\n'))
+ .arg(finalFunction);
+}
+
+void createWidgetBindings()
+{
+ std::ofstream out("/tmp/bindings.cpp", std::ios::out | std::ios::trunc);
+
+ out << createBindings<QObject,
+ QWidget,
+ QDialog,
+ QAbstractButton,
+ QAbstractSlider,
+ QAbstractSpinBox,
+ QCalendarWidget,
+ QComboBox,
+ QDialogButtonBox,
+ QDockWidget,
+ QFocusFrame,
+ QFrame,
+ QGroupBox,
+ QKeySequenceEdit,
+ QLineEdit,
+ QMainWindow,
+ QMdiSubWindow,
+ QMenu,
+ QMenuBar,
+ QProgressBar,
+ QRubberBand,
+ QSizeGrip,
+ QSplashScreen,
+ QSplitterHandle,
+ QStatusBar,
+ QTabBar,
+ QTabWidget,
+ QToolBar,
+ QWizardPage>()
+ .toStdString()
+ << std::endl;
+
+ qDebug() << "Done!";
+}
+
+template<typename... T>
+std::valarray<QString> createTypeBinding(QString typeName, QString fieldTypeName, T... fields)
+{
+ QString cppTemplateStr = R"(
+// %1
+bool sol_lua_check(sol::types<%1>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{ return sol::stack::check<sol::table>(L, index, handler, tracking); }
+%1 sol_lua_get(sol::types<%1>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ return %2;
+}
+int sol_lua_push(sol::types<%1>, lua_State *L, const %1 &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ %3;
+ return sol::stack::push(L, table);
+}
+)";
+
+ QString headerTemplateStr = R"(SOL_CONVERSION_FUNCTIONS(%1)
+)";
+
+ auto createField = [&](const std::pair<QString, QString> &field) {
+ return QString("table.get_or<%1, const char *, %1>(\"%2\", %3)")
+ .arg(fieldTypeName)
+ .arg(field.first)
+ .arg(field.second);
+ };
+ QString createTypeFromTable = QString("%1(%2)").arg(typeName).arg(
+ QStringList{createField(fields)...}.join(','));
+
+ QString createTableFromType
+ = QString("table.set(%1)")
+ .arg(QStringList{QString(R"("%1", value.%1())").arg(fields.first)...}.join(','));
+
+ return {cppTemplateStr.arg(typeName).arg(createTypeFromTable).arg(createTableFromType),
+ headerTemplateStr.arg(typeName)};
+}
+
+void createTypeBindings()
+{
+ std::valarray<QString> code = {"", R"(
+#define SOL_CONVERSION_FUNCTIONS(TYPE) \
+ bool LUA_EXPORT sol_lua_check(sol::types<TYPE>, \
+ lua_State *L, \
+ int index, \
+ std::function<sol::check_handler_type> handler, \
+ sol::stack::record &tracking); \
+ TYPE LUA_EXPORT sol_lua_get(sol::types<TYPE>, \
+ lua_State *L, \
+ int index, \
+ sol::stack::record &tracking); \
+ int LUA_EXPORT sol_lua_push(sol::types<TYPE>, lua_State *L, const TYPE &rect);
+
+ SOL_CONVERSION_FUNCTIONS(QString)
+
+)"};
+
+ using T = std::pair<QString, QString>;
+
+ code += createTypeBinding("QRect",
+ "int",
+ T{"x", "0"},
+ T{"y", "0"},
+ T{"width", "0"},
+ T{"height", "0"});
+ code += createTypeBinding("QSize", "int", T{"width", "0"}, T{"height", "0"});
+ code += createTypeBinding("QPoint", "int", T{"x", "0"}, T{"y", "0"});
+
+ code += createTypeBinding("QRectF",
+ "qreal",
+ T{"x", "0.0"},
+ T{"y", "0.0"},
+ T{"width", "0.0"},
+ T{"height", "0.0"});
+ code += createTypeBinding("QSizeF", "qreal", T{"width", "0.0"}, T{"height", "0.0"});
+ code += createTypeBinding("QPointF", "qreal", T{"x", "0.0"}, T{"y", "0.0"});
+
+ code += createTypeBinding("QColor",
+ "int",
+ T{"red", "0"},
+ T{"green", "0"},
+ T{"blue", "0"},
+ T{"alpha", "255"});
+
+ code[1] += "\n#undef SOL_CONVERSION_FUNCTIONS\n";
+
+ qDebug().noquote() << code[0];
+ qDebug().noquote() << code[1];
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/lua.qbs b/src/plugins/lua/lua.qbs
new file mode 100644
index 00000000000..447b6629f43
--- /dev/null
+++ b/src/plugins/lua/lua.qbs
@@ -0,0 +1,26 @@
+import qbs 1.0
+
+QtcPlugin {
+ name: "Lua"
+
+ Depends { name: "Core" }
+ Depends { name: "Qt"; submodules: ["widgets"] }
+
+ // TODO: Find Lua library, or disable the plugin
+ Depends { name: "Lua"; }
+
+ //cpp.defines: LUA_AVAILABLE SOL_ALL_SAFETIES_ON=1
+
+ files: [
+ "luaplugin.cpp",
+ "luaplugin.h",
+ "luaengine.cpp",
+ "luaengine.h",
+ "luaapiregistry.cpp",
+ "luaapiregistry.h",
+ "luapluginloader.cpp",
+ "luapluginloader.h",
+ "luatr.h"
+ ]
+}
+
diff --git a/src/plugins/lua/lua_global.h b/src/plugins/lua/lua_global.h
new file mode 100644
index 00000000000..e786361010c
--- /dev/null
+++ b/src/plugins/lua/lua_global.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <qglobal.h>
+
+#if defined(LUA_LIBRARY)
+# define LUA_EXPORT Q_DECL_EXPORT
+#elif defined(LUA_STATIC_LIBRARY) // Abuse single files for manual tests
+# define LUA_EXPORT
+#else
+# define LUA_EXPORT Q_DECL_IMPORT
+#endif
diff --git a/src/plugins/lua/luaengine.cpp b/src/plugins/lua/luaengine.cpp
new file mode 100644
index 00000000000..b9c67c99565
--- /dev/null
+++ b/src/plugins/lua/luaengine.cpp
@@ -0,0 +1,249 @@
+// 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 "luapluginspec.h"
+
+#include <utils/algorithm.h>
+
+#include <QJsonArray>
+#include <QJsonObject>
+
+using namespace Utils;
+
+namespace Lua {
+
+class LuaEnginePrivate
+{
+public:
+ LuaEnginePrivate() {}
+
+ QHash<QString, LuaEngine::PackageProvider> m_providers;
+ QList<std::function<void(sol::state_view)>> m_autoProviders;
+
+ QMap<QString, std::function<void(sol::function)>> m_hooks;
+};
+
+LuaEngine &LuaEngine::instance()
+{
+ static LuaEngine luaEngine;
+ return luaEngine;
+}
+
+LuaEngine::LuaEngine()
+ : d(new LuaEnginePrivate())
+{}
+
+LuaEngine::~LuaEngine() = default;
+
+void LuaEngine::registerProvider(const QString &packageName, const PackageProvider &provider)
+{
+ QTC_ASSERT(!instance().d->m_providers.contains(packageName), return);
+ instance().d->m_providers[packageName] = provider;
+}
+
+void LuaEngine::autoRegister(std::function<void(sol::state_view)> registerFunction)
+{
+ instance().d->m_autoProviders.append(registerFunction);
+}
+
+void LuaEngine::registerHook(QString name, std::function<void(sol::function)> hook)
+{
+ instance().d->m_hooks.insert("." + name, hook);
+}
+
+expected_str<void> LuaEngine::connectHooks(sol::state_view lua, const sol::table &table, QString path)
+{
+ for (const auto &[k, v] : table) {
+ if (v.get_type() == sol::type::table) {
+ return connectHooks(lua, v.as<sol::table>(), QStringList{path, k.as<QString>()}.join("."));
+ } else if (v.get_type() == sol::type::function) {
+ QString hookName = QStringList{path, k.as<QString>()}.join(".");
+ auto it = d->m_hooks.find(hookName);
+ if (it == d->m_hooks.end())
+ return make_unexpected(QString("No hook named '%1' found").arg(hookName));
+ else
+ it.value()(v.as<sol::function>());
+ }
+ }
+
+ return {};
+}
+
+expected_str<void> LuaEngine::connectHooks(sol::state_view lua, const sol::table &hookTable)
+{
+ if (!hookTable)
+ return {};
+
+ return instance().connectHooks(lua, hookTable, "");
+}
+
+expected_str<LuaPluginSpec *> LuaEngine::loadPlugin(const Utils::FilePath &path)
+{
+ auto contents = path.fileContents();
+ if (!contents)
+ return make_unexpected(contents.error());
+
+ sol::state lua;
+
+ // TODO: Only open libraries requested by the plugin
+ lua.open_libraries(sol::lib::base,
+ sol::lib::package,
+ sol::lib::coroutine,
+ sol::lib::string,
+ sol::lib::os,
+ sol::lib::math,
+ sol::lib::table,
+ sol::lib::debug,
+ sol::lib::bit32,
+ sol::lib::io);
+
+ lua["print"] = [prefix = path.fileName()](sol::variadic_args va) {
+ QStringList strings;
+ int n = va.size();
+ int i;
+ for (i = 1; i <= n; i++) {
+ size_t l;
+ const char *s = luaL_tolstring(va.lua_state(), i, &l);
+ if (s != nullptr)
+ strings.append(QString::fromUtf8(s, l));
+ }
+
+ qDebug().noquote() << "[" << prefix << "]" << strings.join("\t");
+ };
+
+ for (const auto &[name, func] : d->m_providers.asKeyValueRange()) {
+ lua["package"]["preload"][name.toStdString()] = [func = func](sol::this_state s) {
+ return func(s);
+ };
+ }
+
+ for (const auto &func : d->m_autoProviders)
+ func(lua);
+
+ const QString searchPath = (path.parentDir() / "?.lua").toUserOutput();
+ lua["package"]["path"] = searchPath.toStdString();
+
+ auto result = lua.safe_script(
+ std::string_view(contents->data(), contents->size()),
+ sol::script_pass_on_error,
+ path.fileName().toUtf8().constData());
+
+ if (!result.valid()) {
+ sol::error err = result;
+ return make_unexpected(QString(QString::fromUtf8(err.what())));
+ }
+
+ if (result.get_type() != sol::type::table)
+ return make_unexpected(QString("Script did not return a table"));
+
+ sol::table pluginInfo = result.get<sol::table>();
+ if (!pluginInfo.valid())
+ return make_unexpected(QString("Script did not return a table with plugin info"));
+ return LuaPluginSpec::create(path, std::move(lua), pluginInfo);
+}
+
+bool LuaEngine::isCoroutine(lua_State *state)
+{
+ bool ismain = lua_pushthread(state) == 1;
+ return !ismain;
+}
+
+template<typename KeyType>
+static void setFromJson(sol::table &t, KeyType k, const QJsonValue &v)
+{
+ if (v.isDouble())
+ t[k] = v.toDouble();
+ else if (v.isBool())
+ t[k] = v.toBool();
+ else if (v.isString())
+ t[k] = v.toString();
+ else if (v.isObject())
+ t[k] = LuaEngine::toTable(t.lua_state(), v);
+ else if (v.isArray())
+ t[k] = LuaEngine::toTable(t.lua_state(), v);
+}
+
+sol::table LuaEngine::toTable(sol::state_view lua, const QJsonValue &v)
+{
+ sol::table table(lua, sol::create);
+
+ if (v.isObject()) {
+ QJsonObject o = v.toObject();
+
+ for (auto it = o.constBegin(); it != o.constEnd(); ++it) {
+ setFromJson(table, it.key().toStdString(), it.value());
+ }
+
+ } else if (v.isArray()) {
+ int i = 1;
+ for (const auto &v : v.toArray()) {
+ setFromJson(table, i++, v);
+ }
+ }
+
+ return table;
+}
+
+QJsonValue toJsonValue(sol::object object);
+
+QJsonValue toJsonValue(sol::table table)
+{
+ if (table.get<std::optional<sol::object>>(1)) {
+ // Is Array
+ QJsonArray arr;
+
+ for (size_t i = 0; i < table.size(); ++i) {
+ std::optional<sol::object> v = table.get<std::optional<sol::object>>(i + 1);
+ if (!v)
+ continue;
+ arr.append(toJsonValue(*v));
+ }
+
+ return arr;
+ }
+
+ // Is Object
+ QJsonObject obj;
+ for (const auto &[k, v] : table)
+ obj[k.as<QString>()] = toJsonValue(v);
+
+ return obj;
+}
+
+QJsonValue toJsonValue(sol::object object)
+{
+ switch (object.get_type()) {
+ case sol::type::lua_nil:
+ return {};
+ case sol::type::boolean:
+ return object.as<bool>();
+ case sol::type::number:
+ return object.as<double>();
+ case sol::type::string:
+ return object.as<QString>();
+ case sol::type::table:
+ return toJsonValue(object.as<sol::table>());
+ default:
+ return {};
+ }
+}
+
+QJsonValue LuaEngine::toJson(const sol::table &table)
+{
+ return toJsonValue(table);
+}
+
+expected_str<int> LuaEngine::resumeImpl(sol::this_state s, int nArgs)
+{
+ int res;
+ auto success = lua_resume(s.lua_state(), nullptr, nArgs, &res);
+
+ if (success == LUA_OK || success == LUA_YIELD)
+ return res;
+
+ return make_unexpected((sol::stack::pop<QString>(s.lua_state())));
+}
+
+} // namespace Lua
diff --git a/src/plugins/lua/luaengine.h b/src/plugins/lua/luaengine.h
new file mode 100644
index 00000000000..3246c7de19b
--- /dev/null
+++ b/src/plugins/lua/luaengine.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "lua_global.h"
+
+#include <extensionsystem/iplugin.h>
+#include <extensionsystem/pluginspec.h>
+
+#include <utils/expected.h>
+#include <utils/filepath.h>
+
+#include <sol/sol.hpp>
+
+// this needs to be included after sol/sol.hpp!
+#include "luaqttypes.h"
+
+#include <QJsonValue>
+
+#include <memory>
+
+namespace Lua {
+class LuaEnginePrivate;
+class LuaPluginSpec;
+
+struct CoroutineState
+{
+ bool isMainThread;
+};
+
+class LUA_EXPORT LuaEngine
+{
+private:
+ LuaEngine();
+
+public:
+ using PackageProvider = std::function<sol::object(sol::state_view)>;
+
+ ~LuaEngine();
+ static LuaEngine &instance();
+
+ Utils::expected_str<LuaPluginSpec *> loadPlugin(const Utils::FilePath &path);
+
+ static void registerProvider(const QString &packageName, const PackageProvider &provider);
+ static void autoRegister(std::function<void(sol::state_view)> registerFunction);
+ static void registerHook(QString name, std::function<void(sol::function)> hookProvider);
+
+ static Utils::expected_str<void> connectHooks(sol::state_view lua, const sol::table &hookTable);
+
+ static bool isCoroutine(lua_State *state);
+
+ static sol::table toTable(sol::state_view lua, const QJsonValue &v);
+ static QJsonValue toJson(const sol::table &t);
+
+ static Utils::expected_str<int> resumeImpl(sol::this_state s, int nargs);
+
+ template<typename... Args>
+ static Utils::expected_str<int> resume(sol::this_state s, Args &&...args)
+ {
+ sol::stack::push(s, std::forward<Args>(args)...);
+ return resumeImpl(s, sizeof...(Args));
+ }
+
+ template<typename R, typename... Args>
+ static Utils::expected_str<R> safe_call(sol::protected_function function, Args &&...args)
+ {
+ sol::protected_function_result result = function(std::forward<Args>(args)...);
+ if (!result.valid()) {
+ sol::error err = result;
+ return Utils::make_unexpected(QString::fromLocal8Bit(err.what()));
+ }
+ try {
+ return result.get<R>();
+ } catch (std::runtime_error &e) {
+ return Utils::make_unexpected(QString::fromLocal8Bit(e.what()));
+ }
+ }
+
+ template<typename... Args>
+ static Utils::expected_str<void> void_safe_call(sol::protected_function function, Args &&...args)
+ {
+ sol::protected_function_result result = function(std::forward<Args>(args)...);
+ if (!result.valid()) {
+ sol::error err = result;
+ return Utils::make_unexpected(QString::fromLocal8Bit(err.what()));
+ }
+ return {};
+ }
+
+protected:
+ Utils::expected_str<void> connectHooks(
+ sol::state_view lua, const sol::table &table, QString path);
+
+private:
+ std::unique_ptr<LuaEnginePrivate> d;
+};
+
+} // namespace Lua
diff --git a/src/plugins/lua/luaplugin.cpp b/src/plugins/lua/luaplugin.cpp
new file mode 100644
index 00000000000..e2c2db68d5b
--- /dev/null
+++ b/src/plugins/lua/luaplugin.cpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "luapluginloader.h"
+
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/icore.h>
+
+#include <extensionsystem/iplugin.h>
+#include <extensionsystem/pluginmanager.h>
+
+#include <utils/algorithm.h>
+
+#include <QAction>
+#include <QDebug>
+#include <QMenu>
+
+namespace Lua::Internal {
+
+void addAsyncModule();
+void addFetchModule();
+void addActionModule();
+void addUtilsModule();
+void addMessageManagerModule();
+void addProcessModule();
+void addSettingsModule();
+void addLayoutModule();
+void addQtModule();
+void addCoreModule();
+void addHookModule();
+
+class LuaPlugin : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Lua.json")
+
+public:
+ LuaPlugin() = default;
+ ~LuaPlugin() override = default;
+
+ void initialize() final
+ {
+ addAsyncModule();
+ addFetchModule();
+ addActionModule();
+ addUtilsModule();
+ addMessageManagerModule();
+ addProcessModule();
+ addSettingsModule();
+ addLayoutModule();
+ addQtModule();
+ addCoreModule();
+ addHookModule();
+ }
+
+ bool delayedInitialize() final
+ {
+ LuaPluginLoader::instance().scan(
+ Utils::transform(ExtensionSystem::PluginManager::pluginPaths(),
+ [](const QString &path) -> QString { return path + "/lua-plugins/"; }));
+
+ return true;
+ }
+};
+
+} // namespace Lua::Internal
+
+#include "luaplugin.moc"
diff --git a/src/plugins/lua/luapluginloader.cpp b/src/plugins/lua/luapluginloader.cpp
new file mode 100644
index 00000000000..4395599a1a7
--- /dev/null
+++ b/src/plugins/lua/luapluginloader.cpp
@@ -0,0 +1,64 @@
+// 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 "luapluginloader.h"
+
+#include "luaengine.h"
+#include "luapluginspec.h"
+
+#include <coreplugin/messagemanager.h>
+
+#include <extensionsystem/pluginmanager.h>
+#include <extensionsystem/pluginspec.h>
+
+#include <utils/algorithm.h>
+#include <utils/filepath.h>
+
+namespace Lua {
+
+class LuaPluginLoaderPrivate
+{
+public:
+};
+
+LuaPluginLoader::LuaPluginLoader()
+ : d(std::make_unique<LuaPluginLoaderPrivate>())
+{}
+LuaPluginLoader::~LuaPluginLoader() = default;
+
+LuaPluginLoader &LuaPluginLoader::instance()
+{
+ static LuaPluginLoader luaPluginLoader;
+ return luaPluginLoader;
+}
+
+void LuaPluginLoader::scan(const QStringList &paths)
+{
+ QVector<ExtensionSystem::PluginSpec *> plugins;
+ for (const auto &path : paths) {
+ const auto folders = Utils::FilePath::fromUserInput(path).dirEntries(
+ Utils::FileFilter({}, QDir::Dirs | QDir::NoDotAndDotDot));
+
+ for (const auto &folder : folders) {
+ const auto script = folder / (folder.baseName() + ".lua");
+ const Utils::expected_str<LuaPluginSpec *> result = LuaEngine::instance().loadPlugin(
+ script);
+
+ if (!result) {
+ qWarning() << "Failed to load plugin" << script << ":" << result.error();
+ Core::MessageManager::writeFlashing(tr("Failed to load plugin %1: %2")
+ .arg(script.toUserOutput())
+ .arg(result.error()));
+ continue;
+ }
+
+ plugins.push_back(*result);
+ }
+ }
+
+ ExtensionSystem::PluginManager::addPlugins(plugins);
+ ExtensionSystem::PluginManager::loadPluginsAtRuntime(
+ QSet<ExtensionSystem::PluginSpec *>(plugins.begin(), plugins.end()));
+}
+
+} // namespace Lua
diff --git a/src/plugins/lua/luapluginloader.h b/src/plugins/lua/luapluginloader.h
new file mode 100644
index 00000000000..d4bb5f41791
--- /dev/null
+++ b/src/plugins/lua/luapluginloader.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QObject>
+#include <QStringList>
+
+#include <memory>
+
+namespace Lua {
+class LuaPluginLoaderPrivate;
+class LuaPluginLoader : public QObject
+{
+public:
+ LuaPluginLoader();
+ ~LuaPluginLoader();
+
+ static LuaPluginLoader &instance();
+
+ void scan(const QStringList &paths);
+
+signals:
+ void pluginLoaded(const QString &name);
+
+private:
+ std::unique_ptr<LuaPluginLoaderPrivate> d;
+};
+
+} // namespace Lua
diff --git a/src/plugins/lua/luapluginspec.cpp b/src/plugins/lua/luapluginspec.cpp
new file mode 100644
index 00000000000..44f260bea04
--- /dev/null
+++ b/src/plugins/lua/luapluginspec.cpp
@@ -0,0 +1,140 @@
+// 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 "luapluginspec.h"
+
+#include "luaengine.h"
+#include "luatr.h"
+
+#include <extensionsystem/extensionsystemtr.h>
+
+#include <utils/algorithm.h>
+#include <utils/expected.h>
+
+#include <QJsonDocument>
+#include <QLoggingCategory>
+
+Q_LOGGING_CATEGORY(luaPluginSpecLog, "qtc.lua.pluginspec", QtWarningMsg)
+
+using namespace ExtensionSystem;
+using namespace Utils;
+
+namespace Lua {
+
+class LuaScriptPluginPrivate
+{
+public:
+ QString name;
+ QList<QString> cppDepends;
+ sol::function setup;
+ sol::environment pluginEnvironment;
+};
+
+class LuaPluginSpecPrivate
+{
+public:
+ FilePath pluginScriptPath;
+
+ sol::state lua;
+ sol::table pluginTable;
+
+ sol::function setupFunction;
+};
+
+LuaPluginSpec::LuaPluginSpec()
+ : d(new LuaPluginSpecPrivate())
+{}
+
+expected_str<LuaPluginSpec *> LuaPluginSpec::create(const FilePath &filePath,
+ sol::state lua,
+ sol::table pluginTable)
+{
+ std::unique_ptr<LuaPluginSpec> pluginSpec(new LuaPluginSpec());
+
+ pluginSpec->d->lua = std::move(lua);
+ pluginSpec->d->pluginTable = pluginTable;
+
+ pluginSpec->d->setupFunction = pluginTable.get_or<sol::function>("setup", {});
+ if (!pluginSpec->d->setupFunction)
+ return make_unexpected(QString("Plugin info table did not contain a setup function"));
+
+ QJsonValue v = LuaEngine::toJson(pluginTable);
+ if (luaPluginSpecLog().isDebugEnabled()) {
+ qCDebug(luaPluginSpecLog).noquote()
+ << "Plugin info table:" << QJsonDocument(v.toObject()).toJson(QJsonDocument::Indented);
+ }
+
+ QJsonObject obj = v.toObject();
+ obj["SoftLoadable"] = true;
+
+ auto r = pluginSpec->PluginSpec::readMetaData(obj);
+ if (!r)
+ return make_unexpected(r.error());
+
+ pluginSpec->setFilePath(filePath.toUserOutput());
+ pluginSpec->setLocation(filePath.parentDir().toUserOutput());
+
+ pluginSpec->d->pluginScriptPath = filePath;
+
+ return pluginSpec.release();
+}
+
+ExtensionSystem::IPlugin *LuaPluginSpec::plugin() const
+{
+ return nullptr;
+}
+
+// LuaPluginSpec::For internal use {}
+bool LuaPluginSpec::loadLibrary()
+{
+ // We are actually already loaded, but we need to set the state to loaded as well.
+ // We cannot set it earlier as it is used as a state machine that would break for earlier steps.
+ setState(PluginSpec::State::Loaded);
+ return true;
+}
+bool LuaPluginSpec::initializePlugin()
+{
+ std::optional<sol::table> hookTable = d->pluginTable.get<std::optional<sol::table>>("hooks");
+ if (hookTable) {
+ auto res = LuaEngine::connectHooks(d->lua, *hookTable);
+ if (!res) {
+ setError(Lua::Tr::tr("Failed to connect hooks: %1").arg(res.error()));
+ return false;
+ }
+ }
+ auto result = d->setupFunction.call();
+
+ if (result.get_type() == sol::type::boolean && result.get<bool>() == false) {
+ setError(Lua::Tr::tr("Plugin setup function returned false"));
+ return false;
+ } else if (result.get_type() == sol::type::string) {
+ std::string error = result.get<sol::error>().what();
+ if (!error.empty()) {
+ setError(Lua::Tr::tr("Plugin setup function returned error: %1")
+ .arg(QString::fromStdString(error)));
+ return false;
+ }
+ }
+
+ setState(PluginSpec::State::Initialized);
+ return true;
+}
+
+bool LuaPluginSpec::initializeExtensions()
+{
+ setState(PluginSpec::State::Running);
+ return true;
+}
+
+bool LuaPluginSpec::delayedInitialize()
+{
+ return true;
+}
+ExtensionSystem::IPlugin::ShutdownFlag LuaPluginSpec::stop()
+{
+ return ExtensionSystem::IPlugin::ShutdownFlag::SynchronousShutdown;
+}
+
+void LuaPluginSpec::kill() {}
+
+} // namespace Lua
diff --git a/src/plugins/lua/luapluginspec.h b/src/plugins/lua/luapluginspec.h
new file mode 100644
index 00000000000..208aa8f196d
--- /dev/null
+++ b/src/plugins/lua/luapluginspec.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "sol/forward.hpp"
+
+#include <extensionsystem/iplugin.h>
+#include <extensionsystem/pluginspec.h>
+
+#include <utils/expected.h>
+#include <utils/filepath.h>
+
+#include <QString>
+
+namespace Lua {
+class LuaScriptPluginPrivate;
+class LuaPluginSpecPrivate;
+
+class LuaScriptPlugin : public ExtensionSystem::IPlugin
+{
+public:
+ LuaScriptPlugin() = delete;
+ LuaScriptPlugin(const LuaScriptPlugin &other);
+ LuaScriptPlugin(const std::shared_ptr<LuaScriptPluginPrivate> &d);
+
+ QString name() const;
+ QStringList cppDependencies() const;
+ void setup() const;
+
+private:
+ std::shared_ptr<LuaScriptPluginPrivate> d;
+};
+
+class LuaPluginSpec : public ExtensionSystem::PluginSpec
+{
+ std::unique_ptr<LuaPluginSpecPrivate> d;
+
+ LuaPluginSpec();
+
+public:
+ static Utils::expected_str<LuaPluginSpec *> create(const Utils::FilePath &filePath,
+ sol::state lua,
+ sol::table pluginTable);
+
+ ExtensionSystem::IPlugin *plugin() const override;
+
+ // For internal use only
+ bool loadLibrary() override;
+ bool initializePlugin() override;
+ bool initializeExtensions() override;
+ bool delayedInitialize() override;
+ ExtensionSystem::IPlugin::ShutdownFlag stop() override;
+ void kill() override;
+};
+
+} // namespace Lua
diff --git a/src/plugins/lua/luaqttypes.cpp b/src/plugins/lua/luaqttypes.cpp
new file mode 100644
index 00000000000..cc6ac1a0f69
--- /dev/null
+++ b/src/plugins/lua/luaqttypes.cpp
@@ -0,0 +1,314 @@
+// 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"
+
+// This defines the conversion from QString to lua_string and vice versa
+bool sol_lua_check(sol::types<QString>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ // use sol's method for checking specifically for a string
+ return sol::stack::check<const char *>(L, index, handler, tracking);
+}
+
+QString sol_lua_get(sol::types<QString>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ const char *str = sol::stack::get<const char *>(L, index, tracking);
+ return QString::fromLocal8Bit(str);
+}
+
+int sol_lua_push(sol::types<QString>, lua_State *L, const QString &qStr)
+{
+ // create table
+ sol::state_view lua(L);
+ // use base sol method to push the string
+ int amount = sol::stack::push(L, qStr.toLocal8Bit().data());
+ // return # of things pushed onto stack
+ return amount;
+}
+
+// QRect
+bool sol_lua_check(sol::types<QRect>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ return sol::stack::check<sol::table>(L, index, handler, tracking);
+}
+QRect sol_lua_get(sol::types<QRect>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ return QRect(table.get_or<int, const char *, int>("x", 0),
+ table.get_or<int, const char *, int>("y", 0),
+ table.get_or<int, const char *, int>("width", 0),
+ table.get_or<int, const char *, int>("height", 0));
+}
+int sol_lua_push(sol::types<QRect>, lua_State *L, const QRect &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ table.set("x", value.x(), "y", value.y(), "width", value.width(), "height", value.height());
+ return sol::stack::push(L, table);
+}
+
+// QSize
+bool sol_lua_check(sol::types<QSize>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ return sol::stack::check<sol::table>(L, index, handler, tracking);
+}
+QSize sol_lua_get(sol::types<QSize>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ return QSize(table.get_or<int, const char *, int>("width", 0),
+ table.get_or<int, const char *, int>("height", 0));
+}
+int sol_lua_push(sol::types<QSize>, lua_State *L, const QSize &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ table.set("width", value.width(), "height", value.height());
+ return sol::stack::push(L, table);
+}
+
+// QPoint
+bool sol_lua_check(sol::types<QPoint>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ return sol::stack::check<sol::table>(L, index, handler, tracking);
+}
+QPoint sol_lua_get(sol::types<QPoint>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ return QPoint(table.get_or<int, const char *, int>("x", 0),
+ table.get_or<int, const char *, int>("y", 0));
+}
+int sol_lua_push(sol::types<QPoint>, lua_State *L, const QPoint &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ table.set("x", value.x(), "y", value.y());
+ return sol::stack::push(L, table);
+}
+
+// QRectF
+bool sol_lua_check(sol::types<QRectF>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ return sol::stack::check<sol::table>(L, index, handler, tracking);
+}
+QRectF sol_lua_get(sol::types<QRectF>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ return QRectF(table.get_or<qreal, const char *, qreal>("x", 0.0),
+ table.get_or<qreal, const char *, qreal>("y", 0.0),
+ table.get_or<qreal, const char *, qreal>("width", 0.0),
+ table.get_or<qreal, const char *, qreal>("height", 0.0));
+}
+int sol_lua_push(sol::types<QRectF>, lua_State *L, const QRectF &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ table.set("x", value.x(), "y", value.y(), "width", value.width(), "height", value.height());
+ return sol::stack::push(L, table);
+}
+
+// QSizeF
+bool sol_lua_check(sol::types<QSizeF>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ return sol::stack::check<sol::table>(L, index, handler, tracking);
+}
+QSizeF sol_lua_get(sol::types<QSizeF>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ return QSizeF(table.get_or<qreal, const char *, qreal>("width", 0.0),
+ table.get_or<qreal, const char *, qreal>("height", 0.0));
+}
+int sol_lua_push(sol::types<QSizeF>, lua_State *L, const QSizeF &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ table.set("width", value.width(), "height", value.height());
+ return sol::stack::push(L, table);
+}
+
+// QPointF
+bool sol_lua_check(sol::types<QPointF>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ return sol::stack::check<sol::table>(L, index, handler, tracking);
+}
+QPointF sol_lua_get(sol::types<QPointF>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ return QPointF(table.get_or<qreal, const char *, qreal>("x", 0.0),
+ table.get_or<qreal, const char *, qreal>("y", 0.0));
+}
+int sol_lua_push(sol::types<QPointF>, lua_State *L, const QPointF &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ table.set("x", value.x(), "y", value.y());
+ return sol::stack::push(L, table);
+}
+
+// QColor
+bool sol_lua_check(sol::types<QColor>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ return sol::stack::check<sol::table>(L, index, handler, tracking);
+}
+QColor sol_lua_get(sol::types<QColor>, lua_State *L, int index, sol::stack::record &tracking)
+{
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ return QColor(table.get_or<int, const char *, int>("red", 0),
+ table.get_or<int, const char *, int>("green", 0),
+ table.get_or<int, const char *, int>("blue", 0),
+ table.get_or<int, const char *, int>("alpha", 255));
+}
+int sol_lua_push(sol::types<QColor>, lua_State *L, const QColor &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ table.set("red",
+ value.red(),
+ "green",
+ value.green(),
+ "blue",
+ value.blue(),
+ "alpha",
+ value.alpha());
+ return sol::stack::push(L, table);
+}
+
+// QStringList
+bool sol_lua_check(sol::types<QStringList>,
+ lua_State *L,
+ int index,
+ std::function<sol::check_handler_type> handler,
+ sol::stack::record &tracking)
+{
+ return sol::stack::check<sol::table>(L, index, handler, tracking);
+}
+QStringList sol_lua_get(sol::types<QStringList>,
+ lua_State *L,
+ int index,
+ sol::stack::record &tracking)
+{
+ QStringList result;
+ sol::state_view lua(L);
+ sol::table table = sol::stack::get<sol::table>(L, index, tracking);
+ for (size_t i = 1; i < table.size() + 1; i++) {
+ result.append(table.get<QString>(i));
+ }
+ return result;
+}
+int sol_lua_push(sol::types<QStringList>, lua_State *L, const QStringList &value)
+{
+ sol::state_view lua(L);
+ sol::table table = lua.create_table();
+ for (const QString &str : value)
+ table.add(str);
+ return sol::stack::push(L, table);
+}
+
+namespace Lua::Internal {
+void addQtModule()
+{
+ LuaEngine::registerProvider("Qt", [](sol::state_view lua) {
+ sol::table t(lua, sol::create);
+
+ // clang-format off
+ lua["TextElideMode"] = lua.create_table_with(
+ "ElideLeft", Qt::ElideLeft,
+ "ElideRight", Qt::ElideRight,
+ "ElideMiddle", Qt::ElideMiddle,
+ "ElideNone", Qt::ElideNone
+ );
+
+ lua["QDirIterator"] = lua.create_table_with(
+ "IteratorFlag", lua.create_table_with(
+ "NoIteratorFlags", QDirIterator::NoIteratorFlags,
+ "FollowSymlinks", QDirIterator::FollowSymlinks,
+ "Subdirectories", QDirIterator::Subdirectories
+ )
+ );
+
+ lua["QDir"] = lua.create_table_with(
+ // QDir::Filters
+ "Filters", lua.create_table_with(
+ "Dirs", QDir::Dirs,
+ "Files", QDir::Files,
+ "Drives", QDir::Drives,
+ "NoSymLinks", QDir::NoSymLinks,
+ "AllEntries", QDir::AllEntries,
+ "TypeMask", QDir::TypeMask,
+ "Readable", QDir::Readable,
+ "Writable", QDir::Writable,
+ "Executable", QDir::Executable,
+ "PermissionMask", QDir::PermissionMask,
+ "Modified", QDir::Modified,
+ "Hidden", QDir::Hidden,
+ "System", QDir::System,
+ "AccessMask", QDir::AccessMask,
+ "AllDirs", QDir::AllDirs,
+ "CaseSensitive", QDir::CaseSensitive,
+ "NoDot", QDir::NoDot,
+ "NoDotDot", QDir::NoDotDot,
+ "NoDotAndDotDot", QDir::NoDotAndDotDot,
+ "NoFilter", QDir::NoFilter
+ ),
+
+ // QDir::SortFlag
+ "SortFlags", lua.create_table_with(
+ "Name", QDir::Name,
+ "Time", QDir::Time,
+ "Size", QDir::Size,
+ "Unsorted", QDir::Unsorted,
+ "SortByMask", QDir::SortByMask,
+ "DirsFirst", QDir::DirsFirst,
+ "Reversed", QDir::Reversed,
+ "IgnoreCase", QDir::IgnoreCase,
+ "DirsLast", QDir::DirsLast,
+ "LocaleAware", QDir::LocaleAware,
+ "Type", QDir::Type,
+ "NoSort", QDir::NoSort
+ )
+ );
+ // clang-format on
+
+ return t;
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/luaqttypes.h b/src/plugins/lua/luaqttypes.h
new file mode 100644
index 00000000000..9df10e1a029
--- /dev/null
+++ b/src/plugins/lua/luaqttypes.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+// DO NOT INCLUDE THIS YOURSELF!!1!
+
+#include "lua_global.h"
+
+#include <QColor>
+#include <QList>
+#include <QRect>
+#include <QString>
+
+#define SOL_CONVERSION_FUNCTIONS(TYPE) \
+ bool LUA_EXPORT sol_lua_check(sol::types<TYPE>, \
+ lua_State *L, \
+ int index, \
+ std::function<sol::check_handler_type> handler, \
+ sol::stack::record &tracking); \
+ TYPE LUA_EXPORT sol_lua_get(sol::types<TYPE>, \
+ lua_State *L, \
+ int index, \
+ sol::stack::record &tracking); \
+ int LUA_EXPORT sol_lua_push(sol::types<TYPE>, lua_State *L, const TYPE &rect);
+
+SOL_CONVERSION_FUNCTIONS(QString)
+
+SOL_CONVERSION_FUNCTIONS(QRect)
+SOL_CONVERSION_FUNCTIONS(QSize)
+SOL_CONVERSION_FUNCTIONS(QPoint)
+SOL_CONVERSION_FUNCTIONS(QRectF)
+SOL_CONVERSION_FUNCTIONS(QSizeF)
+SOL_CONVERSION_FUNCTIONS(QPointF)
+SOL_CONVERSION_FUNCTIONS(QColor)
+
+SOL_CONVERSION_FUNCTIONS(QStringList)
+
+#undef SOL_CONVERSION_FUNCTIONS
diff --git a/src/plugins/lua/luatr.h b/src/plugins/lua/luatr.h
new file mode 100644
index 00000000000..0c5fb4acad8
--- /dev/null
+++ b/src/plugins/lua/luatr.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QCoreApplication>
+
+namespace Lua {
+
+struct Tr
+{
+ Q_DECLARE_TR_FUNCTIONS(QtC::Lua)
+};
+
+} // namespace Lua
diff --git a/src/plugins/lua/luauibindings.cpp b/src/plugins/lua/luauibindings.cpp
new file mode 100644
index 00000000000..30485f6d9c5
--- /dev/null
+++ b/src/plugins/lua/luauibindings.cpp
@@ -0,0 +1,141 @@
+
+// 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 "luaqttypes.h"
+
+#include <QMetaProperty>
+
+#include <QDialog>
+
+namespace Lua::Internal {
+
+template<class T = QObject>
+sol::object qobject_index_get(sol::this_state s, QObject *obj, const char *key)
+{
+ auto &metaObject = T::staticMetaObject;
+ int iProp = metaObject.indexOfProperty(key);
+ if (iProp != -1) {
+ QMetaProperty p = metaObject.property(iProp);
+
+ if (p.isEnumType()) {
+ int v = p.read(obj).toInt();
+ return sol::make_object(s.lua_state(), p.enumerator().valueToKey(v));
+ }
+
+#define LUA_VALUE_FROM_PROPERTY(VARIANT_TYPE, TYPE) \
+ case VARIANT_TYPE: { \
+ TYPE r = qvariant_cast<TYPE>(p.read(obj)); \
+ return sol::make_object(s.lua_state(), r); \
+ }
+
+ switch (p.type()) {
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::Rect, QRect)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::Size, QSize)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::Point, QPoint)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::RectF, QRectF)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::SizeF, QSizeF)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::PointF, QPointF)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::Color, QColor)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::Bool, bool)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::Int, int)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::Double, double)
+ LUA_VALUE_FROM_PROPERTY(QVariant::Type::String, QString)
+ default:
+ break;
+ }
+ }
+
+ for (int i = 0; i < metaObject.methodCount(); i++) {
+ QMetaMethod method = metaObject.method(i);
+ if (method.methodType() != QMetaMethod::Signal && method.name() == key) {
+ if (method.parameterCount() == 0) {
+ return sol::make_object(s.lua_state(),
+ [obj, i]() { obj->metaObject()->method(i).invoke(obj); });
+ }
+ }
+ }
+
+ return sol::lua_nil;
+}
+
+template<class T>
+void qobject_index_set(QObject *obj, const char *key, sol::stack_object value)
+{
+ auto &metaObject = T::staticMetaObject;
+ int iProp = metaObject.indexOfProperty(key);
+ if (iProp == -1)
+ return;
+
+ QMetaProperty p = metaObject.property(iProp);
+
+ if (p.isEnumType()) {
+ int v = p.enumerator().keyToValue(value.as<const char *>());
+ p.write(obj, v);
+ } else {
+#define SET_PROPERTY_FROM_LUA(VARIANT_TYPE, TYPE) \
+ case VARIANT_TYPE: { \
+ TYPE r = value.as<TYPE>(); \
+ p.write(obj, QVariant::fromValue(r)); \
+ break; \
+ }
+
+ switch (p.type()) {
+ SET_PROPERTY_FROM_LUA(QVariant::Type::Rect, QRect)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::Size, QSize)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::Point, QPoint)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::RectF, QRectF)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::SizeF, QSizeF)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::PointF, QPointF)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::Color, QColor)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::Bool, bool)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::Int, int)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::Double, double)
+ SET_PROPERTY_FROM_LUA(QVariant::Type::String, QString)
+ default:
+ break;
+ }
+ }
+}
+
+template<class T>
+size_t qobject_index_size(QObject *obj)
+{
+ return 0;
+}
+
+template<class T, class... Bases>
+sol::usertype<T> runtimeObject(sol::state_view lua)
+{
+ auto &metaObject = T::staticMetaObject;
+ auto className = metaObject.className();
+
+ return lua.new_usertype<T>(
+ className,
+ sol::call_constructor,
+ sol::constructors<T>(),
+ sol::meta_function::index,
+ [](sol::this_state s, T *obj, const char *key) { return qobject_index_get<T>(s, obj, key); },
+ sol::meta_function::new_index,
+ [](T *obj, const char *key, sol::stack_object value) {
+ qobject_index_set<T>(obj, key, value);
+ },
+ sol::meta_function::length,
+ [](T *obj) { return qobject_index_size<T>(obj); },
+ sol::base_classes,
+ sol::bases<Bases...>());
+}
+
+void registerUiBindings()
+{
+ LuaEngine::registerProvider("Qt.Gui", [](sol::state_view lua) {
+ runtimeObject<QObject>(lua);
+ runtimeObject<QWidget, QObject>(lua);
+ runtimeObject<QDialog, QWidget, QObject>(lua);
+
+ return sol::object{};
+ });
+}
+
+} // namespace Lua::Internal
diff --git a/src/plugins/lua/meta/action.lua b/src/plugins/lua/meta/action.lua
new file mode 100644
index 00000000000..713dbcddb3a
--- /dev/null
+++ b/src/plugins/lua/meta/action.lua
@@ -0,0 +1,34 @@
+---@meta Action
+
+local action = {}
+
+---@enum CommandAttributes
+action.CommandAttribute = {
+ ---Hide the command from the menu
+ CA_Hide = 1,
+ ---Update the text of the command
+ CA_UpdateText = 2,
+ ---Update the icon of the command
+ CA_UpdateIcon = 4,
+ ---The command cannot be configured
+ CA_NonConfigurable = 8,
+}
+
+---@class ActionOptions
+---@field context? string The context in which the action is available
+---@field text? string The text to display for the action
+---@field iconText? string The icon text to display for the action
+---@field toolTip? string The tooltip to display for the action
+---@field onTrigger? function The callback to call when the action is triggered
+---@field commandAttributes? CommandAttributes The attributes of the action
+---@field commandDescription? string The description of the command
+---@field defaultKeySequence? string The default key sequence for the action
+---@field defaultKeySequences? string[] The default key sequences for the action
+local ActionOptions = {}
+
+---Creates a new Action
+---@param id string The id of the action
+---@param options ActionOptions
+function action.create(id, options) end
+
+return action
diff --git a/src/plugins/lua/meta/async.lua b/src/plugins/lua/meta/async.lua
new file mode 100644
index 00000000000..8f7232a001a
--- /dev/null
+++ b/src/plugins/lua/meta/async.lua
@@ -0,0 +1,62 @@
+---@meta async
+
+local async = {}
+
+
+---Wraps the provided function so it can be started in another thread.
+---
+--- Example:
+--- ```lua
+--- local a = require("async")
+--- local u = require("Utils")
+---
+--- function asyncFunction()
+--- a.wait(u.waitms(500))
+--- end
+---
+--- a.sync(asyncFunction)()
+--- ```
+---@param func function The function to call from the new thread.
+function async.sync(func) end
+
+---@async
+---Calls an async function and waits for it to finish. **Must** be called from async.sync()
+---
+--- Example:
+--- ```lua
+--- local a = require("async")
+--- local u = require("Utils")
+---
+--- function asyncFunction()
+--- a.wait(u.waitms(500))
+--- a.wait(u.waitms(1000))
+--- end
+---
+--- a.sync(asyncFunction)()
+--- ```
+---@param func any The function to call and wait for its result.
+---@return any any The result of the function.
+function async.wait(func) end
+
+---@async
+---Calls multiple async functions and waits for all of them to finish. **Must** be called from async.sync()
+---
+--- Example:
+--- ```lua
+--- local a = require("async")
+--- local u = require("Utils")
+---
+--- function asyncFunction()
+--- a.wait_all {
+--- u.waitms(500),
+--- u.waitms(1000),
+--- }
+--- end
+---
+--- a.sync(asyncFunction)()
+--- ```
+---@param funcs table The functions to call and wait for.
+---@return table table The result of each of the functions as an array.
+function async.wait_all(funcs) end
+
+return async
diff --git a/src/plugins/lua/meta/core.lua b/src/plugins/lua/meta/core.lua
new file mode 100644
index 00000000000..e30e55e73db
--- /dev/null
+++ b/src/plugins/lua/meta/core.lua
@@ -0,0 +1,26 @@
+---@meta Core
+
+Core = {}
+
+---@enum Attribute
+Core.GeneratedFile.Attribute = {
+ OpenEditorAttribute = 0,
+ OpenProjectAttribute = 0,
+ CustomGeneratorAttribute = 0,
+ KeepExistingFileAttribute = 0,
+ ForceOverwrite = 0,
+ TemporaryFile = 0,
+}
+
+---@class GeneratedFile
+---@field filePath FilePath
+---@field contents string
+---@field isBinary boolean
+---@field attributes Attribute A combination of Attribute
+Core.GeneratedFile = {}
+
+---Create a new GeneratedFile
+---@return GeneratedFile
+function Core.GeneratedFile.new() end
+
+return Core;
diff --git a/src/plugins/lua/meta/fetch.lua b/src/plugins/lua/meta/fetch.lua
new file mode 100644
index 00000000000..877f6c99f66
--- /dev/null
+++ b/src/plugins/lua/meta/fetch.lua
@@ -0,0 +1,30 @@
+---@meta Fetch
+local Fetch = {}
+
+---A network reply from fetch
+---@class QNetworkReply
+---@field error integer The error code of the reply or 0 if no error
+local QNetworkReply = {}
+
+---Returns the data of the reply
+---@return string
+function QNetworkReply:readAll() end
+
+---Fetches a url. Call `a.wait` on the returned value to get the result.
+---@param options FetchOptions
+---@return table|QNetworkReply|string
+function Fetch.fetch(options) end
+
+--@param options FetchOptions
+--@param callback function The callback to call when the fetch is done
+function Fetch.fetch_cb(options, callback) end
+
+---@class FetchOptions
+---@field url string The url to fetch
+---@field method? string The method to use (GET, POST, ...), default is GET
+---@field headers? table The headers to send
+---@field body? string The body to send
+---@field convertToTable? boolean If true, the resulting data will expect JSON and converted it to a table
+local FetchOptions = {}
+
+return Fetch
diff --git a/src/plugins/lua/meta/layout.lua b/src/plugins/lua/meta/layout.lua
new file mode 100644
index 00000000000..272171beffb
--- /dev/null
+++ b/src/plugins/lua/meta/layout.lua
@@ -0,0 +1,186 @@
+---@meta Layout
+
+local layout = {}
+
+---The base class of all layout items
+---@class LayoutItem
+layout.LayoutItem = {
+ ---Attaches the layout to the specified widget
+ ---@param widget QWidget
+ attachTo = function(widget) end
+}
+
+---Column layout
+---@class Column : LayoutItem
+local column = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Column
+function layout.Column(children) end
+
+---A group box with a title
+---@class Group : LayoutItem
+local group = {}
+
+---@return Group
+function layout.Group(children) end
+
+---Row layout
+---@class Row : LayoutItem
+local row = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Row
+function layout.Row(children) end
+
+---Flow layout
+---@class Flow : LayoutItem
+local flow = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Flow
+function layout.Flow(children) end
+
+---Grid layout
+---@class Grid : LayoutItem
+local grid = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Grid
+function layout.Grid(children) end
+
+---Form layout
+---@class Form : LayoutItem
+local form = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Form
+function layout.Form(children) end
+
+---An empty widget
+---@class Widget : LayoutItem
+local widget = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Widget
+function layout.Widget(children) end
+
+---A stack of multiple widgets
+---@class Stack : LayoutItem
+local stack = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Stack
+function layout.Stack(children) end
+
+---A Tab widget
+---@class Tab : LayoutItem
+local tab = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Tab
+function layout.Tab(children) end
+
+---A Multiline text edit
+---@class TextEdit : LayoutItem
+local textEdit = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return TextEdit
+function layout.TextEdit(children) end
+
+---A PushButton
+---@class PushButton : LayoutItem
+local pushButton = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return PushButton
+function layout.PushButton(children) end
+
+---A SpinBox
+---@class SpinBox : LayoutItem
+local spinBox = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return SpinBox
+function layout.SpinBox(children) end
+
+---A Splitter
+---@class Splitter : LayoutItem
+local splitter = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return Splitter
+function layout.Splitter(children) end
+
+---A Toolbar
+---@class ToolBar : LayoutItem
+local toolBar = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return ToolBar
+function layout.ToolBar(children) end
+
+---A TabWidget
+---@class TabWidget : LayoutItem
+local tabWidget = {}
+
+---@param children LayoutItem|string|BaseAspect|function
+---@return TabWidget
+function layout.TabWidget(children) end
+
+---A "Line break" in the layout
+function layout.br() end
+
+---A "Stretch" in the layout
+function layout.st() end
+
+---An empty space in the layout
+function layout.empty() end
+
+---A horizontal line in the layout
+function layout.hr() end
+
+---Clears the margin of the layout
+function layout.noMargin() end
+
+---Sets the margin of the layout to the default value
+function layout.normalMargin() end
+
+---Sets the margin of the layout to a custom value
+function layout.customMargin(left, top, right, bottom) end
+
+---Sets the alignment of the layout to "Form"
+function layout.withFormAlignment() end
+
+---Sets the title of the parent object if possible
+function layout.title(text) end
+
+---Sets the text of the parent object if possible
+function layout.text(text) end
+
+---Sets the tooltip of the parent object if possible
+function layout.tooltip(text) end
+
+---Sets the size of the parent object if possible
+function layout.resize(width, height) end
+
+---Sets the stretch of the column at `index`
+function layout.columnStretch(index, stretch) end
+
+---Sets the spacing of the layout
+function layout.spacing(spacing) end
+
+---Sets the window title of the parent object if possible
+function layout.windowTitle(text) end
+
+---Sets the field growth policy of the layout
+function layout.fieldGrowthPolicy(policy) end
+
+---Sets the onClicked handler of the parent object if possible
+function layout.onClicked(f) end
+
+---Sets the onTextChanged handler of the parent object if possible
+function layout.onTextChanged(f) end
+
+return layout
diff --git a/src/plugins/lua/meta/lsp.lua b/src/plugins/lua/meta/lsp.lua
new file mode 100644
index 00000000000..0ea0a4ebc54
--- /dev/null
+++ b/src/plugins/lua/meta/lsp.lua
@@ -0,0 +1,34 @@
+---@meta LSP
+
+local lsp = {}
+
+---@class ClientOptions
+---@field name string The name under which to register the language server.
+---@field cmd string[] The command to start the language server
+---@field transport? "stdio"|"localsocket" Defaults to stdio
+---@field languageFilter LanguageFilter The language filter deciding which files to open with the language server
+---@field startBehavior? "AlwaysOn"|"RequiresFile"|"RequiresProject"
+---@field initializationOptions? table|string The initialization options to pass to the language server, either a json string, or a table
+---@field settings? AspectContainer
+local ClientOptions = {}
+
+---@class LanguageFilter
+---@field patterns? string[] The file patterns supported by the language server
+---@field mimeTypes? string[] The mime types supported by the language server
+local LanguageFilter = {}
+
+---@class Client
+---@field on_instance_start function The callback to call when a language client starts
+lsp.Client = {}
+
+---@param msg string The name of the message to handle
+---@param callback function The callback to call when the message is received
+---Registers a message handler for the message named 'msg'
+function lsp.Client:registerMessage(msg, callback) end
+
+---Creates a new Language Client
+---@param options ClientOptions
+---@return Client
+function lsp.Client.create(options) end
+
+return lsp
diff --git a/src/plugins/lua/meta/messagemanager.lua b/src/plugins/lua/meta/messagemanager.lua
new file mode 100644
index 00000000000..61419ee4b71
--- /dev/null
+++ b/src/plugins/lua/meta/messagemanager.lua
@@ -0,0 +1,17 @@
+---@meta MessageManager
+
+local messagemanager = {}
+
+---Writes a message to the Output pane
+---@param ... any
+function messagemanager.writeSilently(...) end
+
+---Writes a message to the Output pane and flashes the pane if its not open
+---@param ... any
+function messagemanager.writeFlashing(...) end
+
+---Writes a message to the Output pane and opens the pane if its not open
+---@param ... any
+function messagemanager.writeDisrupting(...) end
+
+return messagemanager
diff --git a/src/plugins/lua/meta/process.lua b/src/plugins/lua/meta/process.lua
new file mode 100644
index 00000000000..05199907706
--- /dev/null
+++ b/src/plugins/lua/meta/process.lua
@@ -0,0 +1,11 @@
+---@meta Process
+
+local process = {}
+
+---@async
+---Runs a command in a terminal, has to be called from a coroutine!
+---@param cmd string The command to run
+---@return number The exit code of the command
+function process.runInTerminal(cmd) end
+
+return process
diff --git a/src/plugins/lua/meta/qt.lua b/src/plugins/lua/meta/qt.lua
new file mode 100644
index 00000000000..7d446bce628
--- /dev/null
+++ b/src/plugins/lua/meta/qt.lua
@@ -0,0 +1,66 @@
+---@meta Qt
+
+--- The values in enums here do not matter, as they are defined by the C++ code.
+
+local qt = {}
+
+---@enum TextElideMode
+qt.TextElideMode = {
+ ElideLeft = 0,
+ ElideRight = 0,
+ ElideMiddle = 0,
+ ElideNone = 0,
+}
+
+qt.QDir = {
+ ---@enum Filters
+ Filters = {
+ Dirs = 0,
+ Files = 0,
+ Drives = 0,
+ NoSymLinks = 0,
+ AllEntries = 0,
+ TypeMask = 0,
+ Readable = 0,
+ Writable = 0,
+ Executable = 0,
+ PermissionMask = 0,
+ Modified = 0,
+ Hidden = 0,
+ System = 0,
+ AccessMask = 0,
+ AllDirs = 0,
+ CaseSensitive = 0,
+ NoDot = 0,
+ NoDotDot = 0,
+ NoDotAndDotDot = 0,
+ NoFilter = 0,
+ },
+
+ ---@enum SortFlags
+ SortFlags = {
+ Name = 0,
+ Time = 0,
+ Size = 0,
+ Unsorted = 0,
+ SortByMask = 0,
+ DirsFirst = 0,
+ Reversed = 0,
+ IgnoreCase = 0,
+ DirsLast = 0,
+ LocaleAware = 0,
+ Type = 0,
+ NoSort = 0,
+ }
+}
+
+qt.QDirIterator = {
+ ---@enum IteratorFlag
+ IteratorFlag = {
+ NoIteratorFlags = 0,
+ FollowSymlinks = 0,
+ Subdirectories = 0,
+ }
+}
+
+return qt
diff --git a/src/plugins/lua/meta/qtc.lua b/src/plugins/lua/meta/qtc.lua
new file mode 100644
index 00000000000..8d9d858d501
--- /dev/null
+++ b/src/plugins/lua/meta/qtc.lua
@@ -0,0 +1,40 @@
+---@meta
+
+---The global qtc object defined in the Lua plugin.
+---@class qtc
+Qtc = {}
+
+---@class (exact) QtcPlugin
+---@field Name string The name of the plugin.
+---@field Version string The version of the plugin. (`major.minor.patch`)
+---@field CompatVersion string The lowest previous version of the plugin that this one is compatible to. (`major.minor.patch`)
+---@field Vendor string The vendor of the plugin.
+---@field Category string The category of the plugin.
+---@field Dependencies? QtcPluginDependency[] The dependencies of the plugin.
+---@field Description? string A short one line description of the plugin.
+---@field LongDescription? string A long description of the plugin. Can contain newlines.
+---@field Url? string The url of the plugin.
+---@field License? string The license text of the plugin.
+---@field Revision? string The revision of the plugin.
+---@field Copyright? string The copyright of the plugin.
+---@field Experimental? boolean Whether the plugin is experimental or not. ( Default: true )
+---@field DisabledByDefault? boolean Whether the plugin is disabled by default or not. ( Default: true )
+---@field setup function The setup function of the plugin.
+---@field hooks? Hooks The hooks of the plugin.
+QtcPlugin = {}
+
+---@class QtcPluginDependency
+---@field Name string The name of the dependency.
+---@field Version string The version of the dependency. (`major.minor.patch`)
+---@field Required boolean Whether the dependency is required or not.
+QtcPluginDependency = {}
+
+
+---@class EditorHooks
+---@field documentOpened function function(document)
+---@field documentClosed function function(document)
+EditorHooks = {}
+
+---@class Hooks
+---@field editors? EditorHooks
+Hooks = {}
diff --git a/src/plugins/lua/meta/settings.lua b/src/plugins/lua/meta/settings.lua
new file mode 100644
index 00000000000..cd06bdc9032
--- /dev/null
+++ b/src/plugins/lua/meta/settings.lua
@@ -0,0 +1,182 @@
+---@meta Settings
+
+---@module 'Qt'
+
+local settings = {}
+
+---The base class of all aspects
+---@class BaseAspect
+settings.BaseAspect = {}
+
+---Applies the changes from its volatileValue to its value
+function settings.BaseAspect:apply() end
+
+---@class AspectCreate
+---@field settingsKey? string The settings key of the aspect
+---@field displayName? string The display name of the aspect
+---@field labelText? string The label text of the aspect
+---@field toolTip? string The tool tip of the aspect
+---@field enabler? BoolAspect Enable / Disable this aspect based on the state of the `enabler`
+---@field onValueChanged? function () Called when the value of the aspect changes
+---@field onVolatileValueChanged? function () Called when the volatile value of the aspect changes
+local AspectCreate = {}
+
+---The base class of most typed aspects
+---@generic T
+---@class TypedAspect<T> : BaseAspect
+---@field value `T` The value of the aspect
+---@field volatileValue `T` The temporary value of the aspect
+---@field defaultValue `T` The default value of the aspect
+local TypedAspect = {}
+
+---@generic T
+---@class TypedAspectCreate<T> : AspectCreate
+---@field defaultValue `T` The default value of the aspect
+local TypedAspectCreate = {}
+
+---A container for aspects
+---@class AspectContainer : BaseAspect
+settings.AspectContainer = {}
+
+---Options for creating an AspectContainer
+---@class AspectContainerCreate
+---@field autoApply? boolean Whether the aspects should be applied automatically or not
+AspectContainerCreate = {}
+
+
+---Create a new AspectContainer
+---@param options AspectContainerCreate
+---@return AspectContainer
+function settings.AspectContainer.create(options) end
+
+---A aspect containing a boolean value
+---@class BoolAspect : TypedAspect<boolean>
+settings.BoolAspect = {}
+
+---@enum LabelPlacement
+settings.LabelPlacement = {
+ AtCheckBox = 0,
+ Compact = 0,
+ InExtraLabel = 0
+};
+---@class BoolAspectCreate : TypedAspectCreate<boolean>
+---@field labelPlacement? LabelPlacement:
+BoolAspectCreate = {}
+
+---Create a new BoolAspect
+---@param options BoolAspectCreate
+---@return BoolAspect
+function settings.BoolAspect.create(options) end
+
+settings.ColorAspect = {}
+function settings.ColorAspect.create(options) end
+
+settings.SelectionAspect = {}
+function settings.SelectionAspect.create(options) end
+
+settings.MultiSelectionAspect = {}
+function settings.MultiSelectionAspect.create(options) end
+
+---@enum StringDisplayStyle
+settings.StringDisplayStyle = {
+ Label = 0,
+ LineEdit = 0,
+ TextEdit = 0,
+ PasswordLineEdit = 0,
+};
+
+---@class StringAspectCreate : TypedAspectCreate<string>
+---@field displayStyle? StringDisplayStyle The display type of the aspect
+---@field historyId? string The history id of the aspect
+---@field valueAcceptor? function string (oldvalue: string, newValue: string)
+---@field showToolTipOnLabel? boolean
+---@field displayFilter? function string (value: string)
+---@field placeHolderText? string
+---@field acceptRichText? boolean
+---@field autoApplyOnEditingFinished? boolean
+---@field elideMode? Qt.TextElideMode The elide mode of the aspect
+StringAspectCreate = {}
+
+---@class StringAspect : TypedAspect<string>
+settings.StringAspect = {}
+
+---Create a new StringAspect
+---@param options StringAspectCreate
+function settings.StringAspect.create(options) end
+
+---@enum Kind
+settings.Kind = {
+ ExistingDirectory = 0,
+ Directory = 0,
+ File = 0,
+ SaveFile = 0,
+ ExistingCommand = 0,
+ Command = 0,
+ Any = 0
+};
+
+---@class FilePathAspectCreate
+---@field expectedKind? Kind The kind of path we want to select
+---@field historyId? string The history id of the aspect
+---@field defaultPath? FilePath The default path of the aspect
+---@field promptDialogFilter? string
+---@field promptDialogTitle? string
+---@field commandVersionArguments? string[]
+---@field allowPathFromDevice? boolean
+---@field validatePlaceHolder? boolean
+---@field openTerminalHandler? function
+---@field environment? Environment
+---@field baseFileName? FilePath
+---@field valueAcceptor? function string (oldvalue: string, newValue: string)
+---@field showToolTipOnLabel? boolean
+---@field autoApplyOnEditingFinished? boolean
+---@field validationFunction? function
+---@field displayFilter? function string (value: string)
+---@field placeHolderText? string
+FilePathAspectCreate = {}
+
+---@class FilePathAspect
+---@field expandedValue FilePath The expanded value of the aspect
+settings.FilePathAspect = {}
+
+---Create a new FilePathAspect
+---@param options FilePathAspectCreate : TypedAspectCreate<string>
+---@return FilePathAspect
+function settings.FilePathAspect.create(options) end
+
+settings.IntegerAspect = {}
+function settings.IntegerAspect.create(options) end
+
+settings.DoubleAspect = {}
+function settings.DoubleAspect.create(options) end
+
+settings.StringListAspect = {}
+function settings.StringListAspect.create(options) end
+
+settings.FilePathListAspect = {}
+function settings.FilePathListAspect.create(options) end
+
+settings.IntegersAspect = {}
+function settings.IntegersAspect.create(options) end
+
+settings.StringSelectionAspect = {}
+function settings.StringSelectionAspect.create(options) end
+
+---@class OptionsPage
+settings.OptionsPage = {}
+
+---@class OptionsPageCreate
+---@field id string
+---@field displayName string
+---@field categoryId string
+---@field displayCategory string
+---@field categoryIconPath string
+---@field aspectContainer AspectContainer
+OptionsPageCreate = {}
+
+---Creates a new OptionsPage
+---@param options OptionsPageCreate
+---@return OptionsPage
+function settings.OptionsPage.create(options) end
+
+return settings
diff --git a/src/plugins/lua/meta/simpletypes.lua b/src/plugins/lua/meta/simpletypes.lua
new file mode 100644
index 00000000000..fdb65cf0984
--- /dev/null
+++ b/src/plugins/lua/meta/simpletypes.lua
@@ -0,0 +1,36 @@
+---@meta
+
+---@class QRect
+---@field x integer The x position of the rectangle
+---@field y integer The y position of the rectangle
+---@field width integer The width of the rectangle
+---@field height integer The height of the rectangle
+QRect = {}
+
+---@class QSize
+---@field width integer The width of the size
+---@field height integer The height of the size
+QSize = {}
+
+---@class QPoint
+---@field x integer The x position of the point
+---@field y integer The y position of the point
+QPoint = {}
+
+
+---@class QPointF
+---@field x number The x position of the floating point
+---@field y number The y position of the floating point
+QPointF = {}
+
+---@class QSizeF
+---@field width number The width of the floating point size
+---@field height number The height of the floating point size
+QSizeF = {}
+
+---@class QRectF
+---@field x number The x position of the floating point rectangle
+---@field y number The y position of the floating point rectangle
+---@field width number The width of the floating point rectangle
+---@field height number The height of the floating point rectangle
+QRectF = {}
diff --git a/src/plugins/lua/meta/utils.lua b/src/plugins/lua/meta/utils.lua
new file mode 100644
index 00000000000..e320259d505
--- /dev/null
+++ b/src/plugins/lua/meta/utils.lua
@@ -0,0 +1,62 @@
+---@meta Utils
+
+local utils = {}
+
+---Suspends the current coroutine for the given amount of milliseconds. Call `a.wait` on the returned value to get the result.
+---@param ms number The amount of milliseconds to wait
+function utils.waitms(ms) end
+
+---Calls the callback after the given amount of milliseconds
+---@param ms number The amount of milliseconds to wait
+---@param callback function The callback to call
+function utils.waitms_cb(ms, callback) end
+
+---@class FilePath
+---@field exists boolean True if the path exists
+utils.FilePath = {}
+
+---@param path string The path to convert
+---@return FilePath The converted path
+---Convert and clean a path, returning a FilePath object
+function utils.FilePath.fromUserInput(path) end
+
+---@return FilePath The new absolute path
+---Searches for the path inside the PATH environment variable
+function utils.FilePath:searchInPath() end
+
+---@class (exact) DirEntriesOptions
+---@field nameFilters? string[] The name filters to use (e.g. "*.lua"), defaults to all files
+---@field fileFilters? integer The filters to use (combination of QDir.Filters.*), defaults to QDir.Filters.NoFilter
+---@field flags? integer The iterator flags (combination of QDirIterator.Flags.*), defaults to QDirIterator.Flags.NoIteratorFlags
+
+---Returns all entries in the directory
+---@param options DirEntriesOptions
+---@return FilePath[]
+function utils.FilePath:dirEntries(options) end
+
+---Returns the FilePath as it should be displayed to the user
+---@return string
+function utils.FilePath:toUserOutput() end
+
+---Returns the path portion of FilePath as a string in the hosts native format
+---@return string
+function utils.FilePath:nativePath() end
+
+---Returns the last part of the path
+---@return string
+function utils.FilePath:fileName() end
+
+---Returns the current working path of Qt Creator
+---@return FilePath
+function utils.FilePath.currentWorkingPath() end
+
+---Returns a new FilePath with the given tail appended
+---@param tail string|FilePath The tail to append
+---@return FilePath
+function utils.FilePath:resolvePath(tail) end
+
+---Returns the parent directory of the path
+---@return FilePath
+function utils.FilePath:parentDir() end
+
+return utils
diff --git a/src/plugins/lua/meta/widgets.lua b/src/plugins/lua/meta/widgets.lua
new file mode 100644
index 00000000000..3f28bd49e9d
--- /dev/null
+++ b/src/plugins/lua/meta/widgets.lua
@@ -0,0 +1,111 @@
+---@meta Widgets
+
+local widgets = {}
+
+---A QWidget, see https://doc.qt.io/qt-6/QWidget.html
+---@class QWidget : QObject
+---@field acceptDrops boolean
+---@field accessibleDescription string
+---@field accessibleName string
+---@field autoFillBackground boolean
+---@field baseSize QSize
+---@field childrenRect QRect
+---@field childrenRegion QRegion
+---@field contextMenuPolicy QtContextMenuPolicy
+---@field cursor QCursor
+---@field enabled boolean
+---@field focus boolean
+---@field focusPolicy QtFocusPolicy
+---@field font QFont
+---@field frameGeometry QRect
+---@field frameSize QSize
+---@field fullScreen boolean
+---@field geometry QRect
+---@field height integer
+---@field inputMethodHints QtInputMethodHints
+---@field isActiveWindow boolean
+---@field layoutDirection QtLayoutDirection
+---@field locale QLocale
+---@field maximized boolean
+---@field maximumHeight integer
+---@field maximumSize QSize
+---@field maximumWidth integer
+---@field minimized boolean
+---@field minimumHeight integer
+---@field minimumSize QSize
+---@field minimumSizeHint QSize
+---@field minimumWidth integer
+---@field modal boolean
+---@field mouseTracking boolean
+---@field normalGeometry QRect
+---@field palette QPalette
+---@field pos QPoint
+---@field rect QRect
+---@field size QSize
+---@field sizeHint QSize
+---@field sizeIncrement QSize
+---@field sizePolicy QSizePolicy
+---@field statusTip string
+---@field styleSheet string
+---@field tabletTracking boolean
+---@field toolTip string
+---@field toolTipDuration integer
+---@field updatesEnabled boolean
+---@field visible boolean
+---@field whatsThis string
+---@field width integer
+---@field windowFilePath string
+---@field windowFlags QtWindowFlags
+---@field windowIcon QIcon
+---@field windowModality QtWindowModality
+---@field windowModified boolean
+---@field windowOpacity double
+---@field windowTitle string
+---@field x integer
+---@field y integer
+widgets.QWidget = {}
+
+---@return boolean
+function widgets.QWidget:close() end
+
+function widgets.QWidget:hide() end
+
+function widgets.QWidget:lower() end
+
+function widgets.QWidget:raise() end
+
+function widgets.QWidget:repaint() end
+
+function widgets.QWidget:setDisabled(disable) end
+
+function widgets.QWidget:setEnabled(enabled) end
+
+function widgets.QWidget:setFocus() end
+
+function widgets.QWidget:setHidden(hidden) end
+
+function widgets.QWidget:setStyleSheet(styleSheet) end
+
+function widgets.QWidget:setVisible(visible) end
+
+function widgets.QWidget:setWindowModified(modified) end
+
+function widgets.QWidget:setWindowTitle(title) end
+
+function widgets.QWidget:show() end
+
+function widgets.QWidget:showFullScreen() end
+
+function widgets.QWidget:showMaximized() end
+
+function widgets.QWidget:showMinimized() end
+
+function widgets.QWidget:showNormal() end
+
+function widgets.QWidget:update() end
+
+---A QDialog, see https://doc.qt.io/qt-6/qdialog.html
+---@class QDialog : QWidget
+widgets.QDialog = {}
+
+return widgets
diff --git a/src/plugins/lua/meta/wizard.lua b/src/plugins/lua/meta/wizard.lua
new file mode 100644
index 00000000000..735737e08c9
--- /dev/null
+++ b/src/plugins/lua/meta/wizard.lua
@@ -0,0 +1,64 @@
+---@meta Wizard
+
+---@module "Layout"
+local Layout = require("Layout")
+
+---@module "Core"
+local Core = require("Core")
+
+local wizard = {}
+
+---@class Factory
+
+---@class (exact) WizardFactoryOptions
+---@field id string
+---@field displayName string
+---@field description string
+---@field category string
+---@field displayCategory string
+---@field icon? string
+---@field iconText? string
+---@field factory function A function returning a Wizard
+
+--- Registers a wizard factory.
+---@param options WizardFactoryOptions
+---@return Factory
+function wizard.registerFactory(options) end
+
+---@class Wizard
+Wizard = {}
+
+---@class (exact) WizardPageOptions
+---@field title string
+---@field layout LayoutItem
+---@field initializePage? function The function called before showing the page
+
+---Add a page to the wizard
+---@param options WizardPageOptions
+function Wizard:addPage(options) end
+
+---@class SummaryPage
+Wizard.SummaryPage = {}
+
+---Set the files to be shown on the summary page
+---@param generatedFiles Core.GeneratedFile[]
+function Wizard.SummaryPage:setFiles(generatedFiles) end
+
+---@class SummaryPageOptions
+---@field title? string
+---@field initializePage? function The function called before showing the page
+
+---Add a summary page to the wizard
+---@param options SummaryPageOptions
+---@return SummaryPage
+function Wizard:addSummaryPage(options) end
+
+---@class WizardOptions
+---@field fileFactory function A function returning a GeneratedFile[]
+
+---Create a wizard
+---@param options WizardOptions
+---@return Wizard
+function wizard.create(options) end
+
+return wizard
diff --git a/src/plugins/lua/metabackup/qobject.lua b/src/plugins/lua/metabackup/qobject.lua
new file mode 100644
index 00000000000..4584fc6a9f2
--- /dev/null
+++ b/src/plugins/lua/metabackup/qobject.lua
@@ -0,0 +1,6 @@
+---@meta
+
+---A QObject, see https://doc.qt.io/qt-6/qobject.html
+---@class QObject
+---@field objectName string
+QObject = {}