diff --git a/src/plugins/compilerexplorer/compilerexploreraspects.cpp b/src/plugins/compilerexplorer/compilerexploreraspects.cpp index c9a965f2a92..021914310de 100644 --- a/src/plugins/compilerexplorer/compilerexploreraspects.cpp +++ b/src/plugins/compilerexplorer/compilerexploreraspects.cpp @@ -6,6 +6,7 @@ #include "api/library.h" +#include #include #include @@ -58,6 +59,35 @@ bool LibrarySelectionAspect::guiToBuffer() return oldBuffer != m_buffer; } +QVariantMap toVariantMap(const QMap &map) +{ + QVariantMap variant; + for (const auto &key : map.keys()) + variant.insert(key, map[key]); + + return variant; +} + +QVariant LibrarySelectionAspect::variantValue() const +{ + return toVariantMap(m_internal); +} + +QVariant LibrarySelectionAspect::volatileVariantValue() const +{ + return toVariantMap(m_buffer); +} + +void LibrarySelectionAspect::setVariantValue(const QVariant &value, Announcement howToAnnounce) +{ + QMap map; + QVariantMap variant = value.toMap(); + for (const auto &key : variant.keys()) + map[key] = variant[key].toString(); + + setValue(map, howToAnnounce); +} + void LibrarySelectionAspect::addToLayout(Layouting::LayoutItem &parent) { using namespace Layouting; @@ -110,6 +140,18 @@ void LibrarySelectionAspect::addToLayout(Layouting::LayoutItem &parent) connect(nameCombo, &QComboBox::currentIndexChanged, this, refreshVersionCombo); connect(versionCombo, &QComboBox::activated, this, [this, nameCombo, versionCombo] { + if (undoStack()) { + QVariant old = m_model->data(m_model->index(nameCombo->currentIndex(), 0), + SelectedVersion); + undoStack()->push(new SelectLibraryVersionCommand(this, + nameCombo->currentIndex(), + versionCombo->currentData(), + old)); + + handleGuiChanged(); + return; + } + m_model->setData(m_model->index(nameCombo->currentIndex(), 0), versionCombo->currentData(), SelectedVersion); @@ -118,6 +160,23 @@ void LibrarySelectionAspect::addToLayout(Layouting::LayoutItem &parent) QPushButton *clearBtn = new QPushButton("Clear All"); connect(clearBtn, &QPushButton::clicked, clearBtn, [this, refreshVersionCombo] { + if (undoStack()) { + undoStack()->beginMacro(Tr::tr("Reset used libraries")); + for (int i = 0; i < m_model->rowCount(); i++) { + QModelIndex idx = m_model->index(i, 0); + if (idx.data(SelectedVersion).isValid()) + undoStack()->push(new SelectLibraryVersionCommand(this, + i, + QVariant(), + idx.data(SelectedVersion))); + } + undoStack()->endMacro(); + + handleGuiChanged(); + refreshVersionCombo(); + return; + } + for (int i = 0; i < m_model->rowCount(); i++) m_model->setData(m_model->index(i, 0), QVariant(), SelectedVersion); handleGuiChanged(); @@ -130,10 +189,11 @@ void LibrarySelectionAspect::addToLayout(Layouting::LayoutItem &parent) QStringList libs; for (int i = 0; i < m_model->rowCount(); i++) { QModelIndex idx = m_model->index(i, 0); - if (idx.data(SelectedVersion).isValid()) + if (idx.data(SelectedVersion).isValid()) { libs.append(QString("%1 %2") .arg(idx.data().toString()) .arg(idx.data(SelectedVersion).toString())); + } } if (libs.empty()) displayLabel->setText(Tr::tr("No libraries selected")); @@ -157,7 +217,10 @@ void LibrarySelectionAspect::addToLayout(Layouting::LayoutItem &parent) Row { noMargin, nameCombo, versionCombo, clearBtn }.emerge() }.emerge(); // clang-format on - connect(editBtn, &QPushButton::clicked, this, [stack] { stack->setCurrentIndex(1); }); + connect(editBtn, &QPushButton::clicked, stack, [stack] { stack->setCurrentIndex(1); }); + connect(this, &LibrarySelectionAspect::returnToDisplay, stack, [stack] { + stack->setCurrentIndex(0); + }); addLabeledItem(parent, s); } diff --git a/src/plugins/compilerexplorer/compilerexploreraspects.h b/src/plugins/compilerexplorer/compilerexploreraspects.h index 64f91c1a418..10a0d9cb818 100644 --- a/src/plugins/compilerexplorer/compilerexploreraspects.h +++ b/src/plugins/compilerexplorer/compilerexploreraspects.h @@ -24,6 +24,48 @@ public: SelectedVersion, }; + class SelectLibraryVersionCommand : public QUndoCommand + { + public: + SelectLibraryVersionCommand(LibrarySelectionAspect *aspect, + int libraryIndex, + const QVariant &versionId, + const QVariant &oldVersionId = QVariant()) + : m_aspect(aspect) + , m_libraryIndex(libraryIndex) + , m_versionId(versionId) + , m_oldVersionId(oldVersionId) + {} + + void undo() override + { + m_aspect->m_model->setData(m_aspect->m_model->index(m_libraryIndex, 0), + m_oldVersionId, + LibrarySelectionAspect::SelectedVersion); + m_aspect->handleGuiChanged(); + emit m_aspect->returnToDisplay(); + } + + void redo() override + { + m_aspect->m_model->setData(m_aspect->m_model->index(m_libraryIndex, 0), + m_versionId, + LibrarySelectionAspect::SelectedVersion); + if (!m_firstTime) { + emit m_aspect->returnToDisplay(); + m_aspect->handleGuiChanged(); + } + m_firstTime = false; + } + + private: + LibrarySelectionAspect *m_aspect; + int m_libraryIndex; + QVariant m_versionId; + QVariant m_oldVersionId; + bool m_firstTime{true}; + }; + LibrarySelectionAspect(Utils::AspectContainer *container = nullptr); void addToLayout(Layouting::LayoutItem &parent) override; @@ -36,8 +78,14 @@ public: void bufferToGui() override; bool guiToBuffer() override; + QVariant variantValue() const override; + QVariant volatileVariantValue() const override; + + void setVariantValue(const QVariant &value, Announcement howToAnnounce = DoEmit) override; + signals: void refillRequested(); + void returnToDisplay(); private: FillCallback m_fillCallback; diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.cpp b/src/plugins/compilerexplorer/compilerexplorereditor.cpp index cc387002e01..719b61299fc 100644 --- a/src/plugins/compilerexplorer/compilerexplorereditor.cpp +++ b/src/plugins/compilerexplorer/compilerexplorereditor.cpp @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -25,6 +26,7 @@ #include #include #include +#include #include #include @@ -37,6 +39,7 @@ #include #include #include +#include #include #include @@ -48,30 +51,36 @@ using namespace Utils; namespace CompilerExplorer { -class CodeEditorWidget : public TextEditorWidget +CodeEditorWidget::CodeEditorWidget(const std::shared_ptr &settings, + QUndoStack *undoStack) + : m_settings(settings) + , m_undoStack(undoStack){}; + +void CodeEditorWidget::updateHighlighter() { -public: - CodeEditorWidget(const std::shared_ptr &settings) - : m_settings(settings) - {} + const QString ext = m_settings->languageExtension(); + if (ext.isEmpty()) + return; - void updateHighlighter() - { - const QString ext = m_settings->languageExtension(); - if (ext.isEmpty()) - return; - - Utils::MimeType mimeType = Utils::mimeTypeForFile("foo" + ext); - configureGenericHighlighter(mimeType); - } - - std::shared_ptr m_settings; -}; + Utils::MimeType mimeType = Utils::mimeTypeForFile("foo" + ext); + configureGenericHighlighter(mimeType); +} class SourceTextDocument : public TextDocument { public: - SourceTextDocument(const std::shared_ptr &settings) + class OpaqueUndoCommand : public QUndoCommand + { + public: + OpaqueUndoCommand(SourceTextDocument *doc) + : m_doc(doc) + {} + void undo() override { m_doc->undo(); } + void redo() override { m_doc->redo(); } + SourceTextDocument *m_doc; + }; + + SourceTextDocument(const std::shared_ptr &settings, QUndoStack *undoStack) { setPlainText(settings->source()); @@ -83,18 +92,26 @@ public: if (settings->source.volatileValue() != plainText()) setPlainText(settings->source.volatileValue()); }); + + connect(this->document(), &QTextDocument::undoCommandAdded, this, [this, undoStack] { + undoStack->push(new OpaqueUndoCommand(this)); + }); } + + void undo() { document()->undo(); } + void redo() { document()->redo(); } }; -JsonSettingsDocument::JsonSettingsDocument() +JsonSettingsDocument::JsonSettingsDocument(QUndoStack *undoStack) + : m_undoStack(undoStack) { setId(Constants::CE_EDITOR_ID); setMimeType("application/compiler-explorer"); connect(&m_ceSettings, &CompilerExplorerSettings::changed, this, [this] { emit changed(); }); + m_ceSettings.setAutoApply(true); + m_ceSettings.setUndoStack(undoStack); } -JsonSettingsDocument::~JsonSettingsDocument() {} - Core::IDocument::OpenResult JsonSettingsDocument::open(QString *errorString, const FilePath &filePath, const FilePath &realFilePath) @@ -121,23 +138,21 @@ Core::IDocument::OpenResult JsonSettingsDocument::open(QString *errorString, return OpenResult::Success; } -bool JsonSettingsDocument::saveImpl(QString *errorString, - const FilePath &newFilePath, - bool autoSave) +bool JsonSettingsDocument::saveImpl(QString *errorString, const FilePath &newFilePath, bool autoSave) { - Store map; + Store store; if (autoSave) { if (m_windowStateCallback) m_ceSettings.windowState.setVolatileValue(m_windowStateCallback()); - m_ceSettings.volatileToMap(map); + m_ceSettings.volatileToMap(store); } else { if (m_windowStateCallback) m_ceSettings.windowState.setValue(m_windowStateCallback()); m_ceSettings.apply(); - m_ceSettings.toMap(map); + m_ceSettings.toMap(store); } Utils::FilePath path = newFilePath.isEmpty() ? filePath() : newFilePath; @@ -145,7 +160,7 @@ bool JsonSettingsDocument::saveImpl(QString *errorString, if (!newFilePath.isEmpty() && !autoSave) setFilePath(newFilePath); - auto result = path.writeFileContents(jsonFromStore(map)); + auto result = path.writeFileContents(jsonFromStore(store)); if (!result && errorString) { *errorString = result.error(); return false; @@ -170,12 +185,15 @@ bool JsonSettingsDocument::setContents(const QByteArray &contents) return true; } -SourceEditorWidget::SourceEditorWidget(const std::shared_ptr &settings) +SourceEditorWidget::SourceEditorWidget(const std::shared_ptr &settings, + QUndoStack *undoStack) : m_sourceSettings(settings) { - m_codeEditor = new CodeEditorWidget(m_sourceSettings); + m_codeEditor = new CodeEditorWidget(m_sourceSettings, undoStack); - TextDocumentPtr document = TextDocumentPtr(new SourceTextDocument(m_sourceSettings)); + connect(m_codeEditor, &CodeEditorWidget::gotFocus, this, &SourceEditorWidget::gotFocus); + + TextDocumentPtr document = TextDocumentPtr(new SourceTextDocument(m_sourceSettings, undoStack)); connect(document.get(), &SourceTextDocument::changed, @@ -187,11 +205,12 @@ SourceEditorWidget::SourceEditorWidget(const std::shared_ptr &se 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); - }); + connect(addCompilerButton, &QPushButton::clicked, this, &SourceEditorWidget::addCompiler); + + auto removeSourceButton = new QPushButton; + removeSourceButton->setIcon(Utils::Icons::EDIT_CLEAR.icon()); + removeSourceButton->setToolTip(Tr::tr("Remove source")); + connect(removeSourceButton, &QPushButton::clicked, this, &SourceEditorWidget::remove); // clang-format off using namespace Layouting; @@ -200,6 +219,7 @@ SourceEditorWidget::SourceEditorWidget(const std::shared_ptr &se Row { settings->languageId, addCompilerButton, + removeSourceButton, }, m_codeEditor, }.attachTo(this); @@ -208,13 +228,6 @@ SourceEditorWidget::SourceEditorWidget(const std::shared_ptr &se 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); } @@ -243,11 +256,14 @@ CompilerWidget::CompilerWidget(const std::shared_ptr &sourceSett m_delayTimer, qOverload<>(&QTimer::start)); - m_asmEditor = new TextEditorWidget; + m_asmEditor = new AsmEditorWidget; m_asmDocument = QSharedPointer(new TextDocument); m_asmDocument->setFilePath("asm.asm"); m_asmEditor->setTextDocument(m_asmDocument); m_asmEditor->configureGenericHighlighter(Utils::mimeTypeForName("text/x-asm")); + m_asmEditor->setReadOnly(true); + + connect(m_asmEditor, &AsmEditorWidget::gotFocus, this, &CompilerWidget::gotFocus); auto advButton = new QPushButton; QSplitter *splitter{nullptr}; @@ -268,6 +284,11 @@ CompilerWidget::CompilerWidget(const std::shared_ptr &sourceSett connect(advButton, &QPushButton::clicked, advDlg, &QAction::trigger); advButton->setIcon(advDlg->icon()); + auto removeCompilerBtn = new QPushButton; + removeCompilerBtn->setIcon(Utils::Icons::EDIT_CLEAR.icon()); + removeCompilerBtn->setToolTip(Tr::tr("Remove compiler")); + connect(removeCompilerBtn, &QPushButton::clicked, this, &CompilerWidget::remove); + compile(m_sourceSettings->source()); connect(&m_sourceSettings->source, &Utils::StringAspect::volatileValueChanged, this, [this] { @@ -279,6 +300,7 @@ CompilerWidget::CompilerWidget(const std::shared_ptr &sourceSett Row { m_compilerSettings->compiler, advButton, + removeCompilerBtn, }, Splitter { bindTo(&splitter), @@ -368,84 +390,81 @@ void CompilerWidget::doCompile() m_compileWatcher.reset(new QFutureWatcher); - connect( - m_compileWatcher.get(), &QFutureWatcher::finished, this, [this] { - m_spinner->setVisible(false); - m_asmEditor->setEnabled(true); + connect(m_compileWatcher.get(), &QFutureWatcher::finished, this, [this] { + m_spinner->setVisible(false); + m_asmEditor->setEnabled(true); - try { - Api::CompileResult r = m_compileWatcher->result(); + try { + Api::CompileResult r = m_compileWatcher->result(); - m_resultTerminal->restart(); - m_resultTerminal->writeToTerminal("\x1b[?25l", false); + m_resultTerminal->restart(); + m_resultTerminal->writeToTerminal("\x1b[?25l", false); - for (const auto &err : r.stdErr) + for (const auto &err : r.stdErr) + m_resultTerminal->writeToTerminal((err.text + "\r\n").toUtf8(), false); + for (const auto &out : r.stdOut) + m_resultTerminal->writeToTerminal((out.text + "\r\n").toUtf8(), false); + + m_resultTerminal->writeToTerminal( + QString("ASM generation compiler returned: %1\r\n\r\n").arg(r.code).toUtf8(), true); + + if (r.execResult) { + for (const auto &err : r.execResult->buildResult.stdErr) m_resultTerminal->writeToTerminal((err.text + "\r\n").toUtf8(), false); - for (const auto &out : r.stdOut) + for (const auto &out : r.execResult->buildResult.stdOut) m_resultTerminal->writeToTerminal((out.text + "\r\n").toUtf8(), false); - m_resultTerminal->writeToTerminal( - QString("ASM generation compiler returned: %1\r\n\r\n").arg(r.code).toUtf8(), - true); + m_resultTerminal + ->writeToTerminal(QString("Execution build compiler returned: %1\r\n\r\n") + .arg(r.execResult->buildResult.code) + .toUtf8(), + true); - if (r.execResult) { - for (const auto &err : r.execResult->buildResult.stdErr) - m_resultTerminal->writeToTerminal((err.text + "\r\n").toUtf8(), false); - for (const auto &out : r.execResult->buildResult.stdOut) - m_resultTerminal->writeToTerminal((out.text + "\r\n").toUtf8(), false); + if (r.execResult->didExecute) { + m_resultTerminal->writeToTerminal(QString("Program returned: %1\r\n") + .arg(r.execResult->code) + .toUtf8(), + true); - m_resultTerminal - ->writeToTerminal(QString("Execution build compiler returned: %1\r\n\r\n") - .arg(r.execResult->buildResult.code) - .toUtf8(), - true); - - if (r.execResult->didExecute) { - m_resultTerminal->writeToTerminal(QString("Program returned: %1\r\n") - .arg(r.execResult->code) - .toUtf8(), - true); - - for (const auto &err : r.execResult->stdErrLines) - m_resultTerminal - ->writeToTerminal((" \033[0;31m" + err + "\033[0m\r\n").toUtf8(), - false); - for (const auto &out : r.execResult->stdOutLines) - m_resultTerminal->writeToTerminal((" " + out + "\r\n").toUtf8(), false); - } + for (const auto &err : r.execResult->stdErrLines) + m_resultTerminal + ->writeToTerminal((" \033[0;31m" + err + "\033[0m\r\n\r\n").toUtf8(), + false); + for (const auto &out : r.execResult->stdOutLines) + m_resultTerminal->writeToTerminal((out + "\r\n").toUtf8(), false); } - for (auto mark : m_marks) { - delete mark; - } - m_marks.clear(); - - QString asmText; - for (auto l : r.assemblyLines) - asmText += l.text + "\n"; - - m_asmDocument->setPlainText(asmText); - - int i = 0; - for (auto l : r.assemblyLines) { - i++; - if (l.opcodes.empty()) - continue; - - auto mark = new TextMark(m_asmDocument.get(), - i, - TextMarkCategory{"Bytes", "Bytes"}); - mark->setLineAnnotation(l.opcodes.join(' ')); - m_marks.append(mark); - } - } catch (const std::exception &e) { - qCritical() << "Exception: " << e.what(); } - }); + qDeleteAll(m_marks); + m_marks.clear(); + + QString asmText; + for (auto l : r.assemblyLines) + asmText += l.text + "\n"; + + m_asmDocument->setPlainText(asmText); + + int i = 0; + for (auto l : r.assemblyLines) { + i++; + if (l.opcodes.empty()) + continue; + + auto mark = new TextMark(m_asmDocument.get(), i, TextMarkCategory{"Bytes", "Bytes"}); + mark->setLineAnnotation(l.opcodes.join(' ')); + m_marks.append(mark); + } + } catch (const std::exception &e) { + qCritical() << "Exception: " << e.what(); + } + }); m_compileWatcher->setFuture(f); } -EditorWidget::EditorWidget(const QSharedPointer &document, QWidget *parent) +EditorWidget::EditorWidget(const QSharedPointer &document, + QUndoStack *undoStack, + TextEditorActionHandler &actionHandler, + QWidget *parent) : Utils::FancyMainWindow(parent) , m_document(document) { @@ -473,34 +492,55 @@ EditorWidget::EditorWidget(const QSharedPointer &document, return result; }); - auto addCompiler = [this](const std::shared_ptr &sourceSettings, - const std::shared_ptr &compilerSettings, - int idx) { + auto addCompiler = [this, + &actionHandler](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); + + connect(compiler, + &CompilerWidget::remove, + this, + [sourceSettings = sourceSettings.get(), compilerSettings = compilerSettings.get()] { + sourceSettings->compilers.removeItem(compilerSettings->shared_from_this()); + }); + + connect(compiler, &CompilerWidget::gotFocus, this, [&actionHandler] { + actionHandler.updateCurrentEditor(); + }); }; - auto addSourceEditor = [this, document = document.get(), addCompiler]( + auto addSourceEditor = [this, &actionHandler, document = document.get(), addCompiler, undoStack]( const std::shared_ptr &sourceSettings) { - auto sourceEditor = new SourceEditorWidget(sourceSettings); + auto sourceEditor = new SourceEditorWidget(sourceSettings, undoStack); sourceEditor->setWindowTitle("Source Code #" + QString::number(m_sourceWidgets.size() + 1)); sourceEditor->setObjectName("source_code_editor_" + QString::number(m_sourceWidgets.size() + 1)); QDockWidget *dockWidget = addDockForWidget(sourceEditor); - connect(dockWidget, - &QDockWidget::visibilityChanged, + connect(sourceEditor, + &SourceEditorWidget::remove, this, - [document, sourceSettings = sourceSettings.get(), dockWidget] { - if (!dockWidget->isVisible()) - document->settings()->m_sources.removeItem( - sourceSettings->shared_from_this()); + [document, sourceSettings = sourceSettings.get()] { + document->settings()->m_sources.removeItem(sourceSettings->shared_from_this()); }); + connect(sourceEditor, &SourceEditorWidget::addCompiler, this, [sourceSettings] { + auto newCompiler = std::make_shared( + sourceSettings->apiConfigFunction()); + newCompiler->setLanguageId(sourceSettings->languageId()); + sourceSettings->compilers.addItem(newCompiler); + }); + + connect(sourceEditor, &SourceEditorWidget::gotFocus, this, [&actionHandler] { + actionHandler.updateCurrentEditor(); + }); + addDockWidget(Qt::LeftDockWidgetArea, dockWidget); sourceSettings->compilers.forEachItem( @@ -519,13 +559,19 @@ EditorWidget::EditorWidget(const QSharedPointer &document, sourceSettings->compilers.setItemRemovedCallback( [this](const std::shared_ptr &compilerSettings) { - m_compilerWidgets.removeIf([compilerSettings](const QDockWidget *c) { - return static_cast(c->widget())->m_compilerSettings - == compilerSettings; - }); + auto it = std::find_if(m_compilerWidgets.begin(), + m_compilerWidgets.end(), + [compilerSettings](const QDockWidget *c) { + return static_cast(c->widget()) + ->m_compilerSettings + == compilerSettings; + }); + QTC_ASSERT(it != m_compilerWidgets.end(), return); + delete *it; + m_compilerWidgets.erase(it); }); - Aggregate *agg = Aggregate::parentAggregate(sourceEditor); + /*Aggregate *agg = Aggregate::parentAggregate(sourceEditor); if (!agg) { agg = new Aggregate; agg->add(sourceEditor); @@ -533,15 +579,21 @@ EditorWidget::EditorWidget(const QSharedPointer &document, 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 it = std::find_if(m_sourceWidgets.begin(), + m_sourceWidgets.end(), + [sourceSettings](const QDockWidget *c) { + return static_cast(c->widget()) + ->sourceSettings() + == sourceSettings.get(); + }); + QTC_ASSERT(it != m_sourceWidgets.end(), return); + delete *it; + m_sourceWidgets.erase(it); }; auto recreateEditors = [this, addSourceEditor]() { @@ -593,21 +645,63 @@ EditorWidget::~EditorWidget() m_sourceWidgets.clear(); } +TextEditor::TextEditorWidget *EditorWidget::focusedEditorWidget() const +{ + for (const QDockWidget *sourceWidget : m_sourceWidgets) { + TextEditorWidget *textEditor + = qobject_cast(sourceWidget->widget())->textEditor(); + if (textEditor->hasFocus()) + return textEditor; + } + + for (const QDockWidget *compilerWidget : m_compilerWidgets) { + TextEditorWidget *textEditor + = qobject_cast(compilerWidget->widget())->textEditor(); + if (textEditor->hasFocus()) + return textEditor; + } + + return nullptr; +} + class Editor : public Core::IEditor { public: - Editor() - : m_document(new JsonSettingsDocument()) + Editor(TextEditorActionHandler &actionHandler) + : m_document(new JsonSettingsDocument(&m_undoStack)) { - setWidget(new EditorWidget(m_document)); + setWidget(new EditorWidget(m_document, &m_undoStack, actionHandler)); + + connect(&m_undoStack, &QUndoStack::canUndoChanged, this, [&actionHandler] { + actionHandler.updateActions(); + }); + connect(&m_undoStack, &QUndoStack::canRedoChanged, this, [&actionHandler] { + actionHandler.updateActions(); + }); } - ~Editor() { delete widget(); } + ~Editor() + { + if (m_document->isModified()) { + auto settings = m_document->settings(); + if (settings->isDirty()) { + settings->apply(); + Utils::Store store; + settings->toMap(store); + QJsonDocument doc = QJsonDocument::fromVariant(Utils::mapFromStore(store)); + + CompilerExplorer::settings().defaultDocument.setValue( + QString::fromUtf8(doc.toJson())); + } + } + delete widget(); + } Core::IDocument *document() const override { return m_document.data(); } QWidget *toolBar() override { return nullptr; } QSharedPointer m_document; + QUndoStack m_undoStack; }; EditorFactory::EditorFactory() @@ -615,14 +709,32 @@ EditorFactory::EditorFactory() Constants::CE_EDITOR_CONTEXT_ID, TextEditor::TextEditorActionHandler::None, [](Core::IEditor *editor) -> TextEditorWidget * { - return Aggregation::query(editor->widget()); + return static_cast(editor->widget())->focusedEditorWidget(); }) { setId(Constants::CE_EDITOR_ID); setDisplayName(Tr::tr("Compiler Explorer Editor")); setMimeTypes({"application/compiler-explorer"}); - setEditorCreator([]() { return new Editor(); }); + auto undoStackFromEditor = [](Core::IEditor *editor) -> QUndoStack * { + if (!editor) + return nullptr; + return &static_cast(editor)->m_undoStack; + }; + + m_actionHandler.setCanUndoCallback([undoStackFromEditor](Core::IEditor *editor) { + if (auto undoStack = undoStackFromEditor(editor)) + return undoStack->canUndo(); + return false; + }); + + m_actionHandler.setCanRedoCallback([undoStackFromEditor](Core::IEditor *editor) { + if (auto undoStack = undoStackFromEditor(editor)) + return undoStack->canRedo(); + return false; + }); + + setEditorCreator([this]() { return new Editor(m_actionHandler); }); } } // namespace CompilerExplorer diff --git a/src/plugins/compilerexplorer/compilerexplorereditor.h b/src/plugins/compilerexplorer/compilerexplorereditor.h index f63da2ccc45..54befd824ef 100644 --- a/src/plugins/compilerexplorer/compilerexplorereditor.h +++ b/src/plugins/compilerexplorer/compilerexplorereditor.h @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -29,14 +30,54 @@ namespace CompilerExplorer { class JsonSettingsDocument; class SourceEditorWidget; -class CodeEditorWidget; + +class CodeEditorWidget : public TextEditor::TextEditorWidget +{ + Q_OBJECT +public: + CodeEditorWidget(const std::shared_ptr &settings, QUndoStack *undoStack); + + void updateHighlighter(); + + void undo() override { m_undoStack->undo(); } + void redo() override { m_undoStack->redo(); } + + void focusInEvent(QFocusEvent *event) override + { + TextEditorWidget::focusInEvent(event); + emit gotFocus(); + } + +signals: + void gotFocus(); + +private: + std::shared_ptr m_settings; + QUndoStack *m_undoStack; +}; + +class AsmEditorWidget : public TextEditor::TextEditorWidget +{ + Q_OBJECT + +public: + using TextEditor::TextEditorWidget::TextEditorWidget; + + void focusInEvent(QFocusEvent *event) override + { + TextEditorWidget::focusInEvent(event); + emit gotFocus(); + } + +signals: + void gotFocus(); +}; class JsonSettingsDocument : public Core::IDocument { Q_OBJECT public: - JsonSettingsDocument(); - ~JsonSettingsDocument() override; + JsonSettingsDocument(QUndoStack *undoStack); OpenResult open(QString *errorString, const Utils::FilePath &filePath, @@ -65,22 +106,31 @@ signals: private: mutable CompilerExplorerSettings m_ceSettings; std::function m_windowStateCallback; + QUndoStack *m_undoStack; }; class SourceEditorWidget : public QWidget { Q_OBJECT public: - SourceEditorWidget(const std::shared_ptr &settings); + SourceEditorWidget(const std::shared_ptr &settings, QUndoStack *undoStack); QString sourceCode(); + SourceSettings *sourceSettings() { return m_sourceSettings.get(); } + + void focusInEvent(QFocusEvent *) override { emit gotFocus(); } + + TextEditor::TextEditorWidget *textEditor() { return m_codeEditor; } - std::shared_ptr m_sourceSettings; signals: void sourceCodeChanged(); + void addCompiler(); + void remove(); + void gotFocus(); private: CodeEditorWidget *m_codeEditor{nullptr}; + std::shared_ptr m_sourceSettings; }; class CompilerWidget : public QWidget @@ -99,11 +149,19 @@ public: std::shared_ptr m_sourceSettings; std::shared_ptr m_compilerSettings; + void focusInEvent(QFocusEvent *) override { emit gotFocus(); } + + TextEditor::TextEditorWidget *textEditor() { return m_asmEditor; } + private: void doCompile(); +signals: + void remove(); + void gotFocus(); + private: - TextEditor::TextEditorWidget *m_asmEditor{nullptr}; + AsmEditorWidget *m_asmEditor{nullptr}; Core::SearchableTerminal *m_resultTerminal{nullptr}; SpinnerSolution::Spinner *m_spinner{nullptr}; @@ -120,9 +178,14 @@ class EditorWidget : public Utils::FancyMainWindow { Q_OBJECT public: - EditorWidget(const QSharedPointer &document, QWidget *parent = nullptr); + EditorWidget(const QSharedPointer &document, + QUndoStack *undoStack, + TextEditor::TextEditorActionHandler &actionHandler, + QWidget *parent = nullptr); ~EditorWidget() override; + TextEditor::TextEditorWidget *focusedEditorWidget() const; + signals: void sourceCodeChanged(); @@ -144,6 +207,9 @@ public: private: TextEditor::TextEditorActionHandler m_actionHandler; + + QAction m_undoAction; + QAction m_redoAction; }; } // namespace CompilerExplorer diff --git a/src/plugins/compilerexplorer/compilerexplorersettings.cpp b/src/plugins/compilerexplorer/compilerexplorersettings.cpp index adef327f80a..078b60f73eb 100644 --- a/src/plugins/compilerexplorer/compilerexplorersettings.cpp +++ b/src/plugins/compilerexplorer/compilerexplorersettings.cpp @@ -87,11 +87,12 @@ SourceSettings::SourceSettings(const ApiConfigFunction &apiConfigFunction) return result; }); - for (const auto &aspect : this->aspects()) + for (const auto &aspect : this->aspects()) { connect(aspect, &Utils::BaseAspect::volatileValueChanged, this, - &CompilerExplorerSettings::changed); + &Utils::AspectContainer::changed); + } } void SourceSettings::refresh() @@ -146,11 +147,12 @@ CompilerSettings::CompilerSettings(const ApiConfigFunction &apiConfigFunction) demangleIdentifiers.setLabelText(Tr::tr("Demangle identifiers")); demangleIdentifiers.setDefaultValue(true); - for (const auto &aspect : this->aspects()) + for (const auto &aspect : this->aspects()) { connect(aspect, &Utils::BaseAspect::volatileValueChanged, this, - &CompilerExplorerSettings::changed); + &Utils::AspectContainer::changed); + } } void CompilerSettings::refresh() @@ -325,11 +327,12 @@ CompilerExplorerSettings::CompilerExplorerSettings() m_sources.forEachItem(&SourceSettings::refresh); }); - for (const auto &aspect : this->aspects()) + for (const auto &aspect : this->aspects()) { connect(aspect, &Utils::BaseAspect::volatileValueChanged, this, &CompilerExplorerSettings::changed); + } } CompilerExplorerSettings::~CompilerExplorerSettings() = default; diff --git a/src/plugins/compilerexplorer/compilerexplorersettings.h b/src/plugins/compilerexplorer/compilerexplorersettings.h index 05c2474cb43..de0aa75e300 100644 --- a/src/plugins/compilerexplorer/compilerexplorersettings.h +++ b/src/plugins/compilerexplorer/compilerexplorersettings.h @@ -77,7 +77,8 @@ private: ApiConfigFunction m_apiConfigFunction; }; -class CompilerSettings : public Utils::AspectContainer +class CompilerSettings : public Utils::AspectContainer, + public std::enable_shared_from_this { public: CompilerSettings(const ApiConfigFunction &apiConfigFunction);