Terminal loop, virtual buffers, yank bound

- kte refuses to start when stdin is not a terminal: piped bytes were
  executed as editor commands (C-k s plus a path writes a file), and
  after EOF the loop spun at full CPU with no way to quit.
- The terminal frontend handles all pending input before drawing (a
  paste used to cost a frame and a full redraw per character) and
  redraws only after input, a resize or deferred opens, else once a
  second. The renderer caches, per row and buffer version, where the
  horizontally scrolled part of a line starts and the cursor's render
  column, instead of rescanning the line every frame. Idle CPU with the
  cursor at the end of a 20 MB line: one full core -> ~1%.
- Buffers with a virtual name (+HELP+) are not journaled and saving one
  asks for a file name; it used to write ./+HELP+ and journal under
  that path.
- Yank with a repeat count is capped at 256 MiB in total instead of
  reserving count x size bytes (C-u 1000000 C-y of a large kill).
- Remove an overload clang reports as unused (-Werror on macOS).

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 1005f5dbe8
commit 61a7338f88
10 changed files with 184 additions and 18 deletions
+44 -1
View File
@@ -241,6 +241,45 @@ TerminalRenderer::Draw(Editor &ed)
return 0;
}
};
// Fast-forward to the scroll offset with the draw loop's own skip
// rules (a tab is skipped only if it ends at or before coloffs,
// any other character while it starts before coloffs), cached
// for the row's current content.
if (coloffs > 0 && src_i == 0) {
if (skip_cache_.size() < static_cast<std::size_t>(content_rows))
skip_cache_.resize(static_cast<std::size_t>(content_rows));
SkipCache &sc = skip_cache_[static_cast<std::size_t>(r)];
if (!(sc.valid && sc.buf == buf && sc.version == buf->Version() && sc.row == li &&
sc.coloffs == coloffs)) {
std::size_t s = 0, c = 0;
while (s < line.size()) {
std::mbstate_t st = std::mbstate_t();
wchar_t w = 0;
std::size_t n = std::mbrtowc(&w, &line[s], line.size() - s, &st);
if (n == (size_t) -1 || n == (size_t) -2) {
w = static_cast<unsigned char>(line[s]);
n = 1;
} else if (n == 0) {
w = L'\0';
n = 1;
}
if (w == L'\t') {
const std::size_t next_tab = tabw - (c % tabw);
if (c + next_tab > coloffs)
break;
c += next_tab;
} else {
if (c >= coloffs)
break;
c += static_cast<std::size_t>(kte::CellWidth(w));
}
s += n;
}
sc = SkipCache{buf, buf->Version(), li, coloffs, s, c, true};
}
src_i = sc.src;
render_col = sc.col;
}
while (written < cols) {
bool from_src = false;
wchar_t wch = L' ';
@@ -391,7 +430,10 @@ 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 < nlines) {
if (cy < nlines && cursor_cache_.valid && cursor_cache_.buf == buf &&
cursor_cache_.version == buf->Version() && cursor_cache_.row == cy && cursor_cache_.cx == cx) {
rx_recomputed = cursor_cache_.rx;
} else 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;
@@ -420,6 +462,7 @@ TerminalRenderer::Draw(Editor &ed)
src_i_cur += len;
}
rx_recomputed = render_col_cur;
cursor_cache_ = CursorCache{buf, buf->Version(), cy, cx, rx_recomputed, true};
}
int cur_x = static_cast<int>(rx_recomputed) - static_cast<int>(buf->Coloffs());
if (cur_y >= 0 && cur_y < content_rows && cur_x >= 0 && cur_x < cols) {