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:
@@ -236,5 +236,42 @@ std::vector<ConsoleResultLine> FormatConsoleRpcResultLines(const std::string& re
|
||||
return lines;
|
||||
}
|
||||
|
||||
std::vector<int> ComputeConsoleFoldSpans(const std::vector<std::string>& lines)
|
||||
{
|
||||
std::vector<int> spans(lines.size(), 0);
|
||||
|
||||
auto leadingSpaces = [](const std::string& s) -> std::size_t {
|
||||
std::size_t n = 0;
|
||||
while (n < s.size() && s[n] == ' ') ++n;
|
||||
return n;
|
||||
};
|
||||
auto lastNonSpace = [](const std::string& s) -> char {
|
||||
for (std::size_t i = s.size(); i-- > 0; )
|
||||
if (s[i] != ' ' && s[i] != '\t') return s[i];
|
||||
return '\0';
|
||||
};
|
||||
auto firstNonSpace = [](const std::string& s) -> char {
|
||||
for (char c : s)
|
||||
if (c != ' ' && c != '\t') return c;
|
||||
return '\0';
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
const char last = lastNonSpace(lines[i]);
|
||||
if (last != '{' && last != '[') continue; // not a block opener
|
||||
const std::size_t indent = leadingSpaces(lines[i]);
|
||||
const char wantClose = (last == '{') ? '}' : ']';
|
||||
// Matching closer = the next line at the same indentation whose first non-space char
|
||||
// is the corresponding closing bracket (pretty-printed JSON has aligned brackets).
|
||||
for (std::size_t j = i + 1; j < lines.size(); ++j) {
|
||||
if (leadingSpaces(lines[j]) == indent && firstNonSpace(lines[j]) == wantClose) {
|
||||
if (j - i >= 2) spans[i] = static_cast<int>(j - i); // >=1 inner line
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
|
||||
Reference in New Issue
Block a user