PieceTable: keep the line index across consolidation; reset moved-from
Consolidation rewrites pieces without changing content or offsets but invalidated the line index, so once a buffer passed the piece limit every edit paid a full rescan again. A moved-from table kept the index of the text it gave away, and Insert now updates that index in place, producing ranges past the end of its buffer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
+5
-2
@@ -74,6 +74,7 @@ PieceTable::PieceTable(PieceTable &&other) noexcept
|
|||||||
{
|
{
|
||||||
other.dirty_ = true;
|
other.dirty_ = true;
|
||||||
other.total_size_ = 0;
|
other.total_size_ = 0;
|
||||||
|
other.InvalidateLineIndex(); // its index described the moved-away text
|
||||||
version_ = other.version_;
|
version_ = other.version_;
|
||||||
range_cache_ = {};
|
range_cache_ = {};
|
||||||
find_cache_ = {};
|
find_cache_ = {};
|
||||||
@@ -93,6 +94,7 @@ PieceTable::operator=(PieceTable &&other) noexcept
|
|||||||
total_size_ = other.total_size_;
|
total_size_ = other.total_size_;
|
||||||
other.dirty_ = true;
|
other.dirty_ = true;
|
||||||
other.total_size_ = 0;
|
other.total_size_ = 0;
|
||||||
|
other.InvalidateLineIndex(); // its index described the moved-away text
|
||||||
version_ = other.version_;
|
version_ = other.version_;
|
||||||
range_cache_ = {};
|
range_cache_ = {};
|
||||||
find_cache_ = {};
|
find_cache_ = {};
|
||||||
@@ -603,9 +605,10 @@ PieceTable::consolidateRange(std::size_t start_idx, std::size_t end_idx)
|
|||||||
pieces_.begin() + static_cast<std::ptrdiff_t>(end_idx));
|
pieces_.begin() + static_cast<std::ptrdiff_t>(end_idx));
|
||||||
pieces_.insert(pieces_.begin() + static_cast<std::ptrdiff_t>(start_idx), consolidated);
|
pieces_.insert(pieces_.begin() + static_cast<std::ptrdiff_t>(start_idx), consolidated);
|
||||||
|
|
||||||
// total_size_ unchanged
|
// total_size_ unchanged. Content and byte offsets are unchanged too, so
|
||||||
|
// the line index stays valid (invalidating it here forced a full rescan
|
||||||
|
// on every edit once a buffer had many pieces).
|
||||||
dirty_ = true;
|
dirty_ = true;
|
||||||
InvalidateLineIndex();
|
|
||||||
coalesceNeighbors(start_idx);
|
coalesceNeighbors(start_idx);
|
||||||
// Layout changed; invalidate caches/version
|
// Layout changed; invalidate caches/version
|
||||||
version_++;
|
version_++;
|
||||||
|
|||||||
@@ -511,10 +511,10 @@ TEST(Audit_B6_HorizontalScroll_CountsCells)
|
|||||||
|
|
||||||
// P2: the line index is updated in place by Insert/Delete. Query it after
|
// P2: the line index is updated in place by Insert/Delete. Query it after
|
||||||
// every edit (so it is never dirty) and compare with a plain string model.
|
// every edit (so it is never dirty) and compare with a plain string model.
|
||||||
TEST(Audit_P2_IncrementalLineIndex_MatchesModel)
|
static void
|
||||||
|
check_incremental_line_index(PieceTable &t)
|
||||||
{
|
{
|
||||||
std::mt19937 rng(20260923u);
|
std::mt19937 rng(20260923u);
|
||||||
PieceTable t;
|
|
||||||
std::string model;
|
std::string model;
|
||||||
const char alphabet[] = "ab\nc\n\nd";
|
const char alphabet[] = "ab\nc\n\nd";
|
||||||
for (int step = 0; step < 3000; ++step) {
|
for (int step = 0; step < 3000; ++step) {
|
||||||
@@ -546,3 +546,35 @@ TEST(Audit_P2_IncrementalLineIndex_MatchesModel)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Audit_P2_IncrementalLineIndex_MatchesModel)
|
||||||
|
{
|
||||||
|
PieceTable t;
|
||||||
|
check_incremental_line_index(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Consolidation (forced here by a tiny piece limit) rewrites pieces without
|
||||||
|
// changing content; the incrementally maintained index must stay correct.
|
||||||
|
TEST(Audit_P2_IncrementalLineIndex_WithConsolidation)
|
||||||
|
{
|
||||||
|
PieceTable t(0, 8, 64, 1 << 20);
|
||||||
|
check_incremental_line_index(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A moved-from table must not keep (and incrementally update) the index of
|
||||||
|
// the text it gave away.
|
||||||
|
TEST(Audit_PieceTable_MovedFrom_LineIndexReset)
|
||||||
|
{
|
||||||
|
const std::string text = "l1\nl2\nl3\n";
|
||||||
|
PieceTable a;
|
||||||
|
a.Append(text.data(), text.size());
|
||||||
|
ASSERT_EQ(a.LineCount(), (std::size_t) 4);
|
||||||
|
PieceTable b(std::move(a));
|
||||||
|
a.Insert(0, "x", 1);
|
||||||
|
ASSERT_EQ(a.Size(), (std::size_t) 1);
|
||||||
|
ASSERT_EQ(a.LineCount(), (std::size_t) 1);
|
||||||
|
ASSERT_EQ(b.LineCount(), (std::size_t) 4);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user