Why Existing Flutter Code Editors Broke Down When I Built a Mobile IDE

rust dev.to

When I started building NimoteCode, I didn’t plan to write a code editor.

My assumption was simple:

Flutter already has code editor packages. I would just pick one and move on.

There are several solid options:

  • code_text_field
  • flutter_code_editor
  • re_editor

They all solve a similar problem well:

How to display and edit code inside Flutter.

So I did what most people would do:

I tried to integrate one of them and focus on the “real product”:
SSH, terminal, Git, AI workflows.

That assumption broke quickly.


The problem wasn’t “editing code”

It was this:

I wasn’t building a code editor.

I was building a mobile IDE.

And those are fundamentally different systems.


Where existing editors start to break

On paper, existing Flutter editors look complete:

  • syntax highlighting
  • folding
  • autocomplete
  • basic selection handling

But once I connected real IDE features, cracks started to appear:

  • SSH remote files instead of local strings
  • LSP needing full document lifecycle sync
  • Git diff depending on editor state
  • multi-panel UI sharing the same document
  • large files streamed instead of loaded

At that point, the editor is no longer a component.

It becomes shared infrastructure.


The key architectural difference

The real shift is this:

Most editor packages are built around text rendering.

A mobile IDE needs an editor core.

That difference changes everything.

Instead of “a widget that edits text”, you need:

  • a document model
  • a layout system
  • a state engine
  • a sync layer for external systems

Once multiple systems depend on the editor state, it can’t be passive anymore.

It must become the source of truth.


Why I eventually had to build my own editor

The decision wasn’t about performance or missing features.

It was about control over the model.

Existing editors assume:

  • local text
  • single-file context
  • UI-driven updates

My requirements were different:

  • local + SSH remote files unified
  • LSP lifecycle tightly bound to editor state
  • Git diff tracking per tab lifecycle
  • large files streamed in chunks
  • mobile-first selection and interaction model

These constraints conflict with widget-level architecture.

So instead of extending an editor, I rebuilt the core abstraction.


What I built instead

The new editor architecture is built around a shared core:

  • EditorState as single source of truth
  • FileLoader abstraction (local + remote)
  • Streaming file ingestion for large files
  • Tree-sitter + LSP hybrid analysis pipeline
  • diff + CodeLens + symbols bound to document model
  • mobile-first selection and coordinate system

The key idea:

Everything depends on the same document model.

Not the UI.


The real advantage of self-building

Rewriting the editor wasn’t about “adding features”.

It unlocked structural advantages:

1. Remote-first is native, not patched

SSH files are not adapters — they are first-class documents.

2. LSP becomes part of lifecycle

Not a separate service, but synchronized with editor state.

3. Git diff becomes state-aware

Not snapshot-based, but tab-aware.

4. Mobile editing is designed, not adapted

Selection, handles, scrolling all operate on a custom coordinate system.

5. All IDE features share one model

No duplicated state between panels.

This is where complexity actually decreases long-term.


The conclusion I didn’t expect

I originally thought:

I will save time by using an existing editor.

But the real outcome was the opposite:

Using an existing editor would have shifted complexity elsewhere.

Because the real problem wasn’t syntax highlighting.

It was system design.

And once you treat the editor as infrastructure instead of a widget, rebuilding it becomes the simplest consistent choice.


Final thought

I didn’t replace a code editor.

I replaced the assumption that a code editor is enough for a mobile IDE.

Source: dev.to

arrow_back Back to Tutorials