← Back
Home › Systems › CI/CD and developer tooling

code-editor

Holds source files in memory and lets a person edit them at keystroke speed while the tool highlights, completes, navigates and debugs the code.

VS Code, JetBrains IDEs, Xcode, Zed, Cursor

Open the code-editor pagecode-editor Repository context retrievalagentic-ai: Select relevant files, symbols and snippets from a repository to fit an LLM prompt budgetBreakpoints watchpointscompilers: Software breakpoints (int3 patching), hardware debug registers and single-steppingCode symbol indexingcompilers: Extract definitions and references per file, give each symbol a stable string id, and store symbol to occurrences, re-indexing only files whose content hash changed.›Incremental parsingcompilers: Reparse only the region an edit damaged and splice the new subtrees into the old tree, sharing everything else; O(edit) instead of O(file).›Language server protocolcompilers: One JSON-RPC conversation over a pipe lets any editor drive any language's analysis server; the cost is keeping a live, edited document in sync and answering before it changes.›Lossless syntax treescompilers: Concrete syntax trees with trivia, green/red node split (Roslyn, rowan) for tools that rewrite codeOperational transformationdistributed: Transform concurrent edits against each other for collaborative text editingThree-way mergedistributed: Merge two descendants against their common ancestor: take whichever side changed, report a conflict only where both changed the same spot differently.›Damage trackinggraphics: Track dirty rectangles so each frame repaints and recomposites only what changedFont rasterizationgraphics: TrueType/CFF outline parsing and anti-aliased glyph rasterization with hinting/cachingGap buffergraphics: Array with a movable gap for O(1) edits at the cursor; text editors and slot tablesList virtualization (windowing)graphics: Create and lay out only the items inside the viewport plus a little overscan, and fake the full scroll extent, so frame cost depends on the screen, not on N.›Ropegraphics: A balanced tree of small string chunks with cached byte and newline counts, so seek, insert, delete and snapshot cost O(log n) instead of O(n).›Syntax highlightinggraphics: TextMate grammars or parser-based highlighting into token spansText shapinggraphics: Map Unicode to positioned glyphs via OpenType GSUB/GPOS lookups, clusters and complex scriptsUndo redographics: Command or snapshot history with grouping and coalescingFile system watchinghardware-os: Treat OS change notices as hints, debounce them, and rescan and diff a snapshot whenever notices overflow or may be lost.›Fill in the middleml-training: FIM training and prompting: prefix/suffix/middle reordering with sentinel tokens for code infillFuzzy finder scoringsearch-sort: Subsequence matching with gap/position scoring (fzf-style)Glob matchingsearch-sort: Glob/gitignore pattern matching without backtracking blowupMyers diffsearch-sort: Greedy O(ND) shortest edit script between two sequences, the default of git and GNU diff; fast when the inputs are similar.›Trigram indexsearch-sort: Trigram posting lists accelerating regex and substring searchIncremental computationsystems: Dependency graph of derived values recomputed only when inputs change (reactive cells, spreadsheets)Name resolutioncompilers: Bind every use of a name to its declaration with a scope stack, handling shadowing, hoisting and closure capture in one AST walk.›Protobuf wire formatnetworking: Protocol Buffers wire format: tags, varints, length-delimited fields and schema evolutionParser error recoverycompilers: Recover from syntax errors via synchronization tokens and error nodes to keep a full treeRecursive-descent parsingcompilers: Hand-written top-down parser with one function per grammar rule; simple, fast, with full control over error messages and recovery.›Text position encodingcompilers: Store byte offsets, convert to (line, column) on demand with a line-start table and binary search, and count columns in the unit the peer negotiated.›String interningdata-structures: Store each distinct string once and refer to it by idMessage sequence numbersdistributed: A per-stream counter on every message lets the receiver detect loss, duplicates and reordering, ack cheaply and resume after reconnect with "send from N".›Lowest common ancestorgraphs: LCA/merge-base computation in trees and DAGsInverted indexstorage: Map each term to a sorted, compressed list of the documents containing it; the core of full-text search and other fast set queries.›Hash consingdata-structures: Intern structurally equal immutable terms so equality is pointer comparison and sharing is maximalFont fallback itemizationgraphics: Split text into runs by script, direction and font coverage; pick fallback fonts per runOpentype font parsinggraphics: Sfnt tables (cmap, glyf, CFF, hmtx, GSUB, GPOS), variable font axes, safe parsing+ 7 moreBit packing and PFORcompression: Store blocks of integers with exactly b bits each and patch rare outliers separately (PFOR); the fastest integer codec family, billions of values per second.›Delta encodingcompression: Store differences between consecutive values instead of the values, turning sorted IDs, timestamps and counters into small numbers that pack into few bits.›Varint (LEB128)compression: Variable-length integers in 7-bit groups with a continuation bit; small numbers take 1 byte, the wire format of Protobuf, WebAssembly and DWARF.›BM25 rankingsearch-sort: The standard lexical relevance score: IDF-weighted term frequency with saturation (k1) and length normalization (b); default in Lucene, Elasticsearch and SQLite FTS5.›Unicode bidi algorithmgraphics: UAX 9: resolve embedding levels and reorder mixed left-to-right and right-to-left text+ 5 moreSIMD programminghardware-os: Process 4-16 lanes per instruction with AVX2, AVX-512 or NEON via Rust std::arch, with runtime dispatch and a portable fallback.›Top-k selectionsearch-sort: Keep the k best of n items without sorting all n - bounded min-heap for streams, quickselect for arrays, threshold pruning to skip most items.›+ 1 mored-ary heapdata-structures: Array heap with d children per node (d = 4 typical): half the binary heap's depth, siblings in one cache line, faster priority queues.›

How it is built

Architecture overview

A code editor holds source files in memory, shows them at screen rate, and lets a person change them while the tool understands the code well enough to colour it, complete it, navigate it, debug it and sometimes rewrite it. Its constraint is latency on every keystroke: typing, scrolling and cursor moves must finish within a frame, while everything expensive (parsing, type checking, indexing, search, AI completion) happens somewhere else and arrives later. Examples are VS Code, the JetBrains IDEs, Xcode, Zed and Cursor.

The design that makes this work is a split of three speeds. The editing core (text buffer, undo, selections, layout, rendering) is synchronous, local and never waits on a server. The language layer (incremental parsing, highlighting, folding) is fast enough to follow typing on a background thread, using trees that are updated, not rebuilt. The services layer (language servers, debug adapters, search and symbol indexes, extensions, remote machines) lives in other processes, talks over a message protocol, and may answer slowly or die without taking the editor with it. Every part below is a decision about which of the three speeds a feature belongs to.

Parts:

  • Text buffer — store the document so edits, line lookups and snapshots are cheap — Rope, gap-buffer (no page yet), undo-redo (no page yet), Text position encoding, Three-way merge, Myers diff
  • Syntax layer — incremental parse, highlight, fold, select — Incremental parsing, lossless-syntax-trees (no page yet), parser-error-recovery (no page yet), Recursive-descent parsing, syntax-highlighting (no page yet), hash-consing (no page yet), string-interning (no page yet)
  • Language services — completion, definitions, diagnostics from a separate process — Language Server Protocol, Message sequence numbers, breakpoints-watchpoints (no page yet)
  • Indexing and search — files, symbols, text, fuzzy open — Inverted index, trigram-index (no page yet), Regex engine, Aho-Corasick, SIMD string search, Code symbol indexing, Name resolution, glob-matching (no page yet), fuzzy-finder-scoring (no page yet), Top-k selection, File-system watching
  • Derived-data engine — recompute only what an edit invalidated — incremental-computation (no page yet), Topological sort, Swiss table
  • Rendering — shape text, rasterise, draw only what changed — text-shaping (no page yet), opentype-font-parsing (no page yet), font-fallback-itemization (no page yet), unicode-bidi-algorithm (no page yet), font-rasterization (no page yet), damage-tracking (no page yet), List virtualization, SIMD programming
  • Extension host — run third-party code out of the UI process, local or remote — (process boundary plus the protocols above), protobuf-wire-format (no page yet)
  • Collaboration and AI — shared editing and repository context for model completions — operational-transformation (no page yet), repository-context-retrieval (no page yet), fill-in-the-middle (no page yet)
   keyboard / mouse
         │
         ▼
 ┌──────────────────────────── UI process ────────────────────────────┐
 │ editing core: rope buffer ── undo ── selections ── layout          │
 │      │ edit(range, text)                      │ visible lines only │
 │      ▼                                        ▼                    │
 │ syntax tree (incremental)  ──► highlights   shaping ► raster ► GPU │
 └───────┬───────────────────────────┬────────────────────────────────┘
         │ didChange (versioned)     │ commands, events
         ▼                           ▼
  language server (LSP)        extension host(s) ──► debug adapter (DAP)
  diagnostics, completion      local / remote / web
         ▲
         │ symbols, refs
  index service: file watcher ─► symbol index + text index ─► query (top-k)

How the parts fit together

Text buffer: edit at the cursor, snapshot for everyone else

The buffer must support insertion and deletion anywhere, offset-to-line and line-to-offset conversion, and a cheap consistent snapshot for background readers (highlighting, indexing, a language server request). A Rope, a balanced tree of small chunks with cached byte and newline counts, gives O(log n) for all of these and copy-on-write snapshots; the alternative gap-buffer (no page yet) is O(1) at the cursor and simpler, but a snapshot is a full copy and far-apart edits (multi-cursor, rename) are slow. Ropey is a UTF-8 rope for Rust "designed to be the backing text-buffer for applications such as text editors" (source: Ropey README). The contract is that every consumer holds a snapshot with a version number, and results are applied only if the version still matches.

Positions are the classic bug source. Store byte offsets internally and convert to line and column on demand with a line-start table (Text position encoding). Language servers count columns in UTF-16 code units by default in LSP, so the editor converts at the protocol edge, not in the buffer. Undo (undo-redo, no page yet) is a stack of grouped edit commands that coalesces typing runs; with multiple cursors one user action must be one undo step. Diff and merge use Myers diff for a shortest edit script and Three-way merge when a file changed both on disk and in the buffer.

Syntax layer: trees that follow typing

A regex highlighter is cheap but wrong on nested constructs; a parser-based one is accurate if it survives code that is incomplete, which is most code while someone types. Tree-sitter describes itself as a parser generator and incremental parsing library that builds a concrete syntax tree and efficiently updates it as the source is edited, fast enough to parse on every keystroke, robust to syntax errors, and dependency-free in pure C11 (source: Tree-sitter documentation). That is Incremental parsing: the edit tells the parser which region is damaged, and subtrees outside it are reused. Error recovery (parser-error-recovery, no page yet) keeps a full tree with error nodes, which is the property that lets highlighting and folding keep working mid-edit. A concrete tree that keeps whitespace and comments (lossless-syntax-trees, no page yet) lets refactorings rewrite code without losing formatting; sharing structurally equal nodes (hash-consing, no page yet) and identifiers (string-interning, no page yet) cuts memory. Hand-written servers often use Recursive-descent parsing for its control over error messages. Highlight queries then map nodes to colour spans (syntax-highlighting, no page yet); only the visible range is evaluated.

Language services: a protocol, a process, a version

The Language Server Protocol standardises the conversation between an editor and a language server so one server serves many editors: the protocol defines messages sent using JSON-RPC (source: LSP official page). The costs are real: every request is asynchronous, the server's view of the file must be kept in sync by incremental didChange messages, and answers can arrive after the user moved on. Hence the contract: version every document, cancel superseded requests, apply a result only if the document version matches, and show stale results greyed instead of blocking. Message sequence numbers is the general idea (detect loss, duplicates, reordering); LSP uses request ids and document versions. Debugging uses the Debug Adapter Protocol, which does for debuggers what LSP does for languages (source: DAP official page); the adapter in turn drives breakpoints, watchpoints and single-stepping (breakpoints-watchpoints, no page yet).

Servers can be slow to start and heavy. The editor treats a server as a service that can crash, restart and be restarted lazily.

Indexing and search: files, symbols and text

Three indexes serve three questions. "Open file by name" is a fuzzy subsequence match (fuzzy-finder-scoring, no page yet) over a path list, with a bounded result set via Top-k selection and ignore rules matched by glob-matching (no page yet). "Find in files" can be a streaming scan with SIMD string search and a Regex engine with linear-time guarantees; for very large trees a trigram-index (no page yet) over an Inverted index narrows the candidates before the regex runs, and Aho-Corasick serves many literal patterns at once. "Go to symbol / find references" needs the structure of the code: Code symbol indexing extracts definitions and references per file with stable symbol ids, stored as symbol-to-occurrences, and Name resolution binds uses to declarations with a scope stack. Sourcegraph's SCIP is an open format for precomputed code indexes (see Real implementations).

All three must be kept fresh. File-system watching treats OS change notices as hints, debounces them, and rescans a snapshot when notices overflow; the index is updated per changed file, not rebuilt. Searching unsaved buffers means consulting the editor's snapshots in preference to disk.

Derived-data engine: recompute only what changed

Diagnostics, outlines, symbol tables and type information are functions of other data. An incremental-computation (no page yet) layer records which inputs each derived value read, orders recomputation with Topological sort, and skips values whose inputs are unchanged. This is how a type checker can respond within a keystroke on a large project: the edit changes one file's syntax tree, and only the dependent queries re-run. Lookup tables keyed by identifiers use a fast hash such as a Swiss table.

Rendering: draw the visible, shape the complex

Only visible lines are laid out and drawn (List virtualization); the scrollbar extent is computed from cached line counts in the rope. Text must be shaped, not just mapped to glyphs: OpenType substitution and positioning rules (text-shaping, opentype-font-parsing, both no page yet), fallback fonts per script run (font-fallback-itemization, no page yet) and the bidirectional algorithm for mixed left-to-right and right-to-left text (unicode-bidi-algorithm, no page yet) produce positioned glyphs. Rasterised glyphs go into an atlas (font-rasterization, no page yet) and the GPU draws them as quads. damage-tracking (no page yet) repaints only what changed, which matters on battery and on remote displays. Wide-character width, ligatures and combining marks mean that the cursor is moved by grapheme cluster, not by code point or byte; see Text position encoding.

Extension host: isolate other people's code

VS Code's documentation describes the extension host as the part that runs extensions, with local, web and remote configurations; extensions are loaded lazily by activation events, and the host "prevents extensions from impacting startup performance, slowing down UI operations, modifying the UI" (source: VS Code extension host docs). The contract is a message API in both directions: the UI process exposes a narrow set of capabilities; the extension host calls them asynchronously; a misbehaving extension can hang its own process without freezing typing. For remote development, the same split places the extension host (and language servers) next to the files on the remote machine while the UI stays local, so only small messages cross the network. Messages crossing the boundary use a compact serialisation (protobuf-wire-format, no page yet, is the typical choice for custom services), with versioned schemas so old extensions keep working.

Collaboration and AI

Shared editing needs concurrent edits to converge: operational-transformation (no page yet) transforms edits against each other using the buffer's positions; the alternative is a conflict-free replicated structure. AI completion needs context: repository-context-retrieval (no page yet) selects relevant files and symbols (using the same symbol and text indexes as above) to fit a prompt budget, and fill-in-the-middle prompting (fill-in-the-middle, no page yet) lets a model complete the text between a prefix and a suffix. Treat model output like any other asynchronous service: versioned, cancellable, applied only if the buffer version matches.

Data and control flow

One keystroke:

  1. The key event reaches the editing core, which applies the edit to the rope and creates a new snapshot with a new version. Only the affected line is relaid out.
  2. The syntax tree is updated for the damaged region on a worker thread; highlights for visible lines are recomputed and the line is redrawn (a few milliseconds is the goal; the exact budget is the frame time).
  3. An incremental didChange goes to the language server with the new version; pending requests for the older version are cancelled.
  4. When diagnostics or completions arrive for that version, they are applied; if the version moved on, they are dropped.
  5. The index service learns of the save, re-extracts symbols for the file and updates the symbol and text indexes.

One "find in files":

  1. The query is parsed; literals are extracted from the regex.
  2. The text index (if present) returns candidate files; otherwise the walker scans the tree honouring ignore rules.
  3. Files are searched in parallel with a bounded result count; open buffers are searched from their snapshots.
  4. Results stream into the panel as they arrive; the search is cancelled when the query changes.

One remote session:

  1. The UI connects to a small server on the remote machine and starts a remote extension host and language servers there.
  2. File access, search, indexing and debugging happen remotely; the UI receives results and edits as messages.
  3. If the link drops, the UI keeps the buffer and reconnects; unsaved edits are not lost because the buffer lives in the UI process.

Scaling

In the order you meet the limits:

  1. Large files (logs, generated code). Levers: a rope, a size threshold above which parsing and language services are turned off, streaming load.
  2. Large repositories. Levers: ignore rules, an on-disk index instead of scanning, per-file incremental updates, lazy language server startup per project.
  3. Slow language servers. Levers: cancellation, versioned results, incremental sync, partial results, and showing last-known diagnostics.
  4. Extension load. Levers: lazy activation, one extension host per window or per remote, resource limits, and a profiler that names the extension.
  5. Long lines and complex scripts. Levers: cap layout work per line, cache shaping results per run, virtualise horizontally.
  6. Remote latency. Levers: put language servers beside the files, send edits rather than files, local echo for typing.
  7. Memory in long sessions. Levers: share syntax nodes, free snapshots, evict caches (LRU and CLOCK).

Failure modes

FailureSymptomCauseFix
Keystroke lagtyping stutterssynchronous work in the UI thread (parsing, a plugin, a long line)move work off-thread, budget per frame, cap line length
Stale or wrong resultscompletions apply to the wrong textanswer arrives after further editsversion every document, apply only on match, cancel superseded requests
Off-by-one cursor or editedits land one character offmixed byte, UTF-16 and code-point positionsone internal unit, convert at the boundary, test with non-ASCII text
Server crash loopfeatures vanish, high CPUa language server fails on startup or on a filerestart with backoff, disable after N crashes, show status
Extension hangUI freezes or high memoryextension runs in the UI process or leaksseparate extension host, activation events, kill and restart the host
Missed file changessearch or index is staleOS notice overflow or ignored eventstreat notices as hints, rescan snapshots on overflow, manual refresh
Regex stallsearch never finishesbacktracking regex on large inputlinear-time engine, timeouts, cancellation
Merge clobbers editsexternal change overwrites the bufferno three-way merge with the basetrack base version, three-way merge, conflict markers
Wrong glyphs or orderbroken text for complex scriptsno shaping or bidi handlingshape by script run, fallback fonts, bidi reordering
Lost workcrash discards unsaved editsedits only in memoryperiodic backup of dirty buffers, restore on start

Real implementations

namewhat it isrepolicencenotes
VS Code (Code - OSS)Electron-based editor with an extension host and LSP/DAP clientsmicrosoft/vscodeMITsource layout in the wiki
ZedRust editor with its own rope cratezed-industries/zedper crate; the rope crate is GPL-3.0-or-later (Cargo.toml)licence differs by crate, check each
Helixmodal editor in Rust built on Tree-sitterhelix-editor/helixMPL-2.0—
Neovimextensible Vim forkneovim/neovimApache-2.0, with parts under the Vim licence (per LICENSE.txt)—
Lapcecode editor written in Rustlapce/lapceApache-2.0 (per repo page)—
Tree-sitterincremental parsing library and parser generatortree-sitter/tree-sitterMITused by several editors above
RopeyUTF-8 rope for Rustcessen/ropeyMITtext-buffer library
rust-analyzerRust language serverrust-lang/rust-analyzerApache-2.0 and MITLSP server
SCIPopen code-intelligence index format from Sourcegraphsourcegraph/scipApache-2.0 (per repo page)precomputed symbol index

Proprietary systems named in the graph: JetBrains IDEs, Xcode, Cursor. They are not described here.

Sources

  • Language Server Protocol: https://microsoft.github.io/language-server-protocol/
  • Debug Adapter Protocol: https://microsoft.github.io/debug-adapter-protocol/
  • Tree-sitter documentation: https://tree-sitter.github.io/tree-sitter/
  • VS Code, Extension Host: https://code.visualstudio.com/api/advanced-topics/extension-host
  • VS Code, Language Server Extension Guide: https://code.visualstudio.com/api/language-extensions/language-server-extension-guide
  • Ropey README: https://github.com/cessen/ropey

Similar systems

Share the most building blocks with this one.