Regex: optional PCRE2 engine behind kte::Regex
RegexEngine.h/.cc wrap the regex engine for search, regex replace-all and the terminal and ImGui renderers' match highlighting: - With KTE_USE_PCRE2 (default ON; used when pkg-config finds libpcre2-8, linked as -lpcre2-8) patterns are compiled by PCRE2 with its JIT and matched under a match limit: catastrophic backtracking stops with a status note instead of hanging the editor, and matching does not recurse on the C stack (no large-stack thread; the per-keystroke and per-frame line limit is 1 MiB instead of 2,000 bytes). Otherwise std::regex is used as before. - Replacement strings follow std::regex_replace (ECMAScript as libstdc++ reads it) with either engine: the PCRE2 path expands $&, $n, $`, $', $$ itself and steps over empty matches the same way. - Regex search walks lines over the materialized text instead of calling GetLineView per row. The terminal renderer compiles the pattern once per frame (it compiled it for every row). - Only one checkpoint per journal is queued at a time: whole-file edits in a row on a large buffer each queued a full copy of the buffer. - default.nix and the Dockerfile add pcre2 and pkg-config. On 26 MB: regex keystroke 1.6 -> 0.14 ms, next 62 -> 6 ms, replace-all 929 -> 438 ms; a rarely matching regex over 105 MB: 1.55 s -> 0.10 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
@@ -1246,12 +1246,12 @@ SwapManager::maybe_request_checkpoint(Buffer &buf, const std::size_t approx_edit
|
||||
if (ctx.last_chkpt_ns == 0)
|
||||
ctx.last_chkpt_ns = now;
|
||||
// A checkpoint writes the whole buffer. For a large one, checkpoint
|
||||
// after edits amounting to half its size (and on the timer only
|
||||
// once edits reach a sixteenth), so the bytes written stay in
|
||||
// proportion to the bytes edited; retry a gap less often too.
|
||||
// after edits amounting to its size (and on the timer only once
|
||||
// edits reach a sixteenth), so the bytes written stay in proportion
|
||||
// to the bytes edited; retry a gap less often too.
|
||||
const std::size_t size = buf.ContentBytes();
|
||||
const bool large = size > kLargeCheckpointBytes;
|
||||
const std::size_t bytes_limit = std::max<std::size_t>(cfg.checkpoint_bytes, size / 2);
|
||||
const std::size_t bytes_limit = std::max<std::size_t>(cfg.checkpoint_bytes, large ? size : 0);
|
||||
const bool bytes_hit = (cfg.checkpoint_bytes > 0) && ctx.edit_bytes_since_chkpt >= bytes_limit;
|
||||
const bool time_hit = (cfg.checkpoint_interval_ms > 0) &&
|
||||
(((now - ctx.last_chkpt_ns) / 1000000ULL) >= cfg.checkpoint_interval_ms) &&
|
||||
@@ -1324,6 +1324,13 @@ SwapManager::RecordCheckpoint(Buffer &buf, const bool urgent_flush)
|
||||
auto it = journals_.find(&buf);
|
||||
if (it == journals_.end() || it->second.suspended)
|
||||
return;
|
||||
// One queued checkpoint at a time: records queued after it apply on
|
||||
// top of it, so a second one adds nothing but another full copy of
|
||||
// the buffer in memory (several whole-file edits in a row on a large
|
||||
// buffer queued one each).
|
||||
if (it->second.chkpt_queued)
|
||||
return;
|
||||
it->second.chkpt_queued = true;
|
||||
}
|
||||
|
||||
// Any size: the writer frames large checkpoints as several records.
|
||||
@@ -1416,6 +1423,13 @@ SwapManager::process_one(const Pending &p)
|
||||
{
|
||||
if (!p.buf)
|
||||
return;
|
||||
if (p.type == SwapRecType::CHKPT) {
|
||||
// Being handled now: a later request queues a new checkpoint.
|
||||
std::lock_guard<std::mutex> lg(mtx_);
|
||||
auto it = journals_.find(p.buf);
|
||||
if (it != journals_.end())
|
||||
it->second.chkpt_queued = false;
|
||||
}
|
||||
|
||||
// Any record that is not written leaves a gap in the journal.
|
||||
auto mark_gap = [&]() {
|
||||
|
||||
Reference in New Issue
Block a user