Refactor Buffer to use Line abstraction and improve handling of row operations.
This uses either a GapBuffer or PieceTable depending on the compilation.
This commit is contained in:
40
.idea/workspace.xml
generated
40
.idea/workspace.xml
generated
@@ -33,31 +33,11 @@
|
|||||||
</configurations>
|
</configurations>
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="">
|
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="Add undo/redo infrastructure and buffer management additions.">
|
||||||
<change afterPath="$PROJECT_DIR$/UndoNode.cc" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/UndoNode.h" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/UndoTree.cc" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/UndoTree.h" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/kte-cloc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Buffer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.cc" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Buffer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.cc" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Buffer.h" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.h" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Command.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Command.cc" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Command.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Command.cc" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Command.h" beforeDir="false" afterPath="$PROJECT_DIR$/Command.h" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Editor.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Editor.h" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.h" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/GUIFrontend.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIFrontend.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/GUIInputHandler.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIInputHandler.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/GUIInputHandler.h" beforeDir="false" afterPath="$PROJECT_DIR$/GUIInputHandler.h" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/GUIRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIRenderer.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/GapBuffer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GapBuffer.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/KKeymap.cc" beforeDir="false" afterPath="$PROJECT_DIR$/KKeymap.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/PieceTable.cc" beforeDir="false" afterPath="$PROJECT_DIR$/PieceTable.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/TerminalFrontend.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalFrontend.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/TerminalFrontend.h" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalFrontend.h" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/TerminalInputHandler.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalInputHandler.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/TerminalRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalRenderer.cc" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/main.cc" beforeDir="false" afterPath="$PROJECT_DIR$/main.cc" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -164,8 +144,17 @@
|
|||||||
<option name="number" value="Default" />
|
<option name="number" value="Default" />
|
||||||
<option name="presentableId" value="Default" />
|
<option name="presentableId" value="Default" />
|
||||||
<updated>1764457173148</updated>
|
<updated>1764457173148</updated>
|
||||||
<workItem from="1764457174208" duration="26658000" />
|
<workItem from="1764457174208" duration="27302000" />
|
||||||
</task>
|
</task>
|
||||||
|
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1764485311566</created>
|
||||||
|
<option name="number" value="00001" />
|
||||||
|
<option name="presentableId" value="LOCAL-00001" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1764485311566</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="2" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -176,6 +165,11 @@
|
|||||||
<isAutomaticFoundErrors value="true" />
|
<isAutomaticFoundErrors value="true" />
|
||||||
<isAutomaticReloadCMake value="true" />
|
<isAutomaticReloadCMake value="true" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<MESSAGE value="Refactoring" />
|
||||||
|
<MESSAGE value="Add undo/redo infrastructure and buffer management additions." />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="Add undo/redo infrastructure and buffer management additions." />
|
||||||
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
<select />
|
<select />
|
||||||
|
|||||||
80
Buffer.cc
80
Buffer.cc
@@ -20,21 +20,21 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
|
|||||||
{
|
{
|
||||||
// If the file doesn't exist, initialize an empty, non-file-backed buffer
|
// If the file doesn't exist, initialize an empty, non-file-backed buffer
|
||||||
// with the provided filename. Do not touch the filesystem until Save/SaveAs.
|
// with the provided filename. Do not touch the filesystem until Save/SaveAs.
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!std::filesystem::exists(path)) {
|
||||||
rows_.clear();
|
rows_.clear();
|
||||||
nrows_ = 0;
|
nrows_ = 0;
|
||||||
filename_ = path;
|
filename_ = path;
|
||||||
is_file_backed_ = false;
|
is_file_backed_ = false;
|
||||||
dirty_ = false;
|
dirty_ = false;
|
||||||
|
|
||||||
// Reset cursor/viewport state
|
// Reset cursor/viewport state
|
||||||
curx_ = cury_ = rx_ = 0;
|
curx_ = cury_ = rx_ = 0;
|
||||||
rowoffs_ = coloffs_ = 0;
|
rowoffs_ = coloffs_ = 0;
|
||||||
mark_set_ = false;
|
mark_set_ = false;
|
||||||
mark_curx_ = mark_cury_ = 0;
|
mark_curx_ = mark_cury_ = 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream in(path, std::ios::in | std::ios::binary);
|
std::ifstream in(path, std::ios::in | std::ios::binary);
|
||||||
if (!in) {
|
if (!in) {
|
||||||
@@ -42,19 +42,19 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rows_.clear();
|
rows_.clear();
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(in, line)) {
|
while (std::getline(in, line)) {
|
||||||
// std::getline strips the '\n', keep raw line
|
// std::getline strips the '\n', keep raw line
|
||||||
if (!line.empty() && !in.good()) {
|
if (!line.empty() && !in.good()) {
|
||||||
// fallthrough
|
// fallthrough
|
||||||
}
|
}
|
||||||
// Handle potential Windows CRLF: strip trailing '\r'
|
// Handle potential Windows CRLF: strip trailing '\r'
|
||||||
if (!line.empty() && line.back() == '\r') {
|
if (!line.empty() && line.back() == '\r') {
|
||||||
line.pop_back();
|
line.pop_back();
|
||||||
}
|
}
|
||||||
rows_.push_back(line);
|
rows_.emplace_back(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If file ends with a trailing newline, getline will have produced an empty
|
// If file ends with a trailing newline, getline will have produced an empty
|
||||||
// last line already. If the file is empty and no lines were read, keep rows_ empty.
|
// last line already. If the file is empty and no lines were read, keep rows_ empty.
|
||||||
@@ -86,12 +86,14 @@ Buffer::Save(std::string &err) const
|
|||||||
err = "Failed to open for write: " + filename_;
|
err = "Failed to open for write: " + filename_;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < rows_.size(); ++i) {
|
for (std::size_t i = 0; i < rows_.size(); ++i) {
|
||||||
out.write(rows_[i].data(), static_cast<std::streamsize>(rows_[i].size()));
|
const char *d = rows_[i].Data();
|
||||||
if (i + 1 < rows_.size()) {
|
std::size_t n = rows_[i].Size();
|
||||||
out.put('\n');
|
if (d && n) out.write(d, static_cast<std::streamsize>(n));
|
||||||
}
|
if (i + 1 < rows_.size()) {
|
||||||
}
|
out.put('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!out.good()) {
|
if (!out.good()) {
|
||||||
err = "Write error";
|
err = "Write error";
|
||||||
return false;
|
return false;
|
||||||
@@ -111,12 +113,14 @@ Buffer::SaveAs(const std::string &path, std::string &err)
|
|||||||
err = "Failed to open for write: " + path;
|
err = "Failed to open for write: " + path;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < rows_.size(); ++i) {
|
for (std::size_t i = 0; i < rows_.size(); ++i) {
|
||||||
out.write(rows_[i].data(), static_cast<std::streamsize>(rows_[i].size()));
|
const char *d = rows_[i].Data();
|
||||||
if (i + 1 < rows_.size()) {
|
std::size_t n = rows_[i].Size();
|
||||||
out.put('\n');
|
if (d && n) out.write(d, static_cast<std::streamsize>(n));
|
||||||
}
|
if (i + 1 < rows_.size()) {
|
||||||
}
|
out.put('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!out.good()) {
|
if (!out.good()) {
|
||||||
err = "Write error";
|
err = "Write error";
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
124
Buffer.h
124
Buffer.h
@@ -8,6 +8,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AppendBuffer.h"
|
||||||
|
|
||||||
class Buffer {
|
class Buffer {
|
||||||
public:
|
public:
|
||||||
Buffer();
|
Buffer();
|
||||||
@@ -57,13 +59,129 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[[nodiscard]] const std::vector<std::string> &Rows() const
|
// 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 <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_;
|
return rows_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[[nodiscard]] std::vector<std::string> &Rows()
|
[[nodiscard]] std::vector<Line> &Rows()
|
||||||
{
|
{
|
||||||
return rows_;
|
return rows_;
|
||||||
}
|
}
|
||||||
@@ -154,7 +272,7 @@ private:
|
|||||||
std::size_t rx_ = 0; // render x (tabs expanded)
|
std::size_t rx_ = 0; // render x (tabs expanded)
|
||||||
std::size_t nrows_ = 0; // number of rows
|
std::size_t nrows_ = 0; // number of rows
|
||||||
std::size_t rowoffs_ = 0, coloffs_ = 0; // viewport offsets
|
std::size_t rowoffs_ = 0, coloffs_ = 0; // viewport offsets
|
||||||
std::vector<std::string> rows_; // buffer rows (without trailing newlines)
|
std::vector<Line> rows_; // buffer rows (without trailing newlines)
|
||||||
std::string filename_;
|
std::string filename_;
|
||||||
bool is_file_backed_ = false;
|
bool is_file_backed_ = false;
|
||||||
bool dirty_ = false;
|
bool dirty_ = false;
|
||||||
|
|||||||
@@ -922,9 +922,9 @@ cmd_newline(CommandContext &ctx)
|
|||||||
std::size_t x = buf->Curx();
|
std::size_t x = buf->Curx();
|
||||||
int repeat = ctx.count > 0 ? ctx.count : 1;
|
int repeat = ctx.count > 0 ? ctx.count : 1;
|
||||||
for (int i = 0; i < repeat; ++i) {
|
for (int i = 0; i < repeat; ++i) {
|
||||||
if (y >= rows.size())
|
if (y >= rows.size())
|
||||||
rows.resize(y + 1);
|
rows.resize(y + 1);
|
||||||
std::string &line = rows[y];
|
auto &line = rows[y];
|
||||||
std::string tail;
|
std::string tail;
|
||||||
if (x < line.size()) {
|
if (x < line.size()) {
|
||||||
tail = line.substr(x);
|
tail = line.substr(x);
|
||||||
@@ -1103,7 +1103,7 @@ cmd_kill_line(CommandContext &ctx)
|
|||||||
if (rows.size() == 1) {
|
if (rows.size() == 1) {
|
||||||
// last remaining line: clear its contents
|
// last remaining line: clear its contents
|
||||||
killed_total += rows[0];
|
killed_total += rows[0];
|
||||||
rows[0].clear();
|
rows[0].Clear();
|
||||||
y = 0;
|
y = 0;
|
||||||
} else if (y < rows.size()) {
|
} else if (y < rows.size()) {
|
||||||
// erase current line; keep y pointing at the next line
|
// erase current line; keep y pointing at the next line
|
||||||
|
|||||||
Reference in New Issue
Block a user