diff --git a/src/compose.cpp b/src/compose.cpp index 4cd2224..d251346 100644 --- a/src/compose.cpp +++ b/src/compose.cpp @@ -39,10 +39,9 @@ struct ComposeState { if (currentLine > 0) text += '\n'; // 3-char fold indicator column: " - " expanded, " + " collapsed, " " other // CommandRow has no fold prefix (flush left) - if (lm.lineKind == LineKind::CommandRow || lm.lineKind == LineKind::Blank - || lm.lineKind == LineKind::CommandRow2 + if (lm.lineKind == LineKind::CommandRow || (lm.lineKind == LineKind::Footer && lm.isRootHeader)) { - // no prefix — flush left like CommandRow2 + // no prefix — flush left } else if (lm.foldHead) text += lm.foldCollapsed ? QStringLiteral(" \u25B8 ") : QStringLiteral(" \u25BE "); else @@ -218,13 +217,13 @@ void composeParent(ComposeState& state, const NodeTree& tree, } // Detect root header: first root-level struct — suppressed from display - // (CommandRow2 already shows the root class type + name) + // (CommandRow already shows the root class type + name) bool isRootHeader = (node.parentId == 0 && node.kind == NodeKind::Struct && !state.baseEmitted); if (isRootHeader) state.baseEmitted = true; // Header line (skip for array element structs and root struct) - // Root struct header is on CommandRow2 (type + name + {) + // Root struct header is on CommandRow (type + name + {) if (!isArrayChild && !isRootHeader) { // Get per-scope widths for this header's parent scope int typeW = state.effectiveTypeW(scopeId); @@ -474,8 +473,8 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR state.scopeNameW[0] = qBound(kMinNameW, rootMaxName, kMaxNameW); } - // Emit CommandRow as line 0 (synthetic UI line) - const QString cmdRowText = QStringLiteral("source\u25BE \u203A 0x0"); + // Emit CommandRow as line 0 (combined: source + address + root class type + name) + const QString cmdRowText = QStringLiteral("source\u25BE \u00B7 0x0 \u00B7 struct\u25BE {"); { LineMeta lm; lm.nodeIdx = -1; @@ -484,48 +483,13 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR lm.lineKind = LineKind::CommandRow; lm.foldLevel = SC_FOLDLEVELBASE; lm.foldHead = false; - lm.offsetText.clear(); + lm.offsetText = fmt::fmtOffsetMargin(tree.baseAddress, false); lm.markerMask = 0; lm.effectiveTypeW = state.typeW; lm.effectiveNameW = state.nameW; state.emitLine(cmdRowText, lm); } - // Emit dotted separator (line 1) — fixed-width spaced dots in text + margin - { - // ~20 dots in text area, ~10 dots in margin, using middle dot · (U+00B7) - QString dots(20, QChar(0x00B7)); - QString marginDots(10, QChar(0x00B7)); - LineMeta lm; - lm.nodeIdx = -1; - lm.nodeId = kCommandRowId; - lm.depth = 0; - lm.lineKind = LineKind::Blank; - lm.foldLevel = SC_FOLDLEVELBASE; - lm.foldHead = false; - lm.offsetText = marginDots; - lm.markerMask = 0; - lm.effectiveTypeW = state.typeW; - lm.effectiveNameW = state.nameW; - state.emitLine(dots, lm); - } - - // Emit CommandRow2 as line 2 (root class type + name) - { - LineMeta lm; - lm.nodeIdx = -1; - lm.nodeId = kCommandRow2Id; - lm.depth = 0; - lm.lineKind = LineKind::CommandRow2; - lm.foldLevel = SC_FOLDLEVELBASE; - lm.foldHead = false; - lm.offsetText.clear(); - lm.markerMask = 0; - lm.effectiveTypeW = state.typeW; - lm.effectiveNameW = state.nameW; - state.emitLine(QStringLiteral("struct\u25BE {"), lm); - } - QVector roots = state.childMap.value(0); std::sort(roots.begin(), roots.end(), [&](int a, int b) { return tree.nodes[a].offset < tree.nodes[b].offset; diff --git a/src/controller.cpp b/src/controller.cpp index 2813699..3361072 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -57,7 +57,7 @@ static QString crumbFor(const rcx::NodeTree& t, uint64_t nodeId) { std::reverse(parts.begin(), parts.end()); if (parts.size() > 4) parts = {parts.front(), QStringLiteral("\u2026"), parts[parts.size() - 2], parts.back()}; - return parts.join(QStringLiteral(" \u203A ")); + return parts.join(QStringLiteral(" \u00B7 ")); } // ── RcxDocument ── @@ -205,7 +205,7 @@ 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; CommandRow2 edits too + // CommandRow BaseAddress/Source/RootClass edit has nodeIdx=-1 if (nodeIdx < 0 && target != EditTarget::BaseAddress && target != EditTarget::Source && target != EditTarget::RootClassType && target != EditTarget::RootClassName) { refresh(); return; } switch (target) { @@ -332,7 +332,7 @@ void RcxController::connectEditor(RcxEditor* editor) { if (text.startsWith(QStringLiteral("#saved:"))) { int idx = text.mid(7).toInt(); switchToSavedSource(idx); - } else if (text == QStringLiteral("File")) { + } else if (text == QStringLiteral("file")) { auto* w = qobject_cast(parent()); QString path = QFileDialog::getOpenFileName(w, "Load Binary Data", {}, "All Files (*)"); if (!path.isEmpty()) { @@ -366,7 +366,7 @@ void RcxController::connectEditor(RcxEditor* editor) { refresh(); } } - else if (text == QStringLiteral("Process")) { + else if (text == QStringLiteral("process")) { #ifdef _WIN32 auto* w = qobject_cast(parent()); ProcessPicker picker(w); @@ -1234,7 +1234,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 && nid != kCommandRow2Id) m_selIds.insert(effectiveId(i, nid)); + if (nid != 0 && nid != kCommandRowId) m_selIds.insert(effectiveId(i, nid)); } } } else { // Ctrl+Shift @@ -1246,7 +1246,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 && nid != kCommandRow2Id) m_selIds.insert(effectiveId(i, nid)); + if (nid != 0 && nid != kCommandRowId) m_selIds.insert(effectiveId(i, nid)); } } } @@ -1397,10 +1397,10 @@ void RcxController::updateCommandRow() { // Build the row. If we have a symbol, append it after the address. QString row; if (sym.isEmpty()) { - row = QStringLiteral("%1 \u203A %2") + row = QStringLiteral("%1 \u00B7 %2") .arg(elide(src, 40), elide(addr, 24)); } else { - row = QStringLiteral("%1 \u203A %2 %3") + row = QStringLiteral("%1 \u00B7 %2 %3") .arg(elide(src, 40), elide(addr, 24), elide(sym, 40)); } @@ -1419,9 +1419,10 @@ void RcxController::updateCommandRow() { if (row2.isEmpty()) row2 = QStringLiteral("struct\u25BE {"); + QString combined = row + QStringLiteral(" \u00B7 ") + row2; + for (auto* ed : m_editors) { - ed->setCommandRowText(row); - ed->setCommandRow2Text(row2); + ed->setCommandRowText(combined); } emit selectionChanged(m_selIds.size()); } diff --git a/src/core.h b/src/core.h index 0451512..a346d54 100644 --- a/src/core.h +++ b/src/core.h @@ -393,18 +393,14 @@ struct NodeTree { // ── LineMeta ── enum class LineKind : uint8_t { - CommandRow, // line 0: source + address - Blank, // line 1: dotted separator - CommandRow2, // line 2: root class type + name + CommandRow, // line 0: source + address + root class type + name + Blank, // (unused — kept for enum stability) Header, Field, Continuation, Footer, ArrayElementSeparator }; static constexpr uint64_t kCommandRowId = UINT64_MAX; -static constexpr uint64_t kCommandRow2Id = UINT64_MAX - 1; static constexpr int kCommandRowLine = 0; -static constexpr int kBlankLine = 1; -static constexpr int kCommandRow2Line = 2; -static constexpr int kFirstDataLine = 3; +static constexpr int kFirstDataLine = 1; static constexpr uint64_t kFooterIdBit = 0x8000000000000000ULL; struct LineMeta { @@ -435,7 +431,7 @@ struct LineMeta { }; inline bool isSyntheticLine(const LineMeta& lm) { - return lm.lineKind == LineKind::CommandRow || lm.lineKind == LineKind::Blank || lm.lineKind == LineKind::CommandRow2; + return lm.lineKind == LineKind::CommandRow; } // ── Layout Info ── @@ -573,10 +569,10 @@ inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW = } // ── CommandRow spans ── -// Line format: "source▾ › 0x140000000" +// Line format: "source▾ · 0x140000000" inline ColumnSpan commandRowSrcSpan(const QString& lineText) { - int idx = lineText.indexOf(QStringLiteral(" \u203A")); + int idx = lineText.indexOf(QStringLiteral(" \u00B7")); if (idx < 0) return {}; int start = 0; while (start < idx && !lineText[start].isLetterOrNumber() @@ -590,38 +586,48 @@ inline ColumnSpan commandRowSrcSpan(const QString& lineText) { } inline ColumnSpan commandRowAddrSpan(const QString& lineText) { - int tag = lineText.indexOf(QStringLiteral(" \u203A")); + int tag = lineText.indexOf(QStringLiteral(" \u00B7")); if (tag < 0) return {}; - int start = tag + 3; // after " › " + int start = tag + 3; // after " · " int end = start; while (end < lineText.size() && !lineText[end].isSpace()) end++; if (end <= start) return {}; return {start, end, true}; } -// ── CommandRow2 spans ── -// Line format: "struct▾ ClassName {" +// ── CommandRow root-class spans ── +// Combined CommandRow format ends with: " struct▾ ClassName {" -inline ColumnSpan commandRow2TypeSpan(const QString& lineText) { - int start = 0; - while (start < lineText.size() && lineText[start].isSpace()) start++; - if (start >= lineText.size()) return {}; +inline int commandRowRootStart(const QString& lineText) { + int best = -1; + int i; + i = lineText.lastIndexOf(QStringLiteral("struct\u25BE")); + if (i > best) best = i; + i = lineText.lastIndexOf(QStringLiteral("class\u25BE")); + if (i > best) best = i; + i = lineText.lastIndexOf(QStringLiteral("enum\u25BE")); + if (i > best) best = i; + return best; +} + +inline ColumnSpan commandRowRootTypeSpan(const QString& lineText) { + int start = commandRowRootStart(lineText); + if (start < 0) return {}; int end = start; - while (end < lineText.size() && lineText[end] != QChar(' ') && lineText[end] != QChar(0x25BE)) end++; - if (end <= start) return {start, (int)lineText.size(), true}; + while (end < lineText.size() && lineText[end] != QChar(' ') + && lineText[end] != QChar(0x25BE)) end++; + if (end <= start) return {}; return {start, end, true}; } -inline ColumnSpan commandRow2NameSpan(const QString& lineText) { - // Format: "keyword name {" — extract just the name part (before " {") - int start = 0; - while (start < lineText.size() && lineText[start].isSpace()) start++; - int space = lineText.indexOf(' ', start); +inline ColumnSpan commandRowRootNameSpan(const QString& lineText) { + int base = commandRowRootStart(lineText); + if (base < 0) return {}; + int space = lineText.indexOf(' ', base); if (space < 0) return {}; int nameStart = space + 1; while (nameStart < lineText.size() && lineText[nameStart].isSpace()) nameStart++; if (nameStart >= lineText.size()) return {}; - // Stop before trailing " {" int nameEnd = lineText.indexOf(QStringLiteral(" {"), nameStart); if (nameEnd < 0) nameEnd = lineText.size(); while (nameEnd > nameStart && lineText[nameEnd - 1].isSpace()) nameEnd--; diff --git a/src/editor.cpp b/src/editor.cpp index f5ba4d3..9325b9e 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -24,12 +24,12 @@ static const QColor kFgMarginDim("#505050"); static constexpr int IND_EDITABLE = 8; static constexpr int IND_HEX_DIM = 9; -static constexpr int IND_BASE_ADDR = 10; // Green color for base address +static constexpr int IND_BASE_ADDR = 10; // Default text color override for command row address static constexpr int IND_HOVER_SPAN = 11; // Blue text on hover (link-like) static constexpr int IND_CMD_PILL = 12; // Rounded chip behind command row spans static constexpr int IND_DATA_CHANGED = 13; // Amber text for changed data values static constexpr int IND_CLASS_NAME = 14; // Teal text for root class name -static constexpr int IND_CLASS_ULINE = 15; // Underline for root class name +static constexpr int IND_HINT_GREEN = 15; // Green text for hint/comment text static QString g_fontName = "Consolas"; @@ -148,11 +148,11 @@ void RcxEditor::setupScintilla() { m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE, IND_HEX_DIM, QColor("#505050")); - // Base address indicator — green like comments + // Base address indicator — default text color to override lexer green on command row m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE, IND_BASE_ADDR, 17 /*INDIC_TEXTFORE*/); m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE, - IND_BASE_ADDR, QColor("#5a8248")); + IND_BASE_ADDR, QColor("#d4d4d4")); // Hover span indicator — muted teal text (distinct from blue keywords) m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE, @@ -182,11 +182,11 @@ void RcxEditor::setupScintilla() { m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE, IND_CLASS_NAME, QColor("#4EC9B0")); - // Root class name underline + // Green text for hint/comment annotations m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE, - IND_CLASS_ULINE, (long)0 /*INDIC_PLAIN*/); + IND_HINT_GREEN, 17 /*INDIC_TEXTFORE*/); m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE, - IND_CLASS_ULINE, QColor(124, 180, 226)); + IND_HINT_GREEN, QColor("#5a8248")); } @@ -395,9 +395,6 @@ void RcxEditor::applyMarkers(const QVector& meta) { m_sci->markerAdd(i, M_CMD_ROW); continue; } - if (meta[i].lineKind == LineKind::CommandRow2) { - continue; // regular background (no special marker) - } uint32_t mask = meta[i].markerMask; for (int m = M_CONT; m <= M_STRUCT_BG; m++) { if (mask & (1u << m)) { @@ -636,7 +633,11 @@ void RcxEditor::applyBaseAddressColoring(const QVector& meta) { if (meta.isEmpty() || meta[0].lineKind != LineKind::CommandRow) return; clearIndicatorLine(IND_BASE_ADDR, 0); - // Address in command bar is not colored green (only field values get green) + // Override lexer's green number coloring on the address with default text color + QString t = getLineText(m_sci, 0); + ColumnSpan addr = commandRowAddrSpan(t); + if (addr.valid) + fillIndicatorCols(IND_BASE_ADDR, 0, addr.start, addr.end); } void RcxEditor::applyCommandRowPills() { @@ -645,20 +646,10 @@ void RcxEditor::applyCommandRowPills() { constexpr int line = 0; QString t = getLineText(m_sci, line); - clearIndicatorLine(IND_CMD_PILL, line); clearIndicatorLine(IND_HEX_DIM, line); + clearIndicatorLine(IND_CLASS_NAME, line); - auto fillPadded = [&](ColumnSpan s) { - if (!s.valid) return; - int a = qMax(0, s.start - 1); - int b = qMin(t.size(), s.end + 1); - fillIndicatorCols(IND_CMD_PILL, line, a, b); - }; - - fillPadded(commandRowSrcSpan(t)); - fillPadded(commandRowAddrSpan(t)); - - // Dim label text: source arrow/placeholder and ", address=" + // Dim label text: source arrow/placeholder + its ▾ dropdown arrow ColumnSpan srcSpan = commandRowSrcSpan(t); if (srcSpan.valid) { int quotePos = t.indexOf('\'', srcSpan.start); @@ -666,35 +657,37 @@ void RcxEditor::applyCommandRowPills() { while (kindEnd > srcSpan.start && t[kindEnd - 1].isSpace()) kindEnd--; if (kindEnd > srcSpan.start) fillIndicatorCols(IND_HEX_DIM, line, srcSpan.start, kindEnd); + // Dim the source ▾ dropdown arrow to match (like struct▾) + int srcDrop = t.indexOf(QChar(0x25BE)); + int rootStart = commandRowRootStart(t); + if (srcDrop >= 0 && (rootStart < 0 || srcDrop < rootStart)) + fillIndicatorCols(IND_HEX_DIM, line, srcDrop, srcDrop + 1); } - int addrTag = t.indexOf(QStringLiteral(" \u203A")); - if (addrTag >= 0) - fillIndicatorCols(IND_HEX_DIM, line, addrTag, addrTag + 3); - - // Style CommandRow2 (line kCommandRow2Line) if present - if (m_meta.size() > kCommandRow2Line && m_meta[kCommandRow2Line].lineKind == LineKind::CommandRow2) { - int line2 = kCommandRow2Line; - QString t2 = getLineText(m_sci, line2); - - clearIndicatorLine(IND_HEX_DIM, line2); - clearIndicatorLine(IND_CLASS_NAME, line2); - clearIndicatorLine(IND_CLASS_ULINE, line2); - - ColumnSpan typeSpan = commandRow2TypeSpan(t2); - if (typeSpan.valid) - fillIndicatorCols(IND_HEX_DIM, line2, typeSpan.start, typeSpan.end); - - ColumnSpan nameSpan = commandRow2NameSpan(t2); - if (nameSpan.valid) { - fillIndicatorCols(IND_CLASS_NAME, line2, nameSpan.start, nameSpan.end); - } + // Dim all " · " separators + int searchFrom = 0; + while (true) { + int tag = t.indexOf(QStringLiteral(" \u00B7"), searchFrom); + if (tag < 0) break; + fillIndicatorCols(IND_HEX_DIM, line, tag, tag + 3); + searchFrom = tag + 3; } - // Dim the dotted separator line - if (m_meta.size() > kBlankLine && m_meta[kBlankLine].lineKind == LineKind::Blank) { - QString sep = getLineText(m_sci, kBlankLine); - if (!sep.isEmpty()) - fillIndicatorCols(IND_HEX_DIM, kBlankLine, 0, sep.size()); + // Dim base address to match source/struct grey + ColumnSpan addrSpan = commandRowAddrSpan(t); + if (addrSpan.valid) + fillIndicatorCols(IND_HEX_DIM, line, addrSpan.start, addrSpan.end); + + // Root class styling (type dim + class-name teal, no underline) + ColumnSpan rt = commandRowRootTypeSpan(t); + if (rt.valid) { + fillIndicatorCols(IND_HEX_DIM, line, rt.start, rt.end); + int drop = t.indexOf(QChar(0x25BE), rt.start); + if (drop >= 0) + fillIndicatorCols(IND_HEX_DIM, line, drop, qMin(drop + 2, t.size())); + } + ColumnSpan rn = commandRowRootNameSpan(t); + if (rn.valid) { + fillIndicatorCols(IND_CLASS_NAME, line, rn.start, rn.end); } } @@ -841,26 +834,17 @@ bool RcxEditor::resolvedSpanFor(int line, EditTarget t, const LineMeta* lm = metaForLine(line); if (!lm) return false; - // CommandRow: BaseAddress (ADDR) and Source (SRC) editing + // CommandRow: Source / BaseAddress / Root class (type+name) editing if (lm->lineKind == LineKind::CommandRow) { - if (t != EditTarget::BaseAddress && t != EditTarget::Source) return false; - QString lineText = getLineText(m_sci, line); - ColumnSpan s = (t == EditTarget::Source) - ? commandRowSrcSpan(lineText) - : commandRowAddrSpan(lineText); - out = normalizeSpan(s, lineText, t, /*skipPrefixes=*/(t == EditTarget::BaseAddress)); - if (lineTextOut) *lineTextOut = lineText; - return out.valid; - } - - // CommandRow2: root class type and name - if (lm->lineKind == LineKind::CommandRow2) { - if (t != EditTarget::RootClassType && t != EditTarget::RootClassName) return false; + if (t != EditTarget::BaseAddress && t != EditTarget::Source + && t != EditTarget::RootClassType && t != EditTarget::RootClassName) return false; QString lineText = getLineText(m_sci, line); ColumnSpan s; - if (t == EditTarget::RootClassType) s = commandRow2TypeSpan(lineText); - else s = commandRow2NameSpan(lineText); - out = normalizeSpan(s, lineText, t, false); + if (t == EditTarget::Source) s = commandRowSrcSpan(lineText); + else if (t == EditTarget::BaseAddress) s = commandRowAddrSpan(lineText); + else if (t == EditTarget::RootClassType) s = commandRowRootTypeSpan(lineText); + else s = commandRowRootNameSpan(lineText); + out = normalizeSpan(s, lineText, t, /*skipPrefixes=*/(t == EditTarget::BaseAddress)); if (lineTextOut) *lineTextOut = lineText; return out.valid; } @@ -974,21 +958,17 @@ static bool hitTestTarget(QsciScintilla* sci, return s.valid && col >= s.start && col < s.end; }; - // CommandRow: SRC and ADDR fields are interactive + // CommandRow: interactive SRC/ADDR + root class (type+name) if (lm.lineKind == LineKind::CommandRow) { ColumnSpan ss = commandRowSrcSpan(lineText); if (inSpan(ss)) { outTarget = EditTarget::Source; outLine = line; return true; } ColumnSpan as = commandRowAddrSpan(lineText); if (inSpan(as)) { outTarget = EditTarget::BaseAddress; outLine = line; return true; } - return false; - } - // CommandRow2: root class type and name - if (lm.lineKind == LineKind::CommandRow2) { - ColumnSpan ts = commandRow2TypeSpan(lineText); - if (inSpan(ts)) { outTarget = EditTarget::RootClassType; outLine = line; return true; } - ColumnSpan ns = commandRow2NameSpan(lineText); - if (inSpan(ns)) { outTarget = EditTarget::RootClassName; outLine = line; return true; } + ColumnSpan rts = commandRowRootTypeSpan(lineText); + if (inSpan(rts)) { outTarget = EditTarget::RootClassType; outLine = line; return true; } + ColumnSpan rns = commandRowRootNameSpan(lineText); + if (inSpan(rns)) { outTarget = EditTarget::RootClassName; outLine = line; return true; } return false; } @@ -1122,7 +1102,7 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) { return true; } // CommandRow: try ADDR edit or consume - if (h.nodeId == kCommandRowId || h.nodeId == kCommandRow2Id) { + if (h.nodeId == kCommandRowId) { int tLine; EditTarget t; if (hitTestTarget(m_sci, m_meta, me->pos(), tLine, t)) beginInlineEdit(t, tLine); @@ -1217,7 +1197,7 @@ bool RcxEditor::eventFilter(QObject* obj, QEvent* event) { m_pendingClickNodeId = 0; // cancel deferred selection change // Narrow selection to this node before editing auto h = hitTest(me->pos()); - if (h.nodeId != 0 && h.nodeId != kCommandRowId && h.nodeId != kCommandRow2Id) + if (h.nodeId != 0 && h.nodeId != kCommandRowId) emit nodeClicked(h.line, h.nodeId, Qt::NoModifier); return beginInlineEdit(t, line); } @@ -1403,11 +1383,10 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) { m_sci->getCursorPosition(&line, &col); auto* lm = metaForLine(line); if (!lm) return false; - // Allow nodeIdx=-1 only for CommandRow/CommandRow2 editing + // Allow nodeIdx=-1 only for CommandRow editing (command bar) if (lm->nodeIdx < 0 && !(lm->lineKind == LineKind::CommandRow && - (target == EditTarget::BaseAddress || target == EditTarget::Source)) - && !(lm->lineKind == LineKind::CommandRow2 && - (target == EditTarget::RootClassType || target == EditTarget::RootClassName))) + (target == EditTarget::BaseAddress || target == EditTarget::Source + || target == EditTarget::RootClassType || target == EditTarget::RootClassName))) return false; // Padding: reject value editing (display-only hex bytes) if (target == EditTarget::Value && lm->nodeKind == NodeKind::Padding) @@ -1493,10 +1472,15 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line) { m_sci->viewport()->setCursor(Qt::IBeamCursor); } - // Re-enable selection rendering for inline edit + // Re-enable selection rendering for inline edit (skip for picker-based targets) + bool isPicker = (target == EditTarget::Type || target == EditTarget::Source + || target == EditTarget::ArrayElementType + || target == EditTarget::PointerTarget + || target == EditTarget::RootClassType); m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELFORE, (long)0, (long)0); - m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELBACK, (long)1, - QColor("#264f78")); + if (!isPicker) + m_sci->SendScintilla(QsciScintillaBase::SCI_SETSELBACK, (long)1, + QColor("#264f78")); // Use correct UTF-8 position conversion (not lineStart + col!) m_editState.posStart = posFromCol(m_sci, line, norm.start); @@ -1671,8 +1655,15 @@ void RcxEditor::showSourcePicker() { if (!m_editState.active || m_editState.target != EditTarget::Source) return; QMenu menu; - menu.addAction("File"); - menu.addAction("Process"); + QFont menuFont = editorFont(); + int zoom = (int)m_sci->SendScintilla(QsciScintillaBase::SCI_GETZOOM); + menuFont.setPointSize(menuFont.pointSize() + zoom); + menu.setFont(menuFont); + menu.setStyleSheet(QStringLiteral( + "QMenu { background: #252526; color: #d4d4d4; }" + "QMenu::item:selected { background: #232323; }")); + menu.addAction("file"); + menu.addAction("process"); // Saved sources below separator (with checkmarks) if (!m_savedSourceDisplay.isEmpty()) { @@ -1795,18 +1786,13 @@ void RcxEditor::updatePointerTargetFilter() { void RcxEditor::paintEditableSpans(int line) { const LineMeta* lm = metaForLine(line); if (!lm) return; - // CommandRow: paint Source and BaseAddress spans + // CommandRow: paint Source/BaseAddress + root class (type+name) spans if (lm->lineKind == LineKind::CommandRow) { NormalizedSpan norm; if (resolvedSpanFor(line, EditTarget::Source, norm)) fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end); if (resolvedSpanFor(line, EditTarget::BaseAddress, norm)) fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end); - return; - } - // CommandRow2: paint RootClassType and RootClassName spans - if (lm->lineKind == LineKind::CommandRow2) { - NormalizedSpan norm; if (resolvedSpanFor(line, EditTarget::RootClassType, norm)) fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end); if (resolvedSpanFor(line, EditTarget::RootClassName, norm)) @@ -1991,8 +1977,8 @@ void RcxEditor::setEditComment(const QString& comment) { m_sci->SendScintilla(QsciScintillaBase::SCI_REPLACETARGET, (uintptr_t)utf8.size(), utf8.constData()); - // Apply green color to hint text (reuse IND_BASE_ADDR which is green) - m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_BASE_ADDR); + // Apply green color to hint text + m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, IND_HINT_GREEN); m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, posA, posB - posA); m_updatingComment = false; @@ -2065,42 +2051,6 @@ void RcxEditor::setCommandRowText(const QString& line) { applyCommandRowPills(); } -void RcxEditor::setCommandRow2Text(const QString& line) { - if (m_sci->lines() <= kCommandRow2Line) return; - QString s = line; - s.replace('\n', ' '); - s.replace('\r', ' '); - - bool wasReadOnly = m_sci->isReadOnly(); - bool wasModified = m_sci->SendScintilla(QsciScintillaBase::SCI_GETMODIFY); - long savedPos = m_sci->SendScintilla(QsciScintillaBase::SCI_GETCURRENTPOS); - long savedAnchor = m_sci->SendScintilla(QsciScintillaBase::SCI_GETANCHOR); - - m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, 0); - m_sci->setReadOnly(false); - - long start = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, kCommandRow2Line); - long end = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLINEENDPOSITION, kCommandRow2Line); - long oldLen = end - start; - QByteArray utf8 = s.toUtf8(); - m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, start); - m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND, end); - m_sci->SendScintilla(QsciScintillaBase::SCI_REPLACETARGET, (uintptr_t)utf8.size(), utf8.constData()); - - // Adjust saved cursor/anchor for length change in CommandRow2 line - long delta = (long)utf8.size() - oldLen; - if (savedPos > end) savedPos += delta; - if (savedAnchor > end) savedAnchor += delta; - - if (wasReadOnly) m_sci->setReadOnly(true); - m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, 1); - if (!wasModified) m_sci->SendScintilla(QsciScintillaBase::SCI_SETSAVEPOINT); - m_sci->SendScintilla(QsciScintillaBase::SCI_SETCURRENTPOS, savedPos); - m_sci->SendScintilla(QsciScintillaBase::SCI_SETANCHOR, savedAnchor); - m_sci->SendScintilla(QsciScintillaBase::SCI_COLOURISE, start, start + utf8.size()); - applyCommandRowPills(); -} - void RcxEditor::setEditorFont(const QString& fontName) { g_fontName = fontName; QFont f = editorFont(); diff --git a/src/editor.h b/src/editor.h index 5255e6d..c3d8115 100644 --- a/src/editor.h +++ b/src/editor.h @@ -45,7 +45,6 @@ public: void applySelectionOverlay(const QSet& selIds); void setCommandRowText(const QString& line); - void setCommandRow2Text(const QString& line); void setEditorFont(const QString& fontName); static void setGlobalFontName(const QString& fontName); diff --git a/src/format.cpp b/src/format.cpp index 5cf0b66..e137f35 100644 --- a/src/format.cpp +++ b/src/format.cpp @@ -112,8 +112,8 @@ QString indent(int depth) { // ── Offset margin ── QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation) { - if (isContinuation) return QStringLiteral(" \u00B7"); - return QString::number(absoluteOffset, 16).toUpper(); + if (isContinuation) return QStringLiteral(" \u00B7 "); + return QString::number(absoluteOffset, 16).toUpper() + QChar(' '); } // ── Struct type name (for width calculation) ── diff --git a/tests/test_command_row.cpp b/tests/test_command_row.cpp index f791e9f..00d11d7 100644 --- a/tests/test_command_row.cpp +++ b/tests/test_command_row.cpp @@ -21,7 +21,7 @@ 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 \u203A %2").arg(src, addr); + return QStringLiteral(" %1 \u00B7 %2").arg(src, addr); } // -- Replicate commandRowSrcSpan for testing @@ -32,7 +32,7 @@ struct TestColumnSpan { }; static TestColumnSpan commandRowSrcSpan(const QString& lineText) { - int idx = lineText.indexOf(QStringLiteral(" \u203A")); + int idx = lineText.indexOf(QStringLiteral(" \u00B7")); if (idx < 0) return {}; int start = 0; while (start < idx && !lineText[start].isLetterOrNumber() @@ -77,13 +77,13 @@ private slots: void row_nullProvider() { NullProvider p; QString row = buildCommandRow(p, 0); - QCOMPARE(row, QStringLiteral(" source\u25BE \u203A 0x0")); + QCOMPARE(row, QStringLiteral(" source\u25BE \u00B7 0x0")); } void row_fileProvider() { BufferProvider p(QByteArray(4, '\0'), "test.bin"); QString row = buildCommandRow(p, 0x140000000ULL); - QCOMPARE(row, QStringLiteral(" 'test.bin'\u25BE \u203A 0x140000000")); + QCOMPARE(row, QStringLiteral(" 'test.bin'\u25BE \u00B7 0x140000000")); } // --------------------------------------------------------------- @@ -110,7 +110,7 @@ private slots: void span_processProvider_simulated() { // Simulate a process provider without needing Windows APIs // by building the string directly - QString row = QStringLiteral(" 'notepad.exe'\u25BE \u203A 0x7FF600000000"); + QString row = QStringLiteral(" 'notepad.exe'\u25BE \u00B7 0x7FF600000000"); auto span = commandRowSrcSpan(row); QVERIFY(span.valid); QString extracted = row.mid(span.start, span.end - span.start); diff --git a/tests/test_compose.cpp b/tests/test_compose.cpp index 293f56b..e14b64b 100644 --- a/tests/test_compose.cpp +++ b/tests/test_compose.cpp @@ -35,30 +35,24 @@ private slots: NullProvider prov; ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + 2 fields + root footer = 6 - QCOMPARE(result.meta.size(), 6); + // CommandRow + 2 fields + root footer = 4 + QCOMPARE(result.meta.size(), 4); // Line 0 is CommandRow QCOMPARE(result.meta[0].lineKind, LineKind::CommandRow); - // 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[3].foldHead); - QCOMPARE(result.meta[3].depth, 1); - QVERIFY(!result.meta[4].foldHead); - QCOMPARE(result.meta[4].depth, 1); + QVERIFY(!result.meta[1].foldHead); + QCOMPARE(result.meta[1].depth, 1); + QVERIFY(!result.meta[2].foldHead); + QCOMPARE(result.meta[2].depth, 1); // Offset text - QCOMPARE(result.meta[3].offsetText, QString("0")); - QCOMPARE(result.meta[4].offsetText, QString("4")); + QCOMPARE(result.meta[1].offsetText, QString("0")); + QCOMPARE(result.meta[2].offsetText, QString("4")); - // Line 5 is root footer - QCOMPARE(result.meta[5].lineKind, LineKind::Footer); + // Line 3 is root footer + QCOMPARE(result.meta[3].lineKind, LineKind::Footer); } void testVec3SingleLine() { @@ -82,17 +76,17 @@ private slots: NullProvider prov; ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + 1 Vec3 line + root footer = 5 - QCOMPARE(result.meta.size(), 5); + // CommandRow + 1 Vec3 line + root footer = 3 + QCOMPARE(result.meta.size(), 3); - // 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 1: single Vec3 line, not continuation, depth 1 + QVERIFY(!result.meta[1].isContinuation); + QCOMPARE(result.meta[1].offsetText, QString("0")); + QCOMPARE(result.meta[1].depth, 1); + QCOMPARE(result.meta[1].nodeKind, NodeKind::Vec3); - // Line 4 is root footer - QCOMPARE(result.meta[4].lineKind, LineKind::Footer); + // Line 2 is root footer + QCOMPARE(result.meta[2].lineKind, LineKind::Footer); } void testPaddingMarker() { @@ -116,13 +110,13 @@ private slots: NullProvider prov; ComposeResult result = compose(tree, prov); - // 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); + // CommandRow + padding + root footer = 3 + QCOMPARE(result.meta.size(), 3); + QVERIFY(result.meta[1].markerMask & (1u << M_PAD)); + QCOMPARE(result.meta[1].depth, 1); - // Line 4 is root footer - QCOMPARE(result.meta[4].lineKind, LineKind::Footer); + // Line 2 is root footer + QCOMPARE(result.meta[2].lineKind, LineKind::Footer); } void testNullPointerMarker() { @@ -148,14 +142,14 @@ private slots: BufferProvider prov(data); ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + ptr + root footer = 5 - QCOMPARE(result.meta.size(), 5); + // CommandRow + ptr + root footer = 3 + QCOMPARE(result.meta.size(), 3); // No ambient validation markers — M_PTR0 is no longer set - QVERIFY(!(result.meta[3].markerMask & (1u << M_PTR0))); - QCOMPARE(result.meta[3].depth, 1); + QVERIFY(!(result.meta[1].markerMask & (1u << M_PTR0))); + QCOMPARE(result.meta[1].depth, 1); - // Line 4 is root footer - QCOMPARE(result.meta[4].lineKind, LineKind::Footer); + // Line 2 is root footer + QCOMPARE(result.meta[2].lineKind, LineKind::Footer); } void testCollapsedStruct() { @@ -181,11 +175,11 @@ private slots: ComposeResult result = compose(tree, prov); // Collapsed root: isRootHeader overrides collapse, so children + footer still render - // 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); + // CommandRow + field + root footer = 3 + QCOMPARE(result.meta.size(), 3); + QCOMPARE(result.meta[1].lineKind, LineKind::Field); + QCOMPARE(result.meta[1].depth, 1); + QCOMPARE(result.meta[2].lineKind, LineKind::Footer); } void testUnreadablePointerNoRead() { @@ -212,15 +206,15 @@ private slots: BufferProvider prov(data); ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + ptr + root footer = 5 - QCOMPARE(result.meta.size(), 5); + // CommandRow + ptr + root footer = 3 + QCOMPARE(result.meta.size(), 3); // No ambient validation markers - QVERIFY(!(result.meta[3].markerMask & (1u << M_ERR))); - QVERIFY(!(result.meta[3].markerMask & (1u << M_PTR0))); - QCOMPARE(result.meta[3].depth, 1); + QVERIFY(!(result.meta[1].markerMask & (1u << M_ERR))); + QVERIFY(!(result.meta[1].markerMask & (1u << M_PTR0))); + QCOMPARE(result.meta[1].depth, 1); - // Line 4 is root footer - QCOMPARE(result.meta[4].lineKind, LineKind::Footer); + // Line 2 is root footer + QCOMPARE(result.meta[2].lineKind, LineKind::Footer); } void testFoldLevels() { @@ -253,13 +247,13 @@ private slots: ComposeResult result = compose(tree, prov); // Child header (depth 1, fold head) — root header no longer emitted - QCOMPARE(result.meta[3].foldLevel, 0x401 | 0x2000); - QCOMPARE(result.meta[3].depth, 1); - QVERIFY(result.meta[3].foldHead); + QCOMPARE(result.meta[1].foldLevel, 0x401 | 0x2000); + QCOMPARE(result.meta[1].depth, 1); + QVERIFY(result.meta[1].foldHead); // Leaf (depth 2, not head) - QCOMPARE(result.meta[4].foldLevel, 0x402); - QCOMPARE(result.meta[4].depth, 2); + QCOMPARE(result.meta[2].foldLevel, 0x402); + QCOMPARE(result.meta[2].depth, 2); } void testNestedStruct() { @@ -306,31 +300,31 @@ private slots: NullProvider prov; ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + flags + Inner header + x + y + Inner footer + root footer = 9 - QCOMPARE(result.meta.size(), 9); + // CommandRow + flags + Inner header + x + y + Inner footer + root footer = 7 + QCOMPARE(result.meta.size(), 7); // flags field (depth 1) - QCOMPARE(result.meta[3].lineKind, LineKind::Field); - QCOMPARE(result.meta[3].depth, 1); + QCOMPARE(result.meta[1].lineKind, LineKind::Field); + QCOMPARE(result.meta[1].depth, 1); // Inner header (depth 1, fold head) - 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); + QCOMPARE(result.meta[2].lineKind, LineKind::Header); + QCOMPARE(result.meta[2].depth, 1); + QVERIFY(result.meta[2].foldHead); + QCOMPARE(result.meta[2].foldLevel, 0x401 | 0x2000); // Inner fields at depth 2 - QCOMPARE(result.meta[5].depth, 2); - QCOMPARE(result.meta[5].foldLevel, 0x402); - QCOMPARE(result.meta[6].depth, 2); + QCOMPARE(result.meta[3].depth, 2); + QCOMPARE(result.meta[3].foldLevel, 0x402); + QCOMPARE(result.meta[4].depth, 2); // Inner footer - QCOMPARE(result.meta[7].lineKind, LineKind::Footer); - QCOMPARE(result.meta[7].depth, 1); + QCOMPARE(result.meta[5].lineKind, LineKind::Footer); + QCOMPARE(result.meta[5].depth, 1); // Root footer - QCOMPARE(result.meta[8].lineKind, LineKind::Footer); - QCOMPARE(result.meta[8].depth, 0); + QCOMPARE(result.meta[6].lineKind, LineKind::Footer); + QCOMPARE(result.meta[6].depth, 0); } void testPointerDerefExpansion() { @@ -398,28 +392,28 @@ private slots: ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + magic + ptr(merged fold header) + fn1 + fn2 + ptr footer + Main footer = 9 + // CommandRow + magic + ptr(merged fold header) + fn1 + fn2 + ptr footer + Main footer = 7 // VTable standalone: header + fn1 + fn2 + footer = 4 - // Total = 13 - QCOMPARE(result.meta.size(), 13); + // Total = 11 + QCOMPARE(result.meta.size(), 11); // magic field (depth 1) - QCOMPARE(result.meta[3].lineKind, LineKind::Field); - QCOMPARE(result.meta[3].depth, 1); + QCOMPARE(result.meta[1].lineKind, LineKind::Field); + QCOMPARE(result.meta[1].depth, 1); // 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); + QCOMPARE(result.meta[2].lineKind, LineKind::Header); + QCOMPARE(result.meta[2].depth, 1); + QVERIFY(result.meta[2].foldHead); + QCOMPARE(result.meta[2].nodeKind, NodeKind::Pointer64); // Expanded fields at depth 2 (struct header merged into pointer) - QCOMPARE(result.meta[5].depth, 2); - QCOMPARE(result.meta[6].depth, 2); + QCOMPARE(result.meta[3].depth, 2); + QCOMPARE(result.meta[4].depth, 2); // Pointer fold footer - QCOMPARE(result.meta[7].lineKind, LineKind::Footer); - QCOMPARE(result.meta[7].depth, 1); + QCOMPARE(result.meta[5].lineKind, LineKind::Footer); + QCOMPARE(result.meta[5].depth, 1); } void testPointerDerefNull() { @@ -463,22 +457,22 @@ private slots: ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + ptr(merged fold header) + target field + ptr footer + Main footer = 7 + // CommandRow + ptr(merged fold header) + target field + ptr footer + Main footer = 5 // Target standalone: header + field + footer = 3 - // Total = 10 (null ptr still shows template preview) - QCOMPARE(result.meta.size(), 10); + // Total = 8 (null ptr still shows template preview) + QCOMPARE(result.meta.size(), 8); // 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); + QCOMPARE(result.meta[1].lineKind, LineKind::Header); + QCOMPARE(result.meta[1].depth, 1); + QVERIFY(result.meta[1].foldHead); // Target field shown as template preview - QCOMPARE(result.meta[4].lineKind, LineKind::Field); - QCOMPARE(result.meta[4].depth, 2); + QCOMPARE(result.meta[2].lineKind, LineKind::Field); + QCOMPARE(result.meta[2].depth, 2); // Pointer fold footer - QCOMPARE(result.meta[5].lineKind, LineKind::Footer); + QCOMPARE(result.meta[3].lineKind, LineKind::Footer); } void testPointerDerefCollapsed() { @@ -525,14 +519,14 @@ private slots: ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + ptr(fold head, collapsed) + Main footer = 5 + // CommandRow + ptr(fold head, collapsed) + Main footer = 3 // Target standalone: header + field + footer = 3 - // Total = 8 - QCOMPARE(result.meta.size(), 8); + // Total = 6 + QCOMPARE(result.meta.size(), 6); // Pointer is fold head (depth 1) - QVERIFY(result.meta[3].foldHead); - QCOMPARE(result.meta[3].depth, 1); + QVERIFY(result.meta[1].foldHead); + QCOMPARE(result.meta[1].depth, 1); } void testPointerDerefCycle() { @@ -595,12 +589,12 @@ private slots: QVERIFY(result.meta.size() > 0); QVERIFY(result.meta.size() < 100); // sanity: bounded output - // CommandRow + Blank + CommandRow2 + ptr merged header + data + self merged header + // CommandRow + 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[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) + QVERIFY(result.meta[1].foldHead); // ptr merged fold head + QCOMPARE(result.meta[1].lineKind, LineKind::Header); // ptr merged header + QCOMPARE(result.meta[2].lineKind, LineKind::Field); // data field (first child of Recursive) } void testStructFooterSimple() { @@ -664,21 +658,12 @@ private slots: ComposeResult result = compose(tree, prov); for (int i = 0; i < result.meta.size(); i++) { - // Skip CommandRow / Blank / CommandRow2 (synthetic lines with sentinel nodeId) + // Skip CommandRow (synthetic line 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); - continue; - } QVERIFY2(result.meta[i].nodeId != 0, qPrintable(QString("Line %1 has nodeId=0").arg(i))); int ni = result.meta[i].nodeIdx; @@ -955,16 +940,16 @@ private slots: NullProvider prov; ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + Array header(collapsed) + root footer = 5 - QCOMPARE(result.meta.size(), 5); + // CommandRow + Array header(collapsed) + root footer = 3 + QCOMPARE(result.meta.size(), 3); - // Array header is collapsed (at index 3) + // Array header is collapsed (at index 1) 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, 3); + QCOMPARE(arrLine, 1); QVERIFY(result.meta[arrLine].foldCollapsed); // Header text should NOT contain "{" @@ -1826,10 +1811,10 @@ private slots: QCOMPARE(tree.computeStructAlignment(rootId), 1); } - void testCommandRow2NameSpan() { - // Name span should cover the class name - QString text = "struct\u25BE MyClass"; - ColumnSpan nameSpan = commandRow2NameSpan(text); + void testCommandRowRootNameSpan() { + // Name span should cover the class name in the merged command row + QString text = "source\u25BE \u00B7 0x0 \u00B7 struct\u25BE MyClass {"; + ColumnSpan nameSpan = commandRowRootNameSpan(text); QVERIFY(nameSpan.valid); QString nameText = text.mid(nameSpan.start, nameSpan.end - nameSpan.start); @@ -1881,11 +1866,10 @@ private slots: QVERIFY2(result.meta.size() >= 5, qPrintable(QString("Expected >= 5 lines, got %1").arg(result.meta.size()))); - // Every non-blank line should have text content + // Every 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))); } diff --git a/tests/test_editor.cpp b/tests/test_editor.cpp index 3511992..ee62bc2 100644 --- a/tests/test_editor.cpp +++ b/tests/test_editor.cpp @@ -376,7 +376,7 @@ private slots: // Set CommandRow text with an ADDR value (simulates controller.updateCommandRow) m_editor->setCommandRowText( - QStringLiteral("source\u25BE \u203A 0xD87B5E5000")); + QStringLiteral("source\u25BE \u00B7 0xD87B5E5000")); // BaseAddress should be ALLOWED on CommandRow (ADDR field) bool ok = m_editor->beginInlineEdit(EditTarget::BaseAddress, 0); @@ -394,7 +394,7 @@ private slots: // ── Test: inline edit lifecycle (begin → commit → re-edit) ── void testInlineEditReEntry() { - // Move cursor to line 3 (first field; 0=CommandRow, 1=Blank, 2=CommandRow2, root header suppressed) + // Move cursor to first data line (0=CommandRow, root header suppressed) m_editor->scintilla()->setCursorPosition(kFirstDataLine, 0); // Should not be editing @@ -712,7 +712,7 @@ private slots: void testSelectedNodeIndices() { m_editor->applyDocument(m_result); - // Put cursor on first field line (kFirstDataLine; 0=CommandRow, 1=Blank, 2=CommandRow2) + // Put cursor on first field line (kFirstDataLine; 0=CommandRow) m_editor->scintilla()->setCursorPosition(kFirstDataLine, 0); QSet sel = m_editor->selectedNodeIndices(); QCOMPARE(sel.size(), 1); @@ -750,7 +750,7 @@ private slots: // Set CommandRow text with ADDR value (simulates controller) m_editor->setCommandRowText( - QStringLiteral("source\u25BE \u203A 0xD87B5E5000")); + QStringLiteral("source\u25BE \u00B7 0xD87B5E5000")); // Line 0 is CommandRow const LineMeta* lm = m_editor->metaForLine(0); @@ -902,7 +902,7 @@ private slots: // Set CommandRow text with ADDR value (simulates controller) m_editor->setCommandRowText( - QStringLiteral("source\u25BE \u203A 0xD87B5E5000")); + QStringLiteral("source\u25BE \u00B7 0xD87B5E5000")); // Begin base address edit on line 0 (CommandRow ADDR field) bool ok = m_editor->beginInlineEdit(EditTarget::BaseAddress, 0); @@ -978,7 +978,7 @@ private slots: m_editor->applyDocument(m_result); QApplication::processEvents(); - // Root header (line 2) has fold suppressed; find a nested struct with foldHead + // Root header is suppressed; find a nested struct with foldHead int foldLine = -1; for (int i = 0; i < m_result.meta.size(); i++) { if (m_result.meta[i].foldHead && m_result.meta[i].lineKind == LineKind::Header) { @@ -1033,57 +1033,54 @@ private slots: QVERIFY(!m_editor->isEditing()); } - // ── Test: CommandRow2 exists at kCommandRow2Line ── - void testCommandRow2Exists() { + // ── Test: CommandRow root class edits on line 0 ── + void testCommandRowRootClassEdits() { m_editor->applyDocument(m_result); - // 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); + // Set CommandRow text with root class (simulates controller.updateCommandRow) + m_editor->setCommandRowText( + QStringLiteral("source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct\u25BE _PEB64 {")); - // Type/Name/Value should be rejected on CommandRow2 - 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 CommandRow (line 0) + bool ok = m_editor->beginInlineEdit(EditTarget::RootClassName, 0); + QVERIFY2(ok, "RootClassName edit should be allowed on CommandRow"); + QVERIFY(m_editor->isEditing()); + m_editor->cancelInlineEdit(); - // RootClassName should be allowed on CommandRow2 - m_editor->setCommandRow2Text(QStringLiteral("struct\u25BE _PEB64")); - bool ok = m_editor->beginInlineEdit(EditTarget::RootClassName, kCommandRow2Line); - QVERIFY2(ok, "RootClassName edit should be allowed on CommandRow2"); + // RootClassType should be allowed on CommandRow (line 0) + ok = m_editor->beginInlineEdit(EditTarget::RootClassType, 0); + QVERIFY2(ok, "RootClassType edit should be allowed on CommandRow"); QVERIFY(m_editor->isEditing()); m_editor->cancelInlineEdit(); } - // ── Test: CommandRow2 has class type and name but no alignas ── - void testCommandRow2NoAlignas() { + // ── Test: CommandRow root class name editable ── + void testCommandRowRootClassName() { m_editor->applyDocument(m_result); - // Set CommandRow2 without alignas - m_editor->setCommandRow2Text(QStringLiteral("struct\u25BE _PEB64")); + // Set CommandRow with root class + m_editor->setCommandRowText( + QStringLiteral("source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct\u25BE _PEB64 {")); - // kCommandRow2Line is CommandRow2 - const LineMeta* lm = m_editor->metaForLine(kCommandRow2Line); + // Line 0 is CommandRow + const LineMeta* lm = m_editor->metaForLine(0); QVERIFY(lm); - QCOMPARE(lm->lineKind, LineKind::CommandRow2); + QCOMPARE(lm->lineKind, LineKind::CommandRow); // RootClassName should work - QVERIFY(m_editor->beginInlineEdit(EditTarget::RootClassName, kCommandRow2Line)); + QVERIFY(m_editor->beginInlineEdit(EditTarget::RootClassName, 0)); QVERIFY(m_editor->isEditing()); m_editor->cancelInlineEdit(); m_editor->applyDocument(m_result); } - // ── Test: root header/footer are suppressed (CommandRow2 replaces them) ── + // ── Test: root header/footer are suppressed (CommandRow replaces them) ── void testRootFoldSuppressed() { m_editor->applyDocument(m_result); // Root struct header is completely suppressed from output. - // Line 0 = CommandRow, Line 1 = Blank, Line 2 = CommandRow2, Line 3 = first field. + // Line 0 = CommandRow, Line 1 = first field. const LineMeta* lm2 = m_editor->metaForLine(kFirstDataLine); QVERIFY(lm2); QCOMPARE(lm2->lineKind, LineKind::Field); diff --git a/tests/test_new_features.cpp b/tests/test_new_features.cpp index c8cf1bc..df89b16 100644 --- a/tests/test_new_features.cpp +++ b/tests/test_new_features.cpp @@ -441,11 +441,9 @@ private slots: NullProvider prov; ComposeResult result = compose(tree, prov, 99999); - // Only command rows + blank - QCOMPARE(result.meta.size(), 3); + // Only command row + QCOMPARE(result.meta.size(), 1); QCOMPARE(result.meta[0].lineKind, LineKind::CommandRow); - QCOMPARE(result.meta[1].lineKind, LineKind::Blank); - QCOMPARE(result.meta[2].lineKind, LineKind::CommandRow2); } void testCompose_viewRootId_singleRoot() { @@ -882,13 +880,13 @@ private slots: NullProvider prov; ComposeResult result = compose(tree, prov); - // CommandRow + Blank + CommandRow2 + 1 Vec4 line + footer = 5 - QCOMPARE(result.meta.size(), 5); + // CommandRow + 1 Vec4 line + footer = 3 + QCOMPARE(result.meta.size(), 3); - // 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); + // The Vec4 line (index 1) is a single field line, not continuation + QCOMPARE(result.meta[1].lineKind, LineKind::Field); + QCOMPARE(result.meta[1].nodeKind, NodeKind::Vec4); + QVERIFY(!result.meta[1].isContinuation); // Copy text (equivalent to editor's "Copy All as Text") QString text = result.text; @@ -896,9 +894,9 @@ private slots: 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[3].contains("vec4")); - QVERIFY(lines[3].contains("position")); - QVERIFY(lines[3].contains("0.f, 0.f, 0.f, 0.f")); + QVERIFY(lines[1].contains("vec4")); + QVERIFY(lines[1].contains("position")); + QVERIFY(lines[1].contains("0.f, 0.f, 0.f, 0.f")); } };