Share search-match highlighting across renderers; engine owns its stack policy

SearchHighlight compiles the search pattern once per frame and yields each
line's match ranges; the terminal, ImGui and Qt renderers had three copies
of this, and Qt compiled a std::regex for every visible line on every
frame. Qt now uses kte::Regex like the others.

kte::Regex::RunGuarded replaces Command.cc's run_regex_work, which chose
the large stack by comparing engine names. ForEachRegexMatch had no
callers left and is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 12:19:59 -07:00
committed by Kyle Isom
parent 2496722b43
commit 6bc3a6e0af
12 changed files with 158 additions and 173 deletions
+8 -36
View File
@@ -9,7 +9,7 @@
#include <imgui.h>
#include "ImGuiRenderer.h"
#include "RegexEngine.h"
#include "SearchHighlight.h"
#include "Highlight.h"
#include "GUITheme.h"
#include "Buffer.h"
@@ -157,18 +157,11 @@ ImGuiRenderer::Draw(Editor &ed)
max_width_version_ = buf->Version();
}
// Hoist the search-regex compilation out of the per-line loop. Compiling
// std::regex per line per frame was a large source of idle CPU on macOS.
const bool search_mode = ed.SearchActive() && !ed.SearchQuery().empty();
const bool regex_mode = search_mode && ed.PromptActive() && (
ed.CurrentPromptKind() == Editor::PromptKind::RegexSearch ||
ed.CurrentPromptKind() == Editor::PromptKind::RegexReplaceFind);
kte::Regex search_rx; // RegexEngine.h
bool search_rx_valid = false;
if (regex_mode) {
std::string rx_err;
search_rx_valid = search_rx.Compile(ed.SearchQuery(), rx_err);
}
// Search matches: the pattern is compiled once per frame (compiling it
// per line per frame was a large source of idle CPU on macOS).
const kte::SearchHighlight search_hl(ed);
const bool search_mode = search_hl.Active();
std::vector<std::pair<std::size_t, std::size_t> > hl_src_ranges;
// Compute the visible row range and skip rendering work for off-screen
// lines. ImGui clips drawing, but string allocation, tab expansion,
@@ -350,29 +343,8 @@ ImGuiRenderer::Draw(Editor &ed)
expanded.c_str() + end).x;
};
// Compute search highlight ranges for this line in source indices
std::vector<std::pair<std::size_t, std::size_t> > hl_src_ranges;
if (search_mode) {
// In regex mode, reuse the compiled regex hoisted above the loop.
if (regex_mode) {
// Very long lines are not highlighted (see
// Regex::IncrementalLineLimit).
if (search_rx_valid && line.size() <= kte::Regex::IncrementalLineLimit()) {
std::size_t from = 0, pos = 0, len = 0;
while (from <= line.size() && search_rx.Search(line, from, pos, len)) {
hl_src_ranges.emplace_back(pos, pos + len);
from = pos + std::max<std::size_t>(len, 1);
}
}
} else {
const std::string &q = ed.SearchQuery();
std::size_t pos = 0;
while (!q.empty() && (pos = line.find(q, pos)) != std::string::npos) {
hl_src_ranges.emplace_back(pos, pos + q.size());
pos += q.size();
}
}
}
// Search highlight ranges for this line in source indices
search_hl.Ranges(line, hl_src_ranges);
auto src_to_rx = [&](std::size_t upto_src_exclusive) -> std::size_t {
std::size_t rx = 0;
std::size_t s = 0;