Files
kte/syntax/HighlighterEngine.h
T
Claude d490245f30 Highlighter engine returns cached spans by reference; drop unneeded mutexes
HighlighterEngine::GetLine copied the span vector on every call (twice
on a miss) and did a dynamic_cast per call; it now returns the cached
entry (valid until the next call) and dispatches on Stateful().

PieceTable and HighlighterEngine took a mutex in their read paths while
their writes took none, and PieceTable.h claimed concurrent reads were
safe; they are not (reads update caches). Every Buffer is used from the
main thread only (the swap writer thread receives copies; the regex
large-stack thread runs while its caller waits), so the mutexes are
removed and the headers say so. ASan and the threaded tests under TSan
are clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 12:19:59 -07:00

79 lines
2.6 KiB
C++

// HighlighterEngine.h - caching layer for per-line highlights (main thread
// only, like the Buffer it reads)
#pragma once
#include <cstdint>
#include <map>
#include <memory>
#include <vector>
#include "../Highlight.h"
#include "LanguageHighlighter.h"
class Buffer;
namespace kte {
class HighlighterEngine {
public:
HighlighterEngine();
~HighlighterEngine();
void SetHighlighter(std::unique_ptr<LanguageHighlighter> hl);
// Highlights for a line at a buffer version, computed if not cached.
// The reference is valid until the next call on this engine (a later
// lookup may evict the entry); copy it to keep it longer.
const LineHighlight &GetLine(const Buffer &buf, int row, std::uint64_t buf_version) const;
// Invalidate cached lines from row (inclusive)
void InvalidateFrom(int row);
// The buffer changed at `row` and is now at `buf_version`. Highlighting
// of a row depends only on the rows above it, so rows before `row` stay
// cached. A version change that arrives without OnEdit (a path that
// changed content without reporting it) still clears the whole cache.
void OnEdit(int row, std::uint64_t buf_version);
bool HasHighlighter() const
{
return static_cast<bool>(hl_);
}
// Compute highlights for the visible range so drawing hits the cache.
// There is deliberately no background warming: a worker thread would read
// the Buffer while the main thread edits it (and could outlive a moved
// Buffer), and the PieceTable is not safe for concurrent read and write.
// warm_margin is accepted for source compatibility and ignored.
void PrefetchViewport(const Buffer &buf, int first_row, int row_count, std::uint64_t buf_version,
int warm_margin = 200) const;
private:
std::unique_ptr<LanguageHighlighter> hl_;
// Cached spans by row, valid for version_ (ordered so InvalidateFrom can
// drop a tail cheaply).
mutable std::map<int, LineHighlight> cache_;
// For stateful highlighters: states_[r] is the state after row r, for
// every row 0..states_.size()-1 (always a contiguous prefix).
mutable std::vector<StatefulHighlighter::LineState> states_;
// Buffer version the caches describe.
mutable std::uint64_t version_{0};
mutable bool have_version_{false};
void clear_caches_locked() const;
void invalidate_from_locked(int row) const;
// Span cache bounds: rows within kCacheNear of a requested row are
// cached; past kCacheMax entries, those far from the current row go.
static constexpr int kCacheNear = 1024;
static constexpr std::size_t kCacheMax = 16384;
void trim_cache_locked(int row) const;
};
} // namespace kte