From a331f0a8c8728cb9940b0fd49799484a4532321e Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 17 Aug 2023 08:50:33 +0200 Subject: [PATCH] CompilerExplorer: Implement saving documents Change-Id: Iccfd300a9f0fc25bafae9892c14f172874a99ce1 Reviewed-by: hjk Reviewed-by: --- src/plugins/compilerexplorer/api/compile.h | 6 +- .../compilerexploreraspects.h | 137 ++++++ .../compilerexplorereditor.cpp | 443 ++++++++++++++---- .../compilerexplorer/compilerexplorereditor.h | 78 ++- .../compilerexplorerplugin.cpp | 12 +- .../compilerexplorersettings.cpp | 210 ++++++--- .../compilerexplorersettings.h | 105 +++-- 7 files changed, 782 insertions(+), 209 deletions(-) diff --git a/src/plugins/compilerexplorer/api/compile.h b/src/plugins/compilerexplorer/api/compile.h index d5b93eaa469..618af39a246 100644 --- a/src/plugins/compilerexplorer/api/compile.h +++ b/src/plugins/compilerexplorer/api/compile.h @@ -133,11 +133,11 @@ struct CompileParameters return *this; } - Options &libraries(const CompilerExplorer::LibrarySelectionAspect &aspect) + Options &libraries(const QMap &libraries) { Libraries result; - for (const auto &key : aspect.value().keys()) { - result.add(key, aspect.value()[key]); + for (const auto &key : libraries.keys()) { + result.add(key, libraries[key]); } obj["libraries"] = result.array; return *this; diff --git a/src/plugins/compilerexplorer/compilerexploreraspects.h b/src/plugins/compilerexplorer/compilerexploreraspects.h index dfd0a46b73e..b713a64e655 100644 --- a/src/plugins/compilerexplorer/compilerexploreraspects.h +++ b/src/plugins/compilerexplorer/compilerexploreraspects.h @@ -7,6 +7,9 @@ #include #include +#include +#include +#include #include namespace CompilerExplorer { @@ -67,4 +70,138 @@ private: QStandardItemModel *m_model{nullptr}; }; +template +class AspectListAspect : public Utils::BaseAspect +{ +public: + using ToBaseAspectPtr = std::function; + using CreateItem = std::function; + using ItemCallback = std::function; + using IsDirty = std::function; + using Apply = std::function; + + AspectListAspect(Utils::AspectContainer *container = nullptr) + : Utils::BaseAspect(container) + {} + + void fromMap(const QVariantMap &map) override + { + QTC_ASSERT(!settingsKey().isEmpty(), return); + + QVariantList list = map[settingsKey()].toList(); + for (const auto &entry : list) { + T item = m_createItem(); + m_toBaseAspect(item)->fromMap(entry.toMap()); + m_volatileItems.append(item); + } + m_items = m_volatileItems; + } + + QVariantList toList(bool v) const + { + QVariantList list; + const auto &items = v ? m_volatileItems : m_items; + + for (const auto &item : items) { + QVariantMap childMap; + if (v) + m_toBaseAspect(item)->volatileToMap(childMap); + else + m_toBaseAspect(item)->toMap(childMap); + + list.append(childMap); + } + + return list; + } + + void toMap(Utils::Store &map) const override + { + QTC_ASSERT(!settingsKey().isEmpty(), return); + const QString key = settingsKey(); + map[key] = toList(false); + } + + void volatileToMap(Utils::Store &map) const override + { + QTC_ASSERT(!settingsKey().isEmpty(), return); + const QString key = settingsKey(); + map[key] = toList(true); + } + + T addItem(T item) + { + m_volatileItems.append(item); + if (m_itemAdded) + m_itemAdded(item); + emit volatileValueChanged(); + if (isAutoApply()) + apply(); + return item; + } + + void removeItem(T item) + { + m_volatileItems.removeOne(item); + if (m_itemRemoved) + m_itemRemoved(item); + emit volatileValueChanged(); + if (isAutoApply()) + apply(); + } + + void apply() override + { + m_items = m_volatileItems; + if (m_apply) + forEachItem(m_apply); + emit changed(); + } + + void setToBaseAspectFunction(ToBaseAspectPtr toBaseAspect) { m_toBaseAspect = toBaseAspect; } + void setCreateItemFunction(CreateItem createItem) { m_createItem = createItem; } + void setIsDirtyFunction(IsDirty isDirty) { m_isDirty = isDirty; } + void setApplyFunction(Apply apply) { m_apply = apply; } + + void forEachItem(std::function callback) + { + for (const auto &item : m_volatileItems) + callback(item); + } + + void forEachItem(std::function callback) + { + int idx = 0; + for (const auto &item : m_volatileItems) + callback(item, idx++); + } + + void setItemAddedCallback(const ItemCallback &callback) { m_itemAdded = callback; } + void setItemRemovedCallback(const ItemCallback &callback) { m_itemRemoved = callback; } + + qsizetype size() { return m_volatileItems.size(); } + bool isDirty() override + { + if (m_isDirty) { + for (const auto &item : m_volatileItems) { + if (m_isDirty(item)) + return true; + } + } + return false; + } + + QVariant volatileVariantValue() const override { return {}; }; + +private: + QList m_items; + QList m_volatileItems; + ToBaseAspectPtr m_toBaseAspect; + CreateItem m_createItem; + IsDirty m_isDirty; + Apply m_apply; + ItemCallback m_itemAdded; + ItemCallback m_itemRemoved; +}; + } // namespace CompilerExplorer diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.cpp b/src/plugins/compilerexplorer/compilerexplorereditor.cpp index 122a32c21e4..c43d3a3a34d 100644 --- a/src/plugins/compilerexplorer/compilerexplorereditor.cpp +++ b/src/plugins/compilerexplorer/compilerexplorereditor.cpp @@ -20,10 +20,11 @@ #include +#include #include #include -#include #include +#include #include #include @@ -49,7 +50,7 @@ namespace CompilerExplorer { class CodeEditorWidget : public TextEditorWidget { public: - CodeEditorWidget(Settings *settings) + CodeEditorWidget(const std::shared_ptr &settings) : m_settings(settings){}; void updateHighlighter() @@ -62,26 +63,195 @@ public: configureGenericHighlighter(mimeType); } -private: - Settings *m_settings; + std::shared_ptr m_settings; }; -CompilerWidget::CompilerWidget() - : m_compilerSettings(&settings()) +class SourceTextDocument : public TextDocument +{ +public: + SourceTextDocument(const std::shared_ptr &settings) + { + setPlainText(settings->source()); + + connect(this, &TextDocument::contentsChanged, this, [settings, this] { + settings->source.setVolatileValue(plainText()); + }); + + connect(&settings->source, &Utils::StringAspect::changed, this, [settings, this] { + if (settings->source.volatileValue() != plainText()) + setPlainText(settings->source.volatileValue()); + }); + } +}; + +JsonSettingsDocument::JsonSettingsDocument(CompilerExplorerSettings *ceSettings) + : m_ceSettings(ceSettings) +{ + setId(Constants::CE_EDITOR_ID); + + connect(m_ceSettings, &CompilerExplorerSettings::changed, this, [this] { emit changed(); }); +} + +Core::IDocument::OpenResult JsonSettingsDocument::open(QString *errorString, + const Utils::FilePath &filePath, + const Utils::FilePath &realFilePath) +{ + if (!filePath.isReadableFile()) + return OpenResult::ReadError; + + auto contents = realFilePath.fileContents(); + if (!contents) { + if (errorString) + *errorString = contents.error(); + return OpenResult::ReadError; + } + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(*contents, &error); + if (error.error != QJsonParseError::NoError) { + if (errorString) + *errorString = error.errorString(); + return OpenResult::CannotHandle; + } + + if (!doc.isObject()) { + if (errorString) + *errorString = Tr::tr("Not a valid JSON object."); + return OpenResult::CannotHandle; + } + + m_ceSettings->fromMap(doc.toVariant().toMap()); + emit settingsChanged(); + return OpenResult::Success; +} + +bool JsonSettingsDocument::saveImpl(QString *errorString, + const Utils::FilePath &newFilePath, + bool autoSave) +{ + QVariantMap map; + + if (autoSave) { + if (m_windowStateCallback) + m_ceSettings->windowState.setVolatileValue(m_windowStateCallback()); + + m_ceSettings->volatileToMap(map); + } else { + if (m_windowStateCallback) + m_ceSettings->windowState.setValue(m_windowStateCallback()); + + m_ceSettings->apply(); + m_ceSettings->toMap(map); + } + + QJsonDocument doc = QJsonDocument::fromVariant(map); + + Utils::FilePath path = newFilePath.isEmpty() ? filePath() : newFilePath; + + if (!newFilePath.isEmpty() && !autoSave) + setFilePath(newFilePath); + + auto result = path.writeFileContents(doc.toJson(QJsonDocument::Indented)); + if (!result && errorString) { + *errorString = result.error(); + return false; + } + + return true; +} + +bool JsonSettingsDocument::isModified() const +{ + bool isDirty = m_ceSettings->isDirty(); + return isDirty; +} + +bool JsonSettingsDocument::setContents(const QByteArray &contents) +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(contents, &error); + QTC_ASSERT(error.error == QJsonParseError::NoError, return false); + + QTC_ASSERT(doc.isObject(), return false); + + m_ceSettings->fromMap(doc.toVariant().toMap()); + + emit settingsChanged(); + return true; +} + +SourceEditorWidget::SourceEditorWidget(const std::shared_ptr &settings) + : m_sourceSettings(settings) +{ + m_codeEditor = new CodeEditorWidget(m_sourceSettings); + + TextDocumentPtr document = TextDocumentPtr(new SourceTextDocument(m_sourceSettings)); + + connect(document.get(), + &SourceTextDocument::changed, + this, + &SourceEditorWidget::sourceCodeChanged); + + m_codeEditor->setTextDocument(document); + m_codeEditor->updateHighlighter(); + + auto addCompilerButton = new QPushButton; + addCompilerButton->setText(Tr::tr("Add compiler")); + connect(addCompilerButton, &QPushButton::clicked, this, [this] { + auto newCompiler = std::make_shared(m_sourceSettings->apiConfigFunction()); + newCompiler->setLanguageId(m_sourceSettings->languageId()); + m_sourceSettings->compilers.addItem(newCompiler); + }); + + // clang-format off + using namespace Layouting; + + Column { + Row { + settings->languageId, + addCompilerButton, + }, + m_codeEditor, + }.attachTo(this); + // clang-format on + + setWindowTitle("Source code"); + setObjectName("source_code"); + + Aggregate *agg = Aggregate::parentAggregate(m_codeEditor); + if (!agg) { + agg = new Aggregate; + agg->add(m_codeEditor); + } + agg->add(this); + + setFocusProxy(m_codeEditor); +} + +QString SourceEditorWidget::sourceCode() +{ + if (m_codeEditor && m_codeEditor->textDocument()) + return QString::fromUtf8(m_codeEditor->textDocument()->contents()); + return {}; +} + +CompilerWidget::CompilerWidget(const std::shared_ptr &sourceSettings, + const std::shared_ptr &compilerSettings) + : m_sourceSettings(sourceSettings) + , m_compilerSettings(compilerSettings) { using namespace Layouting; QVariantMap map; - m_compilerSettings.setAutoApply(true); - m_delayTimer = new QTimer(this); m_delayTimer->setSingleShot(true); m_delayTimer->setInterval(500ms); connect(m_delayTimer, &QTimer::timeout, this, &CompilerWidget::doCompile); - for (const auto &aspect : m_compilerSettings.aspects()) - QTC_CHECK( - connect(aspect, &Utils::BaseAspect::changed, m_delayTimer, qOverload<>(&QTimer::start))); + connect(m_compilerSettings.get(), + &CompilerSettings::changed, + m_delayTimer, + qOverload<>(&QTimer::start)); m_asmEditor = new TextEditorWidget; m_asmDocument = QSharedPointer(new TextDocument); @@ -96,7 +266,7 @@ CompilerWidget::CompilerWidget() advDlg->setIcon(Utils::Icons::SETTINGS_TOOLBAR.icon()); advDlg->setIconText(Tr::tr("Advanced Options")); connect(advDlg, &QAction::triggered, this, [advButton, this] { - CompilerExplorerOptions *dlg = new CompilerExplorerOptions(m_compilerSettings, advButton); + CompilerExplorerOptions *dlg = new CompilerExplorerOptions(*m_compilerSettings, advButton); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowFlag(Qt::WindowType::Popup); dlg->show(); @@ -108,10 +278,16 @@ CompilerWidget::CompilerWidget() connect(advButton, &QPushButton::clicked, advDlg, &QAction::trigger); advButton->setIcon(advDlg->icon()); + compile(m_sourceSettings->source()); + + connect(&m_sourceSettings->source, &Utils::StringAspect::volatileValueChanged, this, [this] { + compile(m_sourceSettings->source.volatileValue()); + }); + // clang-format off Column { Row { - m_compilerSettings.compiler, + m_compilerSettings->compiler, advButton, }, Splitter { @@ -125,6 +301,11 @@ CompilerWidget::CompilerWidget() m_spinner = new SpinnerSolution::Spinner(SpinnerSolution::SpinnerSize::Large, this); } +CompilerWidget::~CompilerWidget() +{ + qDebug() << "Good bye!"; +} + Core::SearchableTerminal *CompilerWidget::createTerminal() { m_resultTerminal = new Core::SearchableTerminal(); @@ -166,33 +347,34 @@ void CompilerWidget::doCompile() { using namespace Api; - QString compilerId = m_compilerSettings.compiler(); + QString compilerId = m_compilerSettings->compiler(); if (compilerId.isEmpty()) compilerId = "clang_trunk"; m_spinner->setVisible(true); m_asmEditor->setEnabled(false); - CompileParameters params = CompileParameters(compilerId) - .source(m_source) - .language(settings().languageId()) - .options(CompileParameters::Options() - .userArguments(m_compilerSettings.compilerOptions()) - .compilerOptions({false, false}) - .filters({false, - m_compilerSettings.compileToBinaryObject(), - true, - m_compilerSettings.demangleIdentifiers(), - true, - m_compilerSettings.executeCode(), - m_compilerSettings.intelAsmSyntax(), - true, - false, - false, - false}) - .libraries(m_compilerSettings.libraries)); + CompileParameters params + = CompileParameters(compilerId) + .source(m_source) + .language(m_sourceSettings->languageId.volatileValue()) + .options(CompileParameters::Options() + .userArguments(m_compilerSettings->compilerOptions.volatileValue()) + .compilerOptions({false, false}) + .filters({false, + m_compilerSettings->compileToBinaryObject.volatileValue(), + true, + m_compilerSettings->demangleIdentifiers.volatileValue(), + true, + m_compilerSettings->executeCode.volatileValue(), + m_compilerSettings->intelAsmSyntax.volatileValue(), + true, + false, + false, + false}) + .libraries(m_compilerSettings->libraries.volatileValue())); - auto f = Api::compile(settings().apiConfig(), params); + auto f = Api::compile(m_sourceSettings->apiConfigFunction()(), params); m_compileWatcher.reset(new QFutureWatcher); @@ -273,105 +455,170 @@ void CompilerWidget::doCompile() m_compileWatcher->setFuture(f); } -EditorWidget::EditorWidget(TextDocumentPtr document, QWidget *parent) +EditorWidget::EditorWidget(QSharedPointer document, QWidget *parent) : Utils::FancyMainWindow(parent) + , m_document(document) { setAutoHideTitleBars(false); setDockNestingEnabled(true); setDocumentMode(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::TabPosition::South); - using namespace Layouting; + document->setWindowStateCallback([this] { + auto settings = saveSettings(); + QVariantMap result; - m_codeEditor = new CodeEditorWidget(&settings()); - m_codeEditor->setTextDocument(document); - m_codeEditor->updateHighlighter(); + for (const auto &key : settings.keys()) { + // QTBUG-116339 + if (key != "State") { + result.insert(key, settings.value(key)); + } else { + QVariantMap m; + m["type"] = "Base64"; + m["value"] = settings.value(key).toByteArray().toBase64(); + result.insert(key, m); + } + } - auto addCompilerButton = new QPushButton; - addCompilerButton->setText(Tr::tr("Add compiler")); - connect(addCompilerButton, &QPushButton::clicked, this, &EditorWidget::addCompiler); + return result; + }); - // clang-format off - auto source = - Column { - Row { - settings().languageId, - settings().compilerExplorerUrl, - addCompilerButton, - }, - m_codeEditor, - }.emerge(); - // clang-format on + auto addCompiler = [this](const std::shared_ptr &sourceSettings, + const std::shared_ptr &compilerSettings, + int idx) { + auto compiler = new CompilerWidget(sourceSettings, compilerSettings); + compiler->setWindowTitle("Compiler #" + QString::number(idx)); + compiler->setObjectName("compiler_" + QString::number(idx)); + QDockWidget *dockWidget = addDockForWidget(compiler); + addDockWidget(Qt::RightDockWidgetArea, dockWidget); + m_compilerWidgets.append(dockWidget); + }; - source->setWindowTitle("Source code"); - source->setObjectName("source_code"); - addDockWidget(Qt::LeftDockWidgetArea, addDockForWidget(source)); + auto addSourceEditor = [this, document, addCompiler]( + const std::shared_ptr &sourceSettings) { + auto sourceEditor = new SourceEditorWidget(sourceSettings); + sourceEditor->setWindowTitle("Source Code #" + QString::number(m_sourceWidgets.size() + 1)); + sourceEditor->setObjectName("source_code_editor_" + + QString::number(m_sourceWidgets.size() + 1)); - addCompiler(); + QDockWidget *dockWidget = addDockForWidget(sourceEditor); + connect(dockWidget, + &QDockWidget::visibilityChanged, + this, + [document, sourceSettings = sourceSettings.get(), dockWidget] { + if (!dockWidget->isVisible()) + document->settings()->m_sources.removeItem( + sourceSettings->shared_from_this()); + }); - Aggregate *agg = Aggregate::parentAggregate(m_codeEditor); - if (!agg) { - agg = new Aggregate; - agg->add(m_codeEditor); - } - agg->add(this); + addDockWidget(Qt::LeftDockWidgetArea, dockWidget); + + sourceSettings->compilers.forEachItem( + [addCompiler, sourceSettings](const std::shared_ptr &compilerSettings, + int idx) { + addCompiler(sourceSettings, compilerSettings, idx + 1); + }); + + sourceSettings->compilers.setItemAddedCallback( + [addCompiler, sourceSettings = sourceSettings.get()]( + const std::shared_ptr &compilerSettings) { + addCompiler(sourceSettings->shared_from_this(), + compilerSettings, + sourceSettings->compilers.size()); + }); + + sourceSettings->compilers.setItemRemovedCallback( + [this](const std::shared_ptr &compilerSettings) { + m_compilerWidgets.removeIf([compilerSettings](const QDockWidget *c) { + return static_cast(c->widget())->m_compilerSettings + == compilerSettings; + }); + }); + + Aggregate *agg = Aggregate::parentAggregate(sourceEditor); + if (!agg) { + agg = new Aggregate; + agg->add(sourceEditor); + } + agg->add(this); + + setFocusProxy(sourceEditor); + + m_sourceWidgets.append(dockWidget); + }; + + auto removeSourceEditor = [this](const std::shared_ptr &sourceSettings) { + m_sourceWidgets.removeIf([sourceSettings = sourceSettings.get()](const QDockWidget *c) { + return static_cast(c->widget())->m_sourceSettings + == sourceSettings->shared_from_this(); + }); + }; + + auto recreateEditors = [this, addSourceEditor]() { + qDeleteAll(m_sourceWidgets); + qDeleteAll(m_compilerWidgets); + + m_sourceWidgets.clear(); + m_compilerWidgets.clear(); + + m_document->settings()->m_sources.forEachItem(addSourceEditor); + QVariantMap windowState = m_document->settings()->windowState.value(); + + if (!windowState.isEmpty()) { + QHash hashMap; + for (const auto &key : windowState.keys()) { + if (key != "State") + hashMap.insert(key, windowState.value(key)); + else { + QVariant v = windowState.value(key); + if (v.userType() == QMetaType::QByteArray) { + hashMap.insert(key, v); + } else if (v.userType() == QMetaType::QVariantMap) { + QVariantMap m = v.toMap(); + if (m.value("type") == "Base64") { + hashMap.insert(key, + QByteArray::fromBase64(m.value("value").toByteArray())); + } + } + } + } + + restoreSettings(hashMap); + } + }; + + document->settings()->m_sources.setItemAddedCallback(addSourceEditor); + document->settings()->m_sources.setItemRemovedCallback(removeSourceEditor); + connect(document.get(), &JsonSettingsDocument::settingsChanged, this, recreateEditors); m_context = new Core::IContext(this); m_context->setWidget(this); m_context->setContext(Core::Context(Constants::CE_EDITOR_CONTEXT_ID)); Core::ICore::addContextObject(m_context); - - connect(m_codeEditor, &QPlainTextEdit::textChanged, this, &EditorWidget::sourceCodeChanged); - - connect(&settings(), - &Settings::languagesChanged, - m_codeEditor, - &CodeEditorWidget::updateHighlighter); - - connect(&settings().languageId, - &StringSelectionAspect::changed, - this, - &EditorWidget::onLanguageChanged); - - setFocusProxy(m_codeEditor); } -void EditorWidget::onLanguageChanged() +EditorWidget::~EditorWidget() { - m_codeEditor->updateHighlighter(); -} - -void EditorWidget::addCompiler() -{ - m_compilerCount++; - - auto compiler = new CompilerWidget; - compiler->setWindowTitle("Compiler #" + QString::number(m_compilerCount)); - compiler->setObjectName("compiler_" + QString::number(m_compilerCount)); - - addDockWidget(Qt::RightDockWidgetArea, addDockForWidget(compiler)); - - if (m_codeEditor && m_codeEditor->textDocument()) - compiler->compile(QString::fromUtf8(m_codeEditor->textDocument()->contents())); - - connect(this, &EditorWidget::sourceCodeChanged, compiler, [this, compiler] { - compiler->compile(QString::fromUtf8(m_codeEditor->textDocument()->contents())); - }); + m_compilerWidgets.clear(); + m_sourceWidgets.clear(); } class Editor : public Core::IEditor { public: Editor() - : m_document(new TextDocument(Constants::CE_EDITOR_ID)) + : m_document(new JsonSettingsDocument(&m_settings)) { setWidget(new EditorWidget(m_document)); } + ~Editor() { delete widget(); } + Core::IDocument *document() const override { return m_document.data(); } QWidget *toolBar() override { return nullptr; } - TextDocumentPtr m_document; + CompilerExplorerSettings m_settings; + QSharedPointer m_document; }; EditorFactory::EditorFactory() diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.h b/src/plugins/compilerexplorer/compilerexplorereditor.h index 1eec159c0c6..cb13bda6278 100644 --- a/src/plugins/compilerexplorer/compilerexplorereditor.h +++ b/src/plugins/compilerexplorer/compilerexplorereditor.h @@ -27,18 +27,76 @@ class CppEditorWidget; namespace CompilerExplorer { +class JsonSettingsDocument; +class SourceEditorWidget; class CodeEditorWidget; +class JsonSettingsDocument : public Core::IDocument +{ + Q_OBJECT +public: + JsonSettingsDocument(CompilerExplorerSettings *ceSettings); + OpenResult open(QString *errorString, + const Utils::FilePath &filePath, + const Utils::FilePath &realFilePath) override; + + bool saveImpl(QString *errorString, + const Utils::FilePath &filePath = Utils::FilePath(), + bool autoSave = false) override; + + bool setContents(const QByteArray &contents) override; + + bool shouldAutoSave() const override { return !filePath().isEmpty(); } + bool isModified() const override; + bool isSaveAsAllowed() const override { return true; } + + CompilerExplorerSettings *settings() { return m_ceSettings; } + + void setWindowStateCallback(std::function callback) + { + m_windowStateCallback = callback; + } + +signals: + void settingsChanged(); + +private: + CompilerExplorerSettings *m_ceSettings; + std::function m_windowStateCallback; +}; + +class SourceEditorWidget : public QWidget +{ + Q_OBJECT +public: + SourceEditorWidget(const std::shared_ptr &settings); + + QString sourceCode(); + + std::shared_ptr m_sourceSettings; +signals: + void sourceCodeChanged(); + +private: + CodeEditorWidget *m_codeEditor{nullptr}; +}; + class CompilerWidget : public QWidget { Q_OBJECT public: - CompilerWidget(); + CompilerWidget(const std::shared_ptr &sourceSettings, + const std::shared_ptr &compilerSettings); + + ~CompilerWidget(); Core::SearchableTerminal *createTerminal(); void compile(const QString &source); + std::shared_ptr m_sourceSettings; + std::shared_ptr m_compilerSettings; + private: void doCompile(); @@ -51,8 +109,6 @@ private: std::unique_ptr> m_compileWatcher; - CompilerExplorer::CompilerSettings m_compilerSettings; - QString m_source; QTimer *m_delayTimer{nullptr}; QList m_marks; @@ -62,25 +118,21 @@ class EditorWidget : public Utils::FancyMainWindow { Q_OBJECT public: - EditorWidget(QSharedPointer document = nullptr, QWidget *parent = nullptr); - - void addCompiler(); - -protected: - Core::SearchableTerminal *createTerminal(); - - void onLanguageChanged(); + EditorWidget(QSharedPointer document = nullptr, QWidget *parent = nullptr); + ~EditorWidget() override; signals: void sourceCodeChanged(); private: - CodeEditorWidget *m_codeEditor; - CompilerExplorer::Settings m_currentSettings; QSplitter *m_mainSplitter; int m_compilerCount{0}; + QSharedPointer m_document; Core::IContext *m_context; + + QList m_compilerWidgets; + QList m_sourceWidgets; }; class EditorFactory : public Core::IEditorFactory diff --git a/src/plugins/compilerexplorer/compilerexplorerplugin.cpp b/src/plugins/compilerexplorer/compilerexplorerplugin.cpp index 542cc037b0a..5845aa7dea2 100644 --- a/src/plugins/compilerexplorer/compilerexplorerplugin.cpp +++ b/src/plugins/compilerexplorer/compilerexplorerplugin.cpp @@ -34,11 +34,10 @@ public: auto action = new QAction(Tr::tr("Open Compiler Explorer"), this); connect(action, &QAction::triggered, this, [] { - CompilerExplorer::Settings settings; - - const QString src = settings.source(); QString name("Compiler Explorer"); - EditorManager::openEditorWithContents(Constants::CE_EDITOR_ID, &name, src.toUtf8()); + Core::EditorManager::openEditorWithContents(Constants::CE_EDITOR_ID, + &name, + settings().defaultDocument().toUtf8()); }); ActionContainer *mtools = ActionManager::actionContainer(Core::Constants::M_TOOLS); @@ -47,11 +46,12 @@ public: menu->setTitle(Tr::tr("Compiler Explorer")); mtools->addMenu(mCompilerExplorer); - Command *cmd = ActionManager::registerAction(action, "CompilerExplorer.CompilerExplorerAction"); + Command *cmd = ActionManager::registerAction(action, + "CompilerExplorer.CompilerExplorerAction"); mCompilerExplorer->addAction(cmd); } }; -} // CompilerExplorer::Internl +} // namespace CompilerExplorer::Internal #include "compilerexplorerplugin.moc" diff --git a/src/plugins/compilerexplorer/compilerexplorersettings.cpp b/src/plugins/compilerexplorer/compilerexplorersettings.cpp index 1dbd34f888d..0837afb6e11 100644 --- a/src/plugins/compilerexplorer/compilerexplorersettings.cpp +++ b/src/plugins/compilerexplorer/compilerexplorersettings.cpp @@ -14,12 +14,29 @@ namespace CompilerExplorer { -Settings &settings() +PluginSettings &settings() { - static Settings instance; + static PluginSettings instance; return instance; } +PluginSettings::PluginSettings() +{ + defaultDocument.setSettingsKey("DefaultDocument"); + defaultDocument.setDefaultValue(R"( +{ + "Sources": [{ + "LanguageId": "c++", + "Source": "int main() {\n return 0;\n}", + "Compilers": [{ + "Id": "clang_trunk", + "Options": "-O3" + }] + }] +} + )"); +} + static Api::Languages &cachedLanguages() { static Api::Languages instance; @@ -43,41 +60,58 @@ static QMap> &cachedCompilers() return instance; } -Settings::Settings() +SourceSettings::SourceSettings(const ApiConfigFunction &apiConfigFunction) + : m_apiConfigFunction(apiConfigFunction) { - static QNetworkAccessManager networkManager; - m_networkAccessManager = &networkManager; + setAutoApply(false); - setSettingsGroup("CompilerExplorer"); - - source.setDefaultValue(R"( -int main() -{ - return 0; -} - -)"); - - compilerExplorerUrl.setLabelText(Tr::tr("Compiler Explorer URL:")); - compilerExplorerUrl.setToolTip(Tr::tr("URL of the Compiler Explorer instance to use")); - compilerExplorerUrl.setDefaultValue("https://godbolt.org/"); - compilerExplorerUrl.setDisplayStyle(Utils::StringAspect::DisplayStyle::LineEditDisplay); - compilerExplorerUrl.setHistoryCompleter("CompilerExplorer.Url.History"); + source.setSettingsKey("Source"); + languageId.setSettingsKey("LanguageId"); languageId.setDefaultValue("c++"); languageId.setLabelText(Tr::tr("Language:")); languageId.setFillCallback([this](auto cb) { fillLanguageIdModel(cb); }); - connect(&compilerExplorerUrl, &Utils::StringAspect::changed, this, [this] { - languageId.setValue(languageId.defaultValue()); - cachedLanguages().clear(); - languageId.refill(); + compilers.setSettingsKey("Compilers"); + compilers.setCreateItemFunction([this, apiConfigFunction] { + auto result = std::make_shared(apiConfigFunction); + connect(this, &SourceSettings::languagesChanged, result.get(), &CompilerSettings::refresh); + connect(&languageId, + &StringSelectionAspect::changed, + result.get(), + [this, result = result.get()] { result->setLanguageId(languageId()); }); + + connect(result.get(), &Utils::AspectContainer::changed, this, &SourceSettings::changed); + + result->setLanguageId(languageId()); + return result; }); - readSettings(); + compilers.setToBaseAspectFunction([](const std::shared_ptr &item) { + return static_cast(item.get()); + }); + compilers.setIsDirtyFunction( + [](const std::shared_ptr &settings) { return settings->isDirty(); }); + compilers.setApplyFunction( + [](const std::shared_ptr &settings) { settings->apply(); }); + + for (const auto &aspect : this->aspects()) + connect(aspect, + &Utils::BaseAspect::volatileValueChanged, + this, + &CompilerExplorerSettings::changed); } -QString Settings::languageExtension() const +void SourceSettings::refresh() +{ + languageId.setValue(languageId.defaultValue()); + cachedLanguages().clear(); + languageId.refill(); + + compilers.forEachItem(&CompilerSettings::refresh); +} + +QString SourceSettings::languageExtension() const { auto it = std::find_if(std::begin(cachedLanguages()), std::end(cachedLanguages()), @@ -89,50 +123,70 @@ QString Settings::languageExtension() const return ".cpp"; } -CompilerSettings::CompilerSettings(Settings *settings) - : m_parent(settings) +CompilerSettings::CompilerSettings(const ApiConfigFunction &apiConfigFunction) + : m_apiConfigFunction(apiConfigFunction) { - setAutoApply(true); - compilerOptions.setDefaultValue("-O3"); + setAutoApply(false); + compiler.setSettingsKey("Id"); + compiler.setLabelText(Tr::tr("Compiler:")); + compiler.setFillCallback([this](auto cb) { fillCompilerModel(cb); }); + + compilerOptions.setSettingsKey("Options"); compilerOptions.setLabelText(Tr::tr("Compiler options:")); compilerOptions.setToolTip(Tr::tr("Arguments passed to the compiler")); compilerOptions.setDisplayStyle(Utils::StringAspect::DisplayStyle::LineEditDisplay); - compiler.setDefaultValue("clang_trunk"); - compiler.setLabelText(Tr::tr("Compiler:")); - compiler.setFillCallback([this](auto cb) { fillCompilerModel(cb); }); - + libraries.setSettingsKey("Libraries"); libraries.setLabelText(Tr::tr("Libraries:")); libraries.setFillCallback([this](auto cb) { fillLibraries(cb); }); + executeCode.setSettingsKey("ExecuteCode"); executeCode.setLabelText(Tr::tr("Execute the code")); + + compileToBinaryObject.setSettingsKey("CompileToBinaryObject"); compileToBinaryObject.setLabelText(Tr::tr("Compile to binary object")); + + intelAsmSyntax.setSettingsKey("IntelAsmSyntax"); intelAsmSyntax.setLabelText(Tr::tr("Intel asm syntax")); intelAsmSyntax.setDefaultValue(true); + + demangleIdentifiers.setSettingsKey("DemangleIdentifiers"); demangleIdentifiers.setLabelText(Tr::tr("Demangle identifiers")); demangleIdentifiers.setDefaultValue(true); - connect(&settings->compilerExplorerUrl, &Utils::StringAspect::changed, this, [this] { - cachedCompilers().clear(); - cachedLibraries().clear(); + for (const auto &aspect : this->aspects()) + connect(aspect, + &Utils::BaseAspect::volatileValueChanged, + this, + &CompilerExplorerSettings::changed); +} - compiler.refill(); - libraries.refill(); - }); +void CompilerSettings::refresh() +{ + cachedCompilers().clear(); + cachedLibraries().clear(); - connect(&settings->languageId, &StringSelectionAspect::changed, this, [this] { - compiler.refill(); - libraries.refill(); - if (m_parent->languageId() == "c++") - compilerOptions.setValue("-O3"); - else - compilerOptions.setValue(""); - }); + compiler.refill(); + libraries.refill(); +} + +void CompilerSettings::setLanguageId(const QString &languageId) +{ + m_languageId = languageId; + + compiler.refill(); + libraries.refill(); + + // TODO: Set real defaults ... + if (m_languageId == "c++") + compilerOptions.setValue("-O3"); + else + compilerOptions.setValue(""); } void CompilerSettings::fillLibraries(LibrarySelectionAspect::ResultCallback cb) { - const QString lang = m_parent->languageId(); + const QString lang = m_languageId; auto fillFromCache = [cb, lang] { QList items; for (const Api::Library &lib : cachedLibraries(lang)) { @@ -148,7 +202,7 @@ void CompilerSettings::fillLibraries(LibrarySelectionAspect::ResultCallback cb) return; } - auto future = Api::libraries(m_parent->apiConfig(), lang); + auto future = Api::libraries(m_apiConfigFunction(), lang); auto watcher = new QFutureWatcher(this); watcher->setFuture(future); @@ -166,7 +220,7 @@ void CompilerSettings::fillLibraries(LibrarySelectionAspect::ResultCallback cb) }); } -void Settings::fillLanguageIdModel(StringSelectionAspect::ResultCallback cb) +void SourceSettings::fillLanguageIdModel(StringSelectionAspect::ResultCallback cb) { auto fillFromCache = [cb, this] { QList items; @@ -190,7 +244,7 @@ void Settings::fillLanguageIdModel(StringSelectionAspect::ResultCallback cb) return; } - auto future = Api::languages(apiConfig()); + auto future = Api::languages(m_apiConfigFunction()); auto watcher = new QFutureWatcher(this); watcher->setFuture(future); @@ -220,13 +274,13 @@ void CompilerSettings::fillCompilerModel(StringSelectionAspect::ResultCallback c cb(items); }; - auto it = cachedCompilers().find(m_parent->languageId()); + auto it = cachedCompilers().find(m_languageId); if (it != cachedCompilers().end()) { fillFromCache(it); return; } - auto future = Api::compilers(m_parent->apiConfig(), m_parent->languageId()); + auto future = Api::compilers(m_apiConfigFunction(), m_languageId); auto watcher = new QFutureWatcher(this); watcher->setFuture(future); @@ -236,7 +290,7 @@ void CompilerSettings::fillCompilerModel(StringSelectionAspect::ResultCallback c [watcher, this, fillFromCache]() { try { auto result = watcher->result(); - auto itCache = cachedCompilers().insert(m_parent->languageId(), {}); + auto itCache = cachedCompilers().insert(m_languageId, {}); for (const Api::Compiler &compiler : result) itCache->insert(compiler.name, compiler.id); @@ -249,4 +303,50 @@ void CompilerSettings::fillCompilerModel(StringSelectionAspect::ResultCallback c }); } +CompilerExplorerSettings::CompilerExplorerSettings() +{ + setAutoApply(false); + setSettingsKey("CompilerExplorer"); + static QNetworkAccessManager networkManager; + m_networkAccessManager = &networkManager; + + compilerExplorerUrl.setSettingsKey("CompilerExplorerUrl"); + compilerExplorerUrl.setLabelText(Tr::tr("Compiler Explorer URL:")); + compilerExplorerUrl.setToolTip(Tr::tr("URL of the Compiler Explorer instance to use")); + compilerExplorerUrl.setDefaultValue("https://godbolt.org/"); + compilerExplorerUrl.setDisplayStyle(Utils::StringAspect::DisplayStyle::LineEditDisplay); + compilerExplorerUrl.setHistoryCompleter("CompilerExplorer.Url.History"); + + windowState.setSettingsKey("WindowState"); + + m_sources.setSettingsKey("Sources"); + m_sources.setCreateItemFunction([this] { + auto newSourceSettings = std::make_shared([this] { return apiConfig(); }); + connect(newSourceSettings.get(), + &Utils::AspectContainer::changed, + this, + &CompilerExplorerSettings::changed); + return newSourceSettings; + }); + m_sources.setIsDirtyFunction( + [](const std::shared_ptr &settings) { return settings->isDirty(); }); + m_sources.setApplyFunction( + [](const std::shared_ptr &settings) { settings->apply(); }); + m_sources.setToBaseAspectFunction([](const std::shared_ptr &item) { + return static_cast(item.get()); + }); + + connect(&compilerExplorerUrl, &Utils::StringAspect::volatileValueChanged, this, [this] { + m_sources.forEachItem(&SourceSettings::refresh); + }); + + for (const auto &aspect : this->aspects()) + connect(aspect, + &Utils::BaseAspect::volatileValueChanged, + this, + &CompilerExplorerSettings::changed); +} + +CompilerExplorerSettings::~CompilerExplorerSettings() = default; + } // namespace CompilerExplorer diff --git a/src/plugins/compilerexplorer/compilerexplorersettings.h b/src/plugins/compilerexplorer/compilerexplorersettings.h index 4d93f86c254..34850009af2 100644 --- a/src/plugins/compilerexplorer/compilerexplorersettings.h +++ b/src/plugins/compilerexplorer/compilerexplorersettings.h @@ -11,12 +11,76 @@ #include namespace CompilerExplorer { -class Settings; +class SourceSettings; +class CompilerSettings; + +using ApiConfigFunction = std::function; + +class PluginSettings : public Utils::AspectContainer +{ +public: + PluginSettings(); + Utils::StringAspect defaultDocument{this}; +}; + +PluginSettings &settings(); + +class CompilerExplorerSettings : public Utils::AspectContainer +{ +public: + CompilerExplorerSettings(); + ~CompilerExplorerSettings(); + + Utils::StringAspect compilerExplorerUrl{this}; + Utils::TypedAspect windowState{this}; + + AspectListAspect> m_sources{this}; + + Api::Config apiConfig() const + { + return Api::Config(m_networkAccessManager, compilerExplorerUrl()); + } + + QNetworkAccessManager *networkAccessManager() const { return m_networkAccessManager; } + +private: + QNetworkAccessManager *m_networkAccessManager{nullptr}; +}; + +class SourceSettings : public Utils::AspectContainer, + public std::enable_shared_from_this +{ + Q_OBJECT +public: + SourceSettings(const ApiConfigFunction &apiConfigFunction); + + void refresh(); + + ApiConfigFunction apiConfigFunction() const { return m_apiConfigFunction; } + +public: + StringSelectionAspect languageId{this}; + Utils::StringAspect source{this}; + AspectListAspect> compilers{this}; + +public: + QString languageExtension() const; + +signals: + void languagesChanged(); + +private: + void fillLanguageIdModel(StringSelectionAspect::ResultCallback cb); + +private: + CompilerExplorerSettings *m_parent; + ApiConfigFunction m_apiConfigFunction; +}; class CompilerSettings : public Utils::AspectContainer { public: - CompilerSettings(Settings *settings); + CompilerSettings(const ApiConfigFunction &apiConfigFunction); StringSelectionAspect compiler{this}; @@ -29,42 +93,15 @@ public: Utils::BoolAspect intelAsmSyntax{this}; Utils::BoolAspect demangleIdentifiers{this}; + void refresh(); + void setLanguageId(const QString &languageId); + private: void fillCompilerModel(StringSelectionAspect::ResultCallback cb); void fillLibraries(LibrarySelectionAspect::ResultCallback cb); - Settings *m_parent; + QString m_languageId; + ApiConfigFunction m_apiConfigFunction; }; -class Settings : public Utils::AspectContainer -{ - Q_OBJECT -public: - Settings(); - - StringSelectionAspect languageId{this}; - Utils::StringAspect compilerExplorerUrl{this}; - - Utils::StringAspect source{this}; - - QNetworkAccessManager *networkAccessManager() const { return m_networkAccessManager; } - - Api::Config apiConfig() const - { - return Api::Config(m_networkAccessManager, compilerExplorerUrl()); - } - - QString languageExtension() const; - -signals: - void languagesChanged(); - -private: - void fillLanguageIdModel(StringSelectionAspect::ResultCallback cb); - - QNetworkAccessManager *m_networkAccessManager{nullptr}; -}; - -Settings &settings(); - } // namespace CompilerExplorer