← Back

VS Code

VS Code isn't one program. It's several processes, most of which don't trust each other, communicating over narrow message formats instead of shared memory.

1. The process tree

Electron gives every app a main process (full OS access) and renderer processes (the UI, running inside Chromium). VS Code adds more on top:

  • Main process — starts and orchestrates everything else.

  • Renderer process — the window you type into. Sandboxed since 2020–2022: no direct filesystem or process access, same restriction as a browser tab.

  • Extension Host process — a separate Node.js process where extension code runs.

  • Shared process — file watching, search indexing, terminal PTYs.

  • GPU / utility processes — inherited from Chromium.

The renderer never calls into these directly. It sends messages — Electron IPC, postMessage, or the extension host's own protocol — and gets messages back.

2. What process separation buys you, and what it doesn't

Extensions run in their own process so a slow or crashing one hangs itself, not the editor. That's what "Extension Host is unresponsive" is.

It doesn't buy security isolation. An extension in the Extension Host has the same OS-level privileges as that process — it can read your filesystem, make network calls, spawn commands. Nothing in the process model stops that. Researchers who dug into VS Code's attack surface found real exploit chains: a webview XSS, escalated through a postMessage handler, into arbitrary command execution.

Workspace Trust is the actual answer to that gap. Opening an untrusted folder runs it in restricted mode, disabling extensions that haven't explicitly declared they're safe to use with untrusted data. It's opt-in and extension-declared, so it's only as good as extensions being honest about it — but it's the acknowledgment that a separate process was never a security boundary by itself.

3. Two systems color your code, and they don't agree on what a token is

Highlighting that appears the instant you type comes from a TextMate grammar: regex rules, run through an engine called Oniguruma (compiled to WASM), matching text into scopes like keyword.control or string.quoted. It runs synchronously on every keystroke and understands nothing — it can't tell a declared variable from a typo, just that something looks like an identifier.

Highlighting that does understand your code — a dimmed unused variable, a parameter shaded differently from a local — comes from LSP's textDocument/semanticTokens. The language server, which actually parsed and type-checked the file, returns a flat integer array: line delta, character delta, length, token type, modifiers, repeated per token. Resending that array on every keystroke would be wasteful, so the protocol supports a delta form — semanticTokens/edits — where the server sends only the diff against the token set it last returned, keyed by a previousResultId.

4. What gets sent when you type one character

The editor doesn't resend the whole file on every keystroke. At connection setup, client and server negotiate incremental sync, and each textDocument/didChange carries a small range-based edit plus a version number the server uses to reject stale or out-of-order changes:

json

{
  "textDocument": { "uri": "file:///app.ts", "version": 14 },
  "contentChanges": [
    { "range": { "start": {"line":9,"character":4},
                 "end":   {"line":9,"character":4} },
      "text": "e" }
  ]
}

The server patches its own copy, re-parses only what changed, and pushes back diagnostics and completions asynchronously — which is why intellisense sometimes lags a beat behind typing instead of blocking it.

Before LSP, "good TypeScript support" and "good Rust support" each had to be built separately for every editor: an N-editors × M-languages problem. LSP turns that into N+M — one server per language, one client per editor, agreeing on nothing but the wire format.

5. Watching a filesystem

When a file changes outside the editor — a git checkout, a build script — VS Code has to notice without polling. That means inotify on Linux, FSEvents on macOS, ReadDirectoryChangesW on Windows, unified behind one watcher in the shared process. Large repos can exhaust the OS's inotify watch limit — which is the real, unglamorous reason VS Code lets you exclude folders from watching in settings. It's not a performance nicety. It's a kernel resource limit.

*written with Claude Sonnet 5