perf(console): tail-cap large RPC results without a full copy

A large RPC result was fully split into lines and copied before being
handed to the console, even though only the last few lines could survive
the model's line cap.

- The formatter now takes a line cap and keeps only the last N lines,
  located by a backward newline scan, so a huge result is never fully
  split or copied. JSON classification still keys off the first
  character of the whole result.
- It works over a string view and moves line text through, and fold
  spans are computed over the formatted lines directly instead of a
  separate vector of copies.
- getline semantics are preserved: a trailing newline adds no empty
  line and an empty result yields no lines.

Tests: getline parity for empty, trailing-newline and blank-line inputs,
the backward tail cap, and JSON classification and fold spans over a
capped result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 08:23:30 -05:00
co-authored by Claude Opus 4.8
parent 116a997a3a
commit f7ed1ae115
5 changed files with 104 additions and 31 deletions
+25
View File
@@ -8134,6 +8134,31 @@ void testConsoleInputModel()
auto errorLines = dragonx::ui::FormatConsoleRpcResultLines("bad rpc", true);
EXPECT_EQ(errorLines[0].role, dragonx::ui::ConsoleResultLineRole::Error);
EXPECT_EQ(errorLines[0].text, std::string("Error: bad rpc"));
// C202: getline parity + backward tail cap.
{
using dragonx::ui::FormatConsoleRpcResultLines;
EXPECT_EQ(FormatConsoleRpcResultLines("", false).size(), static_cast<size_t>(0));
EXPECT_EQ(FormatConsoleRpcResultLines("\n", false).size(), static_cast<size_t>(1));
EXPECT_EQ(FormatConsoleRpcResultLines("a\n", false).size(), static_cast<size_t>(1));
EXPECT_EQ(FormatConsoleRpcResultLines("a\n\n", false).size(), static_cast<size_t>(2));
EXPECT_EQ(FormatConsoleRpcResultLines("a\n\nb", false).size(), static_cast<size_t>(3));
auto tail = FormatConsoleRpcResultLines("l1\nl2\nl3\nl4\n", false, 2);
EXPECT_EQ(tail.size(), static_cast<size_t>(2));
EXPECT_EQ(tail[0].text, std::string("l3"));
EXPECT_EQ(tail[1].text, std::string("l4"));
EXPECT_EQ(FormatConsoleRpcResultLines("l1\nl2", false, 2).size(), static_cast<size_t>(2));
EXPECT_EQ(FormatConsoleRpcResultLines("l1\nl2", false, 5).size(), static_cast<size_t>(2));
auto one = FormatConsoleRpcResultLines("l1\nl2\nl3", false, 1);
EXPECT_EQ(one.size(), static_cast<size_t>(1));
EXPECT_EQ(one[0].text, std::string("l3"));
// JSON classification keys off the whole result even when the head is cut.
auto jt = FormatConsoleRpcResultLines("{\n \"a\": 1,\n \"b\": 2\n}", false, 2);
EXPECT_EQ(jt[0].role, dragonx::ui::ConsoleResultLineRole::JsonKey);
auto rs = dragonx::ui::ComputeConsoleResultFoldSpans(
FormatConsoleRpcResultLines("{\n \"a\": 1\n}", false));
EXPECT_EQ(rs[0], 2);
}
}
void testMiningThreadCommit()