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.
This commit is contained in:
2025-12-07 00:30:11 -08:00
parent 657c9bbc19
commit f6f0c11be4
10 changed files with 682 additions and 498 deletions

View File

@@ -292,28 +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;
}
// Write the entire buffer in a single block to minimize I/O calls.
const char *data = content_.Data();
const auto size = static_cast<std::streamsize>(content_.Size());
if (data != nullptr && size > 0) {
out.write(data, size);
}
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;
}
@@ -345,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;
}
// Write whole content in a single I/O operation
const char *data = content_.Data();
const auto size = static_cast<std::streamsize>(content_.Size());
if (data != nullptr && size > 0) {
out.write(data, size);
}
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;