Why Is It Designed This Way?
Why keep editor mutation in one loop?
Text, selections, views, syntax state, history, diagnostics, and UI layers are coupled by user-visible invariants. Serial mutation makes each event an atomic transition without a lock hierarchy or partially updated render.
Why return callbacks from jobs?
A background future cannot safely retain &mut Editor across an arbitrary
wait. Returning a closure lets it capture an owned result while the loop grants
temporary mutation authority at the right moment.
Why model edits as transactions?
The same change description can update the rope, remap positions, compose with nearby edits, notify LSP, and generate an inverse. Direct string mutation would force every caller to reproduce those consequences.
Why use a rope and cheap snapshots?
Editors repeatedly modify large texts and send stable versions to background analysis. Structural sharing makes both operations affordable and avoids borrowing the live document during analysis.
Why is the compositor dynamically dispatched?
Overlays appear and disappear at runtime and have unrelated concrete types. A trait-object stack matches that product behavior while keeping the editor model independent of every prompt, picker, popup, and integration UI.
Why coalesce redraws?
Diagnostics, progress, typing, configuration, timers, and jobs can all request one. Rendering every request wastes terminal bandwidth and can starve input; rendering on a timer preserves responsiveness.
Why have waited and detached jobs?
Some work is advisory and may disappear at quit; saves and lifecycle work may carry durability obligations. One universal cancellation policy would either lose important work or make exit hostage to irrelevant work.
Why bound language-server shutdown?
Helix owes the protocol a shutdown/exit attempt, but it cannot transfer process liveness control to an unreliable child. Waiting for its own write, under a deadline, is the boundary Helix can actually guarantee.