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>
This commit is contained in:
Marcus Tillmanns
2024-04-12 14:36:37 +02:00
parent a296157f58
commit 6e3aab5f1b
49 changed files with 4301 additions and 1 deletions

9
.luarc.json Normal file
View File

@@ -0,0 +1,9 @@
{
"workspace.library": [
"src/plugins/lua/meta"
],
"hint.paramName": "Literal",
"hint.enable": true,
"hint.await": true,
"hint.arrayIndex": "Disable"
}

View File

@@ -598,5 +598,18 @@
"LicenseFile": "src/libs/3rdparty/sol2/LICENSE.txt",
"Copyright": "Copyright (c) 2008 Paul Evans",
"Version": "3.3.1 (revision 9c882a28)"
},
{
"Id": "ms-jpq-lua-async-await",
"Name": "ms-jpq/lua-async-await",
"QDocModule": "qtcreator",
"QtParts": ["tools"],
"QtUsage": "Used for async/await support in Lua modules.",
"Path": "src/plugins/lua/bindings",
"Description": "lua-async-await implements the async/await pattern in Lua.",
"Homepage": "https://github.com/ms-jpq/lua-async-await",
"License": "MIT License",
"LicenseFile": "src/plugins/lua/bindings/ASYNC-LICENSE.txt",
"Copyright": "Copyright (c) 2008 Paul Evans"
}
]

View File

@@ -23,6 +23,8 @@
#include <QJsonValue>
#include <QPluginLoader>
Q_LOGGING_CATEGORY(pluginSpecLog, "qtc.extensionsystem.plugin", QtWarningMsg)
using namespace ExtensionSystem::Internal;
using namespace Utils;
@@ -1120,6 +1122,8 @@ void PluginSpec::setFilePath(const QString &filePath)
void PluginSpec::setError(const QString &errorString)
{
qCWarning(pluginSpecLog).noquote() << "[" << name() << "]"
<< "Plugin error:" << errorString;
d->errorString = errorString;
}

View File

@@ -2,6 +2,7 @@
add_subdirectory(coreplugin)
# Level 1: (only depends of Level 0)
add_subdirectory(lua)
add_subdirectory(texteditor)
add_subdirectory(serialterminal)
add_subdirectory(extensionmanager)
@@ -117,4 +118,3 @@ endif()
add_subdirectory(qnx)
add_subdirectory(mcusupport)
add_subdirectory(qtapplicationmanager)

View File

@@ -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()

View File

@@ -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}
}

115
src/plugins/lua/README.md Normal file
View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

26
src/plugins/lua/lua.qbs Normal file
View File

@@ -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"
]
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

15
src/plugins/lua/luatr.h Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,6 @@
---@meta
---A QObject, see https://doc.qt.io/qt-6/qobject.html
---@class QObject
---@field objectName string
QObject = {}