Files
kte/PieceTable.h
Kyle Isom f6f0c11be4 Add PieceTable-based buffer tests and improvements for file I/O and editing.
- Introduced comprehensive tests:
  - `test_buffer_open_nonexistent_save.cc`: Save after opening a non-existent file.
  - `test_buffer_save.cc`: Save buffer contents to disk.
  - `test_buffer_save_existing.cc`: Save after opening existing files.
- Implemented `PieceTable::WriteToStream()` to directly stream content without full materialization.
- Updated `Buffer::Save` and `Buffer::SaveAs` to use efficient streaming via `PieceTable`.
- Enhanced editing commands (`Insert`, `Delete`, `Replace`, etc.) to use PieceTable APIs, ensuring proper undo and save functionality.
2025-12-07 00:30:11 -08:00

185 lines
5.0 KiB
C++

/*
* PieceTable.h - Alternative to GapBuffer using a piece table representation
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <ostream>
#include <vector>
#include <limits>
class PieceTable {
public:
PieceTable();
explicit PieceTable(std::size_t initialCapacity);
// Advanced constructor allowing configuration of consolidation heuristics
PieceTable(std::size_t initialCapacity,
std::size_t piece_limit,
std::size_t small_piece_threshold,
std::size_t max_consolidation_bytes);
PieceTable(const PieceTable &other);
PieceTable &operator=(const PieceTable &other);
PieceTable(PieceTable &&other) noexcept;
PieceTable &operator=(PieceTable &&other) noexcept;
~PieceTable();
// Public API mirrors GapBuffer
void Reserve(std::size_t newCapacity);
void AppendChar(char c);
void Append(const char *s, std::size_t len);
void Append(const PieceTable &other);
void PrependChar(char c);
void Prepend(const char *s, std::size_t len);
void Prepend(const PieceTable &other);
// Content management
void Clear();
// Accessors
char *Data()
{
materialize();
return materialized_.empty() ? nullptr : materialized_.data();
}
[[nodiscard]] const char *Data() const
{
const_cast<PieceTable *>(this)->materialize();
return materialized_.empty() ? nullptr : materialized_.data();
}
[[nodiscard]] std::size_t Size() const
{
return total_size_;
}
[[nodiscard]] std::size_t Capacity() const
{
// Capacity for piece table isn't directly meaningful; report materialized capacity
return materialized_.capacity();
}
// ===== New buffer-wide API (Phase 1) =====
// Byte-based editing operations
void Insert(std::size_t byte_offset, const char *text, std::size_t len);
void Delete(std::size_t byte_offset, std::size_t len);
// Line-based queries
[[nodiscard]] std::size_t LineCount() const; // number of logical lines
[[nodiscard]] std::string GetLine(std::size_t line_num) const;
[[nodiscard]] std::pair<std::size_t, std::size_t> GetLineRange(std::size_t line_num) const; // [start,end)
// Position conversion
[[nodiscard]] std::pair<std::size_t, std::size_t> ByteOffsetToLineCol(std::size_t byte_offset) const;
[[nodiscard]] std::size_t LineColToByteOffset(std::size_t row, std::size_t col) const;
// Substring extraction
[[nodiscard]] std::string GetRange(std::size_t byte_offset, std::size_t len) const;
// Simple search utility; returns byte offset or npos
[[nodiscard]] std::size_t Find(const std::string &needle, std::size_t start = 0) const;
// Stream out content without materializing the entire buffer
void WriteToStream(std::ostream &out) const;
// Heuristic configuration
void SetConsolidationParams(std::size_t piece_limit,
std::size_t small_piece_threshold,
std::size_t max_consolidation_bytes);
private:
enum class Source : unsigned char { Original, Add };
struct Piece {
Source src;
std::size_t start;
std::size_t len;
};
void addPieceBack(Source src, std::size_t start, std::size_t len);
void addPieceFront(Source src, std::size_t start, std::size_t len);
void materialize() const;
// Helper: locate piece index and inner offset for a global byte offset
[[nodiscard]] std::pair<std::size_t, std::size_t> locate(std::size_t byte_offset) const;
// Helper: try to coalesce neighboring pieces around index
void coalesceNeighbors(std::size_t index);
// Consolidation helpers and heuristics
void maybeConsolidate();
void consolidateRange(std::size_t start_idx, std::size_t end_idx);
void appendPieceDataTo(std::string &out, const Piece &p) const;
// Line index support (rebuilt lazily on demand)
void InvalidateLineIndex() const;
void RebuildLineIndex() const;
// Underlying storages
std::string original_; // unused for builder use-case, but kept for API symmetry
std::string add_;
std::vector<Piece> pieces_;
mutable std::string materialized_;
mutable bool dirty_ = true;
// Monotonic content version. Increment on any mutation that affects content layout
mutable std::uint64_t version_ = 0;
std::size_t total_size_ = 0;
// Cached line index: starting byte offset of each line (always contains at least 1 entry: 0)
mutable std::vector<std::size_t> line_index_;
mutable bool line_index_dirty_ = true;
// Heuristic knobs
std::size_t piece_limit_ = 4096; // trigger consolidation when exceeded
std::size_t small_piece_threshold_ = 64; // bytes
std::size_t max_consolidation_bytes_ = 4096; // cap per consolidation run
// Lightweight caches to avoid redundant work when callers query the same range repeatedly
struct RangeCache {
bool valid = false;
std::uint64_t version = 0;
std::size_t off = 0;
std::size_t len = 0;
std::string data;
};
struct FindCache {
bool valid = false;
std::uint64_t version = 0;
std::string needle;
std::size_t start = 0;
std::size_t result = std::numeric_limits<std::size_t>::max();
};
mutable RangeCache range_cache_;
mutable FindCache find_cache_;
};