Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Developer Guide

RoyiTextEditor is a Windows-first Rust application. Its core is split into small crates so text storage, editing, layout, rendering, and operating-system integration have clear owners.

Architecture

flowchart LR
    Win[Win32 input and window] --> Platform[rte_platform]
    Platform -->|events| App[rte_app: Editor and Document]
    App -->|edit commands| Edit[rte_edit: selections and history]
    Edit -->|transactions| Text[rte_text: persistent text tree]
    Text -->|snapshots| App
    App -->|snapshot and view state| View[rte_view: layout and display list]
    View -->|display list| Render[rte_render: CPU rasterizer]
    Render -->|pixels| Platform
    App -->|snapshots and edits| Syntax[rte_syntax on the syntax thread]
    Syntax -->|trees and spans| App
    App -->|queries| Search[rte_search on the search thread]
    Search -->|matches and counts| App
    App -->|snapshots and ranges| Spell[rte_spell on the spelling thread]
    Spell -->|misspelled words| App
    Workers[Loader and save threads] -->|results and wake events| App

The window thread owns the editor, document, selections, popups, and frame construction. Background threads (loading, saving, search, syntax, spelling) receive immutable snapshots, return results through channels and wake the window; they do not mutate UI state directly. The spelling thread asks the syntax thread which Markdown text is not prose.

CrateResponsibility
rte_textPersistent piece tree, immutable snapshots, line queries, transactions, position mapping, and UTF-8/UTF-16 conversion.
rte_editByte-offset selections, caret motion, editing commands (including comment, auto-closing and indentation edits), clipboard data, and undo history.
rte_searchLiteral, Glob and regular-expression search over snapshots, and replacements.
rte_spellWords of prose, the spelling front end (user words, ignored words, cache), the built-in word list (expanded at build time from the shipped Hunspell dictionary), the Hunspell engine (spellbook) and suggestions.
rte_syntaxLanguage registry and detection, built-in tree-sitter grammars with trimmed queries (queries/, regenerated by cargo xtask syntax-queries), parsing sessions, highlight spans, injections, brackets, syntax-node selection, symbols, and the tree-sitter allocator.
rte_viewInteger-cell layout, hit-test geometry, and display lists for text and popups.
rte_renderCPU glyph rasterization, caches, clipping, and framebuffer drawing.
rte_platformRaw Win32 window, input events, DIB presentation, clipboard, file operations, and the Windows spell checker (COM loaded at run time).
rte_appStartup, command dispatch, document lifecycle, loading, saving, palette, search, syntax and spelling threads, and UI coordination.
rte_assetsBundled default font and build-time font acquisition.
xtaskStartup and performance benchmarks, import checks, release packaging, and query generation.

Recovery journals and external-change merging are not shipped features.

Data Flow

The loader builds immutable text snapshots off the UI thread. Editing commands produce transactions and updated snapshots; the application then rebuilds the visible display list and rasterizes it into the window buffer. Each edit is also sent to the syntax thread, which re-parses incrementally. Saving writes a temporary sibling file and uses the platform replacement path, with conflict checks and backup handling.

Workers should receive owned or immutable data. Keep window and view state on the UI thread, reject stale asynchronous results, and wake the window only when there is new work to present. These boundaries are also described in the architecture plan.

One File per Session

A session (process) handles one file at a time and never starts, closes or contacts another session. editor/lifecycle.rs owns the transitions:

stateDiagram-v2
    [*] --> Empty: no file argument
    [*] --> Open: file argument
    Empty --> Pending: file dropped
    Open --> Pending: file dropped, no unsaved changes
    Pending --> Open: loader confirms a file
    Pending --> Empty: folder or missing (from the empty state)
    Pending --> Open: folder, or unsaved changes meanwhile (old file kept)
    Open --> Empty: Ctrl+W (after Save or Don't Save)
    Empty --> [*]: Alt+F4
    Open --> [*]: Alt+F4 (after Save or Don't Save)
  • A dropped file loads while the current file stays; it replaces it only when its loader’s first answer is a file and the current file still has no unsaved changes.
  • Replacing or closing a file resets every per-file state: the loader is stopped (it checks a stop flag between blocks and before publishing), the syntax thread is dropped (its trees are freed on that thread) and the search box is closed. Search generations only grow, and the count checkpoints holding the old text are freed.
  • The empty state is the no_file edit lock: edits and saves are refused, and only the empty-state commands run (works_without_file).
  • Drops arrive as WM_DROPFILES (the WS_EX_ACCEPTFILES window style); shell32 is loaded on the first drop, so startup is unchanged.

Syntax Threads

Highlighting starts while the window is still being created. The work runs in parallel on short-lived threads: in the benchmarks, files of 10,000 lines of TypeScript, Python and C are fully highlighted 36, 43 and 70 ms after the process starts.

sequenceDiagram
    participant UI as UI thread
    participant Loader as Loader thread
    participant Query as Query thread
    participant Worker as Syntax worker
    participant Full as Whole-file parse thread
    UI->>Query: compile the highlight query of the file name's language
    UI->>Loader: read the file
    par Window
        UI->>UI: create the window, draw the first frame as plain text
    and Syntax
        Loader->>Worker: start with the complete text
        Worker->>Full: above 128 KiB, parse the whole file
        Worker->>Worker: parse the file, or above 128 KiB a 32 KiB window
        Query-->>Worker: compiled query
        Worker-->>UI: first trees, the next frame is highlighted
        Full-->>Worker: whole-file tree
        Worker-->>UI: whole-file trees, fully highlighted
    end
    UI->>Worker: edits, visible range, span and symbol requests
    Worker-->>UI: trees and spans
  • The loader starts the syntax worker as soon as the text is read. The editor adopts that worker when the language and text match; otherwise it starts its own.
  • The highlight query compiles on its own thread while the text parses; the first view waits for it.
  • Files above 128 KiB parse a 32 KiB window around the view first. The whole file parses at the same time on its own thread; edits made meanwhile are applied to its tree when it arrives, and it is adopted before any further window parse.
  • The UI thread never waits for these threads. It draws the visible rows from the latest trees within 1 ms per frame and asks the worker for the rest. Trees it replaces go back to the worker to be freed.

Memory Allocation

Tree-sitter allocates its parse trees through mimalloc (rte_syntax::install_allocator, an unsafe function called first in main, before any tree-sitter object exists; a failed allocation aborts the process). Parsing is 6-16% faster than with the Windows heap, and Go to Symbol about 25% faster. The rest of the application keeps the Windows heap: mimalloc there gave no measurable gain and doubled the memory of small files.

Fast rendering with a higher memory footprint is the project owner’s design choice: when the two conflict, speed wins, within explicit limits such as syntax.maxFileMiB. The parallel threads and mimalloc’s per-thread pages add 2-3 MiB to the peak memory of small and 10,000-line files, and editing an 8 MiB file while it is still being parsed can peak 13-20 MiB higher, because the whole-file parse no longer pauses for the edits. In return highlighting is complete sooner, and 8 MiB files peak about 24 MiB lower. Startup memory is unchanged (5.1 MiB for an empty file), and --syntax none restores the memory footprint of 0.3.0 (a 64 KiB JSON file peaks at 5.7 MiB instead of 10.7 MiB). The design and measurements are recorded in Done/SyntaxPerformance.md.

Build and Validation

The repository targets Windows x64 with the GNU Rust toolchain and x86-64-v3 CPU features. windres is required to embed the application manifest and icon. Missing font assets are fetched from pinned releases and SHA-256 verified by the rte_assets build script.

Run focused tests for the crate being changed. Do not run the whole workspace suite or performance suite unless requested; see AGENTS.md.

TaskCommand
Run the editorcargo run --release -p rte_app -- <file>
Test a cratecargo test -p <crate>
Check formattingcargo fmt --all --check
Run the documentation build./tools/Build-Docs.ps1
Measure startupcargo xtask bench-startup
Run the short performance reportcargo xtask bench-perf --short
Run selected performance testscargo xtask bench-perf --only L01,F01
Package a release ZIPcargo xtask package

The workspace and shipped behavior are documented in Done/Architecture001.md, DEVELOPMENT.md, and AGENTS.md.