Caxton
Features Benchmarks Pricing Docs How it works Changelog Support About Download Free
Guide
Getting Started Opening Large Files Find & Replace Filtering CSV Workbench Editing at Scale Power Editing Navigation & Bookmarks Log Mode Compare View & Themes Sessions & Recovery
Reference
Keyboard Shortcuts Menu Reference Settings & Storage Encodings How Caxton Works Benchmarks

How Caxton works

Caxton's speed at large sizes is not an optimization pass on an ordinary editor. It comes from four decisions made at the start: map the file instead of loading it, keep edits in a piece table, index lines in tiers, and draw only the viewport with Core Text. This page describes each, and what they cost.

The file is memory mapped

Opening a file maps it into the address space read-only. Nothing is copied, converted, or loaded up front, which is why a 50 GB log opens as fast as a 10 KB config. The operating system pages bytes in when something reads them and drops them again under pressure, so the app's memory footprint does not grow with the file.

Mapped files can change underneath an editor: a network volume drops, a disk is ejected, another process truncates the file. The mapping is created so that a page the backing store can no longer supply reads as zeros instead of crashing the app, and every read is clamped to the file's current size, revalidated as the file is watched. The file watcher then delivers the real event: a reload, or a prompt.

Edits live in a piece table

The document is a list of pieces. Each piece points at a run of bytes in one of two buffers: the mapped original, which never changes, or an append-only buffer that holds everything you have typed or pasted. Inserting text appends the new bytes and splices a piece into the list. Deleting text only changes the list. The original file is never modified until you save.

Undo records each edit as an operation against the piece table, so undoing a Replace All across more than a million sites is bookkeeping, not copying, and it lands as one step. Undo history is limited by memory rather than by count. The limit is sized from the memory the system could actually give back at that moment, and when an operation is too large to keep, Caxton applies it and says that the step cannot be undone instead of silently dropping history.

The line index is tiered

Line numbers, Go to Line, filtering, and the gutter all need to know where each line starts. The index is built in the background from the first moment the window appears, and everything works while it builds: the editor renders and edits against what has been indexed so far.

A request for a line past the indexed frontier jumps to the frontier, says so, and completes on its own when the index arrives. Percent and byte targets need no index at all.

Rendering: an NSView and Core Text, not TextKit

The text view is a plain NSView that draws with Core Text. On each frame it asks the index which lines intersect the viewport, reads those bytes through the piece table, lays them out, and draws them. Laid-out lines are cached against the document's version counter, so an edit invalidates exactly what it must.

Long lines get the same treatment horizontally. Every line is split into fixed-size byte segments, and only the segments that overlap the visible horizontal range are decoded, laid out, and drawn. That is why a 500 MB file that is a single line scrolls, and why you can jump deep into that line and land at once.

TextKit was not an option. It lays text out from a text storage object that holds the document as a string, and a mapped 50 GB file has no such string to hand over. Building one would mean loading the file, which is the thing this design exists to avoid.

What leaving TextKit costs

Everything TextKit gives an app for free had to be written, and is maintained, by hand:

The trade is deliberate. The cost is engineering time; the return is that every feature runs at file scale, and that numbers on the benchmarks page hold at 50 GB.

Saving is atomic, and interrupted saves recover

A save streams the piece table to a temporary file beside the original and swaps it in, so a crash or power cut during a save cannot leave a truncated file. Before writing, the save compares the file on disk with what was opened, and again before the swap, so an external change is never overwritten without asking. A file opened through a symbolic link is written to its target.

When the edits allow it, Caxton patches the file in place instead of rewriting it. Before the first byte is written, the original bytes of every region it will touch are recorded in a patch journal and flushed to disk. If the process dies mid-patch, the next launch finds the journal and offers to roll the file back.

Separately, a recovery journal records your unsaved edits every 30 seconds, off the main thread, in Caxton's own storage. It never touches the original file. After a crash, the next launch offers to restore the session's edits, and a journal whose file has moved offers Locate, Discard, or Decide Later rather than being deleted. More in Sessions & Recovery.

The grid is the same document

The CSV grid is not an import. It is a second view of the same piece table. A background pass builds a record index with one entry per CSV record: where the record starts and how many fields it has. Field positions are not stored; the grid parses the handful of records on screen each frame. That keeps the index small at ten million rows.

Everything is local

There is no server component. Files are never uploaded, there is no cloud processing, and there is no telemetry. The only network calls are the update check and license activation, both described in the privacy policy.

The measured results of this design, with the methodology to reproduce them, are on the benchmarks page. The reasons it exists are on the about page.