perf(console): extend the filtered view incrementally
With any console filter on, each batch of new output rescanned all N lines to rebuild the visible set. A text filter lowercase-copied the needle and every line to test it: O(total characters) and 2N allocations per batch. When the filter is unchanged and the model only appended or evicted lines since the last pass, the visible indices drop the evicted lines and shift down, and only the new lines are tested. The result equals a full rescan, since line text and channel never change and the filtered view has no folds. A filter change, Clear, or a model window that doesn't follow on from the last one still rescans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -948,15 +948,28 @@ void ConsoleTab::computeVisibleLines(bool& hasTextFilter, std::string& filterLow
|
||||
// fold triangles aren't drawn/clickable while filtering (else clicks would silently mutate the
|
||||
// collapsed flag with no visible effect and the glyph would disagree with the rendered block).
|
||||
folding_active_ = !has_filter;
|
||||
visible_indices_.clear();
|
||||
const int n = static_cast<int>(model_.size());
|
||||
if (has_filter) {
|
||||
const std::uint64_t seqFirst = model_.firstSeq();
|
||||
const int flatFlags = (outputFilter.daemonMessagesEnabled ? 1 : 0) | (outputFilter.errorsOnly ? 2 : 0) |
|
||||
(outputFilter.rpcTraceEnabled ? 4 : 0) | (outputFilter.appMessagesEnabled ? 8 : 0);
|
||||
// Filtered (flat) view with the exact same filter as the last pass and a model that only appended/evicted
|
||||
// since: drop evicted indices, rebase, test only the new lines. Identical to the full scan below (line text +
|
||||
// channel are immutable; the flat view ignores folds) without its per-drain O(total chars) lowercase pass.
|
||||
const bool extended = has_filter && vis_flat_valid_ && vis_flat_flags_ == flatFlags &&
|
||||
vis_flat_filter_ == outputFilter.text &&
|
||||
ExtendFlatVisibleIndices(visible_indices_, vis_flat_first_, vis_flat_end_, seqFirst, n,
|
||||
[&](int i) { return consoleLinePassesFilter(model_[i].text, model_[i].channel, outputFilter); });
|
||||
if (extended) {
|
||||
// visible_indices_ updated in place
|
||||
} else if (has_filter) {
|
||||
visible_indices_.clear();
|
||||
// Filtered view is flat — folding is bypassed so every match is reachable.
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (!consoleLinePassesFilter(model_[i].text, model_[i].channel, outputFilter)) continue;
|
||||
visible_indices_.push_back(i);
|
||||
}
|
||||
} else {
|
||||
visible_indices_.clear();
|
||||
// No filter — show all lines, but hide the interior of any collapsed JSON block
|
||||
// (its opener stays visible; the opener + foldSpan lines through the closer are skipped).
|
||||
for (int i = 0; i < n; ) {
|
||||
@@ -966,6 +979,11 @@ void ConsoleTab::computeVisibleLines(bool& hasTextFilter, std::string& filterLow
|
||||
else i++;
|
||||
}
|
||||
}
|
||||
vis_flat_valid_ = has_filter;
|
||||
vis_flat_flags_ = flatFlags;
|
||||
vis_flat_filter_ = outputFilter.text;
|
||||
vis_flat_first_ = seqFirst;
|
||||
vis_flat_end_ = seqFirst + static_cast<std::uint64_t>(n);
|
||||
filter_match_count_ = hasTextFilter ? static_cast<int>(visible_indices_.size()) : 0;
|
||||
}
|
||||
|
||||
@@ -2129,6 +2147,7 @@ void ConsoleTab::clear()
|
||||
++vis_generation_;
|
||||
layout_ = ConsoleLayout{}; // drop the refs into wrap_cache_ BEFORE resetting it (no dangling segments)
|
||||
wrap_cache_.reset();
|
||||
vis_flat_valid_ = false;
|
||||
stop_confirm_pending_ = false; // a pending 'stop' confirmation is cancelled by clearing
|
||||
addLine(TR("console_cleared"), ConsoleChannel::Info);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user