/* * Buffer.h - editor buffer representing an open document */ #ifndef KTE_BUFFER_H #define KTE_BUFFER_H #include #include #include #include #include "AppendBuffer.h" #include "UndoSystem.h" class Buffer { public: Buffer(); Buffer(const Buffer &other); Buffer &operator=(const Buffer &other); Buffer(Buffer &&other) noexcept; Buffer &operator=(Buffer &&other) noexcept; explicit Buffer(const std::string &path); // File operations bool OpenFromFile(const std::string &path, std::string &err); bool Save(std::string &err) const; // saves to existing filename; returns false if not file-backed bool SaveAs(const std::string &path, std::string &err); // saves to path and makes buffer file-backed // Accessors [[nodiscard]] std::size_t Curx() const { return curx_; } [[nodiscard]] std::size_t Cury() const { return cury_; } [[nodiscard]] std::size_t Rx() const { return rx_; } [[nodiscard]] std::size_t Nrows() const { return nrows_; } [[nodiscard]] std::size_t Rowoffs() const { return rowoffs_; } [[nodiscard]] std::size_t Coloffs() const { return coloffs_; } // Line wrapper backed by AppendBuffer (GapBuffer/PieceTable) class Line { public: Line() = default; Line(const char *s) { assign_from(s ? std::string(s) : std::string()); } Line(const std::string &s) { assign_from(s); } Line(const Line &other) = default; Line &operator=(const Line &other) = default; Line(Line &&other) noexcept = default; Line &operator=(Line &&other) noexcept = default; // capacity helpers void Clear() { buf_.Clear(); } // size/access [[nodiscard]] std::size_t size() const { return buf_.Size(); } [[nodiscard]] bool empty() const { return size() == 0; } // read-only raw view [[nodiscard]] const char *Data() const { return buf_.Data(); } [[nodiscard]] std::size_t Size() const { return buf_.Size(); } // element access (read-only) [[nodiscard]] char operator[](std::size_t i) const { const char *d = buf_.Data(); return (i < buf_.Size() && d) ? d[i] : '\0'; } // conversions operator std::string() const { return std::string(buf_.Data() ? buf_.Data() : "", buf_.Size()); } // string-like API used by command/renderer layers (implemented via materialization for now) std::string substr(std::size_t pos) const { const std::size_t n = buf_.Size(); if (pos >= n) return std::string(); return std::string(buf_.Data() + pos, n - pos); } std::string substr(std::size_t pos, std::size_t len) const { const std::size_t n = buf_.Size(); if (pos >= n) return std::string(); const std::size_t take = (pos + len > n) ? (n - pos) : len; return std::string(buf_.Data() + pos, take); } void erase(std::size_t pos) { // erase to end material_edit([&](std::string &s) { if (pos < s.size()) s.erase(pos); }); } void erase(std::size_t pos, std::size_t len) { material_edit([&](std::string &s) { if (pos < s.size()) s.erase(pos, len); }); } void insert(std::size_t pos, const std::string &seg) { material_edit([&](std::string &s) { if (pos > s.size()) pos = s.size(); s.insert(pos, seg); }); } Line &operator+=(const Line &other) { buf_.Append(other.buf_.Data(), other.buf_.Size()); return *this; } Line &operator+=(const std::string &s) { buf_.Append(s.data(), s.size()); return *this; } Line &operator=(const std::string &s) { assign_from(s); return *this; } private: void assign_from(const std::string &s) { buf_.Clear(); if (!s.empty()) buf_.Append(s.data(), s.size()); } template void material_edit(F fn) { std::string tmp = static_cast(*this); fn(tmp); assign_from(tmp); } AppendBuffer buf_; }; [[nodiscard]] const std::vector &Rows() const { return rows_; } [[nodiscard]] std::vector &Rows() { return rows_; } [[nodiscard]] const std::string &Filename() const { return filename_; } [[nodiscard]] bool IsFileBacked() const { return is_file_backed_; } [[nodiscard]] bool Dirty() const { return dirty_; } void SetCursor(std::size_t x, std::size_t y) { curx_ = x; cury_ = y; } void SetRenderX(std::size_t rx) { rx_ = rx; } void SetOffsets(std::size_t row, std::size_t col) { rowoffs_ = row; coloffs_ = col; } void SetDirty(bool d) { dirty_ = d; } // Mark support void ClearMark() { mark_set_ = false; } void SetMark(std::size_t x, std::size_t y) { mark_set_ = true; mark_curx_ = x; mark_cury_ = y; } [[nodiscard]] bool MarkSet() const { return mark_set_; } [[nodiscard]] std::size_t MarkCurx() const { return mark_curx_; } [[nodiscard]] std::size_t MarkCury() const { return mark_cury_; } [[nodiscard]] std::string AsString() const; // Raw, low-level editing APIs used by UndoSystem apply(). // These must NOT trigger undo recording. They also do not move the cursor. void insert_text(int row, int col, std::string_view text); void delete_text(int row, int col, std::size_t len); void split_line(int row, int col); void join_lines(int row); void insert_row(int row, std::string_view text); void delete_row(int row); // Undo system accessors (created per-buffer) UndoSystem *Undo(); const UndoSystem *Undo() const; private: // State mirroring original C struct (without undo_tree) std::size_t curx_ = 0, cury_ = 0; // cursor position in characters std::size_t rx_ = 0; // render x (tabs expanded) std::size_t nrows_ = 0; // number of rows std::size_t rowoffs_ = 0, coloffs_ = 0; // viewport offsets std::vector rows_; // buffer rows (without trailing newlines) std::string filename_; bool is_file_backed_ = false; bool dirty_ = false; bool mark_set_ = false; std::size_t mark_curx_ = 0, mark_cury_ = 0; // Per-buffer undo state std::unique_ptr undo_tree_; std::unique_ptr undo_sys_; }; #endif // KTE_BUFFER_H