Model owns deterministic state
Buffers, active/focused surfaces, prompts and pickers, workspace tree, notifications, keymaps, macro registers, hex views, highlight spans, diagnostics, jump history, structural-selection ladders, and mouse-drag state.
A deterministic editor core surrounded by explicit runtime boundaries. The update path stays synchronous; slow or stateful work returns through the message queue.
Terminal input and completed background work meet in one message queue.
Editor.update applies one message to deterministic state and returns the next model
plus descriptions of work for the runtime. Rendering projects that model to a cell grid; effects
leave the pure core and eventually post results back to the same queue.
terminal input async completions
keys · mouse · paste · resize files · LSP · plugins · parsing
| |
| +-------------------+
| | ConcurrentQueue |
| +-------------------+
| |
+------------------+-------------------+
|
v Msg
+-------------------+
| Editor.update | pure
+-------------------+
|
(Model', Effect list)
+------------------+-------------------+
| |
Model' v v Effect list
+-----------------+ +-------------------+
| Layout.render | pure | Runtime.startEffect | impure
+-----------------+ +-------------------+
| I/O · tree-sitter · RPC
Screen |
| v
v result Msg returns to
+-----------------+ ConcurrentQueue
| Renderer.render |
| ANSI diff |
+-----------------+
|
v
terminal
Effects never mutate the model directly. They enqueue a result message, and
Editor.update decides whether that result is still relevant.
Buffers, active/focused surfaces, prompts and pickers, workspace tree, notifications, keymaps, macro registers, hex views, highlight spans, diagnostics, jump history, structural-selection ladders, and mouse-drag state.
Terminal handles, filesystem watchers, cancellation tokens, task chains, the tree-sitter registry, the plugin-host client, and live language-server processes.
This is MVU at the domain boundary, not a claim that every process resource fits in one immutable record. The model contains everything required to render and make deterministic decisions; the runtime interprets requested work.
+-----------------------------------------------------------------------+ | fedit (NativeAOT process) | | | | Model <-> Editor.update <-> Layout <-> Renderer | | | | | v Effect | | +-------------+ | | | Runtime | | | +------+------+ | | | | | +----------------+----------------+------------------+ | | | | | | | | file/config tree-sitter PluginHostClient LspClient map | | clipboard parse worker | | | | watcher + registry | JSON-RPC | JSON-RPC | +---------------------------------------|------------------|--------------+ v v +------------------+ +------------------+ | Fedit.PluginHost | | language servers | | plugin ALCs | | stdio processes | +------------------+ +------------------+
Buffer · PieceTable · Hex · Runtime Text stays LF-normalized in memory and preserves its original line ending on save. Binary files take a raw-byte, byte-exact hex path.
Highlight · Runtime Tree-sitter parsing runs off the update thread. Today each edit schedules a full reparse; stale results are rejected by edit tick.
Lsp* · Runtime One stdio JSON-RPC client per server and project root. Diagnostics and navigation return as messages with stale-result guards.
PluginHostClient · Fedit.PluginHost The NativeAOT editor talks newline-delimited JSON-RPC to a separate .NET host that builds, loads, and invokes trusted F# plugins.
MacroIO · Editor Registers store semantic action and command steps, persist in ~/.config/fedit/macros, and replay through a fenced queue.
TerminalCapabilities · Input · Renderer Capability detection selects keyboard, mouse, image, and color behavior. Rendering downgrades through truecolor, 256-color, 16-color, and terminal defaults.
Rendering stays a projection until the final terminal write. The screen diff keeps ordinary edits small, while terminal capability detection selects the best color representation the current terminal can display.
Model | | Layout.render (pure) v Screen Width x Height grid of Cell(glyph, style) | | compare with previous Screen v changed cells only | | Renderer.render +-- truecolor terminal -> 24-bit RGB SGR +-- 256-color terminal -> quantized ANSI cube +-- 16-color terminal -> nearest standard color +-- default colors -> terminal-owned foreground/background | v ANSI output + cursor placement
F# compile order in Fedit.fsproj is the dependency map. Primitive data structures
come first; editor state and update logic sit in the middle; terminal/runtime interpreters and
the CLI come last. New subsystems should enter through a message/effect seam instead of calling
runtime resources from Editor.update.
Background parsing, file reads, LSP requests, and selection-ladder computation carry buffer identity and version information. The update layer drops completions for a closed, switched, or edited buffer. Cancellation reduces wasted work; version checks preserve correctness.
primitives | Primitives -> Keys -> Events
|
terminal | TerminalCapabilities -> MouseProtocol -> ImageProtocol -> KittyImage
|
text/storage | PieceTable -> Buffer -> Hex -> Workspace
|
screen/theme | Screen -> Color -> Themes -> Highlight
|
commands/keys | Commands -> Actions -> Keymap
|
plugins | Plugins -> PluginWire -> PluginProtocol -> PluginHostClient
|
LSP | LspTypes -> LspWire -> LspTransport -> LspClient
|
UI types | PickerTypes -> PromptTypes -> Model -> Config -> Pickers
| -> KeymapIO -> MacroIO -> Prompt -> Dock
|
update | Editor
|
render/input | Status -> Renderer -> Input -> View
|
runtime | Terminal -> Runtime -> Cli -> Cli/Commands/*
|
entry | Program