mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Add class keyword picker, alignas alignment picker with member realignment
- Class keyword (struct/class/union/enum) persists in JSON, drives generator output - CommandRow2 shows alignas(N) pill computed from struct member alignment - Clicking alignas opens picker (1, 4, 8, 16) to realign all members - Going up: members repositioned to N-byte boundaries, padding fills gaps - Going down: excess padding removed, members pack tighter - Add cmd::ChangeOffset for undoable offset repositioning - All 11 tests pass Co-Authored-By: combuter <combuter@users.noreply.github.com>
This commit is contained in:
@@ -175,8 +175,10 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
||||
// Inline editing signals
|
||||
connect(editor, &RcxEditor::inlineEditCommitted,
|
||||
this, [this](int nodeIdx, int subLine, EditTarget target, const QString& text) {
|
||||
// CommandRow BaseAddress/Source edit has nodeIdx=-1
|
||||
if (nodeIdx < 0 && target != EditTarget::BaseAddress && target != EditTarget::Source) { refresh(); return; }
|
||||
// CommandRow BaseAddress/Source edit has nodeIdx=-1; CommandRow2 edits too
|
||||
if (nodeIdx < 0 && target != EditTarget::BaseAddress && target != EditTarget::Source
|
||||
&& target != EditTarget::RootClassType && target != EditTarget::RootClassName
|
||||
&& target != EditTarget::Alignas) { refresh(); return; }
|
||||
switch (target) {
|
||||
case EditTarget::Name: {
|
||||
if (text.isEmpty()) break;
|
||||
@@ -423,10 +425,59 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EditTarget::RootClassType: {
|
||||
QString kw = text.toLower().trimmed();
|
||||
if (kw != QStringLiteral("struct") && kw != QStringLiteral("class") && kw != QStringLiteral("enum")) break;
|
||||
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
|
||||
auto& n = m_doc->tree.nodes[i];
|
||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||
QString oldKw = n.resolvedClassKeyword();
|
||||
if (oldKw != kw) {
|
||||
m_doc->undoStack.push(new RcxCommand(this,
|
||||
cmd::ChangeClassKeyword{n.id, oldKw, kw}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EditTarget::RootClassName: {
|
||||
// Rename the root struct's structTypeName
|
||||
if (!text.isEmpty()) {
|
||||
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
|
||||
auto& n = m_doc->tree.nodes[i];
|
||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||
QString oldName = n.structTypeName;
|
||||
if (oldName != text) {
|
||||
m_doc->undoStack.push(new RcxCommand(this,
|
||||
cmd::ChangeStructTypeName{n.id, oldName, text}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EditTarget::ArrayIndex:
|
||||
case EditTarget::ArrayCount:
|
||||
// Array navigation removed - these cases are unreachable
|
||||
break;
|
||||
case EditTarget::Alignas: {
|
||||
// Parse "alignas(N)" → N
|
||||
int paren = text.indexOf('(');
|
||||
int close = text.indexOf(')');
|
||||
if (paren < 0 || close < 0) break;
|
||||
int newAlign = text.mid(paren + 1, close - paren - 1).toInt();
|
||||
if (newAlign <= 0) break;
|
||||
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
|
||||
const auto& n = m_doc->tree.nodes[i];
|
||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||
performRealignment(n.id, newAlign);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Always refresh to restore canonical text (handles parse failures, no-ops, etc.)
|
||||
refresh();
|
||||
@@ -447,11 +498,26 @@ void RcxController::refresh() {
|
||||
for (auto& lm : m_lastResult.meta) {
|
||||
if (lm.nodeIdx < 0 || lm.nodeIdx >= m_doc->tree.nodes.size()) continue;
|
||||
int64_t offset = m_doc->tree.computeOffset(lm.nodeIdx);
|
||||
int sz = m_doc->tree.nodes[lm.nodeIdx].byteSize();
|
||||
for (int64_t b = offset; b < offset + sz; b++) {
|
||||
if (m_changedOffsets.contains(b)) {
|
||||
lm.dataChanged = true;
|
||||
break;
|
||||
const Node& node = m_doc->tree.nodes[lm.nodeIdx];
|
||||
|
||||
if (isHexPreview(node.kind)) {
|
||||
// Per-byte tracking for hex preview nodes
|
||||
int lineOff = (node.kind == NodeKind::Padding) ? lm.subLine * 8 : 0;
|
||||
int byteCount = lm.lineByteCount;
|
||||
for (int b = 0; b < byteCount; b++) {
|
||||
if (m_changedOffsets.contains(offset + lineOff + b)) {
|
||||
lm.changedByteIndices.append(b);
|
||||
lm.dataChanged = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Existing boolean logic for non-hex nodes
|
||||
int sz = node.byteSize();
|
||||
for (int64_t b = offset; b < offset + sz; b++) {
|
||||
if (m_changedOffsets.contains(b)) {
|
||||
lm.dataChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -718,6 +784,14 @@ void RcxController::applyCommand(const Command& command, bool isUndo) {
|
||||
int idx = tree.indexOfId(c.nodeId);
|
||||
if (idx >= 0)
|
||||
tree.nodes[idx].structTypeName = isUndo ? c.oldName : c.newName;
|
||||
} else if constexpr (std::is_same_v<T, cmd::ChangeClassKeyword>) {
|
||||
int idx = tree.indexOfId(c.nodeId);
|
||||
if (idx >= 0)
|
||||
tree.nodes[idx].classKeyword = isUndo ? c.oldKeyword : c.newKeyword;
|
||||
} else if constexpr (std::is_same_v<T, cmd::ChangeOffset>) {
|
||||
int idx = tree.indexOfId(c.nodeId);
|
||||
if (idx >= 0)
|
||||
tree.nodes[idx].offset = isUndo ? c.oldOffset : c.newOffset;
|
||||
}
|
||||
}, command);
|
||||
|
||||
@@ -1052,7 +1126,7 @@ void RcxController::handleNodeClick(RcxEditor* source, int line,
|
||||
int to = qMax(m_anchorLine, line);
|
||||
for (int i = from; i <= to && i < m_lastResult.meta.size(); i++) {
|
||||
uint64_t nid = m_lastResult.meta[i].nodeId;
|
||||
if (nid != 0 && nid != kCommandRowId) m_selIds.insert(effectiveId(i, nid));
|
||||
if (nid != 0 && nid != kCommandRowId && nid != kCommandRow2Id) m_selIds.insert(effectiveId(i, nid));
|
||||
}
|
||||
}
|
||||
} else { // Ctrl+Shift
|
||||
@@ -1064,7 +1138,7 @@ void RcxController::handleNodeClick(RcxEditor* source, int line,
|
||||
int to = qMax(m_anchorLine, line);
|
||||
for (int i = from; i <= to && i < m_lastResult.meta.size(); i++) {
|
||||
uint64_t nid = m_lastResult.meta[i].nodeId;
|
||||
if (nid != 0 && nid != kCommandRowId) m_selIds.insert(effectiveId(i, nid));
|
||||
if (nid != 0 && nid != kCommandRowId && nid != kCommandRow2Id) m_selIds.insert(effectiveId(i, nid));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1092,6 +1166,100 @@ void RcxController::applySelectionOverlays() {
|
||||
editor->applySelectionOverlay(m_selIds);
|
||||
}
|
||||
|
||||
void RcxController::performRealignment(uint64_t structId, int targetAlign) {
|
||||
auto& tree = m_doc->tree;
|
||||
int rootIdx = tree.indexOfId(structId);
|
||||
if (rootIdx < 0) return;
|
||||
|
||||
// Gather direct children sorted by offset
|
||||
QVector<int> kids = tree.childrenOf(structId);
|
||||
std::sort(kids.begin(), kids.end(), [&](int a, int b) {
|
||||
return tree.nodes[a].offset < tree.nodes[b].offset;
|
||||
});
|
||||
|
||||
// Separate into real nodes (non-Padding) and padding nodes
|
||||
struct NodeInfo { uint64_t id; int offset; int size; };
|
||||
QVector<NodeInfo> realNodes;
|
||||
QVector<uint64_t> padIds;
|
||||
|
||||
for (int ci : kids) {
|
||||
const Node& child = tree.nodes[ci];
|
||||
int sz = (child.kind == NodeKind::Struct || child.kind == NodeKind::Array)
|
||||
? tree.structSpan(child.id) : child.byteSize();
|
||||
if (child.kind == NodeKind::Padding)
|
||||
padIds.append(child.id);
|
||||
else
|
||||
realNodes.append({child.id, child.offset, sz});
|
||||
}
|
||||
|
||||
auto roundUp = [](int x, int align) -> int {
|
||||
return align <= 1 ? x : ((x + align - 1) / align) * align;
|
||||
};
|
||||
|
||||
// Compute new offsets for real nodes
|
||||
struct OffChange { uint64_t id; int oldOff; int newOff; };
|
||||
QVector<OffChange> offChanges;
|
||||
int cursor = 0;
|
||||
for (auto& rn : realNodes) {
|
||||
int newOff = roundUp(cursor, targetAlign);
|
||||
if (newOff != rn.offset)
|
||||
offChanges.append({rn.id, rn.offset, newOff});
|
||||
rn.offset = newOff; // update local copy for gap computation
|
||||
cursor = newOff + rn.size;
|
||||
}
|
||||
|
||||
// Compute where padding is needed (gaps between consecutive nodes)
|
||||
struct PadInsert { int offset; int size; };
|
||||
QVector<PadInsert> padsNeeded;
|
||||
|
||||
for (int i = 0; i < realNodes.size(); i++) {
|
||||
int gapStart = (i == 0) ? 0 : realNodes[i - 1].offset + realNodes[i - 1].size;
|
||||
int gapEnd = realNodes[i].offset;
|
||||
if (gapEnd > gapStart)
|
||||
padsNeeded.append({gapStart, gapEnd - gapStart});
|
||||
}
|
||||
|
||||
// Check if anything actually changes
|
||||
if (offChanges.isEmpty() && padIds.isEmpty() && padsNeeded.isEmpty())
|
||||
return;
|
||||
|
||||
// Apply as undoable macro
|
||||
bool wasSuppressed = m_suppressRefresh;
|
||||
m_suppressRefresh = true;
|
||||
m_doc->undoStack.beginMacro(QStringLiteral("Realign to %1").arg(targetAlign));
|
||||
|
||||
// 1. Remove all existing Padding nodes (no offset adjustments — we recompute)
|
||||
for (uint64_t pid : padIds) {
|
||||
int idx = tree.indexOfId(pid);
|
||||
if (idx < 0) continue;
|
||||
QVector<Node> subtree;
|
||||
subtree.append(tree.nodes[idx]);
|
||||
m_doc->undoStack.push(new RcxCommand(this,
|
||||
cmd::Remove{pid, subtree, {}}));
|
||||
}
|
||||
|
||||
// 2. Reposition real nodes
|
||||
for (const auto& oc : offChanges) {
|
||||
m_doc->undoStack.push(new RcxCommand(this,
|
||||
cmd::ChangeOffset{oc.id, oc.oldOff, oc.newOff}));
|
||||
}
|
||||
|
||||
// 3. Insert new padding in gaps
|
||||
for (const auto& pi : padsNeeded) {
|
||||
Node pad;
|
||||
pad.kind = NodeKind::Padding;
|
||||
pad.parentId = structId;
|
||||
pad.offset = pi.offset;
|
||||
pad.arrayLen = pi.size;
|
||||
pad.id = tree.reserveId();
|
||||
m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{pad}));
|
||||
}
|
||||
|
||||
m_doc->undoStack.endMacro();
|
||||
m_suppressRefresh = wasSuppressed;
|
||||
if (!m_suppressRefresh) refresh();
|
||||
}
|
||||
|
||||
void RcxController::updateCommandRow() {
|
||||
// -- Source label: driven by provider metadata --
|
||||
QString src;
|
||||
@@ -1128,8 +1296,26 @@ void RcxController::updateCommandRow() {
|
||||
.arg(elide(src, 40), elide(addr, 24), elide(sym, 40));
|
||||
}
|
||||
|
||||
for (auto* ed : m_editors)
|
||||
// Build row 2: root class type + name + alignment
|
||||
QString row2;
|
||||
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
|
||||
const auto& n = m_doc->tree.nodes[i];
|
||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||
QString keyword = n.resolvedClassKeyword();
|
||||
QString className = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
||||
int alignment = m_doc->tree.computeStructAlignment(n.id);
|
||||
row2 = QStringLiteral("%1 %2 alignas(%3)")
|
||||
.arg(keyword, className).arg(alignment);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (row2.isEmpty())
|
||||
row2 = QStringLiteral("struct <no class> alignas(1)");
|
||||
|
||||
for (auto* ed : m_editors) {
|
||||
ed->setCommandRowText(row);
|
||||
ed->setCommandRow2Text(row2);
|
||||
}
|
||||
emit selectionChanged(m_selIds.size());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user