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.