Skip to main content
The frontend is React 19 with zustand stores and CodeMirror 6 for editors, bundled by Vite and run in the system webview.

Layout of src/

Features and activate()

Each feature exports activate(): void | (() => void). It registers actions, tool windows, status items, editor contributions and services, and returns a disposer that undoes all of it. src/features/index.ts lists the features in activation order and activates each inside a try, so one feature that throws doesn’t stop the others. src/workbench/activate.ts runs first and provides the shell services (tool windows, notifications) that features rely on. A whole feature can be this small:
src/features/logcat/index.ts
Load heavy views lazily (lazyComponent) so they don’t weigh on startup; pnpm perf budgets the initial bundle.

Feature isolation

Features never import each other. They communicate through three channels:
  • Actions: executeAction("project.dontOpen") runs another feature’s behaviour by its stable id, without importing it.
  • Contributions: a feature registers into an extension point in src/workbench/contributions.ts (or src/editor/api.ts); whoever renders that point doesn’t know who contributed.
  • Services: cross-feature services are provided once and looked up by accessor (below).
Shared code that several features need goes into platform/, ui/, workbench/ or editor/api, not into one feature that the others import.

Actions

Everything a user can invoke is an action with a stable, dotted id (src/platform/actions.ts). Menus, keybindings, toolbar buttons, context menus and the Actions tab of Search Everywhere all resolve to the same registry, so a feature registers its behaviour once:
Main fields: Run actions with executeAction(id, arg?). registerAction returns a disposer.

Keymaps and the dispatcher

Default shortcuts live in src/platform/keymaps/, never on the action. A keymap maps action ids to bindings:
src/platform/keymaps/intellij-macos.ts
Presets: intellij-macos (default on macOS), intellij-windows (default on Windows and Linux), vscode-macos, vscode-windows, eclipse-macos, eclipse-windows. When you add an action with a default shortcut, add it to both IntelliJ presets, and to the others where an equivalent exists. Binding syntax: modifiers Meta Ctrl Alt Shift joined with + to a key (Meta+Shift+O); a space separates chord strokes (Meta+K Meta+C); a doubled modifier is Shift Shift; mouse buttons are Button1…Button5. Keys are matched by physical position (KeyboardEvent.code). installKeyboardDispatcher listens to keydown in the capture phase so IDE shortcuts win over CodeMirror’s defaults, resolves the stroke against the active keymap plus the user’s overrides, and runs the first enabled action (when and keyWhen). A component that needs a keystroke for itself registers a key claim (registerKeyClaim), e.g. Vim mode for its shortcut conflicts.

Context keys and when clauses

Components publish facts as context keys (src/platform/context.ts); actions and keybindings are enabled by a when expression over them.
Grammar: ||, &&, !, parentheses, and key, key == value, key != value. An empty clause is always true. Clauses are compiled once and cached. Plugin menu items use the same clauses.

Contributions

src/workbench/contributions.ts holds the workbench’s extension points. Each register* function returns a disposer. Editors are extended through src/editor/api.ts: registerEditorContribution({ id, extensions }) adds CodeMirror extensions per document. editor/core owns the views; LSP, VCS gutters, breakpoints and inline debug values are all contributions and never import editor internals. Plugin UI contributions reach these same functions: the backend validates them, and src/features/plugins/projection.ts maps each one onto an existing register* call. See Plugin host.

Services

Services are cross-feature APIs with a single implementation, provided by whoever owns it: The non-maybe accessors throw if nothing provided the service, so call them at use time, not at module load.

Talking to the backend

Features call the backend only through commands from @/platform/ipc and listen with onIdeEvent; see IPC. Several features also keep an in-memory backend for the plain-browser mode (pnpm dev) that unit and E2E tests use, chosen by isTauri().