DEV Community

Cover image for Why I Deleted egui’s TextEdit and Wrote a Text Editor From Scratch
Ola Prøis
Ola Prøis

Posted on

Why I Deleted egui’s TextEdit and Wrote a Text Editor From Scratch

When I started Ferrite, I didn’t plan to write a text editor.

Ferrite began as a side project: a fast, native Markdown editor built with Rust and egui. I didn’t know Rust particularly well, I definitely didn’t know GUI programming, and I really didn’t want to reinvent things that already existed.

egui had a TextEdit widget. It worked. So I used it.

For a long time, that decision was absolutely the right one.

But in Ferrite v0.2.6, I deleted egui::TextEdit entirely and replaced it with a custom editor written from scratch.

This is the story of why.


The Editor That Was “Good Enough”

Ferrite shipped a lot of features on top of egui’s TextEdit:

  • WYSIWYG Markdown editing
  • Split view (raw + rendered)
  • Minimap
  • Syntax highlighting
  • Search and replace
  • Undo/redo

At the time, it felt almost magical that this worked at all.

I wrote previously about shipping Ferrite without really knowing Rust, and that was true here as well. egui’s abstraction let me move fast and focus on features, not editor internals.

And for small to medium files, everything was fine.

That’s the key part: until it wasn’t.


The Bug That Broke the Illusion

The issue that forced my hand was simple to describe and horrifying to observe.

Opening a 4MB text file caused Ferrite to use 1.8GB of RAM.

An 80MB file was effectively unusable.

This wasn’t a slow leak or a missing drop. It was architectural.

After a lot of profiling, the root causes became clear:

  • The entire document was being cloned every frame
  • Undo relied on full snapshots
  • Search paths allocated whole-document copies
  • Bracket matching scanned the entire buffer repeatedly
  • egui’s text model assumed “the whole text exists, every frame”

None of this is wrong for an immediate‑mode UI.

But it is fundamentally incompatible with large files.

At some point I had to admit something uncomfortable:

I wasn’t dealing with a bug anymore.\
I was dealing with the limits of the abstraction I chose.


Why You Can’t Patch Your Way Out of This

I tried to optimize my way out.

I really did.

I added guards. Thresholds. Debounces. Hashes. Special‑case logic for “large files”. I fixed one O(N) path only to discover another one hiding behind it.

But egui’s TextEdit makes some assumptions that are deeply baked in:

  • The full document is owned by the widget
  • Text is a String
  • Layout happens eagerly
  • Undo is snapshot‑based
  • Rendering isn’t viewport‑aware

Those assumptions are totally reasonable for UI text fields.

They are not reasonable for a code editor.

At some point, optimizations stop being improvements and start being denial.


The Moment the Plan Changed

Rewriting the editor wasn’t originally an impulsive decision.

From early on, I knew there were things I eventually wanted that egui’s TextEdit simply wasn’t built for. Features like proper code folding, reliable sync scrolling, and deeper editor–preview integration were already pushing against its limits.

Because of that, a custom editor had been part of the roadmap for v0.3.0. It was a “someday” project, something to tackle once the surrounding features were stable.

Then the large‑file issue landed.

Opening a 4MB file using gigabytes of memory wasn’t just inconvenient; it was a hard failure. At that point, the rewrite stopped being a future improvement and became an immediate requirement.

The question was no longer if the editor needed to be replaced, but when.

So I moved the plan forward.

What was meant to be a v0.3.0 rewrite became the core of v0.2.6.

It was still intimidating, text editors are iceberg problems, but postponing it would have meant shipping an editor that couldn’t scale or be trusted.

So I did the thing earlier than planned.

I deleted the editor and started over.


FerriteEditor: What Changed Architecturally

The new editor, FerriteEditor, is built around a few core ideas.

1. Rope‑based Text Storage

Instead of storing text as a String, Ferrite uses the ropey crate.

This gives:

  • O(log n) inserts and deletes
  • Cheap slicing
  • Predictable memory usage

Large files stop being special cases and start being normal input.


2. Virtual Scrolling

FerriteEditor only renders what you can see.

Not “most of the document”. Not “everything and hope it’s fine”.

Just:

  • Visible lines
  • Plus a small buffer

This single change unlocked editing 100MB+ files smoothly.


3. Viewport‑Aware Everything

Operations that used to scan the entire document are now windowed:

  • Bracket matching looks at ~200 lines around the cursor
  • Syntax highlighting is per‑line, per‑viewport
  • Search highlights are capped to what’s visible

If it’s off‑screen, it doesn’t matter right now.


4. Operation‑Based Undo

Undo is no longer “clone the document”.

Instead, edits are stored as operations and grouped over time (500ms).

For large files:

  • Undo depth is reduced
  • Memory stays bounded
  • Editing remains responsive

5. Caching Where It Matters

Text layout is expensive.

Ferrite uses an LRU cache for rendered lines (egui galleys), so scrolling doesn’t recreate text every frame.


The Numbers That Actually Matter

Here’s what changed in practice:

  • Opening a 4MB file went from using around 1.8GB of RAM to roughly 80MB.
  • Files around 80MB, which were previously unusable, now open and edit smoothly.
  • Bracket matching, which used to scan the entire document every frame, now operates on a small window of roughly 20KB around the cursor.
  • Undo/redo no longer relies on full document snapshots, but on a compact, operation‑based history.

This wasn’t a micro‑optimization release.

It was a survival release.


What Nearly Broke Along the Way

Some things were far harder than expected:

  • IME support\ Handling composition text correctly is non‑optional if you want global users.
  • Word wrap + cursor math\ Visual rows vs logical lines is where many editors quietly fail.
  • UTF‑8 offsets\ Byte offsets and character offsets must never be confused. I confused them. More than once.
  • Scrolling accuracy\ “Scroll to line 3000” sounds easy. It is not.

None of these problems are visible in screenshots.

All of them are visible to users.


When You Should Not Rewrite Your Editor

This is important.

You should not rewrite your editor because:

  • It feels “cleaner”
  • You want control
  • Someone on the internet told you to

I only did this because:

  • The old path was fundamentally blocked
  • Users were hitting real limits
  • Features like multi‑cursor and large‑file support required it

Rewrites are expensive. This one was too.


What This Unlocks Next

With the new editor in place, Ferrite can now do things that were previously impossible or fragile:

  • Stable multi‑cursor editing
  • Reliable large‑file workflows
  • Better editor‑preview synchronization
  • Predictable memory usage
  • Long‑term maintainability

The editor is no longer the thing holding the project back.


Final Thoughts

egui’s TextEdit wasn’t a mistake.

It let Ferrite exist.

But software grows, and sometimes the abstractions that helped you move fast become the ones you need to leave behind.

Writing a text editor from scratch was the hardest part of Ferrite so far.

It was also the most important.


If you’ve been following Ferrite since the early days: thank you.\
And if you’re building something similar: I hope this saves you a few wrong turns.

https://getferrite.dev

Top comments (0)