feat(console): collapsible JSON in command output
Add fold/unfold of JSON objects and arrays in console command results, built on the refactored channel + model foundation. - ComputeConsoleFoldSpans() (console_input_model): pure brace/bracket matcher over a block of pretty-printed JSON lines; each opener line gets the offset to its matching closer at the same indentation (empty/one-line blocks are not foldable). Unit tested (nested objects, arrays, empty blocks, non-JSON, and a string value that merely contains a brace). - ConsoleModelLine gains foldSpan (relative, so it survives front-eviction by the line cap) + a collapsed flag; ingest() carries the span and toggleCollapsed() flips openers (main thread). addFormattedResult computes the block's spans and ingests them atomically. - computeVisibleLines() skips a collapsed block's interior (opener stays, its foldSpan lines through the closer are hidden); folding is bypassed while a filter is active so every match stays reachable. - drawVisibleLines() draws a fold triangle in the free left gutter of opener lines (result/JSON channels carry no accent bar there), toggled by a click in the gutter cell, and appends a dim " ... }" / " ... ]" summary on collapsed openers. Full-node + lite build clean; ctest green (incl. fold-span + model-fold tests); source hygiene clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2562,6 +2562,66 @@ void testConsoleModel()
|
||||
EXPECT_EQ(drained, static_cast<size_t>(kThreads * kPerThread));
|
||||
EXPECT_EQ(m.size(), static_cast<size_t>(kThreads * kPerThread));
|
||||
}
|
||||
|
||||
// JSON fold state: ingest carries a fold span; toggleCollapsed flips only openers.
|
||||
{
|
||||
ConsoleModel m;
|
||||
m.ingest("{", ConsoleChannel::JsonBrace, 3);
|
||||
m.ingest(" \"a\": 1", ConsoleChannel::JsonKey, 0);
|
||||
m.ingest("}", ConsoleChannel::JsonBrace, 0);
|
||||
m.drain();
|
||||
EXPECT_EQ(m[0].foldSpan, 3);
|
||||
EXPECT_FALSE(m[0].collapsed);
|
||||
EXPECT_TRUE(m.toggleCollapsed(0)); // opener -> collapsed
|
||||
EXPECT_TRUE(m[0].collapsed);
|
||||
EXPECT_FALSE(m.toggleCollapsed(0)); // -> expanded
|
||||
EXPECT_FALSE(m[0].collapsed);
|
||||
EXPECT_FALSE(m.toggleCollapsed(1)); // not an opener -> no-op
|
||||
EXPECT_FALSE(m.toggleCollapsed(99)); // out of range -> no-op
|
||||
}
|
||||
}
|
||||
|
||||
// ComputeConsoleFoldSpans — brace/bracket matching over pretty-printed JSON lines.
|
||||
void testConsoleFoldSpans()
|
||||
{
|
||||
using dragonx::ui::ComputeConsoleFoldSpans;
|
||||
|
||||
// Nested object: outer '{' spans the whole block; inner "obj": { spans its two-line body.
|
||||
std::vector<std::string> obj = {
|
||||
"{", // 0 -> closer at 6, span 6
|
||||
" \"a\": 1,", // 1
|
||||
" \"obj\": {", // 2 -> closer at 4, span 2
|
||||
" \"b\": 2", // 3
|
||||
" },", // 4 (closer for 2)
|
||||
" \"c\": 3", // 5
|
||||
"}" // 6 (closer for 0)
|
||||
};
|
||||
auto spans = ComputeConsoleFoldSpans(obj);
|
||||
EXPECT_EQ(spans.size(), static_cast<size_t>(7));
|
||||
EXPECT_EQ(spans[0], 6);
|
||||
EXPECT_EQ(spans[1], 0);
|
||||
EXPECT_EQ(spans[2], 2);
|
||||
EXPECT_EQ(spans[3], 0);
|
||||
EXPECT_EQ(spans[4], 0); // a closer is not an opener
|
||||
EXPECT_EQ(spans[5], 0);
|
||||
EXPECT_EQ(spans[6], 0);
|
||||
|
||||
// Array opener '['.
|
||||
std::vector<std::string> arr = { "\"outputs\": [", " 1,", " 2", "]" };
|
||||
EXPECT_EQ(ComputeConsoleFoldSpans(arr)[0], 3);
|
||||
|
||||
// Empty block (opener immediately followed by closer) is not worth folding (span < 2).
|
||||
std::vector<std::string> empty = { "{", "}" };
|
||||
EXPECT_EQ(ComputeConsoleFoldSpans(empty)[0], 0);
|
||||
|
||||
// Plain non-JSON lines: no openers.
|
||||
std::vector<std::string> plain = { "hello", "world" };
|
||||
EXPECT_EQ(ComputeConsoleFoldSpans(plain)[0], 0);
|
||||
EXPECT_EQ(ComputeConsoleFoldSpans(plain)[1], 0);
|
||||
|
||||
// A string value merely containing a brace does not open a block (last char isn't '{').
|
||||
std::vector<std::string> str = { "\"note\": \"a { brace\",", "\"x\": 1" };
|
||||
EXPECT_EQ(ComputeConsoleFoldSpans(str)[0], 0);
|
||||
}
|
||||
|
||||
void testRendererHelpers()
|
||||
@@ -5251,6 +5311,7 @@ int main()
|
||||
testDaemonLifecycleAdapters();
|
||||
testConsoleTextLayout();
|
||||
testConsoleModel();
|
||||
testConsoleFoldSpans();
|
||||
testConsoleScrollController();
|
||||
testConsoleSelectionController();
|
||||
testRendererHelpers();
|
||||
|
||||
Reference in New Issue
Block a user