| 582 | } |
| 583 | |
| 584 | void RcxController::refresh() { |
| 585 | // Bracket compose with thread-local doc pointer for type name resolution |
| 586 | s_composeDoc = m_doc; |
| 587 | |
| 588 | // Compose against snapshot provider if active, otherwise real provider |
| 589 | if (m_snapshotProv) |
| 590 | m_lastResult = rcx::compose(m_doc->tree, *m_snapshotProv, m_viewRootId); |
| 591 | else |
| 592 | m_lastResult = m_doc->compose(m_viewRootId); |
| 593 | |
| 594 | s_composeDoc = nullptr; |
| 595 | |
| 596 | // Mark lines whose node data changed since last refresh |
| 597 | if (!m_changedOffsets.isEmpty()) { |
| 598 | for (auto& lm : m_lastResult.meta) { |
| 599 | if (lm.nodeIdx < 0 || lm.nodeIdx >= m_doc->tree.nodes.size()) continue; |
| 600 | int64_t offset = m_doc->tree.computeOffset(lm.nodeIdx); |
| 601 | const Node& node = m_doc->tree.nodes[lm.nodeIdx]; |
| 602 | |
| 603 | if (isHexPreview(node.kind)) { |
| 604 | // Per-byte tracking for hex preview nodes |
| 605 | int lineOff = (node.kind == NodeKind::Padding) ? lm.subLine * 8 : 0; |
| 606 | int byteCount = lm.lineByteCount; |
| 607 | for (int b = 0; b < byteCount; b++) { |
| 608 | if (m_changedOffsets.contains(offset + lineOff + b)) { |
| 609 | lm.changedByteIndices.append(b); |
| 610 | lm.dataChanged = true; |
| 611 | } |
| 612 | } |
| 613 | } else { |
| 614 | // Use structSpan for containers (byteSize returns 0 for Array-of-Struct) |
| 615 | int sz = (node.kind == NodeKind::Struct || node.kind == NodeKind::Array) |
| 616 | ? m_doc->tree.structSpan(node.id) : node.byteSize(); |
| 617 | for (int64_t b = offset; b < offset + sz; b++) { |
| 618 | if (m_changedOffsets.contains(b)) { |
| 619 | lm.dataChanged = true; |
| 620 | break; |
| 621 | } |
| 622 | } |
| 623 | } |
| 624 | } |
| 625 | } |
| 626 | |
| 627 | // Prune stale selections (nodes removed by undo/redo/delete) |
| 628 | QSet<uint64_t> valid; |
| 629 | for (uint64_t id : m_selIds) { |
| 630 | uint64_t nodeId = id & ~kFooterIdBit; // Strip footer bit for lookup |
| 631 | if (m_doc->tree.indexOfId(nodeId) >= 0) |
| 632 | valid.insert(id); // Keep original ID (with footer bit if present) |
| 633 | } |
| 634 | m_selIds = valid; |
| 635 | |
| 636 | // Collect unique struct type names for the type picker |
| 637 | QStringList customTypes; |
| 638 | QSet<QString> seen; |
| 639 | for (const auto& node : m_doc->tree.nodes) { |
| 640 | if (node.kind == NodeKind::Struct && !node.structTypeName.isEmpty()) { |
| 641 | if (!seen.contains(node.structTypeName)) { |