86,420,337 lines — a 10 GB log — open in 80 ms, and 0 bytes of it live in memory. A Mac-native viewer and editor for the files that break everything else: edit in place, save with atomic writes, diff and merge side by side.
24
stars
277
commits
Swift
primary language
Sep 11, 2026
updated
English | 日本語
The Mac editor for text you didn't write.
Logs. CSVs. Exports, dumps, diffs. Text that arrived from somewhere else — so you don't get to choose its size, its encoding, or how badly it is formatted. MrEditor opens it, filters it, lines up its columns, compares it, fixes it, and saves it without ever leaving the file half-written.
That is also why there is no autocomplete and no LSP here: this is not for writing code, it is for finding out what happened and correcting it.
Japan's full corporate registry as CSV — 1.18 GiB, 5,816,535 rows — starts displaying in 52–60 ms (five runs). Excel opens the same file and stops at 1,048,576 rows, its hard limit, taking about 60 seconds and 2.24 GB to silently lose 4,767,959 rows. Numbers never opened it at all.
Size is not the trick, it is the floor: a 10 GB, 86,420,337-line log starts displaying in
about 100 ms (ten runs: 97–107 ms), vmmap reports 0 bytes dirty for it, and jumping to
the last line takes 0.1 ms. Nothing about the app changes as the file grows.
(All measured 2026-09-03 on the shipping 1.14.0 build, Apple Silicon. Ranges are what several runs actually produced, not the best one — see Performance for how to reproduce them.)
It started life as a fast read-only viewer (full-file search, filtered view / live grep,
tail -f). v0.4 makes the name literal: it edits and saves too.
And it can tail -f a growing 10 GB log (⌥⌘F) while you edit it in place — without reloading.
The mmap index extends by the new bytes only, so it holds up even while the file is still being
written at 10 GB. Editors that tail (BBEdit, Sakura) reload the whole file; log viewers that scale
to 10 GB (klogg, lnav) are read-only. Following a multi-GB log incrementally while staying
editable — we couldn't find that combination anywhere else.

Left to right, top to bottom: a service log; a 5,816,535-row CSV filtered while its columns stay lined up; a minified JSON response pretty-printed; two config files compared down to the character that changed. None of it was written by the person looking at it.

The first 10 seconds of a single uncut take, at real speed: the 10.00 GB file opens, and we scroll it while the line index is still building. Watch the status bar — the line count is an estimate until the index lands (9.1 s), then it settles at the exact 86,420,337. The view never blocks; you can read, search and edit throughout. The whole 27-second take, uncut, ending with ⌘L to the last line.
The usual answer on macOS is NSTextView, but it keeps the whole document in
NSTextStorage. Hand it 10 GB and it falls over. MrEditor takes the large-log-viewer
approach (klogg / glogg / lnav):
NSScroller (so we never build a 1.6-billion-point document view that would
blow past float precision).See docs/ARCHITECTURE_v0.1.md for the full design.
Toolbar — the six defaults are this app saying what it is
tail -f), AI diagnosis, plus a sidebar toggle.
Everything here used to live in menus and shortcuts, which means it did not exist for anyone
who just launched the app.Viewing
tail -f, ⌥⌘F) — auto-scrolls as the file grows. It extends the index
incrementally instead of reloading, so it holds up on a 10 GB log that's still being written
(following pauses while you have unsaved edits, and resumes on save).Editing (new in v0.4)
NSTextView; large files edit
through a piece table over the mmap, so even a huge log stays responsive while you type.Workspace
.log,
.txt, .csv, .json and friends. It never steals the default app.Customization (new in v0.5)
ESC[…m) in logs while viewing (the escape sequences are stripped automatically).Search (⌘F) — streams over the mmap, never loads the file
.* toggle — including lookahead / lookbehind assertions), and a case-sensitive toggle.kubectl logs -f pod/api | mreditor
pipes straight in (sh scripts/install-cli.sh installs the mreditor command), and
app.log.gz and logs.zip are expanded before they open, whether you double-click them or
pass them on the command line — a zip with several files asks which one you want. The decision is made from the bytes, not the extension: a .log that is
really gzip opens expanded, and a .gz that is really plain text opens as it is. The input
is read to the end first — showing you part of a stream as if it were the whole thing is
worse than waiting.⌘B marks the current line; ⌘' / ⇧⌘' jump to the next and previous mark — you move between
marks far more often than you set them, so moving is the two-key one. Investigation goes back and forth, and you can hold one or two return points in your
head before you start writing line numbers on paper. Marks show in the gutter, live for the session,
and nothing is written to the file.⌘F opens the way you left it (since 1.12.6) — the funnel (matching lines only) remembers the
state you last put it in. While you read something structured, the point is to narrow, not to jump
between matches, so you no longer spend a keystroke re-enabling it each time. Panes that cannot
filter (structured, JSON) are left alone and pick it up again when you come back. Off by default —
if you never use the funnel, ⌘F opens exactly as before.grep -C. Type a number into the ± box in
the search bar, or press ⌥⌘] / ⌥⌘[ to open and close it one line at a time. Filtering leaves
you the matching line and nothing else, but what went wrong is usually in the line before it.
Overlapping windows never repeat a line, and the gutter keeps the real line numbers, so you can
see where the text jumps. The value is remembered (up to 100 lines).Remote (new in v1.14.0) — File ▸ Open Remote… (⌃⌘O)
ssh host:/var/log/app.log opens a log that lives on another machine.
Nothing is downloaded — only the part you look at is fetched, so a 10GB file
opens without a wait (if it downloaded first, scp would do the same job).wc -l runs on the far side; only a number comes back).grep -n): matching lines and their numbers come
back without transferring a single byte of the file. The ± field adds context
around each match (like grep -C) — what a hit means is usually on the line before it.tail -f from the far side. Stopping stops it
over there too — it does not leave processes running on your server./usr/bin/ssh: ssh_config, ProxyJump, jump hosts and
ssh-agent all apply (if your terminal can reach it, so can this). No private key is stored.head / wc / tail / grep); if any are missing — BusyBox, say — only that
feature is folded away, and it tells you why.Compare / diff (new in v1.1) — View ▸ Compare (Diff)
status=200 → 500 stands out.2026-08-19 vs 2026/08/19,
007 vs 7 and 123 vs 123 stay differences. Kana and kanji collapse as values too, but
full-width punctuation and the ideographic space stay part of the shape. It works from all four ways in,
and merging is locked while it is on — same shape means different contents, so pushing one across would erase them.Structured view (new in v0.6) — read-only, toggled from View ▸ Structured View
tail -f all keep
working with the columns lined up, so you can narrow 5.8 million rows down to the matching ones
and still read them as a table. Replace is the one thing that's refused while formatting is on:
rewriting through a padded view would change something other than what you're looking at.1-8). It is the
one mode that cannot be detected from the contents, so if there is no definition yet it asks for one.Column ruler and fixed-width fields (new in 1.12) — View ▸ Column Ruler (⌥⌘K)
1-8,9-14,15-40
(full-width digits, commas and dashes are accepted too, since specs get pasted that way). A definition
that can't be read is never half-applied — the sheet comes back until it parses.Lining text up by column (new in 1.12.2) — draw a guide and the text becomes columns
column -t), so ⌥Tab works as the very
first thing you press.1-8,9-14,15-40 with ⇧⌥⌘K. Anything that touches columns brings the ruler up, so ⌥⌘K first is not required.JSON query (new in 1.4) — View ▸ JSON Query… (⌥⌘J), on a JSON document
a.b.c), array indexes (items[0], items[-1]), wildcard projection (items[*].name,
m.*), and filters (items[?age >= 30].name, comparators == != < <= > >=).Text toolbox (new in 1.5) — the Format menu, acting on the current selection (one undo each)
jq .,
sort, sed 's/a/b/g' — and replace it with the output. Runs off the main thread with a timeout.Appearance & sharing (new in 1.6) — Preferences ▸ Colors
.mreditortheme file, or Copy Link — a self-contained mreditor:// link that anyone with
MrEditor can open to apply it in one click. Applying always asks first. No account, no server.UI localized in English and Japanese.
Download MrEditor-<version>.dmg from Releases, open it, and drag
MrEditor to Applications.
Runs on both Apple Silicon and Intel (universal build).
As of v0.9 the app is signed with an Apple Developer ID and notarized by Apple.
No right-click, no xattr — just double-click it.
Or just build from source (below).
Requires macOS 13+ and a Swift toolchain (Xcode 15+).
swift build
sh scripts/make_app.sh debug # wrap the binary into MrEditor.app
open .build/MrEditor.app --args "/path/to/big.log"
Generate test data (the testdata/ dir is git-ignored):
python3 scripts/gen_testdata.py --encoding-set --out-dir testdata/ # UTF-8 / SJIS / EUC samples
python3 scripts/gen_testdata.py --size 10G --jp --out testdata/test_10gb.log
Build a distributable disk image (.build/MrEditor-1.14.0.dmg):
sh scripts/make_dmg.sh
On a 10.00 GB / 86,420,337-line Japanese UTF-8 log.
| Metric | Result |
|---|---|
| Time to first paint | ~100 ms — ten runs gave 97–107 ms |
| Full background index | 0.5–7.4 s, depending on how much of the file the OS still has cached (does not block display) |
| Seek to last line | 0.1 ms |
| The file's own pages | 9.1 GB resident, 0 bytes dirty |
| App's own dirty memory | ~159 MB |
Ranges, not best-of. An earlier version of this table said 45–80 ms, which came from a single good run. Re-measuring on 1.14.0 gave ~100 ms ten times over — and building v1.7 from its tag and measuring it on the same machine, the same day, on the same file gave 97–107 ms as well. Nothing regressed; the old number was optimistic. The figures above are what several runs actually produced.
The index time moves the most, and the reason is the page cache rather than the app: the first open of a cold 10 GB file took 7.4 s, and repeat opens took 0.3–0.9 s. Either way the view is usable immediately — the line count shows as an estimate until the index lands.
The last two rows are the honest picture, so read them together. The 10 GB you opened costs
nothing: it is mapped, not copied, and vmmap attributes 0 dirty bytes to it — the resident
pages are file-backed and the OS can drop them whenever it likes. The app's own ~159 MB is window
backing store and the kernel page tables for a 10 GB mapping; none of it is your log. ps RSS
reads several GB while indexing for the same reason, and means just as little.
The 1.18 GiB / 5,816,535-row CSV opens in 52–60 ms (five runs) with an index in 0.3–0.9 s.
Reproduce it yourself:
MREDITOR_TIMING=1 .build/MrEditor.app/Contents/MacOS/MrEditor testdata/test_10gb.log
# → first paint: 103.1 ms
# → index complete: 7.40 s (86420337 lines) ← cold; repeat opens land under a second
vmmap $(pgrep -x MrEditor) | grep test_10gb.log # → 10.0G 9.1G 0K (vsize resident dirty)
tail -f, copy ✅mreditor:// share link that applies it in one click ✅max_tokens, which those models refuse with a 400. Now it sends max_completion_tokens. The connection test also gave itself more room, because that limit covers reasoning plus the answer — a correct key could fail with an empty reply. And "the answer came back empty" now says so in your own language instead of English. Anthropic and OpenAI streaming are both verified against the real APIs now — the gap noted in 1.9 and 1.10 is closed ✅581… near the end of that file. Widths are now sampled from both ends ✅tail -f — the one operation you most want on a CSV was unavailable exactly when the CSV was readable. You can now grep with the columns still lined up, at any size ✅sed over it, switch branches — the file changed and the open window said nothing. Open files are now watched: with no unsaved changes the new contents load in place, keeping your caret and scroll position. With unsaved changes nothing is overwritten — a banner says the file changed and lets you decide. A large file that only grew extends its index instead of being reopened, so a 10 GB log does not pay 8 seconds per append. It also closes a hole where text appended by another process vanished the moment you started editing (Follow mode had it too) ✅1-8,9-14,15-40 (⇧⌥⌘K). The definition is remembered per file, so reopening brings it back. Feed it into the structured view's Fixed-width mode and the records read as aligned columns, named by the columns themselves (1-8). Columns are counted in display width, so a line containing full-width characters keeps its guides where they belong ✅2026-08-19 and 2026/08/19, or 007 and 7, stay differences. Merging is locked while it is on — same shape means different contents. Measured: on a 200,000-line CSV, the same rows with every value replaced compare as identical in 0.14 s. Also fixed something that had never worked: menu check marks and greying out (validateMenuItem was never called by AppKit). Which structured mode is on, and whether the column ruler is showing, are finally visible in the menu ✅ERROR rows left a 34-character column reading con…. Widths are now re-measured from the matching rows while a filter is up (200 rows from each end when there are many — 0.084 s measured on a 10 GB file). Also fixed small files showing an incoherent screen when the structured view was switched on while a filter was up: the text reverted to the unfiltered original while the column-name strip and the row count stayed as they were. The filter is now dropped before formatting (a small file has its text replaced by the formatted version, so the two cannot coexist) ✅grep -C). Type a number into the ± box in the search bar, or press ⌥⌘] / ⌥⌘[ to open it one line at a time. It is a box with a number in it rather than a button or an icon — "±2" on screen tells you what you are looking at. Overlapping windows never repeat a line, and there are no separator rows: the gutter keeps the real line numbers, so the jumps are readable. It works the same on a 10 GB file and a small one, and with the structured view on, the context rows are measured for column width too. The one place it stays off is a drag on the time distribution — pulling in rows outside the span you selected would make the selection a lie⌘F always opened as a plain search, spending a keystroke on the funnel every time. Worse was the round trip: moving to a pane that cannot filter (structured, JSON) dropped the toggle, and that drop was recorded as if you had switched it off yourself, so the intent died every time you went back and forth. ⌘F now opens the way you left it. What is remembered is the intent; whether it can be applied is the pane's call. Nothing is applied while the search bar is closed — text narrowing with no bar in sight reads as an accident. Off by default, so ⌘F is unchanged for anyone who never touches the funnelkubectl logs -f pod/api | mreditor now pipes straight in, and .gz / .zip are expanded before they open — decided from the bytes, not the extension, because a .log that is really gzip and a .gz that is really plain text both exist in the wild. A zip with several files asks which one you want, and never offers __MACOSX/ or .DS_Store. Also bookmarks (⌘B to mark, ⌘; / ⇧⌘; to step between them): investigation goes back and forth, and you can hold one or two return points in your head before you start writing line numbers on paper** ✅ssh host:/var/log/app.log in File ▸ Open Remote… (⌃⌘O) opens it without downloading it: only the part you are looking at comes over, so a 10 GB file does not make you wait. It opens at the tail, where the incident is, and the line numbers are real (wc -l runs over there and one number comes back). Filtering runs on the far side (grep -n), so a match and its line number return without a single byte of the body crossing the wire — faster than scanning locally, because nothing is sent. Following the tail streams tail -f from over there and stops it when you stop, so no process is left behind on your server** ✅ (this release)⚠️ Builds up to v0.7 do not launch on a Mac that downloaded them. The
.appbundle was never code-signed, so its signature seal was inconsistent and macOS killed the quarantined app on launch ("quit unexpectedly"). Fixed in v0.8. The build is now universal (Apple Silicon & Intel) as well — previously it was arm64-only.v0.8 launches, but needs a right-click → Open on the first run (it is only ad-hoc signed). v0.9 and later are signed and notarized, so even that is unnecessary.
Everything in this repository stays free and MIT. What is being prepared separately is MrkEditor (Pro), and the line is: reading one file is free; pulling an answer out of it is Pro.
| Feature | What it does | Measured (release, M4 Max/24GB, Aug 2026) |
|---|---|---|
| Count by value | per-value counts for a column or key=value (what sort | uniq -c | sort -rn does) | 3.40 s over 10 GB / 86,420,337 lines |
| Column stats | every column of a CSV in one pass (type, blanks, distinct, min/max, sum/average) | 4.78 s for 30 columns of 1.18 GiB / 5,816,534 data rows |
| Time histogram | when did it spike; lines without a timestamp are counted, not dropped | 3.18 s over 10 GB / 67,156,671 lines |
| Search across folders | click a hit and the file opens at that line; Shift-JIS / EUC-JP detected per file | 3.14 s for one literal over a single 10 GB log (on par with ripgrep) |
The free app has the same Analyze menu in the same place (never greyed out); choosing an item shows one page explaining that feature. It is not on sale yet — there is no price and no checkout.
Syntax / log highlighting and deeper analysis tooling. Editing landed in v0.4 — the piece-table design keeps even a 10 GB file editable without giving up the fast, low-memory open that MrEditor is built around.
Bug fixes, performance, viewing/editing/search improvements, and translations are welcome — see CONTRIBUTING.md. The core is a fast viewer/editor for huge files; heavier automation and analysis tooling are out of scope (it's open-core — fork freely).
MIT © 2026 TABATA Hitoshi
🇯🇵 日本語の README は README.ja.md にあります。
274 commits
3 commits
Swift
82.0%
HTML
13.9%
Python
2.2%
Shell
1.9%
86,420,337 lines — a 10 GB log — open in 80 ms, and 0 bytes of it live in memory. A Mac-native viewer and editor for the files that break everything else: edit in place, save with atomic writes, diff and merge side by side.
24
stars
277
commits
Swift
primary language
Sep 11, 2026
updated
English | 日本語
The Mac editor for text you didn't write.
Logs. CSVs. Exports, dumps, diffs. Text that arrived from somewhere else — so you don't get to choose its size, its encoding, or how badly it is formatted. MrEditor opens it, filters it, lines up its columns, compares it, fixes it, and saves it without ever leaving the file half-written.
That is also why there is no autocomplete and no LSP here: this is not for writing code, it is for finding out what happened and correcting it.
Japan's full corporate registry as CSV — 1.18 GiB, 5,816,535 rows — starts displaying in 52–60 ms (five runs). Excel opens the same file and stops at 1,048,576 rows, its hard limit, taking about 60 seconds and 2.24 GB to silently lose 4,767,959 rows. Numbers never opened it at all.
Size is not the trick, it is the floor: a 10 GB, 86,420,337-line log starts displaying in
about 100 ms (ten runs: 97–107 ms), vmmap reports 0 bytes dirty for it, and jumping to
the last line takes 0.1 ms. Nothing about the app changes as the file grows.
(All measured 2026-09-03 on the shipping 1.14.0 build, Apple Silicon. Ranges are what several runs actually produced, not the best one — see Performance for how to reproduce them.)
It started life as a fast read-only viewer (full-file search, filtered view / live grep,
tail -f). v0.4 makes the name literal: it edits and saves too.
And it can tail -f a growing 10 GB log (⌥⌘F) while you edit it in place — without reloading.
The mmap index extends by the new bytes only, so it holds up even while the file is still being
written at 10 GB. Editors that tail (BBEdit, Sakura) reload the whole file; log viewers that scale
to 10 GB (klogg, lnav) are read-only. Following a multi-GB log incrementally while staying
editable — we couldn't find that combination anywhere else.

Left to right, top to bottom: a service log; a 5,816,535-row CSV filtered while its columns stay lined up; a minified JSON response pretty-printed; two config files compared down to the character that changed. None of it was written by the person looking at it.

The first 10 seconds of a single uncut take, at real speed: the 10.00 GB file opens, and we scroll it while the line index is still building. Watch the status bar — the line count is an estimate until the index lands (9.1 s), then it settles at the exact 86,420,337. The view never blocks; you can read, search and edit throughout. The whole 27-second take, uncut, ending with ⌘L to the last line.
The usual answer on macOS is NSTextView, but it keeps the whole document in
NSTextStorage. Hand it 10 GB and it falls over. MrEditor takes the large-log-viewer
approach (klogg / glogg / lnav):
NSScroller (so we never build a 1.6-billion-point document view that would
blow past float precision).See docs/ARCHITECTURE_v0.1.md for the full design.
Toolbar — the six defaults are this app saying what it is
tail -f), AI diagnosis, plus a sidebar toggle.
Everything here used to live in menus and shortcuts, which means it did not exist for anyone
who just launched the app.Viewing
tail -f, ⌥⌘F) — auto-scrolls as the file grows. It extends the index
incrementally instead of reloading, so it holds up on a 10 GB log that's still being written
(following pauses while you have unsaved edits, and resumes on save).Editing (new in v0.4)
NSTextView; large files edit
through a piece table over the mmap, so even a huge log stays responsive while you type.Workspace
.log,
.txt, .csv, .json and friends. It never steals the default app.Customization (new in v0.5)
ESC[…m) in logs while viewing (the escape sequences are stripped automatically).Search (⌘F) — streams over the mmap, never loads the file
.* toggle — including lookahead / lookbehind assertions), and a case-sensitive toggle.kubectl logs -f pod/api | mreditor
pipes straight in (sh scripts/install-cli.sh installs the mreditor command), and
app.log.gz and logs.zip are expanded before they open, whether you double-click them or
pass them on the command line — a zip with several files asks which one you want. The decision is made from the bytes, not the extension: a .log that is
really gzip opens expanded, and a .gz that is really plain text opens as it is. The input
is read to the end first — showing you part of a stream as if it were the whole thing is
worse than waiting.⌘B marks the current line; ⌘' / ⇧⌘' jump to the next and previous mark — you move between
marks far more often than you set them, so moving is the two-key one. Investigation goes back and forth, and you can hold one or two return points in your
head before you start writing line numbers on paper. Marks show in the gutter, live for the session,
and nothing is written to the file.⌘F opens the way you left it (since 1.12.6) — the funnel (matching lines only) remembers the
state you last put it in. While you read something structured, the point is to narrow, not to jump
between matches, so you no longer spend a keystroke re-enabling it each time. Panes that cannot
filter (structured, JSON) are left alone and pick it up again when you come back. Off by default —
if you never use the funnel, ⌘F opens exactly as before.grep -C. Type a number into the ± box in
the search bar, or press ⌥⌘] / ⌥⌘[ to open and close it one line at a time. Filtering leaves
you the matching line and nothing else, but what went wrong is usually in the line before it.
Overlapping windows never repeat a line, and the gutter keeps the real line numbers, so you can
see where the text jumps. The value is remembered (up to 100 lines).Remote (new in v1.14.0) — File ▸ Open Remote… (⌃⌘O)
ssh host:/var/log/app.log opens a log that lives on another machine.
Nothing is downloaded — only the part you look at is fetched, so a 10GB file
opens without a wait (if it downloaded first, scp would do the same job).wc -l runs on the far side; only a number comes back).grep -n): matching lines and their numbers come
back without transferring a single byte of the file. The ± field adds context
around each match (like grep -C) — what a hit means is usually on the line before it.tail -f from the far side. Stopping stops it
over there too — it does not leave processes running on your server./usr/bin/ssh: ssh_config, ProxyJump, jump hosts and
ssh-agent all apply (if your terminal can reach it, so can this). No private key is stored.head / wc / tail / grep); if any are missing — BusyBox, say — only that
feature is folded away, and it tells you why.Compare / diff (new in v1.1) — View ▸ Compare (Diff)
status=200 → 500 stands out.2026-08-19 vs 2026/08/19,
007 vs 7 and 123 vs 123 stay differences. Kana and kanji collapse as values too, but
full-width punctuation and the ideographic space stay part of the shape. It works from all four ways in,
and merging is locked while it is on — same shape means different contents, so pushing one across would erase them.Structured view (new in v0.6) — read-only, toggled from View ▸ Structured View
tail -f all keep
working with the columns lined up, so you can narrow 5.8 million rows down to the matching ones
and still read them as a table. Replace is the one thing that's refused while formatting is on:
rewriting through a padded view would change something other than what you're looking at.1-8). It is the
one mode that cannot be detected from the contents, so if there is no definition yet it asks for one.Column ruler and fixed-width fields (new in 1.12) — View ▸ Column Ruler (⌥⌘K)
1-8,9-14,15-40
(full-width digits, commas and dashes are accepted too, since specs get pasted that way). A definition
that can't be read is never half-applied — the sheet comes back until it parses.Lining text up by column (new in 1.12.2) — draw a guide and the text becomes columns
column -t), so ⌥Tab works as the very
first thing you press.1-8,9-14,15-40 with ⇧⌥⌘K. Anything that touches columns brings the ruler up, so ⌥⌘K first is not required.JSON query (new in 1.4) — View ▸ JSON Query… (⌥⌘J), on a JSON document
a.b.c), array indexes (items[0], items[-1]), wildcard projection (items[*].name,
m.*), and filters (items[?age >= 30].name, comparators == != < <= > >=).Text toolbox (new in 1.5) — the Format menu, acting on the current selection (one undo each)
jq .,
sort, sed 's/a/b/g' — and replace it with the output. Runs off the main thread with a timeout.Appearance & sharing (new in 1.6) — Preferences ▸ Colors
.mreditortheme file, or Copy Link — a self-contained mreditor:// link that anyone with
MrEditor can open to apply it in one click. Applying always asks first. No account, no server.UI localized in English and Japanese.
Download MrEditor-<version>.dmg from Releases, open it, and drag
MrEditor to Applications.
Runs on both Apple Silicon and Intel (universal build).
As of v0.9 the app is signed with an Apple Developer ID and notarized by Apple.
No right-click, no xattr — just double-click it.
Or just build from source (below).
Requires macOS 13+ and a Swift toolchain (Xcode 15+).
swift build
sh scripts/make_app.sh debug # wrap the binary into MrEditor.app
open .build/MrEditor.app --args "/path/to/big.log"
Generate test data (the testdata/ dir is git-ignored):
python3 scripts/gen_testdata.py --encoding-set --out-dir testdata/ # UTF-8 / SJIS / EUC samples
python3 scripts/gen_testdata.py --size 10G --jp --out testdata/test_10gb.log
Build a distributable disk image (.build/MrEditor-1.14.0.dmg):
sh scripts/make_dmg.sh
On a 10.00 GB / 86,420,337-line Japanese UTF-8 log.
| Metric | Result |
|---|---|
| Time to first paint | ~100 ms — ten runs gave 97–107 ms |
| Full background index | 0.5–7.4 s, depending on how much of the file the OS still has cached (does not block display) |
| Seek to last line | 0.1 ms |
| The file's own pages | 9.1 GB resident, 0 bytes dirty |
| App's own dirty memory | ~159 MB |
Ranges, not best-of. An earlier version of this table said 45–80 ms, which came from a single good run. Re-measuring on 1.14.0 gave ~100 ms ten times over — and building v1.7 from its tag and measuring it on the same machine, the same day, on the same file gave 97–107 ms as well. Nothing regressed; the old number was optimistic. The figures above are what several runs actually produced.
The index time moves the most, and the reason is the page cache rather than the app: the first open of a cold 10 GB file took 7.4 s, and repeat opens took 0.3–0.9 s. Either way the view is usable immediately — the line count shows as an estimate until the index lands.
The last two rows are the honest picture, so read them together. The 10 GB you opened costs
nothing: it is mapped, not copied, and vmmap attributes 0 dirty bytes to it — the resident
pages are file-backed and the OS can drop them whenever it likes. The app's own ~159 MB is window
backing store and the kernel page tables for a 10 GB mapping; none of it is your log. ps RSS
reads several GB while indexing for the same reason, and means just as little.
The 1.18 GiB / 5,816,535-row CSV opens in 52–60 ms (five runs) with an index in 0.3–0.9 s.
Reproduce it yourself:
MREDITOR_TIMING=1 .build/MrEditor.app/Contents/MacOS/MrEditor testdata/test_10gb.log
# → first paint: 103.1 ms
# → index complete: 7.40 s (86420337 lines) ← cold; repeat opens land under a second
vmmap $(pgrep -x MrEditor) | grep test_10gb.log # → 10.0G 9.1G 0K (vsize resident dirty)
tail -f, copy ✅mreditor:// share link that applies it in one click ✅max_tokens, which those models refuse with a 400. Now it sends max_completion_tokens. The connection test also gave itself more room, because that limit covers reasoning plus the answer — a correct key could fail with an empty reply. And "the answer came back empty" now says so in your own language instead of English. Anthropic and OpenAI streaming are both verified against the real APIs now — the gap noted in 1.9 and 1.10 is closed ✅581… near the end of that file. Widths are now sampled from both ends ✅tail -f — the one operation you most want on a CSV was unavailable exactly when the CSV was readable. You can now grep with the columns still lined up, at any size ✅sed over it, switch branches — the file changed and the open window said nothing. Open files are now watched: with no unsaved changes the new contents load in place, keeping your caret and scroll position. With unsaved changes nothing is overwritten — a banner says the file changed and lets you decide. A large file that only grew extends its index instead of being reopened, so a 10 GB log does not pay 8 seconds per append. It also closes a hole where text appended by another process vanished the moment you started editing (Follow mode had it too) ✅1-8,9-14,15-40 (⇧⌥⌘K). The definition is remembered per file, so reopening brings it back. Feed it into the structured view's Fixed-width mode and the records read as aligned columns, named by the columns themselves (1-8). Columns are counted in display width, so a line containing full-width characters keeps its guides where they belong ✅2026-08-19 and 2026/08/19, or 007 and 7, stay differences. Merging is locked while it is on — same shape means different contents. Measured: on a 200,000-line CSV, the same rows with every value replaced compare as identical in 0.14 s. Also fixed something that had never worked: menu check marks and greying out (validateMenuItem was never called by AppKit). Which structured mode is on, and whether the column ruler is showing, are finally visible in the menu ✅ERROR rows left a 34-character column reading con…. Widths are now re-measured from the matching rows while a filter is up (200 rows from each end when there are many — 0.084 s measured on a 10 GB file). Also fixed small files showing an incoherent screen when the structured view was switched on while a filter was up: the text reverted to the unfiltered original while the column-name strip and the row count stayed as they were. The filter is now dropped before formatting (a small file has its text replaced by the formatted version, so the two cannot coexist) ✅grep -C). Type a number into the ± box in the search bar, or press ⌥⌘] / ⌥⌘[ to open it one line at a time. It is a box with a number in it rather than a button or an icon — "±2" on screen tells you what you are looking at. Overlapping windows never repeat a line, and there are no separator rows: the gutter keeps the real line numbers, so the jumps are readable. It works the same on a 10 GB file and a small one, and with the structured view on, the context rows are measured for column width too. The one place it stays off is a drag on the time distribution — pulling in rows outside the span you selected would make the selection a lie⌘F always opened as a plain search, spending a keystroke on the funnel every time. Worse was the round trip: moving to a pane that cannot filter (structured, JSON) dropped the toggle, and that drop was recorded as if you had switched it off yourself, so the intent died every time you went back and forth. ⌘F now opens the way you left it. What is remembered is the intent; whether it can be applied is the pane's call. Nothing is applied while the search bar is closed — text narrowing with no bar in sight reads as an accident. Off by default, so ⌘F is unchanged for anyone who never touches the funnelkubectl logs -f pod/api | mreditor now pipes straight in, and .gz / .zip are expanded before they open — decided from the bytes, not the extension, because a .log that is really gzip and a .gz that is really plain text both exist in the wild. A zip with several files asks which one you want, and never offers __MACOSX/ or .DS_Store. Also bookmarks (⌘B to mark, ⌘; / ⇧⌘; to step between them): investigation goes back and forth, and you can hold one or two return points in your head before you start writing line numbers on paper** ✅ssh host:/var/log/app.log in File ▸ Open Remote… (⌃⌘O) opens it without downloading it: only the part you are looking at comes over, so a 10 GB file does not make you wait. It opens at the tail, where the incident is, and the line numbers are real (wc -l runs over there and one number comes back). Filtering runs on the far side (grep -n), so a match and its line number return without a single byte of the body crossing the wire — faster than scanning locally, because nothing is sent. Following the tail streams tail -f from over there and stops it when you stop, so no process is left behind on your server** ✅ (this release)⚠️ Builds up to v0.7 do not launch on a Mac that downloaded them. The
.appbundle was never code-signed, so its signature seal was inconsistent and macOS killed the quarantined app on launch ("quit unexpectedly"). Fixed in v0.8. The build is now universal (Apple Silicon & Intel) as well — previously it was arm64-only.v0.8 launches, but needs a right-click → Open on the first run (it is only ad-hoc signed). v0.9 and later are signed and notarized, so even that is unnecessary.
Everything in this repository stays free and MIT. What is being prepared separately is MrkEditor (Pro), and the line is: reading one file is free; pulling an answer out of it is Pro.
| Feature | What it does | Measured (release, M4 Max/24GB, Aug 2026) |
|---|---|---|
| Count by value | per-value counts for a column or key=value (what sort | uniq -c | sort -rn does) | 3.40 s over 10 GB / 86,420,337 lines |
| Column stats | every column of a CSV in one pass (type, blanks, distinct, min/max, sum/average) | 4.78 s for 30 columns of 1.18 GiB / 5,816,534 data rows |
| Time histogram | when did it spike; lines without a timestamp are counted, not dropped | 3.18 s over 10 GB / 67,156,671 lines |
| Search across folders | click a hit and the file opens at that line; Shift-JIS / EUC-JP detected per file | 3.14 s for one literal over a single 10 GB log (on par with ripgrep) |
The free app has the same Analyze menu in the same place (never greyed out); choosing an item shows one page explaining that feature. It is not on sale yet — there is no price and no checkout.
Syntax / log highlighting and deeper analysis tooling. Editing landed in v0.4 — the piece-table design keeps even a 10 GB file editable without giving up the fast, low-memory open that MrEditor is built around.
Bug fixes, performance, viewing/editing/search improvements, and translations are welcome — see CONTRIBUTING.md. The core is a fast viewer/editor for huge files; heavier automation and analysis tooling are out of scope (it's open-core — fork freely).
MIT © 2026 TABATA Hitoshi
🇯🇵 日本語の README は README.ja.md にあります。
274 commits
3 commits
Swift
82.0%
HTML
13.9%
Python
2.2%
Shell
1.9%