mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Expand recursive structs instead of silently skipping cycle fields
Self-referential struct children (e.g. Ball containing Ball) now render as collapsed struct headers with a cycle marker. Clicking the fold margin materializes the referenced children and auto-expands the recursive child, allowing unlimited depth exploration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -345,12 +345,36 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
std::sort(refChildren.begin(), refChildren.end(), [&](int a, int b) {
|
std::sort(refChildren.begin(), refChildren.end(), [&](int a, int b) {
|
||||||
return tree.nodes[a].offset < tree.nodes[b].offset;
|
return tree.nodes[a].offset < tree.nodes[b].offset;
|
||||||
});
|
});
|
||||||
|
// Use the referenced struct's scope widths (children come from there)
|
||||||
|
uint64_t refScopeId = node.refId;
|
||||||
for (int childIdx : refChildren) {
|
for (int childIdx : refChildren) {
|
||||||
// Skip self-referential children (e.g. struct Ball has a field of type Ball)
|
const Node& child = tree.nodes[childIdx];
|
||||||
if (state.visiting.contains(tree.nodes[childIdx].id))
|
// Self-referential child → show as collapsed struct (non-expandable)
|
||||||
|
if (state.visiting.contains(child.id)) {
|
||||||
|
int typeW = state.effectiveTypeW(refScopeId);
|
||||||
|
int nameW = state.effectiveNameW(refScopeId);
|
||||||
|
LineMeta lm;
|
||||||
|
lm.nodeIdx = nodeIdx; // parent struct — materialize target
|
||||||
|
lm.nodeId = child.id;
|
||||||
|
lm.depth = childDepth;
|
||||||
|
lm.lineKind = LineKind::Header;
|
||||||
|
lm.offsetText = fmt::fmtOffsetMargin(
|
||||||
|
tree.baseAddress + absAddr + child.offset, false,
|
||||||
|
state.offsetHexDigits);
|
||||||
|
lm.offsetAddr = tree.baseAddress + absAddr + child.offset;
|
||||||
|
lm.nodeKind = child.kind;
|
||||||
|
lm.foldHead = true;
|
||||||
|
lm.foldCollapsed = true;
|
||||||
|
lm.foldLevel = computeFoldLevel(childDepth, true);
|
||||||
|
lm.markerMask = (1u << M_STRUCT_BG) | (1u << M_CYCLE);
|
||||||
|
lm.effectiveTypeW = typeW;
|
||||||
|
lm.effectiveNameW = nameW;
|
||||||
|
state.emitLine(fmt::fmtStructHeader(child, childDepth,
|
||||||
|
/*collapsed=*/true, typeW, nameW), lm);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
composeNode(state, tree, prov, childIdx, childDepth,
|
composeNode(state, tree, prov, childIdx, childDepth,
|
||||||
absAddr, node.refId, false, node.id);
|
absAddr, node.refId, false, refScopeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -792,6 +792,44 @@ void RcxController::toggleCollapse(int nodeIdx) {
|
|||||||
cmd::Collapse{node.id, node.collapsed, !node.collapsed}));
|
cmd::Collapse{node.id, node.collapsed, !node.collapsed}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RcxController::materializeRefChildren(int nodeIdx) {
|
||||||
|
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
||||||
|
auto& tree = m_doc->tree;
|
||||||
|
|
||||||
|
// Snapshot values before addNode invalidates references
|
||||||
|
const uint64_t parentId = tree.nodes[nodeIdx].id;
|
||||||
|
const uint64_t refId = tree.nodes[nodeIdx].refId;
|
||||||
|
const NodeKind parentKind = tree.nodes[nodeIdx].kind;
|
||||||
|
const QString parentName = tree.nodes[nodeIdx].name;
|
||||||
|
|
||||||
|
if (refId == 0) return;
|
||||||
|
if (!tree.childrenOf(parentId).isEmpty()) return; // already materialized
|
||||||
|
|
||||||
|
// Clone all children of the referenced struct as real children of this struct
|
||||||
|
QVector<int> refChildren = tree.childrenOf(refId);
|
||||||
|
for (int ci : refChildren) {
|
||||||
|
Node copy = tree.nodes[ci];
|
||||||
|
copy.id = 0; // auto-assign new ID
|
||||||
|
copy.parentId = parentId;
|
||||||
|
copy.collapsed = true; // start collapsed
|
||||||
|
tree.addNode(copy);
|
||||||
|
}
|
||||||
|
tree.invalidateIdCache();
|
||||||
|
|
||||||
|
// Auto-expand the self-referential child (the one that was the cycle)
|
||||||
|
// so the user gets expand in a single click
|
||||||
|
QVector<int> newChildren = tree.childrenOf(parentId);
|
||||||
|
for (int ci : newChildren) {
|
||||||
|
auto& c = tree.nodes[ci];
|
||||||
|
if (c.kind == parentKind && c.name == parentName && c.refId == refId) {
|
||||||
|
c.collapsed = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
void RcxController::applyCommand(const Command& command, bool isUndo) {
|
void RcxController::applyCommand(const Command& command, bool isUndo) {
|
||||||
auto& tree = m_doc->tree;
|
auto& tree = m_doc->tree;
|
||||||
|
|
||||||
@@ -1965,7 +2003,10 @@ void RcxController::handleMarginClick(RcxEditor* editor, int margin,
|
|||||||
if (!lm) return;
|
if (!lm) return;
|
||||||
|
|
||||||
if (lm->foldHead && (margin == 0 || margin == 1)) {
|
if (lm->foldHead && (margin == 0 || margin == 1)) {
|
||||||
toggleCollapse(lm->nodeIdx);
|
if (lm->markerMask & (1u << M_CYCLE))
|
||||||
|
materializeRefChildren(lm->nodeIdx);
|
||||||
|
else
|
||||||
|
toggleCollapse(lm->nodeIdx);
|
||||||
} else if (margin == 0 || margin == 1) {
|
} else if (margin == 0 || margin == 1) {
|
||||||
emit nodeSelected(lm->nodeIdx);
|
emit nodeSelected(lm->nodeIdx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public:
|
|||||||
void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name);
|
void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name);
|
||||||
void removeNode(int nodeIdx);
|
void removeNode(int nodeIdx);
|
||||||
void toggleCollapse(int nodeIdx);
|
void toggleCollapse(int nodeIdx);
|
||||||
|
void materializeRefChildren(int nodeIdx);
|
||||||
void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false);
|
void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false);
|
||||||
void duplicateNode(int nodeIdx);
|
void duplicateNode(int nodeIdx);
|
||||||
void showContextMenu(RcxEditor* editor, int line, int nodeIdx, int subLine, const QPoint& globalPos);
|
void showContextMenu(RcxEditor* editor, int line, int nodeIdx, int subLine, const QPoint& globalPos);
|
||||||
|
|||||||
Reference in New Issue
Block a user