From d989e2a947e25773d4874458c210549a2335a1c0 Mon Sep 17 00:00:00 2001 From: IChooseYou Date: Thu, 19 Feb 2026 10:06:13 -0700 Subject: [PATCH] feat: safe workspace tree deletion with reference cleanup and confirmation - Add deleteRootStruct() that clears orphaned refId references before removal - Show confirmation dialog listing all fields that reference the deleted type - Auto-switch view to next root struct when the viewed one is deleted - Entire operation is a single undo macro (Ctrl+Z restores everything) --- src/controller.cpp | 42 ++++++++++++++++++++++++++++++++++++++++ src/controller.h | 1 + src/main.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/controller.cpp b/src/controller.cpp index 1d7f553..f9bb411 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -776,6 +776,48 @@ void RcxController::removeNode(int nodeIdx) { cmd::Remove{nodeId, subtree, adjs})); } +void RcxController::deleteRootStruct(uint64_t structId) { + int ni = m_doc->tree.indexOfId(structId); + if (ni < 0) return; + const Node& node = m_doc->tree.nodes[ni]; + if (node.parentId != 0 || node.kind != NodeKind::Struct) return; + + bool wasSuppressed = m_suppressRefresh; + m_suppressRefresh = true; + m_doc->undoStack.beginMacro(QStringLiteral("Delete root struct")); + + // Clear all refId references pointing to this struct + for (int i = 0; i < m_doc->tree.nodes.size(); i++) { + auto& n = m_doc->tree.nodes[i]; + if (n.refId == structId) { + m_doc->undoStack.push(new RcxCommand(this, + cmd::ChangePointerRef{n.id, n.refId, (uint64_t)0})); + } + } + + // Remove the struct + subtree (re-lookup since commands may shift indices) + ni = m_doc->tree.indexOfId(structId); + if (ni >= 0) + removeNode(ni); + + m_doc->undoStack.endMacro(); + m_suppressRefresh = wasSuppressed; + + // Switch view if we just deleted the viewed root + if (m_viewRootId == structId) { + uint64_t nextRoot = 0; + for (const auto& n : m_doc->tree.nodes) { + if (n.parentId == 0 && n.kind == NodeKind::Struct) { + nextRoot = n.id; + break; + } + } + setViewRootId(nextRoot); + } + + if (!m_suppressRefresh) refresh(); +} + void RcxController::toggleCollapse(int nodeIdx) { if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return; auto& node = m_doc->tree.nodes[nodeIdx]; diff --git a/src/controller.h b/src/controller.h index f809d3d..c28d690 100644 --- a/src/controller.h +++ b/src/controller.h @@ -99,6 +99,7 @@ public: void showContextMenu(RcxEditor* editor, int line, int nodeIdx, int subLine, const QPoint& globalPos); void batchRemoveNodes(const QVector& nodeIndices); void batchChangeKind(const QVector& nodeIndices, NodeKind newKind); + void deleteRootStruct(uint64_t structId); void applyCommand(const Command& cmd, bool isUndo); void refresh(); diff --git a/src/main.cpp b/src/main.cpp index f6827a1..e69e7a4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1698,7 +1698,53 @@ void MainWindow::createWorkspaceDock() { QAction* chosen = menu.exec(m_workspaceTree->viewport()->mapToGlobal(pos)); if (chosen == actDelete) { - tab.ctrl->removeNode(ni); + QString typeName = tab.doc->tree.nodes[ni].structTypeName.isEmpty() + ? tab.doc->tree.nodes[ni].name + : tab.doc->tree.nodes[ni].structTypeName; + if (typeName.isEmpty()) typeName = QStringLiteral("(unnamed)"); + + // Collect detailed reference info + QStringList refDetails; + for (const auto& n : tab.doc->tree.nodes) { + if (n.refId == structId) { + QString ownerName; + uint64_t pid = n.parentId; + while (pid != 0) { + int pi = tab.doc->tree.indexOfId(pid); + if (pi < 0) break; + if (tab.doc->tree.nodes[pi].parentId == 0) { + ownerName = tab.doc->tree.nodes[pi].structTypeName.isEmpty() + ? tab.doc->tree.nodes[pi].name + : tab.doc->tree.nodes[pi].structTypeName; + break; + } + pid = tab.doc->tree.nodes[pi].parentId; + } + QString fieldDesc = ownerName.isEmpty() + ? n.name + : QStringLiteral("%1::%2").arg(ownerName, n.name); + refDetails << QStringLiteral(" \u2022 %1 (%2)") + .arg(fieldDesc, kindToString(n.kind)); + } + } + + QString msg; + if (refDetails.isEmpty()) { + msg = QString("Delete '%1'?").arg(typeName); + } else { + msg = QString("Delete '%1'?\n\n" + "The following %2 field(s) reference this type " + "and will become untyped (void):\n\n%3") + .arg(typeName) + .arg(refDetails.size()) + .arg(refDetails.join('\n')); + } + + auto answer = QMessageBox::question(this, "Delete Type", msg, + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (answer != QMessageBox::Yes) return; + + tab.ctrl->deleteRootStruct(structId); rebuildWorkspaceModel(); } else if (chosen && chosen == actConvert) { QString newKw = kw == QStringLiteral("class")