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
79 lines
2.6 KiB
C++
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
|