diff --git a/Buffer.h b/Buffer.h index faca09b..54ff446 100644 --- a/Buffer.h +++ b/Buffer.h @@ -44,7 +44,6 @@ #include #include "syntax/HighlighterEngine.h" #include "Highlight.h" -#include // Edit mode determines which font class is used for a buffer. enum class EditMode { Code, Writing }; diff --git a/ImGuiRenderer.cc b/ImGuiRenderer.cc index fd74926..70a6146 100644 --- a/ImGuiRenderer.cc +++ b/ImGuiRenderer.cc @@ -438,7 +438,7 @@ ImGuiRenderer::Draw(Editor &ed) } // Draw syntax-colored runs (text above background highlights) if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) { - kte::LineHighlight lh = buf->Highlighter()->GetLine( + const kte::LineHighlight &lh = buf->Highlighter()->GetLine( *buf, static_cast(i), buf->Version()); // Sanitize spans defensively: clamp to [0, line.size()], ensure end>=start, drop empties struct SSpan { diff --git a/PieceTable.cc b/PieceTable.cc index cdd599c..b550bad 100644 --- a/PieceTable.cc +++ b/PieceTable.cc @@ -321,7 +321,6 @@ PieceTable::addPieceFront(Source src, std::size_t start, std::size_t len) void PieceTable::materialize() const { - std::lock_guard lock(mutex_); if (!dirty_) { return; } @@ -401,7 +400,6 @@ PieceTable::coalesceNeighbors(std::size_t index) void PieceTable::InvalidateLineIndex() const { - std::lock_guard lock(mutex_); line_index_dirty_ = true; } @@ -435,7 +433,6 @@ PieceTable::applyLineShift() const void PieceTable::lineIndexOnInsert(std::size_t offset, const char *text, std::size_t len) const { - std::lock_guard lock(mutex_); if (line_index_dirty_) return; // Line starts after the insertion point move right; a start exactly at @@ -461,7 +458,6 @@ PieceTable::lineIndexOnInsert(std::size_t offset, const char *text, std::size_t void PieceTable::lineIndexOnDelete(std::size_t offset, std::size_t len) const { - std::lock_guard lock(mutex_); if (line_index_dirty_) return; // Starts in (offset, offset+len] followed a deleted newline: drop them. @@ -482,7 +478,6 @@ PieceTable::lineIndexOnDelete(std::size_t offset, std::size_t len) const void PieceTable::RebuildLineIndex() const { - std::lock_guard lock(mutex_); if (!line_index_dirty_) { return; @@ -930,7 +925,6 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const // Fast path: return cached value if version/offset/len match { - std::lock_guard lock(mutex_); if (range_cache_.valid && range_cache_.version == version_ && range_cache_.off == byte_offset && range_cache_.len == len) { return range_cache_.data; @@ -940,7 +934,6 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const std::string out; out.reserve(len); if (!dirty_) { - std::lock_guard lock(mutex_); // Already materialized; slice directly out.assign(materialized_.data() + static_cast(byte_offset), len); } else { @@ -965,7 +958,6 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const // Update cache { - std::lock_guard lock(mutex_); range_cache_.valid = true; range_cache_.version = version_; range_cache_.off = byte_offset; @@ -984,7 +976,6 @@ PieceTable::Find(const std::string &needle, std::size_t start) const if (start > total_size_) return std::numeric_limits::max(); { - std::lock_guard lock(mutex_); if (find_cache_.valid && find_cache_.version == version_ && find_cache_.needle == needle && @@ -996,7 +987,6 @@ PieceTable::Find(const std::string &needle, std::size_t start) const materialize(); std::size_t pos; { - std::lock_guard lock(mutex_); pos = materialized_.find(needle, start); if (pos == std::string::npos) pos = std::numeric_limits::max(); diff --git a/PieceTable.h b/PieceTable.h index 5356ca0..3033f48 100644 --- a/PieceTable.h +++ b/PieceTable.h @@ -33,7 +33,8 @@ * * Implementation notes: * - Consolidation heuristics prevent piece fragmentation (configurable via SetConsolidationParams) - * - Thread-safe for concurrent reads (mutex protects caches and lazy rebuilds) + * - Not thread-safe: even reads update caches. Buffers are used from the + * main thread only (the swap writer thread gets copies of the bytes). * - Version tracking invalidates caches on mutations */ #pragma once @@ -43,7 +44,6 @@ #include #include #include -#include #include #include "TextSpan.h" @@ -297,6 +297,4 @@ private: mutable RangeCache range_cache_; mutable FindCache find_cache_; - - mutable std::mutex mutex_; }; \ No newline at end of file diff --git a/QtFrontend.cc b/QtFrontend.cc index 16e3870..6417655 100644 --- a/QtFrontend.cc +++ b/QtFrontend.cc @@ -308,7 +308,7 @@ protected: // as operators). if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()-> HasHighlighter()) { - kte::LineHighlight lh = buf->Highlighter()->GetLine( + const kte::LineHighlight &lh = buf->Highlighter()->GetLine( *buf, static_cast(i), buf->Version()); struct SSpan { std::size_t s; diff --git a/TerminalRenderer.cc b/TerminalRenderer.cc index 7fae63b..142e795 100644 --- a/TerminalRenderer.cc +++ b/TerminalRenderer.cc @@ -163,7 +163,7 @@ TerminalRenderer::Draw(Editor &ed) std::vector sane_spans; if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()-> HasHighlighter()) { - kte::LineHighlight lh_val = buf->Highlighter()->GetLine( + const kte::LineHighlight &lh_val = buf->Highlighter()->GetLine( *buf, static_cast(li), buf->Version()); // Sanitize defensively: clamp to [0, line.size()], ensure end>=start, drop empties const std::size_t line_len = line.size(); diff --git a/syntax/HighlighterEngine.cc b/syntax/HighlighterEngine.cc index e94c81a..03e163a 100644 --- a/syntax/HighlighterEngine.cc +++ b/syntax/HighlighterEngine.cc @@ -14,7 +14,6 @@ HighlighterEngine::~HighlighterEngine() = default; void HighlighterEngine::SetHighlighter(std::unique_ptr hl) { - std::lock_guard lock(mtx_); hl_ = std::move(hl); clear_caches_locked(); } @@ -39,63 +38,52 @@ HighlighterEngine::invalidate_from_locked(int row) const } -LineHighlight +const LineHighlight & HighlighterEngine::GetLine(const Buffer &buf, int row, std::uint64_t buf_version) const { - std::lock_guard lock(mtx_); if (!have_version_ || buf_version != version_) { // Content changed without OnEdit: nothing cached can be trusted. clear_caches_locked(); version_ = buf_version; have_version_ = true; } - auto it = cache_.find(row); - if (it != cache_.end()) - return it->second; // return by value (copy) + if (auto it = cache_.find(row); it != cache_.end()) + return it->second; LineHighlight result; result.version = buf_version; - - if (!hl_ || row < 0) { - cache_[row] = result; - return result; - } - - auto *stateful = dynamic_cast(hl_.get()); - if (!stateful) { + if (hl_ && row >= 0 && !hl_->Stateful()) { hl_->HighlightLine(buf, row, result.spans); - cache_[row] = result; - trim_cache_locked(row); - return result; + } else if (hl_ && row >= 0) { + const auto *stateful = static_cast(hl_.get()); + // Continue from the last row with a known end state (or the row + // just above the target, if that is known), caching every row's + // spans and state along the way so neighbouring rows are cache hits. + const int known = static_cast(states_.size()) - 1; + const int start = std::min(known, row - 1); + StatefulHighlighter::LineState state = (start >= 0) ? states_[static_cast(start)] + : StatefulHighlighter::LineState{}; + const int nrows = static_cast(buf.Nrows()); + for (int r = start + 1; r <= row && r < nrows; ++r) { + LineHighlight lh; + lh.version = buf_version; + state = stateful->HighlightLineStateful(buf, r, state, lh.spans); + if (static_cast(r) == states_.size()) + states_.push_back(state); + else + states_[static_cast(r)] = state; + if (r == row) + result = std::move(lh); + else if (row - r <= kCacheNear) + cache_.insert_or_assign(r, std::move(lh)); + // Rows further above only needed their end state (kept in + // states_): caching their spans too held about 10x the file size + // in memory after jumping to the end of a large file. + } } - - // Stateful: continue from the last row with a known end state (or the - // row just above the target, if that is known), caching every row's - // spans and state along the way so neighbouring rows are cache hits. - const int known = static_cast(states_.size()) - 1; - const int start = std::min(known, row - 1); - StatefulHighlighter::LineState state = (start >= 0) ? states_[static_cast(start)] - : StatefulHighlighter::LineState{}; - const int nrows = static_cast(buf.Nrows()); - for (int r = start + 1; r <= row && r < nrows; ++r) { - LineHighlight lh; - lh.version = buf_version; - state = stateful->HighlightLineStateful(buf, r, state, lh.spans); - if (static_cast(r) == states_.size()) - states_.push_back(state); - else - states_[static_cast(r)] = state; - if (r == row) - result = lh; - else if (row - r <= kCacheNear) - cache_.insert_or_assign(r, std::move(lh)); - // Rows further above only needed their end state (kept in states_): - // caching their spans too held about 10x the file size in memory - // after jumping to the end of a large file. - } - cache_[row] = result; + // Trim first: the entry returned must survive until the next call. trim_cache_locked(row); - return result; + return cache_.insert_or_assign(row, std::move(result)).first->second; } @@ -112,7 +100,6 @@ HighlighterEngine::trim_cache_locked(const int row) const void HighlighterEngine::InvalidateFrom(int row) { - std::lock_guard lock(mtx_); invalidate_from_locked(row); } @@ -120,7 +107,6 @@ HighlighterEngine::InvalidateFrom(int row) void HighlighterEngine::OnEdit(int row, std::uint64_t buf_version) { - std::lock_guard lock(mtx_); if (have_version_) invalidate_from_locked(row); version_ = buf_version; diff --git a/syntax/HighlighterEngine.h b/syntax/HighlighterEngine.h index ff8d0ae..78de1aa 100644 --- a/syntax/HighlighterEngine.h +++ b/syntax/HighlighterEngine.h @@ -1,11 +1,11 @@ -// HighlighterEngine.h - caching layer for per-line highlights +// HighlighterEngine.h - caching layer for per-line highlights (main thread +// only, like the Buffer it reads) #pragma once #include #include #include #include -#include #include "../Highlight.h" #include "LanguageHighlighter.h" @@ -21,10 +21,10 @@ public: void SetHighlighter(std::unique_ptr hl); - // Retrieve highlights for a given line and buffer version. - // Returns a copy to avoid lifetime issues across threads/renderers. - // If cache is stale, recompute using the current highlighter. - LineHighlight GetLine(const Buffer &buf, int row, std::uint64_t buf_version) const; + // 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); @@ -74,8 +74,5 @@ private: static constexpr std::size_t kCacheMax = 16384; void trim_cache_locked(int row) const; - - // Guards the caches above. - mutable std::mutex mtx_; }; } // namespace kte