diff --git a/Buffer.cc b/Buffer.cc index 4b6fa36..2e1e0b1 100644 --- a/Buffer.cc +++ b/Buffer.cc @@ -292,29 +292,29 @@ Buffer::OpenFromFile(const std::string &path, std::string &err) bool Buffer::Save(std::string &err) const { - if (!is_file_backed_ || filename_.empty()) { - err = "Buffer is not file-backed; use SaveAs()"; - return false; - } - std::ofstream out(filename_, std::ios::out | std::ios::binary | std::ios::trunc); - if (!out) { - err = "Failed to open for write: " + filename_ + ". Error: " + std::string(std::strerror(errno)); - return false; - } - // Stream the content directly from the piece table to avoid relying on - // full materialization, which may yield an empty pointer when size > 0. - if (content_.Size() > 0) { - content_.WriteToStream(out); - } - // Ensure data hits the OS buffers - out.flush(); - if (!out.good()) { - err = "Write error: " + filename_ + ". Error: " + std::string(std::strerror(errno)); - return false; - } - // Note: const method cannot change dirty_. Intentionally const to allow UI code - // to decide when to flip dirty flag after successful save. - return true; + if (!is_file_backed_ || filename_.empty()) { + err = "Buffer is not file-backed; use SaveAs()"; + return false; + } + std::ofstream out(filename_, std::ios::out | std::ios::binary | std::ios::trunc); + if (!out) { + err = "Failed to open for write: " + filename_ + ". Error: " + std::string(std::strerror(errno)); + return false; + } + // Stream the content directly from the piece table to avoid relying on + // full materialization, which may yield an empty pointer when size > 0. + if (content_.Size() > 0) { + content_.WriteToStream(out); + } + // Ensure data hits the OS buffers + out.flush(); + if (!out.good()) { + err = "Write error: " + filename_ + ". Error: " + std::string(std::strerror(errno)); + return false; + } + // Note: const method cannot change dirty_. Intentionally const to allow UI code + // to decide when to flip dirty flag after successful save. + return true; } @@ -346,16 +346,16 @@ Buffer::SaveAs(const std::string &path, std::string &err) err = "Failed to open for write: " + out_path + ". Error: " + std::string(std::strerror(errno)); return false; } - // Stream content without forcing full materialization - if (content_.Size() > 0) { - content_.WriteToStream(out); - } - // Ensure data hits the OS buffers - out.flush(); - if (!out.good()) { - err = "Write error: " + out_path + ". Error: " + std::string(std::strerror(errno)); - return false; - } + // Stream content without forcing full materialization + if (content_.Size() > 0) { + content_.WriteToStream(out); + } + // Ensure data hits the OS buffers + out.flush(); + if (!out.good()) { + err = "Write error: " + out_path + ". Error: " + std::string(std::strerror(errno)); + return false; + } filename_ = out_path; is_file_backed_ = true; @@ -412,6 +412,7 @@ Buffer::GetLineView(std::size_t row) const void Buffer::ensure_rows_cache() const { + std::lock_guard lock(buffer_mutex_); if (!rows_cache_dirty_) return; rows_.clear(); @@ -454,8 +455,8 @@ Buffer::delete_text(int row, int col, std::size_t len) const std::size_t L = line.size(); if (c < L) { const std::size_t take = std::min(remaining, L - c); - c += take; - remaining -= take; + c += take; + remaining -= take; } if (remaining == 0) break; @@ -463,8 +464,8 @@ Buffer::delete_text(int row, int col, std::size_t len) if (r + 1 < lc) { if (remaining > 0) { remaining -= 1; // the newline - r += 1; - c = 0; + r += 1; + c = 0; } } else { // At last line and still remaining: delete to EOF diff --git a/Buffer.h b/Buffer.h index 3d1684f..ede95b5 100644 --- a/Buffer.h +++ b/Buffer.h @@ -14,6 +14,7 @@ #include #include "syntax/HighlighterEngine.h" #include "Highlight.h" +#include // Forward declaration for swap journal integration namespace kte { @@ -482,4 +483,6 @@ private: std::unique_ptr highlighter_; // Non-owning pointer to swap recorder managed by Editor/SwapManager kte::SwapRecorder *swap_rec_ = nullptr; + + mutable std::mutex buffer_mutex_; }; \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 776d81b..63a9fcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(kte) include(GNUInstallDirs) set(CMAKE_CXX_STANDARD 20) -set(KTE_VERSION "1.5.8") +set(KTE_VERSION "1.5.9") # Default to terminal-only build to avoid SDL/OpenGL dependency by default. # Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available. diff --git a/PieceTable.cc b/PieceTable.cc index f99de78..7e271b6 100644 --- a/PieceTable.cc +++ b/PieceTable.cc @@ -218,9 +218,9 @@ PieceTable::addPieceBack(const Source src, const std::size_t start, const std::s std::size_t expectStart = last.start + last.len; if (expectStart == start) { - last.len += len; + last.len += len; total_size_ += len; - dirty_ = true; + dirty_ = true; version_++; range_cache_ = {}; find_cache_ = {}; @@ -231,7 +231,7 @@ PieceTable::addPieceBack(const Source src, const std::size_t start, const std::s pieces_.push_back(Piece{src, start, len}); total_size_ += len; - dirty_ = true; + dirty_ = true; InvalidateLineIndex(); version_++; range_cache_ = {}; @@ -251,9 +251,9 @@ PieceTable::addPieceFront(Source src, std::size_t start, std::size_t len) Piece &first = pieces_.front(); if (first.src == src && start + len == first.start) { first.start = start; - first.len += len; + first.len += len; total_size_ += len; - dirty_ = true; + dirty_ = true; version_++; range_cache_ = {}; find_cache_ = {}; @@ -262,7 +262,7 @@ PieceTable::addPieceFront(Source src, std::size_t start, std::size_t len) } pieces_.insert(pieces_.begin(), Piece{src, start, len}); total_size_ += len; - dirty_ = true; + dirty_ = true; InvalidateLineIndex(); version_++; range_cache_ = {}; @@ -273,6 +273,7 @@ PieceTable::addPieceFront(Source src, std::size_t start, std::size_t len) void PieceTable::materialize() const { + std::lock_guard lock(mutex_); if (!dirty_) { return; } @@ -348,6 +349,7 @@ PieceTable::coalesceNeighbors(std::size_t index) void PieceTable::InvalidateLineIndex() const { + std::lock_guard lock(mutex_); line_index_dirty_ = true; } @@ -355,22 +357,29 @@ PieceTable::InvalidateLineIndex() const void PieceTable::RebuildLineIndex() const { - if (!line_index_dirty_) + std::lock_guard lock(mutex_); + + if (!line_index_dirty_) { return; + } line_index_.clear(); line_index_.push_back(0); + std::size_t pos = 0; for (const auto &pc: pieces_) { const std::string &src = pc.src == Source::Original ? original_ : add_; const char *base = src.data() + static_cast(pc.start); + for (std::size_t j = 0; j < pc.len; ++j) { if (base[j] == '\n') { // next line starts after the newline line_index_.push_back(pos + j + 1); } } + pos += pc.len; } + line_index_dirty_ = false; } @@ -391,7 +400,7 @@ PieceTable::Insert(std::size_t byte_offset, const char *text, std::size_t len) if (pieces_.empty()) { pieces_.push_back(Piece{Source::Add, add_start, len}); total_size_ += len; - dirty_ = true; + dirty_ = true; InvalidateLineIndex(); maybeConsolidate(); version_++; @@ -405,7 +414,7 @@ PieceTable::Insert(std::size_t byte_offset, const char *text, std::size_t len) // insert at end pieces_.push_back(Piece{Source::Add, add_start, len}); total_size_ += len; - dirty_ = true; + dirty_ = true; InvalidateLineIndex(); coalesceNeighbors(pieces_.size() - 1); maybeConsolidate(); @@ -433,7 +442,7 @@ PieceTable::Insert(std::size_t byte_offset, const char *text, std::size_t len) pieces_.insert(pieces_.begin() + static_cast(idx), repl.begin(), repl.end()); total_size_ += len; - dirty_ = true; + dirty_ = true; InvalidateLineIndex(); // Try coalescing around the inserted position (the inserted piece is at idx + (inner>0 ? 1 : 0)) std::size_t ins_index = idx + (inner > 0 ? 1 : 0); @@ -488,13 +497,13 @@ PieceTable::Delete(std::size_t byte_offset, std::size_t len) // entire piece removed pieces_.erase(pieces_.begin() + static_cast(idx)); // stay at same idx for next piece - inner = 0; + inner = 0; remaining -= take; continue; } // After modifying current idx, next deletion continues at beginning of the next logical region - inner = 0; + inner = 0; remaining -= take; if (remaining == 0) break; @@ -503,7 +512,7 @@ PieceTable::Delete(std::size_t byte_offset, std::size_t len) } total_size_ -= len; - dirty_ = true; + dirty_ = true; InvalidateLineIndex(); if (idx < pieces_.size()) coalesceNeighbors(idx); @@ -692,14 +701,18 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const len = total_size_ - byte_offset; // Fast path: return cached value if version/offset/len match - if (range_cache_.valid && range_cache_.version == version_ && - range_cache_.off == byte_offset && range_cache_.len == len) { - return range_cache_.data; + { + 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; + } } 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 { @@ -714,8 +727,8 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const const char *base = src.data() + static_cast(p.start + inner); out.append(base, take); remaining -= take; - inner = 0; - idx += 1; + inner = 0; + idx += 1; } else { break; } @@ -723,11 +736,14 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const } // Update cache - range_cache_.valid = true; - range_cache_.version = version_; - range_cache_.off = byte_offset; - range_cache_.len = len; - range_cache_.data = out; + { + std::lock_guard lock(mutex_); + range_cache_.valid = true; + range_cache_.version = version_; + range_cache_.off = byte_offset; + range_cache_.len = len; + range_cache_.data = out; + } return out; } @@ -739,23 +755,30 @@ PieceTable::Find(const std::string &needle, std::size_t start) const return start <= total_size_ ? start : std::numeric_limits::max(); if (start > total_size_) return std::numeric_limits::max(); - if (find_cache_.valid && - find_cache_.version == version_ && - find_cache_.needle == needle && - find_cache_.start == start) { - return find_cache_.result; + { + std::lock_guard lock(mutex_); + if (find_cache_.valid && + find_cache_.version == version_ && + find_cache_.needle == needle && + find_cache_.start == start) { + return find_cache_.result; + } } materialize(); - auto pos = materialized_.find(needle, start); - if (pos == std::string::npos) - pos = std::numeric_limits::max(); - // Update cache - find_cache_.valid = true; - find_cache_.version = version_; - find_cache_.needle = needle; - find_cache_.start = start; - find_cache_.result = pos; + std::size_t pos; + { + std::lock_guard lock(mutex_); + pos = materialized_.find(needle, start); + if (pos == std::string::npos) + pos = std::numeric_limits::max(); + // Update cache + find_cache_.valid = true; + find_cache_.version = version_; + find_cache_.needle = needle; + find_cache_.start = start; + find_cache_.result = pos; + } return pos; } @@ -763,12 +786,15 @@ PieceTable::Find(const std::string &needle, std::size_t start) const void PieceTable::WriteToStream(std::ostream &out) const { - // Stream the content piece-by-piece without forcing full materialization - for (const auto &p : pieces_) { - if (p.len == 0) - continue; - const std::string &src = (p.src == Source::Original) ? original_ : add_; - const char *base = src.data() + static_cast(p.start); - out.write(base, static_cast(p.len)); - } + // Stream the content piece-by-piece without forcing full materialization + // No lock needed for original_ and add_ if they are not being modified. + // Since this is a const method and kte's piece table isn't modified by multiple threads + // (only queried), we just iterate pieces_. + for (const auto &p: pieces_) { + if (p.len == 0) + continue; + const std::string &src = (p.src == Source::Original) ? original_ : add_; + const char *base = src.data() + static_cast(p.start); + out.write(base, static_cast(p.len)); + } } diff --git a/PieceTable.h b/PieceTable.h index 50218c8..ac9e0b7 100644 --- a/PieceTable.h +++ b/PieceTable.h @@ -8,6 +8,7 @@ #include #include #include +#include class PieceTable { @@ -181,4 +182,6 @@ private: mutable RangeCache range_cache_; mutable FindCache find_cache_; + + mutable std::mutex mutex_; };