Commands: keep line structure intact at end of buffer; undo visual newline
- Regex replace-all, reflow and kill-region replaced or deleted whole rows, which added or left a stray newline when the text ended on a last line without one. They now edit the byte range in place. - Regex replace-all no longer matches the empty position after a final newline. Before, a zero-width pattern such as ^ matched it, added a line, and matched the new empty last row again: the editor hung. - Kill-line on the last row kills an unterminated line with the newline before it, and does nothing on the empty row after a final newline (it used to record an undo step that added a line). - The visual-line newline records its splits as one undo step, and places the cursor correctly for a repeat count. - Buffer::delete_text computes its byte range directly instead of materializing every line it spans. Audit findings D3, D4, D5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
@@ -699,51 +699,18 @@ Buffer::delete_text(int row, int col, std::size_t len)
|
||||
if (col < 0)
|
||||
col = 0;
|
||||
|
||||
// `len` counts each newline as one character, so the deletion is simply
|
||||
// the next `len` bytes from the (clamped) start, capped at end of buffer.
|
||||
const std::size_t start = content_.LineColToByteOffset(static_cast<std::size_t>(row),
|
||||
static_cast<std::size_t>(col));
|
||||
std::size_t r = static_cast<std::size_t>(row);
|
||||
std::size_t c = static_cast<std::size_t>(col);
|
||||
std::size_t remaining = len;
|
||||
const std::size_t lc = content_.LineCount();
|
||||
|
||||
while (remaining > 0 && r < lc) {
|
||||
const std::string line = content_.GetLine(r); // logical line (without trailing '\n')
|
||||
const std::size_t L = line.size();
|
||||
if (c < L) {
|
||||
const std::size_t take = std::min(remaining, L - c);
|
||||
c += take;
|
||||
remaining -= take;
|
||||
}
|
||||
if (remaining == 0)
|
||||
break;
|
||||
// Consume newline between lines as one char, if there is a next line
|
||||
if (r + 1 < lc) {
|
||||
remaining -= 1; // the newline
|
||||
r += 1;
|
||||
c = 0;
|
||||
} else {
|
||||
// At last line and still remaining: delete to EOF
|
||||
const std::size_t total = content_.Size();
|
||||
const std::size_t actual = (total > start) ? (total - start) : 0;
|
||||
if (actual == 0)
|
||||
return;
|
||||
content_.Delete(start, actual);
|
||||
rows_cache_dirty_ = true;
|
||||
if (swap_rec_)
|
||||
swap_rec_->OnDelete(row, col, actual);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute end offset at (r,c)
|
||||
std::size_t end = content_.LineColToByteOffset(r, c);
|
||||
if (end > start) {
|
||||
const std::size_t actual = end - start;
|
||||
content_.Delete(start, actual);
|
||||
rows_cache_dirty_ = true;
|
||||
if (swap_rec_)
|
||||
swap_rec_->OnDelete(row, col, actual);
|
||||
}
|
||||
const std::size_t total = content_.Size();
|
||||
const std::size_t actual = (total > start) ? std::min(len, total - start) : 0;
|
||||
if (actual == 0)
|
||||
return;
|
||||
content_.Delete(start, actual);
|
||||
rows_cache_dirty_ = true;
|
||||
if (swap_rec_)
|
||||
swap_rec_->OnDelete(row, col, actual);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user