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
+6 -6
View File
@@ -2052,15 +2052,15 @@ void ConsoleTab::renderCommandsPopup(ConsoleCommandExecutor& exec)
void ConsoleTab::addFormattedResult(const std::string& result, bool is_error)
{
const std::vector<ConsoleResultLine> resultLines = FormatConsoleRpcResultLines(result, is_error);
// Only the last maxLines lines can survive the model's cap, so a huge result is tail-capped
// up front instead of being fully split and copied.
std::vector<ConsoleResultLine> resultLines =
FormatConsoleRpcResultLines(result, is_error, model_.maxLines());
// Precompute JSON fold spans over the whole block so each opener line carries the offset
// to its matching closer (the spans are relative, so they stay valid as the model's line
// cap evicts from the front). The block is ingested under one lock (ingestBatch).
std::vector<std::string> texts;
texts.reserve(resultLines.size());
for (const auto& rl : resultLines) texts.push_back(rl.text);
const std::vector<int> foldSpans = ComputeConsoleFoldSpans(texts);
const std::vector<int> foldSpans = ComputeConsoleResultFoldSpans(resultLines);
std::vector<ConsoleModelLine> block;
block.reserve(resultLines.size());
@@ -2075,7 +2075,7 @@ void ConsoleTab::addFormattedResult(const std::string& result, bool is_error)
case ConsoleResultLineRole::Result: break;
}
ConsoleModelLine ml;
ml.text = resultLines[i].text;
ml.text = std::move(resultLines[i].text);
ml.channel = channel;
ml.foldSpan = foldSpans[i];
block.push_back(std::move(ml));