Redox Devlog[0] - Planning
Here's a project link. It should work once things get going.
First things first... what is Redox?
Well, I'm currently entering the final semester of my computer science undergrad, and it's time to start on my final capstone project. I've been building out my custom Rust TUI framework, MinUI for a while now, and I figured the mandatory capstone project would be a great opportunity to build something with it.
As such, Redox is basically going to be a terminal-based text editor program built on top of MinUI.
It'll basically be a vim clone. Due to the short capstone timeframe of ~4 months, I'm not going to be able to implement a ton of stuff, but I still think it'll be a great project to test out the framework (since other than example files, Redox is essentially the first thing to be built with it).
MinUI is an "immediate-mode framework" — unlike retained-mode frameworks where you mutate a widget tree, MinUI rebuilds the UI every frame. This makes it conceptually simpler and closer to how game engines work, perfect for a responsive editor where state changes frequently.
Key MinUI features I'll leverage include:
- UiScene routing: Centralized focus, capture, and event routing without boilerplate.
- Deferred cursor API: Avoids flicker by applying cursor changes once per frame.
- Widget primitives: Containers, text rendering, scrollbars, etc. All the basics are solid.
Oh, and the name "Redox" is sort of a play on words. A "redox reaction" is a category of chemical reaction that includes metals oxidizing... aka rust! The "dox" part of the name also fits with text editors, where you could be generating "docs". Get it? I thought it was clever.
Project Architecture
So far in MinUI, I've got pretty solid immediate-mode rendering, a UiScene routing system, a small but capable widget set, and a bunch more little behind-the-scenes features for you to take for granted. I absolutely plan to cram all of those features into Redox.
Probably the most important architectural decision I've made early on is to strictly separate the editor core from the TUI layer.
editor_core (the brain): This crate will be pure Rust logic. It won't know what a terminal is, nor will it know anything about MinUI. Its job is to handle stuff like buffers, cursor math, the undo tree, and "intent." This should make the logic 100% testable with standard unit tests.
editor_tui (the body): This crate is the thin adapter. It uses MinUI systems to render the state, captures keyboard/mouse events, and translates them into "actions" for the core to process.
The Workspace Layout
I plan on using a Cargo workspace to keep these concerns physically separated in the file system, something like this:
redox/
├── crates/
│ ├── editor_core/ # Buffer logic, action/effect system, undo tree
│ └── editor_tui/ # MinUI implementation, widgets, input translation
The Data Model: Keeping it Simple
For the minimum viable product, I'm focusing on a single-buffer model. While I hope to eventually support tabs and split views, the priority is to first provide a rock-solid editing experience.
1. The Text Buffer
For the text buffer, I plan to go with a rope data structure. While a simple string is easier to start with, it becomes O(n) for every insertion or deletion. This would make the editor slow to a crawl when opening large files like logs or minified JS.
By using a rope, the text is essentially stored as a tree of smaller strings. This provides several massive advantages for the project:
- Performance: Insertions and deletions are now O(log n).
- Efficient Slicing: Grabbing only the lines visible in the viewport is nearly instantaneous.
- Scalability: It can handle files much larger than the available contiguous memory blocks.
The tradeoff here is added complexity. Rope operations are more involved than simple string indexing, but for a Vim-like editor that prioritizes responsiveness on large files, I think it's the right call
2. View State & Cursors
In my opinion, this is one of the make-or-break things for how the editor feels to use, and is something I believe most people take for granted in their daily lives. I mean think about it, cursor position is not as simple as just staying in the same column as you move up or down.
Example: If the cursor is on column 10 of a 50-character line, then moves up to a 5-character line, it snaps to column 5 (end of line). When moving back down, it returns to column 10 — the preferred position — instead of staying at column 5.
Even with the text represented as a rope, I’ll be tracking the cursor using cell columns (the visual position in the terminal) rather than raw character offsets.
The rope should make this easier. Most rope implementations provide efficient methods to convert between byte offsets, character indices, and line numbers. This allows me to map:
(line, col_cells) ↔ Rope index
This mapping is critical for vertical movement. By storing a preferred_col_cells, I can ensure that moving the cursor up and down through lines of different lengths feels "sticky" and natural, just like in a proper editor.
The Action & Effect System
To keep the UI and core decoupled, I'm implementing an Action/Effect pattern.
- Events (keypress/mouse) come into
editor_tui. - The TUI translates these into an action (e.g.,
MoveDown,InsertChar('a')). - The core processes the action and returns a list of effects (e.g.,
WriteFile,SetStatus,Exit).
This ensures that the "brain" never reaches out to the "body," it only sends signals.
Example:
// Pseudocode
let effects = editor.apply(Action::InsertChar('a'));
for effect in effects {
match effect {
Effect::WriteFile { path, contents } => fs::write(path, contents)?,
Effect::SetStatus(msg) => statusline.set(msg),
Effect::Quit => return Ok(()),
}
}
Hopefully you can see the separation of concerns there. The TUI translates the event into an action, and then the core processes the action and returns the appropriate effects.
Testing Strategy
By keeping editor_core terminal-agnostic, I can write unit tests for all critical logic:
- Cursor movement (including edge cases like empty lines, Unicode)
- Insert/delete operations
- Undo/redo correctness in the undo tree
- Search result positions
The editor_tui layer will have minimal tests (mainly input mapping), with most correctness guarantees living in the core.
The Roadmap (Project Milestones)
Given I only have about 4 months to work on this, I've broken the development into four distinct phases. Stuff like this never quite goes to plan, but here's the ideal timeline:
| Phase | Approximate Timeline | Focus | Key Features |
|---|---|---|---|
| 1: The MVP | Month 1 | Core functionality | File I/O, basic editing (insert/normal modes), scrolling |
| 2: The Road to Vim | Month 2 | UX & flow | Visual mode, yank/paste, undo tree, search |
| 3: Applying the Polish | Month 3 | Quality of life | Things like syntax highlighting with treesitter, LSP support, and custom config files |
| 4: If I have time... | Month 4? | Some extra "nice to have" features | Statusline, command palette (:w, :q), multiple buffers |