Commit Graph
4 Commits
Author SHA1 Message Date
Claude b1b0b3de57 Fix round-four regressions: save fallback, replace memory, regex skip
- Save's in-place fallbacks (no temp file; no hard-link copy) now apply
  only when the directory refuses a new name (EACCES, EPERM,
  ENAMETOOLONG). On ENOSPC, EDQUOT or EIO the in-place write truncated
  the file and then failed, leaving it corrupt; now the save fails and
  the file is untouched (checked on a full tmpfs).
- replace_rows_text/replace_buffer_text delete and insert only the span
  between the old and new text's common prefix and suffix (kept on
  UTF-8 boundaries). Replace-all recorded the whole buffer twice in the
  undo tree per run.
- Regex search skips long lines only while typing (limit lowered to
  2,000 bytes, as for renderer highlighting; '.*q' on a 20 KB line took
  12 s), reports how many were skipped, and next/previous match search
  every line. Previously matches on long lines could not be found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 09:16:04 -07:00
Claude 91ceb9bb01 Perf: replace-all, yank and repeat counts as single edits
Each piece-table edit costs time linear in the buffer (piece lookup,
line-index update), so operations issuing one edit per match, line or
repetition were quadratic:

- Plain and regex replace-all build the new text in one pass and apply
  it as one delete + one insert (one undo group, one chunked journal
  record). 12 MB with 229k matches: minutes -> 0.14 s.
- Yank inserts the whole kill in one edit (it split and re-trimmed line
  by line): 200k lines in 0.02 s.
- InsertText with a repeat count inserts the repeated text once; the
  total is capped at 256 MiB (also across visual-line broadcast).
- Incremental regex search skips lines over 20,000 bytes and renderer
  match highlighting over 10,000 (the previous change raised the render
  limit to 1 MB): std::regex can be quadratic in line length even for
  ordinary patterns such as '.*q', uninterruptibly, and these run on
  every keystroke or frame. Explicit regex replace still processes all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 09:16:04 -07:00
Claude fb50a02f6a Swap ownership, fsync race, RowsView memory, renderer regex stack
- A session never unlinks a journal it does not own: Detach, ResetJournal
  and NotifyFilenameChanged skip removal when this session was locked
  out or another session holds the lock, and PruneSwapDir skips held
  journals. A second session used to delete the first session's live
  journal on save or close.
- The writer thread's periodic fsync now runs under mtx_. It fsynced fds
  that the main thread could close concurrently (or that had been
  reused); an in-flight fsync also kept a just-closed journal's flock
  alive, so the owner's own cleanup saw its journal as held.
- Compaction locks the compacted file before renaming it into place and
  keeps using it, so the journal is never unlocked in between; a failed
  lock on the truncating reopen marks the session locked out.
- Unnamed-buffer journals include the pid, so sessions no longer share
  (and delete) each other's unnamed-N.swp.
- RowsView keeps only the previous generation of cached rows: keeping
  all of them made C-u 2000 M-d on a 1 MiB line use ~2 GB.
- Renderers run regex match highlighting through ForEachRegexMatch,
  which uses the large-stack worker for lines over 512 bytes; some
  patterns overflowed the main stack well under the previous 10,000
  byte limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 09:16:04 -07:00
Claude 9f3da72765 Regex: don't overflow the stack on long lines
libstdc++'s std::regex matcher recurses about once per matched
character, so '.*', 'a+' or '(a|b)*' on a line of a few tens of
thousands of characters (minified JS/JSON) overflowed the 8 MiB stack
and crashed the editor from the regex search prompt or regex replace.

Regex search and regex replace now run their matching on a worker
thread with a 1 GiB reserved stack (RunWithLargeStack; the caller
waits, so nothing is shared concurrently). Renderers skip regex match
highlighting on lines over 10,000 bytes. Catastrophic backtracking
(e.g. (a*)*b) is still possible: std::regex has no time limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 09:16:04 -07:00