Perf: update the line index in place; renderers read only visible rows

- PieceTable::Insert/Delete adjust a valid line index directly (shift
  later line starts, add or drop starts for inserted or deleted
  newlines) instead of invalidating it, which forced a byte-by-byte
  rescan of the whole buffer on the next line query. On a 38 MB,
  1M-line buffer, insert + line query drops from 14.7 ms to 0.28 ms.
- The terminal and ImGui renderers fetch only the rows they draw
  (Nrows + GetLineString) instead of Buffer::Rows(), which rebuilt a
  string for every line of the file on the first frame after each edit.
  Qt's line-count uses switch to Nrows().

ImGuiRenderer.cc was syntax-checked with g++ and clang++ against the
vendored ImGui headers; the GUI targets could not be built here (no
SDL2 or Qt6).

Audit findings P1, P2.

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 09:16:04 -07:00
committed by Kyle Isom
parent 2a1c49a0f0
commit 4c42761fc0
6 changed files with 112 additions and 24 deletions
+10 -8
View File
@@ -42,7 +42,9 @@ TerminalRenderer::Draw(Editor &ed)
int saved_cur_y = -1, saved_cur_x = -1; // logical cursor position within content area
if (buf) {
const auto &lines = buf->Rows();
// Per-row access only: Rows() rebuilds a string for every line of the
// file after each edit, which made every frame O(file size).
const std::size_t nlines = buf->Nrows();
std::size_t rowoffs = buf->Rowoffs();
std::size_t coloffs = buf->Coloffs();
@@ -62,8 +64,8 @@ TerminalRenderer::Draw(Editor &ed)
// Compute matches for this line if search highlighting is active
bool search_mode = ed.SearchActive() && !ed.SearchQuery().empty();
std::vector<std::pair<std::size_t, std::size_t> > ranges; // [start, end)
if (search_mode && li < lines.size()) {
std::string sline = static_cast<std::string>(lines[li]);
if (search_mode && li < nlines) {
std::string sline = buf->GetLineString(li);
// If regex search prompt is active (RegexSearch or RegexReplaceFind), use regex to compute highlight ranges
if (ed.PromptActive() && (
ed.CurrentPromptKind() == Editor::PromptKind::RegexSearch || ed.
@@ -140,8 +142,8 @@ TerminalRenderer::Draw(Editor &ed)
return true;
};
int written = 0;
if (li < lines.size()) {
std::string line = static_cast<std::string>(lines[li]);
if (li < nlines) {
std::string line = buf->GetLineString(li);
const bool vsel_on_line = vsel_active && li >= vsel_sy && li <= vsel_ey;
const std::size_t vsel_spot_src = vsel_on_line
? std::min(buf->Curx(), line.size())
@@ -389,8 +391,8 @@ TerminalRenderer::Draw(Editor &ed)
std::size_t cx = buf->Curx();
int cur_y = static_cast<int>(cy) - static_cast<int>(buf->Rowoffs());
std::size_t rx_recomputed = 0;
if (cy < lines.size()) {
const std::string line_for_cursor = static_cast<std::string>(lines[cy]);
if (cy < nlines) {
const std::string line_for_cursor = buf->GetLineString(cy);
std::size_t src_i_cur = 0;
std::size_t render_col_cur = 0;
while (src_i_cur < line_for_cursor.size() && src_i_cur < cx) {
@@ -541,7 +543,7 @@ TerminalRenderer::Draw(Editor &ed)
left += " [RO]";
// Append total line count as "<n>L"
if (b) {
unsigned long lcount = static_cast<unsigned long>(b->Rows().size());
unsigned long lcount = static_cast<unsigned long>(b->Nrows());
left += " ";
left += std::to_string(lcount);
left += "L";