mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Fold arrows, pointer format, teal custom types, collapsed pointers by default
- Change fold indicators from +/- to arrow icons (▸ collapsed, ▾ expanded) - Move ▾ dropdown arrow after text on command rows (source▾, struct▾) - Change pointer display from ptr64<Type> to Type* format - Color custom/user-defined types teal (#4EC9B0) via lexer GlobalClass - Keep built-in types blue (#569cd6) via KeywordSet2 - Remove underline from root class name on CommandRow2 - Pointer children start collapsed by default (lazy expansion) - Demo data updated accordingly
This commit is contained in:
@@ -13,15 +13,15 @@ using namespace rcx;
|
||||
static QString buildSourceLabel(const Provider& prov) {
|
||||
QString provName = prov.name();
|
||||
if (provName.isEmpty())
|
||||
return QStringLiteral("<Select Source>");
|
||||
return QStringLiteral("%1 '%2'").arg(prov.kind(), provName);
|
||||
return QStringLiteral("source\u25BE");
|
||||
return QStringLiteral("'%1'\u25BE").arg(provName);
|
||||
}
|
||||
|
||||
static QString buildCommandRow(const Provider& prov, uint64_t baseAddress) {
|
||||
QString src = buildSourceLabel(prov);
|
||||
QString addr = QStringLiteral("0x") +
|
||||
QString::number(baseAddress, 16).toUpper();
|
||||
return QStringLiteral(" %1 Address: %2").arg(src, addr);
|
||||
return QStringLiteral(" %1 \u203A %2").arg(src, addr);
|
||||
}
|
||||
|
||||
// -- Replicate commandRowSrcSpan for testing
|
||||
@@ -32,13 +32,17 @@ struct TestColumnSpan {
|
||||
};
|
||||
|
||||
static TestColumnSpan commandRowSrcSpan(const QString& lineText) {
|
||||
int idx = lineText.indexOf(QStringLiteral(" Address: "));
|
||||
int idx = lineText.indexOf(QStringLiteral(" \u203A"));
|
||||
if (idx < 0) return {};
|
||||
int start = 0;
|
||||
while (start < idx && !lineText[start].isLetterOrNumber()
|
||||
&& lineText[start] != '<') start++;
|
||||
&& lineText[start] != '<' && lineText[start] != '\'') start++;
|
||||
if (start >= idx) return {};
|
||||
return {start, idx, true};
|
||||
// Exclude trailing ▾ from the editable span
|
||||
int end = idx;
|
||||
while (end > start && lineText[end - 1] == QChar(0x25BE)) end--;
|
||||
if (end <= start) return {};
|
||||
return {start, end, true};
|
||||
}
|
||||
|
||||
class TestCommandRow : public QObject {
|
||||
@@ -52,18 +56,18 @@ private slots:
|
||||
|
||||
void label_nullProvider_showsSelectSource() {
|
||||
NullProvider p;
|
||||
QCOMPARE(buildSourceLabel(p), QStringLiteral("<Select Source>"));
|
||||
QCOMPARE(buildSourceLabel(p), QStringLiteral("source\u25BE"));
|
||||
}
|
||||
|
||||
void label_bufferNoName_showsSelectSource() {
|
||||
// BufferProvider with empty name also triggers <Select Source>
|
||||
// BufferProvider with empty name also triggers source▾
|
||||
BufferProvider p(QByteArray(4, '\0'));
|
||||
QCOMPARE(buildSourceLabel(p), QStringLiteral("<Select Source>"));
|
||||
QCOMPARE(buildSourceLabel(p), QStringLiteral("source\u25BE"));
|
||||
}
|
||||
|
||||
void label_bufferWithName_showsFileAndName() {
|
||||
BufferProvider p(QByteArray(4, '\0'), "dump.bin");
|
||||
QCOMPARE(buildSourceLabel(p), QStringLiteral("File 'dump.bin'"));
|
||||
QCOMPARE(buildSourceLabel(p), QStringLiteral("'dump.bin'\u25BE"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
@@ -73,13 +77,13 @@ private slots:
|
||||
void row_nullProvider() {
|
||||
NullProvider p;
|
||||
QString row = buildCommandRow(p, 0);
|
||||
QCOMPARE(row, QStringLiteral(" <Select Source> Address: 0x0"));
|
||||
QCOMPARE(row, QStringLiteral(" source\u25BE \u203A 0x0"));
|
||||
}
|
||||
|
||||
void row_fileProvider() {
|
||||
BufferProvider p(QByteArray(4, '\0'), "test.bin");
|
||||
QString row = buildCommandRow(p, 0x140000000ULL);
|
||||
QCOMPARE(row, QStringLiteral(" File 'test.bin' Address: 0x140000000"));
|
||||
QCOMPARE(row, QStringLiteral(" 'test.bin'\u25BE \u203A 0x140000000"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
@@ -91,7 +95,7 @@ private slots:
|
||||
auto span = commandRowSrcSpan(row);
|
||||
QVERIFY(span.valid);
|
||||
QString extracted = row.mid(span.start, span.end - span.start);
|
||||
QCOMPARE(extracted, QStringLiteral("<Select Source>"));
|
||||
QCOMPARE(extracted, QStringLiteral("source"));
|
||||
}
|
||||
|
||||
void span_fileProvider() {
|
||||
@@ -100,17 +104,17 @@ private slots:
|
||||
auto span = commandRowSrcSpan(row);
|
||||
QVERIFY(span.valid);
|
||||
QString extracted = row.mid(span.start, span.end - span.start);
|
||||
QCOMPARE(extracted, QStringLiteral("File 'dump.bin'"));
|
||||
QCOMPARE(extracted, QStringLiteral("'dump.bin'"));
|
||||
}
|
||||
|
||||
void span_processProvider_simulated() {
|
||||
// Simulate a process provider without needing Windows APIs
|
||||
// by building the string directly
|
||||
QString row = QStringLiteral(" Process 'notepad.exe' Address: 0x7FF600000000");
|
||||
QString row = QStringLiteral(" 'notepad.exe'\u25BE \u203A 0x7FF600000000");
|
||||
auto span = commandRowSrcSpan(row);
|
||||
QVERIFY(span.valid);
|
||||
QString extracted = row.mid(span.start, span.end - span.start);
|
||||
QCOMPARE(extracted, QStringLiteral("Process 'notepad.exe'"));
|
||||
QCOMPARE(extracted, QStringLiteral("'notepad.exe'"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
@@ -120,11 +124,11 @@ private slots:
|
||||
void switching_nullToFileToProcess() {
|
||||
// Start with NullProvider
|
||||
std::unique_ptr<Provider> prov = std::make_unique<NullProvider>();
|
||||
QCOMPARE(buildSourceLabel(*prov), QStringLiteral("<Select Source>"));
|
||||
QCOMPARE(buildSourceLabel(*prov), QStringLiteral("source\u25BE"));
|
||||
|
||||
// User loads a file
|
||||
prov = std::make_unique<BufferProvider>(QByteArray(64, '\0'), "game.exe");
|
||||
QCOMPARE(buildSourceLabel(*prov), QStringLiteral("File 'game.exe'"));
|
||||
QCOMPARE(buildSourceLabel(*prov), QStringLiteral("'game.exe'\u25BE"));
|
||||
|
||||
// User switches to a "process" -- simulate with a named BufferProvider
|
||||
// (ProcessProvider needs Windows, but the label logic is the same)
|
||||
|
||||
@@ -35,27 +35,30 @@ private slots:
|
||||
NullProvider prov;
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + 2 fields + root footer = 5
|
||||
QCOMPARE(result.meta.size(), 5);
|
||||
// CommandRow + Blank + CommandRow2 + 2 fields + root footer = 6
|
||||
QCOMPARE(result.meta.size(), 6);
|
||||
|
||||
// Line 0 is CommandRow
|
||||
QCOMPARE(result.meta[0].lineKind, LineKind::CommandRow);
|
||||
|
||||
// Line 1 is CommandRow2
|
||||
QCOMPARE(result.meta[1].lineKind, LineKind::CommandRow2);
|
||||
// Line 1 is Blank separator
|
||||
QCOMPARE(result.meta[1].lineKind, LineKind::Blank);
|
||||
|
||||
// Line 2 is CommandRow2
|
||||
QCOMPARE(result.meta[2].lineKind, LineKind::CommandRow2);
|
||||
|
||||
// Fields at depth 1
|
||||
QVERIFY(!result.meta[2].foldHead);
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QVERIFY(!result.meta[3].foldHead);
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
QVERIFY(!result.meta[4].foldHead);
|
||||
QCOMPARE(result.meta[4].depth, 1);
|
||||
|
||||
// Offset text
|
||||
QCOMPARE(result.meta[2].offsetText, QString("0"));
|
||||
QCOMPARE(result.meta[3].offsetText, QString("4"));
|
||||
QCOMPARE(result.meta[3].offsetText, QString("0"));
|
||||
QCOMPARE(result.meta[4].offsetText, QString("4"));
|
||||
|
||||
// Line 4 is root footer
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Footer);
|
||||
// Line 5 is root footer
|
||||
QCOMPARE(result.meta[5].lineKind, LineKind::Footer);
|
||||
}
|
||||
|
||||
void testVec3SingleLine() {
|
||||
@@ -79,17 +82,17 @@ private slots:
|
||||
NullProvider prov;
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + 1 Vec3 line + root footer = 4
|
||||
QCOMPARE(result.meta.size(), 4);
|
||||
// CommandRow + Blank + CommandRow2 + 1 Vec3 line + root footer = 5
|
||||
QCOMPARE(result.meta.size(), 5);
|
||||
|
||||
// Line 2: single Vec3 line, not continuation, depth 1
|
||||
QVERIFY(!result.meta[2].isContinuation);
|
||||
QCOMPARE(result.meta[2].offsetText, QString("0"));
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QCOMPARE(result.meta[2].nodeKind, NodeKind::Vec3);
|
||||
// Line 3: single Vec3 line, not continuation, depth 1
|
||||
QVERIFY(!result.meta[3].isContinuation);
|
||||
QCOMPARE(result.meta[3].offsetText, QString("0"));
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
QCOMPARE(result.meta[3].nodeKind, NodeKind::Vec3);
|
||||
|
||||
// Line 3 is root footer
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Footer);
|
||||
// Line 4 is root footer
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Footer);
|
||||
}
|
||||
|
||||
void testPaddingMarker() {
|
||||
@@ -113,13 +116,13 @@ private slots:
|
||||
NullProvider prov;
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + padding + root footer = 4
|
||||
QCOMPARE(result.meta.size(), 4);
|
||||
QVERIFY(result.meta[2].markerMask & (1u << M_PAD));
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
// CommandRow + Blank + CommandRow2 + padding + root footer = 5
|
||||
QCOMPARE(result.meta.size(), 5);
|
||||
QVERIFY(result.meta[3].markerMask & (1u << M_PAD));
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
|
||||
// Line 3 is root footer
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Footer);
|
||||
// Line 4 is root footer
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Footer);
|
||||
}
|
||||
|
||||
void testNullPointerMarker() {
|
||||
@@ -145,14 +148,14 @@ private slots:
|
||||
BufferProvider prov(data);
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + ptr + root footer = 4
|
||||
QCOMPARE(result.meta.size(), 4);
|
||||
// CommandRow + Blank + CommandRow2 + ptr + root footer = 5
|
||||
QCOMPARE(result.meta.size(), 5);
|
||||
// No ambient validation markers — M_PTR0 is no longer set
|
||||
QVERIFY(!(result.meta[2].markerMask & (1u << M_PTR0)));
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QVERIFY(!(result.meta[3].markerMask & (1u << M_PTR0)));
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
|
||||
// Line 3 is root footer
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Footer);
|
||||
// Line 4 is root footer
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Footer);
|
||||
}
|
||||
|
||||
void testCollapsedStruct() {
|
||||
@@ -178,11 +181,11 @@ private slots:
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// Collapsed root: isRootHeader overrides collapse, so children + footer still render
|
||||
// CommandRow + CommandRow2 + field + root footer = 4
|
||||
QCOMPARE(result.meta.size(), 4);
|
||||
QCOMPARE(result.meta[2].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Footer);
|
||||
// CommandRow + Blank + CommandRow2 + field + root footer = 5
|
||||
QCOMPARE(result.meta.size(), 5);
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Footer);
|
||||
}
|
||||
|
||||
void testUnreadablePointerNoRead() {
|
||||
@@ -209,15 +212,15 @@ private slots:
|
||||
BufferProvider prov(data);
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + ptr + root footer = 4
|
||||
QCOMPARE(result.meta.size(), 4);
|
||||
// CommandRow + Blank + CommandRow2 + ptr + root footer = 5
|
||||
QCOMPARE(result.meta.size(), 5);
|
||||
// No ambient validation markers
|
||||
QVERIFY(!(result.meta[2].markerMask & (1u << M_ERR)));
|
||||
QVERIFY(!(result.meta[2].markerMask & (1u << M_PTR0)));
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QVERIFY(!(result.meta[3].markerMask & (1u << M_ERR)));
|
||||
QVERIFY(!(result.meta[3].markerMask & (1u << M_PTR0)));
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
|
||||
// Line 3 is root footer
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Footer);
|
||||
// Line 4 is root footer
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Footer);
|
||||
}
|
||||
|
||||
void testFoldLevels() {
|
||||
@@ -250,13 +253,13 @@ private slots:
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// Child header (depth 1, fold head) — root header no longer emitted
|
||||
QCOMPARE(result.meta[2].foldLevel, 0x401 | 0x2000);
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QVERIFY(result.meta[2].foldHead);
|
||||
QCOMPARE(result.meta[3].foldLevel, 0x401 | 0x2000);
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
QVERIFY(result.meta[3].foldHead);
|
||||
|
||||
// Leaf (depth 2, not head)
|
||||
QCOMPARE(result.meta[3].foldLevel, 0x402);
|
||||
QCOMPARE(result.meta[3].depth, 2);
|
||||
QCOMPARE(result.meta[4].foldLevel, 0x402);
|
||||
QCOMPARE(result.meta[4].depth, 2);
|
||||
}
|
||||
|
||||
void testNestedStruct() {
|
||||
@@ -303,31 +306,31 @@ private slots:
|
||||
NullProvider prov;
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + flags + Inner header + x + y + Inner footer + root footer = 8
|
||||
QCOMPARE(result.meta.size(), 8);
|
||||
// CommandRow + Blank + CommandRow2 + flags + Inner header + x + y + Inner footer + root footer = 9
|
||||
QCOMPARE(result.meta.size(), 9);
|
||||
|
||||
// flags field (depth 1)
|
||||
QCOMPARE(result.meta[2].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
|
||||
// Inner header (depth 1, fold head)
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Header);
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
QVERIFY(result.meta[3].foldHead);
|
||||
QCOMPARE(result.meta[3].foldLevel, 0x401 | 0x2000);
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Header);
|
||||
QCOMPARE(result.meta[4].depth, 1);
|
||||
QVERIFY(result.meta[4].foldHead);
|
||||
QCOMPARE(result.meta[4].foldLevel, 0x401 | 0x2000);
|
||||
|
||||
// Inner fields at depth 2
|
||||
QCOMPARE(result.meta[4].depth, 2);
|
||||
QCOMPARE(result.meta[4].foldLevel, 0x402);
|
||||
QCOMPARE(result.meta[5].depth, 2);
|
||||
QCOMPARE(result.meta[5].foldLevel, 0x402);
|
||||
QCOMPARE(result.meta[6].depth, 2);
|
||||
|
||||
// Inner footer
|
||||
QCOMPARE(result.meta[6].lineKind, LineKind::Footer);
|
||||
QCOMPARE(result.meta[6].depth, 1);
|
||||
QCOMPARE(result.meta[7].lineKind, LineKind::Footer);
|
||||
QCOMPARE(result.meta[7].depth, 1);
|
||||
|
||||
// Root footer
|
||||
QCOMPARE(result.meta[7].lineKind, LineKind::Footer);
|
||||
QCOMPARE(result.meta[7].depth, 0);
|
||||
QCOMPARE(result.meta[8].lineKind, LineKind::Footer);
|
||||
QCOMPARE(result.meta[8].depth, 0);
|
||||
}
|
||||
|
||||
void testPointerDerefExpansion() {
|
||||
@@ -395,28 +398,28 @@ private slots:
|
||||
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + magic + ptr(merged fold header) + fn1 + fn2 + ptr footer + Main footer = 8
|
||||
// CommandRow + Blank + CommandRow2 + magic + ptr(merged fold header) + fn1 + fn2 + ptr footer + Main footer = 9
|
||||
// VTable standalone: header + fn1 + fn2 + footer = 4
|
||||
// Total = 12
|
||||
QCOMPARE(result.meta.size(), 12);
|
||||
// Total = 13
|
||||
QCOMPARE(result.meta.size(), 13);
|
||||
|
||||
// magic field (depth 1)
|
||||
QCOMPARE(result.meta[2].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
|
||||
// Pointer as merged fold header: "ptr64<VTable> ptr {"
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Header);
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
QVERIFY(result.meta[3].foldHead);
|
||||
QCOMPARE(result.meta[3].nodeKind, NodeKind::Pointer64);
|
||||
|
||||
// Pointer as merged fold header: "VTable* ptr {"
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Header);
|
||||
QCOMPARE(result.meta[4].depth, 1);
|
||||
QVERIFY(result.meta[4].foldHead);
|
||||
QCOMPARE(result.meta[4].nodeKind, NodeKind::Pointer64);
|
||||
|
||||
// Expanded fields at depth 2 (struct header merged into pointer)
|
||||
QCOMPARE(result.meta[4].depth, 2);
|
||||
QCOMPARE(result.meta[5].depth, 2);
|
||||
QCOMPARE(result.meta[6].depth, 2);
|
||||
|
||||
// Pointer fold footer
|
||||
QCOMPARE(result.meta[6].lineKind, LineKind::Footer);
|
||||
QCOMPARE(result.meta[6].depth, 1);
|
||||
QCOMPARE(result.meta[7].lineKind, LineKind::Footer);
|
||||
QCOMPARE(result.meta[7].depth, 1);
|
||||
}
|
||||
|
||||
void testPointerDerefNull() {
|
||||
@@ -460,18 +463,22 @@ private slots:
|
||||
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + ptr(merged fold header) + ptr footer + Main footer = 5
|
||||
// CommandRow + Blank + CommandRow2 + ptr(merged fold header) + target field + ptr footer + Main footer = 7
|
||||
// Target standalone: header + field + footer = 3
|
||||
// Total = 8
|
||||
QCOMPARE(result.meta.size(), 8);
|
||||
// Total = 10 (null ptr still shows template preview)
|
||||
QCOMPARE(result.meta.size(), 10);
|
||||
|
||||
// Pointer as merged fold header (expanded but empty — null ptr)
|
||||
QCOMPARE(result.meta[2].lineKind, LineKind::Header);
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QVERIFY(result.meta[2].foldHead);
|
||||
// Pointer as merged fold header (expanded — shows template at offset 0)
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Header);
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
QVERIFY(result.meta[3].foldHead);
|
||||
|
||||
// Pointer fold footer (empty expansion)
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Footer);
|
||||
// Target field shown as template preview
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[4].depth, 2);
|
||||
|
||||
// Pointer fold footer
|
||||
QCOMPARE(result.meta[5].lineKind, LineKind::Footer);
|
||||
}
|
||||
|
||||
void testPointerDerefCollapsed() {
|
||||
@@ -518,14 +525,14 @@ private slots:
|
||||
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + ptr(fold head, collapsed) + Main footer = 4
|
||||
// CommandRow + Blank + CommandRow2 + ptr(fold head, collapsed) + Main footer = 5
|
||||
// Target standalone: header + field + footer = 3
|
||||
// Total = 7
|
||||
QCOMPARE(result.meta.size(), 7);
|
||||
// Total = 8
|
||||
QCOMPARE(result.meta.size(), 8);
|
||||
|
||||
// Pointer is fold head (depth 1)
|
||||
QVERIFY(result.meta[2].foldHead);
|
||||
QCOMPARE(result.meta[2].depth, 1);
|
||||
QVERIFY(result.meta[3].foldHead);
|
||||
QCOMPARE(result.meta[3].depth, 1);
|
||||
}
|
||||
|
||||
void testPointerDerefCycle() {
|
||||
@@ -588,12 +595,12 @@ private slots:
|
||||
QVERIFY(result.meta.size() > 0);
|
||||
QVERIFY(result.meta.size() < 100); // sanity: bounded output
|
||||
|
||||
// CommandRow + CommandRow2 + ptr merged header + data + self merged header
|
||||
// CommandRow + Blank + CommandRow2 + ptr merged header + data + self merged header
|
||||
// Second expansion blocked by cycle guard: no children under self
|
||||
// Then: self footer + ptr footer + Main footer + standalone Recursive rendering
|
||||
QVERIFY(result.meta[2].foldHead); // ptr merged fold head
|
||||
QCOMPARE(result.meta[2].lineKind, LineKind::Header); // ptr merged header
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Field); // data field (first child of Recursive)
|
||||
QVERIFY(result.meta[3].foldHead); // ptr merged fold head
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Header); // ptr merged header
|
||||
QCOMPARE(result.meta[4].lineKind, LineKind::Field); // data field (first child of Recursive)
|
||||
}
|
||||
|
||||
void testStructFooterSimple() {
|
||||
@@ -657,12 +664,16 @@ private slots:
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
for (int i = 0; i < result.meta.size(); i++) {
|
||||
// Skip CommandRow / CommandRow2 (synthetic lines with sentinel nodeId)
|
||||
// Skip CommandRow / Blank / CommandRow2 (synthetic lines with sentinel nodeId)
|
||||
if (result.meta[i].lineKind == LineKind::CommandRow) {
|
||||
QCOMPARE(result.meta[i].nodeId, kCommandRowId);
|
||||
QCOMPARE(result.meta[i].nodeIdx, -1);
|
||||
continue;
|
||||
}
|
||||
if (result.meta[i].lineKind == LineKind::Blank) {
|
||||
QCOMPARE(result.meta[i].nodeIdx, -1);
|
||||
continue;
|
||||
}
|
||||
if (result.meta[i].lineKind == LineKind::CommandRow2) {
|
||||
QCOMPARE(result.meta[i].nodeId, kCommandRow2Id);
|
||||
QCOMPARE(result.meta[i].nodeIdx, -1);
|
||||
@@ -944,16 +955,16 @@ private slots:
|
||||
NullProvider prov;
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + Array header(collapsed) + root footer = 4
|
||||
QCOMPARE(result.meta.size(), 4);
|
||||
// CommandRow + Blank + CommandRow2 + Array header(collapsed) + root footer = 5
|
||||
QCOMPARE(result.meta.size(), 5);
|
||||
|
||||
// Array header is collapsed (at index 2)
|
||||
// Array header is collapsed (at index 3)
|
||||
int arrLine = -1;
|
||||
for (int i = 0; i < result.meta.size(); i++) {
|
||||
if (result.meta[i].isArrayHeader) { arrLine = i; break; }
|
||||
}
|
||||
QVERIFY(arrLine >= 0);
|
||||
QCOMPARE(arrLine, 2);
|
||||
QCOMPARE(arrLine, 3);
|
||||
QVERIFY(result.meta[arrLine].foldCollapsed);
|
||||
|
||||
// Header text should NOT contain "{"
|
||||
@@ -1024,7 +1035,7 @@ private slots:
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
|
||||
void testPointerDefaultVoid() {
|
||||
// Pointer64 with no refId should display as "ptr64<void>"
|
||||
// Pointer64 with no refId should display as "void*"
|
||||
NodeTree tree;
|
||||
tree.baseAddress = 0;
|
||||
|
||||
@@ -1059,8 +1070,8 @@ private slots:
|
||||
|
||||
QStringList lines = result.text.split('\n');
|
||||
QString text = lines[ptrLine];
|
||||
QVERIFY2(text.contains("ptr64<void>"),
|
||||
qPrintable("Pointer with no refId should show 'ptr64<void>': " + text));
|
||||
QVERIFY2(text.contains("void*"),
|
||||
qPrintable("Pointer with no refId should show 'void*': " + text));
|
||||
|
||||
// pointerTargetName should be empty (void)
|
||||
QVERIFY(result.meta[ptrLine].pointerTargetName.isEmpty());
|
||||
@@ -1094,13 +1105,13 @@ private slots:
|
||||
QStringList lines = result.text.split('\n');
|
||||
bool foundPtr32 = false;
|
||||
for (const QString& l : lines) {
|
||||
if (l.contains("ptr32<void>")) { foundPtr32 = true; break; }
|
||||
if (l.contains("void*")) { foundPtr32 = true; break; }
|
||||
}
|
||||
QVERIFY2(foundPtr32, "Pointer32 with no refId should show 'ptr32<void>'");
|
||||
QVERIFY2(foundPtr32, "Pointer32 with no refId should show 'void*'");
|
||||
}
|
||||
|
||||
void testPointerDisplaysTargetName() {
|
||||
// Pointer64 with refId displays "ptr64<TargetName>"
|
||||
// Pointer64 with refId displays "TargetName*"
|
||||
NodeTree tree;
|
||||
tree.baseAddress = 0;
|
||||
|
||||
@@ -1153,8 +1164,8 @@ private slots:
|
||||
QVERIFY(ptrLine >= 0);
|
||||
|
||||
QStringList lines = result.text.split('\n');
|
||||
QVERIFY2(lines[ptrLine].contains("ptr64<PlayerData>"),
|
||||
qPrintable("Should show 'ptr64<PlayerData>': " + lines[ptrLine]));
|
||||
QVERIFY2(lines[ptrLine].contains("PlayerData*"),
|
||||
qPrintable("Should show 'PlayerData*': " + lines[ptrLine]));
|
||||
|
||||
// pointerTargetName metadata
|
||||
QCOMPARE(result.meta[ptrLine].pointerTargetName, QString("PlayerData"));
|
||||
@@ -1200,7 +1211,7 @@ private slots:
|
||||
QStringList lines = result.text.split('\n');
|
||||
bool found = false;
|
||||
for (const QString& l : lines) {
|
||||
if (l.contains("ptr64<MyStruct>")) { found = true; break; }
|
||||
if (l.contains("MyStruct*")) { found = true; break; }
|
||||
}
|
||||
QVERIFY2(found, "Should use struct name when structTypeName is empty");
|
||||
}
|
||||
@@ -1252,22 +1263,19 @@ private slots:
|
||||
QString lineText = lines[ptrLine];
|
||||
const LineMeta& lm = result.meta[ptrLine];
|
||||
|
||||
// Kind span: covers "ptr64"
|
||||
// Kind span: no longer applicable in "Type*" format
|
||||
ColumnSpan kindSpan = pointerKindSpanFor(lm, lineText);
|
||||
QVERIFY2(kindSpan.valid, "pointerKindSpanFor must return valid span");
|
||||
QString kindText = lineText.mid(kindSpan.start, kindSpan.end - kindSpan.start);
|
||||
QVERIFY2(kindText.contains("ptr64"),
|
||||
qPrintable("Kind span should cover 'ptr64', got: '" + kindText + "'"));
|
||||
QVERIFY2(!kindSpan.valid, "pointerKindSpanFor should return invalid in Type* format");
|
||||
|
||||
// Target span: covers "VTable"
|
||||
// Target span: covers "VTable" (before the '*')
|
||||
ColumnSpan targetSpan = pointerTargetSpanFor(lm, lineText);
|
||||
QVERIFY2(targetSpan.valid, "pointerTargetSpanFor must return valid span");
|
||||
QString targetText = lineText.mid(targetSpan.start, targetSpan.end - targetSpan.start);
|
||||
QString targetText = lineText.mid(targetSpan.start, targetSpan.end - targetSpan.start).trimmed();
|
||||
QCOMPARE(targetText, QString("VTable"));
|
||||
}
|
||||
|
||||
void testPointerVoidSpans() {
|
||||
// Even void* pointer should have valid kind and target spans
|
||||
// void* pointer should have valid target span but no kind span
|
||||
NodeTree tree;
|
||||
tree.baseAddress = 0;
|
||||
|
||||
@@ -1302,19 +1310,19 @@ private slots:
|
||||
QString lineText = lines[ptrLine];
|
||||
const LineMeta& lm = result.meta[ptrLine];
|
||||
|
||||
// Kind span: "ptr64"
|
||||
// Kind span: no longer applicable in "Type*" format
|
||||
ColumnSpan kindSpan = pointerKindSpanFor(lm, lineText);
|
||||
QVERIFY2(kindSpan.valid, "void* pointer should have valid kind span");
|
||||
QVERIFY2(!kindSpan.valid, "Kind span should be invalid in Type* format");
|
||||
|
||||
// Target span: "void"
|
||||
// Target span: "void" (before the '*')
|
||||
ColumnSpan targetSpan = pointerTargetSpanFor(lm, lineText);
|
||||
QVERIFY2(targetSpan.valid, "void* pointer should have valid target span");
|
||||
QString targetText = lineText.mid(targetSpan.start, targetSpan.end - targetSpan.start);
|
||||
QString targetText = lineText.mid(targetSpan.start, targetSpan.end - targetSpan.start).trimmed();
|
||||
QCOMPARE(targetText, QString("void"));
|
||||
}
|
||||
|
||||
void testPointerToPointerChain() {
|
||||
// ptr64<StructB> → StructB { ptr64<StructC> } → StructC { field }
|
||||
// StructB* → StructB { StructC* } → StructC { field }
|
||||
NodeTree tree;
|
||||
tree.baseAddress = 0;
|
||||
|
||||
@@ -1390,18 +1398,18 @@ private slots:
|
||||
QVERIFY(result.meta.size() > 0);
|
||||
QVERIFY(result.meta.size() < 200);
|
||||
|
||||
// Check that ptr64<Wrapper> and ptr64<InnerData> both appear in text
|
||||
// Check that Wrapper* and InnerData* both appear in text
|
||||
bool foundWrapper = false, foundInner = false;
|
||||
QStringList lines = result.text.split('\n');
|
||||
for (const QString& l : lines) {
|
||||
if (l.contains("ptr64<Wrapper>")) foundWrapper = true;
|
||||
if (l.contains("ptr64<InnerData>")) foundInner = true;
|
||||
if (l.contains("Wrapper*")) foundWrapper = true;
|
||||
if (l.contains("InnerData*")) foundInner = true;
|
||||
}
|
||||
QVERIFY2(foundWrapper, "Should display 'ptr64<Wrapper>'");
|
||||
QVERIFY2(foundInner, "Should display 'ptr64<InnerData>'");
|
||||
QVERIFY2(foundWrapper, "Should display 'Wrapper*'");
|
||||
QVERIFY2(foundInner, "Should display 'InnerData*'");
|
||||
|
||||
// The chain: Root → ptr64<Wrapper>(fold head) → Wrapper expanded →
|
||||
// ptr64<InnerData>(fold head) → InnerData expanded
|
||||
// The chain: Root → Wrapper*(fold head) → Wrapper expanded →
|
||||
// InnerData*(fold head) → InnerData expanded
|
||||
int foldHeadCount = 0;
|
||||
for (const LineMeta& lm : result.meta) {
|
||||
if (lm.foldHead && lm.nodeKind == NodeKind::Pointer64)
|
||||
@@ -1490,15 +1498,15 @@ private slots:
|
||||
qPrintable(QString("Cycle should be bounded, got %1 lines")
|
||||
.arg(result.meta.size())));
|
||||
|
||||
// Both ptr64<StructB> and ptr64<Main> should appear
|
||||
// Both StructB* and Main* should appear
|
||||
bool foundToB = false, foundToMain = false;
|
||||
QStringList lines = result.text.split('\n');
|
||||
for (const QString& l : lines) {
|
||||
if (l.contains("ptr64<StructB>")) foundToB = true;
|
||||
if (l.contains("ptr64<Main>")) foundToMain = true;
|
||||
if (l.contains("StructB*")) foundToB = true;
|
||||
if (l.contains("Main*")) foundToMain = true;
|
||||
}
|
||||
QVERIFY2(foundToB, "Should display 'ptr64<StructB>'");
|
||||
QVERIFY2(foundToMain, "Should display 'ptr64<Main>'");
|
||||
QVERIFY2(foundToB, "Should display 'StructB*'");
|
||||
QVERIFY2(foundToMain, "Should display 'Main*'");
|
||||
|
||||
// The first expansion of each pointer works;
|
||||
// the cycle is caught on the second attempt.
|
||||
@@ -1555,10 +1563,10 @@ private slots:
|
||||
NullProvider prov;
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// Every struct name should appear in a "ptr64<Name>" format
|
||||
// Every struct name should appear in a "Name*" format
|
||||
QStringList lines = result.text.split('\n');
|
||||
for (const QString& sname : structNames) {
|
||||
QString expected = QString("ptr64<%1>").arg(sname);
|
||||
QString expected = QString("%1*").arg(sname);
|
||||
bool found = false;
|
||||
for (const QString& l : lines) {
|
||||
if (l.contains(expected)) { found = true; break; }
|
||||
@@ -1594,9 +1602,9 @@ private slots:
|
||||
QStringList lines = result.text.split('\n');
|
||||
bool foundVoid = false;
|
||||
for (const QString& l : lines) {
|
||||
if (l.contains("ptr64<void>")) { foundVoid = true; break; }
|
||||
if (l.contains("void*")) { foundVoid = true; break; }
|
||||
}
|
||||
QVERIFY2(foundVoid, "Dangling refId should degrade to ptr64<void>");
|
||||
QVERIFY2(foundVoid, "Dangling refId should degrade to void*");
|
||||
}
|
||||
|
||||
void testPointerCollapsedNoExpansion() {
|
||||
@@ -1662,7 +1670,7 @@ private slots:
|
||||
}
|
||||
|
||||
void testPointerWidthComputation() {
|
||||
// Type column must be wide enough for "ptr64<LongStructName>"
|
||||
// Type column must be wide enough for "LongStructName*"
|
||||
NodeTree tree;
|
||||
tree.baseAddress = 0;
|
||||
|
||||
@@ -1698,7 +1706,7 @@ private slots:
|
||||
QStringList lines = result.text.split('\n');
|
||||
bool foundFull = false;
|
||||
for (const QString& l : lines) {
|
||||
if (l.contains("ptr64<VeryLongStructNameForTesting>")) {
|
||||
if (l.contains("VeryLongStructNameForTesting*")) {
|
||||
foundFull = true;
|
||||
break;
|
||||
}
|
||||
@@ -1707,9 +1715,9 @@ private slots:
|
||||
"Type column should be wide enough for long pointer target names");
|
||||
|
||||
// Layout type width should accommodate the long name
|
||||
// "ptr64<VeryLongStructNameForTesting>" = 35 chars
|
||||
QVERIFY2(result.layout.typeW >= 35,
|
||||
qPrintable(QString("typeW=%1, should be >= 35").arg(result.layout.typeW)));
|
||||
// "VeryLongStructNameForTesting*" = 29 chars
|
||||
QVERIFY2(result.layout.typeW >= 29,
|
||||
qPrintable(QString("typeW=%1, should be >= 29").arg(result.layout.typeW)));
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
@@ -1820,7 +1828,7 @@ private slots:
|
||||
|
||||
void testCommandRow2NameSpan() {
|
||||
// Name span should cover the class name
|
||||
QString text = "struct MyClass";
|
||||
QString text = "struct\u25BE MyClass";
|
||||
ColumnSpan nameSpan = commandRow2NameSpan(text);
|
||||
QVERIFY(nameSpan.valid);
|
||||
|
||||
@@ -1873,10 +1881,11 @@ private slots:
|
||||
QVERIFY2(result.meta.size() >= 5,
|
||||
qPrintable(QString("Expected >= 5 lines, got %1").arg(result.meta.size())));
|
||||
|
||||
// Every line should have text content
|
||||
// Every non-blank line should have text content
|
||||
QStringList lines = result.text.split('\n');
|
||||
QCOMPARE(lines.size(), result.meta.size());
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
if (result.meta[i].lineKind == LineKind::Blank) continue;
|
||||
QVERIFY2(!lines[i].isEmpty(),
|
||||
qPrintable(QString("Line %1 is empty").arg(i)));
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ private slots:
|
||||
|
||||
// Set CommandRow text with an ADDR value (simulates controller.updateCommandRow)
|
||||
m_editor->setCommandRowText(
|
||||
QStringLiteral(" File Address: 0xD87B5E5000"));
|
||||
QStringLiteral("source\u25BE \u203A 0xD87B5E5000"));
|
||||
|
||||
// BaseAddress should be ALLOWED on CommandRow (ADDR field)
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::BaseAddress, 0);
|
||||
@@ -394,14 +394,14 @@ private slots:
|
||||
|
||||
// ── Test: inline edit lifecycle (begin → commit → re-edit) ──
|
||||
void testInlineEditReEntry() {
|
||||
// Move cursor to line 2 (first field; line 0=CommandRow, 1=CommandRow2, root header suppressed)
|
||||
m_editor->scintilla()->setCursorPosition(2, 0);
|
||||
// Move cursor to line 3 (first field; 0=CommandRow, 1=Blank, 2=CommandRow2, root header suppressed)
|
||||
m_editor->scintilla()->setCursorPosition(kFirstDataLine, 0);
|
||||
|
||||
// Should not be editing
|
||||
QVERIFY(!m_editor->isEditing());
|
||||
|
||||
// Begin edit on Name column
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Name, 2);
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Name, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
QVERIFY(m_editor->isEditing());
|
||||
|
||||
@@ -413,7 +413,7 @@ private slots:
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Should be able to edit again
|
||||
ok = m_editor->beginInlineEdit(EditTarget::Name, 2);
|
||||
ok = m_editor->beginInlineEdit(EditTarget::Name, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
QVERIFY(m_editor->isEditing());
|
||||
|
||||
@@ -425,10 +425,10 @@ private slots:
|
||||
// ── Test: commit inline edit then re-edit same line ──
|
||||
void testCommitThenReEdit() {
|
||||
m_editor->applyDocument(m_result);
|
||||
m_editor->scintilla()->setCursorPosition(2, 0);
|
||||
m_editor->scintilla()->setCursorPosition(kFirstDataLine, 0);
|
||||
|
||||
// Begin value edit
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Value, 2);
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Value, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
QVERIFY(m_editor->isEditing());
|
||||
|
||||
@@ -445,7 +445,7 @@ private slots:
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Must be able to edit the same line again
|
||||
ok = m_editor->beginInlineEdit(EditTarget::Value, 2);
|
||||
ok = m_editor->beginInlineEdit(EditTarget::Value, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
QVERIFY(m_editor->isEditing());
|
||||
|
||||
@@ -456,7 +456,7 @@ private slots:
|
||||
void testMouseClickCommitsEdit() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Name, 2);
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Name, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
QVERIFY(m_editor->isEditing());
|
||||
|
||||
@@ -478,7 +478,7 @@ private slots:
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Begin type edit on a field line
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Type, 2);
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Type, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
QVERIFY(m_editor->isEditing());
|
||||
|
||||
@@ -598,7 +598,7 @@ private slots:
|
||||
void testTypeAutocompleteTypingAndCommit() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Type, 2);
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Type, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
|
||||
// Autocomplete is deferred via QTimer::singleShot(0) — poll until active
|
||||
@@ -635,7 +635,7 @@ private slots:
|
||||
void testTypeEditClickAwayNoChange() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Type, 2);
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Type, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
|
||||
// Process deferred autocomplete
|
||||
@@ -652,7 +652,7 @@ private slots:
|
||||
QCOMPARE(commitSpy.count(), 1);
|
||||
|
||||
// The committed text should be the original typeName (no change)
|
||||
// First field at line 2 is InheritedAddressSpace (UInt8 → "uint8_t")
|
||||
// First field at kFirstDataLine is InheritedAddressSpace (UInt8 → "uint8_t")
|
||||
QList<QVariant> args = commitSpy.first();
|
||||
QString committedText = args.at(3).toString();
|
||||
QVERIFY2(committedText == "uint8_t",
|
||||
@@ -665,8 +665,8 @@ private slots:
|
||||
void testColumnSpanHitTest() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Line 2 is a field line (UInt8), verify spans are valid (line 0=CommandRow, 1=CommandRow2)
|
||||
const LineMeta* lm = m_editor->metaForLine(2);
|
||||
// kFirstDataLine is a field line (UInt8), verify spans are valid
|
||||
const LineMeta* lm = m_editor->metaForLine(kFirstDataLine);
|
||||
QVERIFY(lm);
|
||||
QCOMPARE(lm->lineKind, LineKind::Field);
|
||||
|
||||
@@ -683,7 +683,7 @@ private slots:
|
||||
// Value span should be valid for field lines
|
||||
QString lineText;
|
||||
int len = (int)m_editor->scintilla()->SendScintilla(
|
||||
QsciScintillaBase::SCI_LINELENGTH, (unsigned long)2);
|
||||
QsciScintillaBase::SCI_LINELENGTH, (unsigned long)kFirstDataLine);
|
||||
QVERIFY(len > 0);
|
||||
ColumnSpan vs = RcxEditor::valueSpan(*lm, len);
|
||||
QVERIFY(vs.valid);
|
||||
@@ -712,13 +712,13 @@ private slots:
|
||||
void testSelectedNodeIndices() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Put cursor on first field line (line 2; 0=CommandRow, 1=CommandRow2, root header suppressed)
|
||||
m_editor->scintilla()->setCursorPosition(2, 0);
|
||||
// Put cursor on first field line (kFirstDataLine; 0=CommandRow, 1=Blank, 2=CommandRow2)
|
||||
m_editor->scintilla()->setCursorPosition(kFirstDataLine, 0);
|
||||
QSet<int> sel = m_editor->selectedNodeIndices();
|
||||
QCOMPARE(sel.size(), 1);
|
||||
|
||||
// The node index should match the first field
|
||||
const LineMeta* lm = m_editor->metaForLine(2);
|
||||
const LineMeta* lm = m_editor->metaForLine(kFirstDataLine);
|
||||
QVERIFY(lm);
|
||||
QVERIFY(sel.contains(lm->nodeIdx));
|
||||
}
|
||||
@@ -736,8 +736,8 @@ private slots:
|
||||
QVERIFY2(!result.text.contains("// base:"),
|
||||
"Composed text should not contain '// base:' (consolidated into cmd bar)");
|
||||
|
||||
// Line 2 should be the first field (root header suppressed)
|
||||
const LineMeta* lm = m_editor->metaForLine(2);
|
||||
// kFirstDataLine should be the first field (root header suppressed)
|
||||
const LineMeta* lm = m_editor->metaForLine(kFirstDataLine);
|
||||
QVERIFY(lm);
|
||||
QCOMPARE(lm->lineKind, LineKind::Field);
|
||||
|
||||
@@ -750,7 +750,7 @@ private slots:
|
||||
|
||||
// Set CommandRow text with ADDR value (simulates controller)
|
||||
m_editor->setCommandRowText(
|
||||
QStringLiteral(" File Address: 0xD87B5E5000"));
|
||||
QStringLiteral("source\u25BE \u203A 0xD87B5E5000"));
|
||||
|
||||
// Line 0 is CommandRow
|
||||
const LineMeta* lm = m_editor->metaForLine(0);
|
||||
@@ -852,14 +852,14 @@ private slots:
|
||||
void testValueEditCommitUpdatesSignal() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Line 2 = first UInt8 field (InheritedAddressSpace, root header suppressed)
|
||||
const LineMeta* lm = m_editor->metaForLine(2);
|
||||
// kFirstDataLine = first UInt8 field (InheritedAddressSpace, root header suppressed)
|
||||
const LineMeta* lm = m_editor->metaForLine(kFirstDataLine);
|
||||
QVERIFY(lm);
|
||||
QCOMPARE(lm->lineKind, LineKind::Field);
|
||||
QVERIFY(lm->nodeKind != NodeKind::Padding);
|
||||
|
||||
// Begin value edit
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Value, 2);
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::Value, kFirstDataLine);
|
||||
QVERIFY(ok);
|
||||
QVERIFY(m_editor->isEditing());
|
||||
|
||||
@@ -902,7 +902,7 @@ private slots:
|
||||
|
||||
// Set CommandRow text with ADDR value (simulates controller)
|
||||
m_editor->setCommandRowText(
|
||||
QStringLiteral(" File Address: 0xD87B5E5000"));
|
||||
QStringLiteral("source\u25BE \u203A 0xD87B5E5000"));
|
||||
|
||||
// Begin base address edit on line 0 (CommandRow ADDR field)
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::BaseAddress, 0);
|
||||
@@ -919,7 +919,7 @@ private slots:
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Click on a field line at the indent area (col 0 — not over editable text)
|
||||
QPoint clickPos = colToViewport(m_editor->scintilla(), 2, 0);
|
||||
QPoint clickPos = colToViewport(m_editor->scintilla(), kFirstDataLine, 0);
|
||||
sendLeftClick(m_editor->scintilla()->viewport(), clickPos);
|
||||
QApplication::processEvents();
|
||||
|
||||
@@ -932,8 +932,8 @@ private slots:
|
||||
void testCursorShapeOverText() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Line 2 is a field (UInt8 InheritedAddressSpace)
|
||||
const LineMeta* lm = m_editor->metaForLine(2);
|
||||
// kFirstDataLine is a field (UInt8 InheritedAddressSpace)
|
||||
const LineMeta* lm = m_editor->metaForLine(kFirstDataLine);
|
||||
QVERIFY(lm);
|
||||
|
||||
// Get the name span (padded to kColName width)
|
||||
@@ -941,14 +941,14 @@ private slots:
|
||||
QVERIFY(ns.valid);
|
||||
|
||||
// Move mouse to the start of the name span (should be over text)
|
||||
QPoint textPos = colToViewport(m_editor->scintilla(), 2, ns.start + 1);
|
||||
QPoint textPos = colToViewport(m_editor->scintilla(), kFirstDataLine, ns.start + 1);
|
||||
sendMouseMove(m_editor->scintilla()->viewport(), textPos);
|
||||
QApplication::processEvents();
|
||||
QCOMPARE(viewportCursor(m_editor), Qt::IBeamCursor);
|
||||
|
||||
// Move mouse to far padding area (past end of text, within padded span)
|
||||
// The padded span ends at ns.end but the trimmed text is shorter
|
||||
QPoint padPos = colToViewport(m_editor->scintilla(), 2, ns.end - 1);
|
||||
QPoint padPos = colToViewport(m_editor->scintilla(), kFirstDataLine, ns.end - 1);
|
||||
sendMouseMove(m_editor->scintilla()->viewport(), padPos);
|
||||
QApplication::processEvents();
|
||||
// Should be Arrow (padding whitespace, not actual text)
|
||||
@@ -959,7 +959,7 @@ private slots:
|
||||
void testCursorShapeOverType() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
const LineMeta* lm = m_editor->metaForLine(2);
|
||||
const LineMeta* lm = m_editor->metaForLine(kFirstDataLine);
|
||||
QVERIFY(lm);
|
||||
|
||||
// Type span starts after the fold column + indent
|
||||
@@ -967,7 +967,7 @@ private slots:
|
||||
QVERIFY(ts.valid);
|
||||
|
||||
// Move to start of type text (e.g. "uint8_t")
|
||||
QPoint typePos = colToViewport(m_editor->scintilla(), 2, ts.start + 1);
|
||||
QPoint typePos = colToViewport(m_editor->scintilla(), kFirstDataLine, ts.start + 1);
|
||||
sendMouseMove(m_editor->scintilla()->viewport(), typePos);
|
||||
QApplication::processEvents();
|
||||
QCOMPARE(viewportCursor(m_editor), Qt::PointingHandCursor);
|
||||
@@ -1013,18 +1013,18 @@ private slots:
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Click on a field to select the node
|
||||
const LineMeta* lm = m_editor->metaForLine(2);
|
||||
const LineMeta* lm = m_editor->metaForLine(kFirstDataLine);
|
||||
QVERIFY(lm);
|
||||
ColumnSpan ns = RcxEditor::nameSpan(*lm, lm->effectiveTypeW, lm->effectiveNameW);
|
||||
QVERIFY(ns.valid);
|
||||
|
||||
// Click in the name area (selects the node)
|
||||
QPoint clickPos = colToViewport(m_editor->scintilla(), 2, ns.start + 1);
|
||||
QPoint clickPos = colToViewport(m_editor->scintilla(), kFirstDataLine, ns.start + 1);
|
||||
sendLeftClick(m_editor->scintilla()->viewport(), clickPos);
|
||||
QApplication::processEvents();
|
||||
|
||||
// Now move mouse to col 0 (indent area — non-editable)
|
||||
QPoint emptyPos = colToViewport(m_editor->scintilla(), 2, 0);
|
||||
QPoint emptyPos = colToViewport(m_editor->scintilla(), kFirstDataLine, 0);
|
||||
sendMouseMove(m_editor->scintilla()->viewport(), emptyPos);
|
||||
QApplication::processEvents();
|
||||
|
||||
@@ -1033,26 +1033,26 @@ private slots:
|
||||
QVERIFY(!m_editor->isEditing());
|
||||
}
|
||||
|
||||
// ── Test: CommandRow2 exists at line 1 ──
|
||||
// ── Test: CommandRow2 exists at kCommandRow2Line ──
|
||||
void testCommandRow2Exists() {
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Line 1 should be CommandRow2
|
||||
const LineMeta* lm = m_editor->metaForLine(1);
|
||||
// kCommandRow2Line should be CommandRow2
|
||||
const LineMeta* lm = m_editor->metaForLine(kCommandRow2Line);
|
||||
QVERIFY(lm);
|
||||
QCOMPARE(lm->lineKind, LineKind::CommandRow2);
|
||||
QCOMPARE(lm->nodeId, kCommandRow2Id);
|
||||
QCOMPARE(lm->nodeIdx, -1);
|
||||
|
||||
// Type/Name/Value should be rejected on CommandRow2
|
||||
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Type, 1));
|
||||
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Name, 1));
|
||||
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Value, 1));
|
||||
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Type, kCommandRow2Line));
|
||||
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Name, kCommandRow2Line));
|
||||
QVERIFY(!m_editor->beginInlineEdit(EditTarget::Value, kCommandRow2Line));
|
||||
QVERIFY(!m_editor->isEditing());
|
||||
|
||||
// RootClassName should be allowed on CommandRow2
|
||||
m_editor->setCommandRow2Text(QStringLiteral("struct _PEB64"));
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::RootClassName, 1);
|
||||
m_editor->setCommandRow2Text(QStringLiteral("struct\u25BE _PEB64"));
|
||||
bool ok = m_editor->beginInlineEdit(EditTarget::RootClassName, kCommandRow2Line);
|
||||
QVERIFY2(ok, "RootClassName edit should be allowed on CommandRow2");
|
||||
QVERIFY(m_editor->isEditing());
|
||||
m_editor->cancelInlineEdit();
|
||||
@@ -1063,15 +1063,15 @@ private slots:
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Set CommandRow2 without alignas
|
||||
m_editor->setCommandRow2Text(QStringLiteral("struct _PEB64"));
|
||||
m_editor->setCommandRow2Text(QStringLiteral("struct\u25BE _PEB64"));
|
||||
|
||||
// Line 1 is CommandRow2
|
||||
const LineMeta* lm = m_editor->metaForLine(1);
|
||||
// kCommandRow2Line is CommandRow2
|
||||
const LineMeta* lm = m_editor->metaForLine(kCommandRow2Line);
|
||||
QVERIFY(lm);
|
||||
QCOMPARE(lm->lineKind, LineKind::CommandRow2);
|
||||
|
||||
// RootClassName should work
|
||||
QVERIFY(m_editor->beginInlineEdit(EditTarget::RootClassName, 1));
|
||||
QVERIFY(m_editor->beginInlineEdit(EditTarget::RootClassName, kCommandRow2Line));
|
||||
QVERIFY(m_editor->isEditing());
|
||||
m_editor->cancelInlineEdit();
|
||||
|
||||
@@ -1083,15 +1083,15 @@ private slots:
|
||||
m_editor->applyDocument(m_result);
|
||||
|
||||
// Root struct header is completely suppressed from output.
|
||||
// Line 0 = CommandRow, Line 1 = CommandRow2, Line 2 = first field.
|
||||
const LineMeta* lm2 = m_editor->metaForLine(2);
|
||||
// Line 0 = CommandRow, Line 1 = Blank, Line 2 = CommandRow2, Line 3 = first field.
|
||||
const LineMeta* lm2 = m_editor->metaForLine(kFirstDataLine);
|
||||
QVERIFY(lm2);
|
||||
QCOMPARE(lm2->lineKind, LineKind::Field);
|
||||
|
||||
// Verify no root header exists anywhere in the output
|
||||
// Verify no root header line exists in the output (footer may have isRootHeader for flush-left)
|
||||
bool foundRootHeader = false;
|
||||
for (int i = 0; i < m_result.meta.size(); i++) {
|
||||
if (m_result.meta[i].isRootHeader) {
|
||||
if (m_result.meta[i].isRootHeader && m_result.meta[i].lineKind == LineKind::Header) {
|
||||
foundRootHeader = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ private slots:
|
||||
QCOMPARE(name, QString("float"));
|
||||
|
||||
name = doc.resolveTypeName(NodeKind::Hex64);
|
||||
QCOMPARE(name, QString("Hex64"));
|
||||
QCOMPARE(name, QString("hex64"));
|
||||
}
|
||||
|
||||
void testResolveTypeName_withAlias() {
|
||||
@@ -441,10 +441,11 @@ private slots:
|
||||
NullProvider prov;
|
||||
ComposeResult result = compose(tree, prov, 99999);
|
||||
|
||||
// Only command rows
|
||||
QCOMPARE(result.meta.size(), 2);
|
||||
// Only command rows + blank
|
||||
QCOMPARE(result.meta.size(), 3);
|
||||
QCOMPARE(result.meta[0].lineKind, LineKind::CommandRow);
|
||||
QCOMPARE(result.meta[1].lineKind, LineKind::CommandRow2);
|
||||
QCOMPARE(result.meta[1].lineKind, LineKind::Blank);
|
||||
QCOMPARE(result.meta[2].lineKind, LineKind::CommandRow2);
|
||||
}
|
||||
|
||||
void testCompose_viewRootId_singleRoot() {
|
||||
@@ -881,23 +882,23 @@ private slots:
|
||||
NullProvider prov;
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// CommandRow + CommandRow2 + 1 Vec4 line + footer = 4
|
||||
QCOMPARE(result.meta.size(), 4);
|
||||
// CommandRow + Blank + CommandRow2 + 1 Vec4 line + footer = 5
|
||||
QCOMPARE(result.meta.size(), 5);
|
||||
|
||||
// The Vec4 line (index 2) is a single field line, not continuation
|
||||
QCOMPARE(result.meta[2].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[2].nodeKind, NodeKind::Vec4);
|
||||
QVERIFY(!result.meta[2].isContinuation);
|
||||
// The Vec4 line (index 3) is a single field line, not continuation
|
||||
QCOMPARE(result.meta[3].lineKind, LineKind::Field);
|
||||
QCOMPARE(result.meta[3].nodeKind, NodeKind::Vec4);
|
||||
QVERIFY(!result.meta[3].isContinuation);
|
||||
|
||||
// Copy text (equivalent to editor's "Copy All as Text")
|
||||
QString text = result.text;
|
||||
// NullProvider reads 0 for all floats, so values are "0, 0, 0, 0"
|
||||
QVERIFY(text.contains("0, 0, 0, 0"));
|
||||
// NullProvider reads 0 for all floats, values are "0.f, 0.f, 0.f, 0.f"
|
||||
QVERIFY(text.contains("0.f, 0.f, 0.f, 0.f"));
|
||||
// Confirm type, name, and values all on the same line
|
||||
QStringList lines = text.split('\n');
|
||||
QVERIFY(lines[2].contains("Vec4"));
|
||||
QVERIFY(lines[2].contains("position"));
|
||||
QVERIFY(lines[2].contains("0, 0, 0, 0"));
|
||||
QVERIFY(lines[3].contains("vec4"));
|
||||
QVERIFY(lines[3].contains("position"));
|
||||
QVERIFY(lines[3].contains("0.f, 0.f, 0.f, 0.f"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user