fedit

architecture

A deterministic editor core surrounded by explicit runtime boundaries. The update path stays synchronous; slow or stateful work returns through the message queue.

the main loop

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.

data flow
  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.

state and resources

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.

Runtime owns operational resources

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.

process and resource boundaries
+-----------------------------------------------------------------------+
|                     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  |
                              +------------------+   +------------------+

subsystems

files + buffers

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.

syntax

Highlight · Runtime

Tree-sitter parsing runs off the update thread. Today each edit schedules a full reparse; stale results are rejected by edit tick.

language servers

Lsp* · Runtime

One stdio JSON-RPC client per server and project root. Diagnostics and navigation return as messages with stale-result guards.

plugins

PluginHostClient · Fedit.PluginHost

The NativeAOT editor talks newline-delimited JSON-RPC to a separate .NET host that builds, loads, and invokes trusted F# plugins.

macros

MacroIO · Editor

Registers store semantic action and command steps, persist in ~/.config/fedit/macros, and replay through a fenced queue.

terminal adaptation

TerminalCapabilities · Input · Renderer

Capability detection selects keyboard, mouse, image, and color behavior. Rendering downgrades through truecolor, 256-color, 16-color, and terminal defaults.

rendering pipeline

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.

one frame
 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

ordering and stale work

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.

module order (Fedit.fsproj)
   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

read the subsystem guides