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) {
|
||||
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) {
|
||||
// Skip self-referential children (e.g. struct Ball has a field of type Ball)
|
||||
if (state.visiting.contains(tree.nodes[childIdx].id))
|
||||
const Node& child = tree.nodes[childIdx];
|
||||
// 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;
|
||||
}
|
||||
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}));
|
||||
}
|
||||
|
||||
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) {
|
||||
auto& tree = m_doc->tree;
|
||||
|
||||
@@ -1965,7 +2003,10 @@ void RcxController::handleMarginClick(RcxEditor* editor, int margin,
|
||||
if (!lm) return;
|
||||
|
||||
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) {
|
||||
emit nodeSelected(lm->nodeIdx);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ public:
|
||||
void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name);
|
||||
void removeNode(int nodeIdx);
|
||||
void toggleCollapse(int nodeIdx);
|
||||
void materializeRefChildren(int nodeIdx);
|
||||
void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false);
|
||||
void duplicateNode(int nodeIdx);
|
||||
void showContextMenu(RcxEditor* editor, int line, int nodeIdx, int subLine, const QPoint& globalPos);
|
||||
|
||||
Reference in New Issue
Block a user