Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
472 lines
7.9 KiB
C++
472 lines
7.9 KiB
C++
/*
|
|
* Buffer.h - editor buffer representing an open document
|
|
*/
|
|
#ifndef KTE_BUFFER_H
|
|
#define KTE_BUFFER_H
|
|
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <string_view>
|
|
|
|
#include "AppendBuffer.h"
|
|
#include "UndoSystem.h"
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include "syntax/HighlighterEngine.h"
|
|
#include "Highlight.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;
|
|
|
|
|
|
explicit Line(const char *s)
|
|
{
|
|
assign_from(s ? std::string(s) : std::string());
|
|
}
|
|
|
|
|
|
explicit 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
|
|
explicit operator std::string() const
|
|
{
|
|
return {buf_.Data() ? buf_.Data() : "", buf_.Size()};
|
|
}
|
|
|
|
|
|
// string-like API used by command/renderer layers (implemented via materialization for now)
|
|
[[nodiscard]] std::string substr(std::size_t pos) const
|
|
{
|
|
const std::size_t n = buf_.Size();
|
|
if (pos >= n)
|
|
return {};
|
|
return {buf_.Data() + pos, n - pos};
|
|
}
|
|
|
|
|
|
[[nodiscard]] std::string substr(std::size_t pos, std::size_t len) const
|
|
{
|
|
const std::size_t n = buf_.Size();
|
|
if (pos >= n)
|
|
return {};
|
|
const std::size_t take = (pos + len > n) ? (n - pos) : len;
|
|
return {buf_.Data() + pos, take};
|
|
}
|
|
|
|
|
|
// minimal find() to support search within a line
|
|
[[nodiscard]] std::size_t find(const std::string &needle, const std::size_t pos = 0) const
|
|
{
|
|
// Materialize to std::string for now; Line is backed by AppendBuffer
|
|
const auto s = static_cast<std::string>(*this);
|
|
return s.find(needle, pos);
|
|
}
|
|
|
|
|
|
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<typename F>
|
|
void material_edit(F fn)
|
|
{
|
|
std::string tmp = static_cast<std::string>(*this);
|
|
fn(tmp);
|
|
assign_from(tmp);
|
|
}
|
|
|
|
|
|
AppendBuffer buf_;
|
|
};
|
|
|
|
|
|
[[nodiscard]] const std::vector<Line> &Rows() const
|
|
{
|
|
return rows_;
|
|
}
|
|
|
|
|
|
[[nodiscard]] std::vector<Line> &Rows()
|
|
{
|
|
return rows_;
|
|
}
|
|
|
|
|
|
[[nodiscard]] const std::string &Filename() const
|
|
{
|
|
return filename_;
|
|
}
|
|
|
|
|
|
// Set a virtual (non file-backed) display name for this buffer, e.g. "+HELP+"
|
|
// This does not mark the buffer as file-backed.
|
|
void SetVirtualName(const std::string &name)
|
|
{
|
|
filename_ = name;
|
|
is_file_backed_ = false;
|
|
}
|
|
|
|
|
|
[[nodiscard]] bool IsFileBacked() const
|
|
{
|
|
return is_file_backed_;
|
|
}
|
|
|
|
|
|
[[nodiscard]] bool Dirty() const
|
|
{
|
|
return dirty_;
|
|
}
|
|
|
|
|
|
// Read-only flag
|
|
[[nodiscard]] bool IsReadOnly() const
|
|
{
|
|
return read_only_;
|
|
}
|
|
|
|
|
|
void SetReadOnly(bool ro)
|
|
{
|
|
read_only_ = ro;
|
|
}
|
|
|
|
|
|
void ToggleReadOnly()
|
|
{
|
|
read_only_ = !read_only_;
|
|
}
|
|
|
|
|
|
void SetCursor(const std::size_t x, const std::size_t y)
|
|
{
|
|
curx_ = x;
|
|
cury_ = y;
|
|
}
|
|
|
|
|
|
void SetRenderX(const std::size_t rx)
|
|
{
|
|
rx_ = rx;
|
|
}
|
|
|
|
|
|
void SetOffsets(const std::size_t row, const std::size_t col)
|
|
{
|
|
rowoffs_ = row;
|
|
coloffs_ = col;
|
|
}
|
|
|
|
|
|
void SetDirty(bool d)
|
|
{
|
|
dirty_ = d;
|
|
if (d) {
|
|
++version_;
|
|
if (highlighter_) {
|
|
highlighter_->InvalidateFrom(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Mark support
|
|
void ClearMark()
|
|
{
|
|
mark_set_ = false;
|
|
}
|
|
|
|
|
|
void SetMark(const std::size_t x, const 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;
|
|
|
|
// Syntax highlighting integration (per-buffer)
|
|
[[nodiscard]] std::uint64_t Version() const
|
|
{
|
|
return version_;
|
|
}
|
|
|
|
|
|
void SetSyntaxEnabled(bool on)
|
|
{
|
|
syntax_enabled_ = on;
|
|
}
|
|
|
|
|
|
[[nodiscard]] bool SyntaxEnabled() const
|
|
{
|
|
return syntax_enabled_;
|
|
}
|
|
|
|
|
|
void SetFiletype(const std::string &ft)
|
|
{
|
|
filetype_ = ft;
|
|
}
|
|
|
|
|
|
[[nodiscard]] const std::string &Filetype() const
|
|
{
|
|
return filetype_;
|
|
}
|
|
|
|
|
|
kte::HighlighterEngine *Highlighter()
|
|
{
|
|
return highlighter_.get();
|
|
}
|
|
|
|
|
|
const kte::HighlighterEngine *Highlighter() const
|
|
{
|
|
return highlighter_.get();
|
|
}
|
|
|
|
|
|
void EnsureHighlighter()
|
|
{
|
|
if (!highlighter_)
|
|
highlighter_ = std::make_unique<kte::HighlighterEngine>();
|
|
}
|
|
|
|
|
|
// 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();
|
|
|
|
[[nodiscard]] 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<Line> rows_; // buffer rows (without trailing newlines)
|
|
std::string filename_;
|
|
bool is_file_backed_ = false;
|
|
bool dirty_ = false;
|
|
bool read_only_ = false;
|
|
bool mark_set_ = false;
|
|
std::size_t mark_curx_ = 0, mark_cury_ = 0;
|
|
|
|
// Per-buffer undo state
|
|
std::unique_ptr<struct UndoTree> undo_tree_;
|
|
std::unique_ptr<UndoSystem> undo_sys_;
|
|
|
|
// Syntax/highlighting state
|
|
std::uint64_t version_ = 0; // increment on edits
|
|
bool syntax_enabled_ = true;
|
|
std::string filetype_;
|
|
std::unique_ptr<kte::HighlighterEngine> highlighter_;
|
|
};
|
|
|
|
#endif // KTE_BUFFER_H
|