2024-04-12 14:36:37 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
|
2024-05-02 14:33:21 +02:00
|
|
|
#include "luaengine.h"
|
2024-05-06 11:54:53 +02:00
|
|
|
#include "luapluginspec.h"
|
2024-06-13 14:50:37 +02:00
|
|
|
#include "luatr.h"
|
2024-04-12 14:36:37 +02:00
|
|
|
|
2024-05-16 12:56:07 +02:00
|
|
|
#include <coreplugin/icore.h>
|
2024-06-13 14:50:37 +02:00
|
|
|
#include <coreplugin/ioutputpane.h>
|
2024-05-16 12:56:07 +02:00
|
|
|
#include <coreplugin/jsexpander.h>
|
2024-05-06 11:54:53 +02:00
|
|
|
#include <coreplugin/messagemanager.h>
|
2024-04-12 14:36:37 +02:00
|
|
|
|
|
|
|
#include <extensionsystem/iplugin.h>
|
|
|
|
#include <extensionsystem/pluginmanager.h>
|
|
|
|
|
|
|
|
#include <utils/algorithm.h>
|
2024-06-13 14:50:37 +02:00
|
|
|
#include <utils/layoutbuilder.h>
|
|
|
|
#include <utils/qtcprocess.h>
|
|
|
|
#include <utils/theme/theme.h>
|
2024-04-12 14:36:37 +02:00
|
|
|
|
|
|
|
#include <QDebug>
|
2024-06-25 07:14:05 +02:00
|
|
|
#include <QKeyEvent>
|
2024-06-13 14:50:37 +02:00
|
|
|
#include <QLabel>
|
|
|
|
#include <QLineEdit>
|
|
|
|
#include <QListView>
|
|
|
|
#include <QStringListModel>
|
2024-05-06 11:54:53 +02:00
|
|
|
|
|
|
|
using namespace Core;
|
|
|
|
using namespace Utils;
|
|
|
|
using namespace ExtensionSystem;
|
2024-04-12 14:36:37 +02:00
|
|
|
|
|
|
|
namespace Lua::Internal {
|
|
|
|
|
|
|
|
void addAsyncModule();
|
|
|
|
void addFetchModule();
|
|
|
|
void addActionModule();
|
|
|
|
void addUtilsModule();
|
|
|
|
void addMessageManagerModule();
|
|
|
|
void addProcessModule();
|
|
|
|
void addSettingsModule();
|
2024-05-30 16:35:45 +02:00
|
|
|
void addGuiModule();
|
2024-04-12 14:36:37 +02:00
|
|
|
void addQtModule();
|
|
|
|
void addCoreModule();
|
|
|
|
void addHookModule();
|
2024-05-14 13:47:50 +02:00
|
|
|
void addInstallModule();
|
2024-04-12 14:36:37 +02:00
|
|
|
|
2024-05-16 12:56:07 +02:00
|
|
|
class LuaJsExtension : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit LuaJsExtension(QObject *parent = nullptr)
|
|
|
|
: QObject(parent)
|
|
|
|
{}
|
|
|
|
|
|
|
|
Q_INVOKABLE QString metaFolder() const
|
|
|
|
{
|
|
|
|
return Core::ICore::resourcePath("lua/meta").toFSPathString();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-06-13 14:50:37 +02:00
|
|
|
class LuaTerminal : public QListView
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
std::unique_ptr<LuaState> m_luaState;
|
|
|
|
sol::function m_readCallback;
|
|
|
|
|
|
|
|
QStringListModel m_model;
|
|
|
|
|
|
|
|
public:
|
|
|
|
LuaTerminal(QWidget *parent = nullptr)
|
|
|
|
: QListView(parent)
|
|
|
|
{
|
|
|
|
setModel(&m_model);
|
|
|
|
}
|
|
|
|
|
|
|
|
void showEvent(QShowEvent *) override
|
|
|
|
{
|
|
|
|
if (m_luaState) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
resetTerminal();
|
|
|
|
}
|
|
|
|
|
|
|
|
void keyPressEvent(QKeyEvent *e) override { emit keyPressed(e); }
|
|
|
|
|
|
|
|
void handleRequestResult(const QString &result)
|
|
|
|
{
|
|
|
|
auto cb = m_readCallback;
|
|
|
|
m_readCallback = {};
|
|
|
|
cb(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
void resetTerminal()
|
|
|
|
{
|
|
|
|
m_model.setStringList({});
|
|
|
|
m_readCallback = {};
|
|
|
|
|
|
|
|
QFile f(":/lua/scripts/ilua.lua");
|
|
|
|
f.open(QIODevice::ReadOnly);
|
|
|
|
const auto ilua = QString::fromUtf8(f.readAll());
|
|
|
|
m_luaState = LuaEngine::instance().runScript(ilua, "ilua.lua", [this](sol::state &lua) {
|
|
|
|
lua["print"] = [this](sol::variadic_args va) {
|
|
|
|
const QStringList msgs = LuaEngine::variadicToStringList(va)
|
|
|
|
.join("\t")
|
|
|
|
.replace("\r\n", "\n")
|
|
|
|
.split('\n');
|
|
|
|
m_model.setStringList(m_model.stringList() + msgs);
|
|
|
|
scrollToBottom();
|
|
|
|
};
|
|
|
|
|
|
|
|
sol::table async = lua.script("return require('async')", "_ilua_").get<sol::table>();
|
|
|
|
sol::function wrap = async["wrap"];
|
|
|
|
|
|
|
|
lua["readline_cb"] = [this](const QString &prompt, sol::function callback) {
|
|
|
|
m_model.setStringList(m_model.stringList() << prompt);
|
|
|
|
scrollToBottom();
|
|
|
|
emit inputRequested(prompt);
|
|
|
|
m_readCallback = callback;
|
|
|
|
};
|
|
|
|
|
|
|
|
lua["readline"] = wrap(lua["readline_cb"]);
|
|
|
|
});
|
|
|
|
|
|
|
|
QListView::reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
signals:
|
|
|
|
void inputRequested(const QString &prompt);
|
|
|
|
void keyPressed(QKeyEvent *e);
|
|
|
|
};
|
|
|
|
|
|
|
|
class LineEdit : public FancyLineEdit
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using FancyLineEdit::FancyLineEdit;
|
|
|
|
void keyPressEvent(QKeyEvent *e) override { FancyLineEdit::keyPressEvent(e); }
|
|
|
|
};
|
|
|
|
|
|
|
|
class LuaPane : public Core::IOutputPane
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
protected:
|
|
|
|
QWidget *m_ui{nullptr};
|
|
|
|
LuaTerminal *m_terminal{nullptr};
|
|
|
|
|
|
|
|
public:
|
|
|
|
LuaPane(QObject *parent = nullptr)
|
|
|
|
: Core::IOutputPane(parent)
|
|
|
|
{
|
|
|
|
setId("LuaPane");
|
|
|
|
setDisplayName(Tr::tr("Lua"));
|
|
|
|
setPriorityInStatusBar(20);
|
|
|
|
}
|
|
|
|
|
|
|
|
QWidget *outputWidget(QWidget *parent) override
|
|
|
|
{
|
|
|
|
using namespace Layouting;
|
|
|
|
|
|
|
|
if (!m_ui && parent) {
|
|
|
|
m_terminal = new LuaTerminal;
|
|
|
|
LineEdit *inputEdit = new LineEdit;
|
|
|
|
QLabel *prompt = new QLabel;
|
|
|
|
|
|
|
|
// clang-format off
|
|
|
|
m_ui = Column {
|
|
|
|
m_terminal,
|
|
|
|
Row { prompt, inputEdit },
|
|
|
|
}.emerge();
|
|
|
|
// clang-format on
|
|
|
|
|
|
|
|
inputEdit->setReadOnly(true);
|
|
|
|
inputEdit->setHistoryCompleter(Utils::Key("LuaREPL.InputHistory"));
|
|
|
|
|
|
|
|
connect(inputEdit, &QLineEdit::returnPressed, this, [this, inputEdit] {
|
|
|
|
inputEdit->setReadOnly(true);
|
|
|
|
m_terminal->handleRequestResult(inputEdit->text());
|
|
|
|
inputEdit->onEditingFinished();
|
|
|
|
inputEdit->clear();
|
|
|
|
});
|
|
|
|
connect(
|
|
|
|
m_terminal,
|
|
|
|
&LuaTerminal::inputRequested,
|
|
|
|
this,
|
|
|
|
[prompt, inputEdit](const QString &p) {
|
|
|
|
prompt->setText(p);
|
|
|
|
inputEdit->setReadOnly(false);
|
|
|
|
});
|
|
|
|
connect(m_terminal, &LuaTerminal::keyPressed, this, [inputEdit](QKeyEvent *e) {
|
|
|
|
inputEdit->keyPressEvent(e);
|
|
|
|
inputEdit->setFocus();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_ui;
|
|
|
|
}
|
|
|
|
|
|
|
|
void visibilityChanged(bool) override {};
|
|
|
|
|
|
|
|
void clearContents() override
|
|
|
|
{
|
|
|
|
if (m_terminal)
|
|
|
|
m_terminal->resetTerminal();
|
|
|
|
}
|
|
|
|
void setFocus() override { outputWidget(nullptr)->setFocus(); }
|
|
|
|
bool hasFocus() const override { return true; }
|
|
|
|
bool canFocus() const override { return true; }
|
|
|
|
|
|
|
|
bool canNavigate() const override { return false; }
|
|
|
|
bool canNext() const override { return false; }
|
|
|
|
bool canPrevious() const override { return false; }
|
|
|
|
void goToNext() override {}
|
|
|
|
void goToPrev() override {}
|
|
|
|
|
|
|
|
QList<QWidget *> toolBarWidgets() const override { return {}; }
|
|
|
|
};
|
|
|
|
|
2024-05-06 11:54:53 +02:00
|
|
|
class LuaPlugin : public IPlugin
|
2024-04-12 14:36:37 +02:00
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Lua.json")
|
|
|
|
|
2024-05-02 14:33:21 +02:00
|
|
|
private:
|
|
|
|
std::unique_ptr<LuaEngine> m_luaEngine;
|
2024-06-13 14:50:37 +02:00
|
|
|
LuaPane *m_pane = nullptr;
|
2024-05-02 14:33:21 +02:00
|
|
|
|
2024-04-12 14:36:37 +02:00
|
|
|
public:
|
2024-05-06 11:54:53 +02:00
|
|
|
LuaPlugin() {}
|
2024-04-12 14:36:37 +02:00
|
|
|
~LuaPlugin() override = default;
|
|
|
|
|
2024-06-13 11:23:22 +02:00
|
|
|
void initialize() final
|
2024-04-12 14:36:37 +02:00
|
|
|
{
|
2024-05-06 11:54:53 +02:00
|
|
|
m_luaEngine.reset(new LuaEngine());
|
|
|
|
|
2024-04-12 14:36:37 +02:00
|
|
|
addAsyncModule();
|
|
|
|
addFetchModule();
|
|
|
|
addActionModule();
|
|
|
|
addUtilsModule();
|
|
|
|
addMessageManagerModule();
|
|
|
|
addProcessModule();
|
|
|
|
addSettingsModule();
|
2024-05-30 16:35:45 +02:00
|
|
|
addGuiModule();
|
2024-04-12 14:36:37 +02:00
|
|
|
addQtModule();
|
|
|
|
addCoreModule();
|
|
|
|
addHookModule();
|
2024-05-14 13:47:50 +02:00
|
|
|
addInstallModule();
|
2024-05-16 12:56:07 +02:00
|
|
|
|
|
|
|
Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); });
|
2024-06-13 14:50:37 +02:00
|
|
|
|
|
|
|
m_pane = new LuaPane;
|
|
|
|
ExtensionSystem::PluginManager::addObject(m_pane);
|
2024-04-12 14:36:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool delayedInitialize() final
|
|
|
|
{
|
2024-06-13 10:52:43 +02:00
|
|
|
scanForPlugins(PluginManager::pluginPaths());
|
2024-04-12 14:36:37 +02:00
|
|
|
return true;
|
|
|
|
}
|
2024-05-06 11:54:53 +02:00
|
|
|
|
2024-06-13 11:23:22 +02:00
|
|
|
void scanForPlugins(const FilePaths &pluginPaths)
|
2024-05-06 11:54:53 +02:00
|
|
|
{
|
|
|
|
QSet<PluginSpec *> plugins;
|
2024-06-13 11:23:22 +02:00
|
|
|
for (const FilePath &path : pluginPaths) {
|
2024-05-30 14:32:34 +02:00
|
|
|
FilePaths folders = path.dirEntries(FileFilter({}, QDir::Dirs | QDir::NoDotAndDotDot));
|
2024-05-06 11:54:53 +02:00
|
|
|
|
|
|
|
for (const FilePath &folder : folders) {
|
|
|
|
const FilePath script = folder / (folder.baseName() + ".lua");
|
2024-06-13 11:23:22 +02:00
|
|
|
if (!script.exists())
|
|
|
|
continue;
|
|
|
|
|
2024-05-06 11:54:53 +02:00
|
|
|
const expected_str<LuaPluginSpec *> result = m_luaEngine->loadPlugin(script);
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
qWarning() << "Failed to load plugin" << script << ":" << result.error();
|
|
|
|
MessageManager::writeFlashing(tr("Failed to load plugin %1: %2")
|
|
|
|
.arg(script.toUserOutput())
|
|
|
|
.arg(result.error()));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
plugins.insert(*result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PluginManager::addPlugins({plugins.begin(), plugins.end()});
|
|
|
|
PluginManager::loadPluginsAtRuntime(plugins);
|
|
|
|
}
|
2024-04-12 14:36:37 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace Lua::Internal
|
|
|
|
|
|
|
|
#include "luaplugin.moc"
|