Review fixes: exception-safe AdoptOriginal; Qt draws cells straddling the left edge

AdoptOriginal cleared the table before allocating its piece list, so a
bad_alloc left a live buffer with the file's size and no pieces;
OpenFromFile no longer caught it either. The piece list is now built
before anything is touched (the rest cannot throw) and OpenFromFile
again reports "File too large to load" with the buffer unchanged.

Qt skipped every character starting left of the horizontal scroll
offset, so a tab or wide character straddling it left blank columns
while selection and cursor still used the real ones. Such a character
is now drawn at its own column and clipped by the viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 12:19:59 -07:00
committed by Kyle Isom
parent e1ebf5900b
commit 3be85ef4ad
4 changed files with 30 additions and 9 deletions
+10 -5
View File
@@ -617,11 +617,16 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
return false; return false;
} }
(void) kte::syscall::Close(fd); (void) kte::syscall::Close(fd);
// Build the new content aside and swap it in only once complete: if the // The bytes become the piece table's original storage, uncopied. If that
// copy runs out of memory, the buffer keeps its old text (clearing first // runs out of memory the buffer keeps its old text (clearing first left
// left an empty buffer that a later save wrote over the file). // an empty buffer that a later save wrote over the file).
// The bytes become the piece table's original storage, uncopied. try {
content_.AdoptOriginal(std::move(data)); // not used under PieceTable content_.AdoptOriginal(std::move(data));
} catch (const std::bad_alloc &) {
err = "File too large to load: " + norm;
kte::ErrorHandler::Instance().Error("Buffer", err, norm);
return false;
}
filename_ = norm; filename_ = norm;
is_file_backed_ = true; is_file_backed_ = true;
is_virtual_ = false; is_virtual_ = false;
+6 -2
View File
@@ -206,13 +206,17 @@ PieceTable::Clear()
void void
PieceTable::AdoptOriginal(std::string &&bytes) PieceTable::AdoptOriginal(std::string &&bytes)
{ {
// Allocate before touching the current content: if this throws, the
// table is unchanged (everything below is non-throwing).
std::vector<Piece> pieces;
if (!bytes.empty())
pieces.push_back(Piece{Source::Original, 0, bytes.size()});
Clear(); Clear();
std::string().swap(add_); // release a previous content's capacity std::string().swap(add_); // release a previous content's capacity
std::string().swap(materialized_); std::string().swap(materialized_);
original_ = std::move(bytes); original_ = std::move(bytes);
total_size_ = original_.size(); total_size_ = original_.size();
if (total_size_ > 0) pieces_.swap(pieces);
pieces_.push_back(Piece{Source::Original, 0, total_size_});
version_++; version_++;
} }
+1
View File
@@ -86,6 +86,7 @@ public:
// Replace the content with `bytes`, taken over without copying (a loaded // Replace the content with `bytes`, taken over without copying (a loaded
// file). Undo spans into the old storage are invalidated, as by Clear(). // file). Undo spans into the old storage are invalidated, as by Clear().
// If it throws (std::bad_alloc), the content is unchanged.
void AdoptOriginal(std::string &&bytes); void AdoptOriginal(std::string &&bytes);
// Accessors // Accessors
+13 -2
View File
@@ -204,10 +204,21 @@ protected:
p.drawText(QPointF(col_x(col), baseline), QString::fromUtf8(d, static_cast<int>(n))); p.drawText(QPointF(col_x(col), baseline), QString::fromUtf8(d, static_cast<int>(n)));
}; };
auto draw_seg = [&](std::size_t s, std::size_t e, const QColor &color) { auto draw_seg = [&](std::size_t s, std::size_t e, const QColor &color) {
// Only the visible columns: characters left of coloffs or // Only the visible columns: characters that end at or
// right of the viewport are skipped. // before coloffs, or start right of the viewport, are
// skipped. A tab or wide character straddling coloffs
// starts left of it; it is drawn at its own column and
// the clip cuts its left part.
const std::size_t first = s;
s = static_cast<std::size_t>(std::lower_bound(col_of.begin() + s, col_of.begin() + e, s = static_cast<std::size_t>(std::lower_bound(col_of.begin() + s, col_of.begin() + e,
coloffs) - col_of.begin()); coloffs) - col_of.begin());
if (s > first && col_of[s] > coloffs) {
// The character before s ends past coloffs: back up to
// its first byte.
--s;
while (s > first && (static_cast<unsigned char>(line[s]) & 0xC0) == 0x80)
--s;
}
e = static_cast<std::size_t>(std::lower_bound(col_of.begin() + s, col_of.begin() + e, e = static_cast<std::size_t>(std::lower_bound(col_of.begin() + s, col_of.begin() + e,
coloffs + vis_cols) - col_of.begin()); coloffs + vis_cols) - col_of.begin());
if (e <= s) if (e <= s)