Skip to content

editor: integrate optional editor-view GPU renderer - #336479

Draft
Alexandru Dima (alexdima) wants to merge 26 commits into
mainfrom
alexd/editor-view
Draft

Alexandru Dima (alexdima) wants to merge 26 commits into
mainfrom
alexd/editor-view

Conversation

@alexdima

Copy link
Copy Markdown
Member

Summary

Integrate the experimental @vscode/editor-view Rust/WASM renderer as an optional desktop distro dependency, alongside the existing DOM and TypeScript GPU renderers.

  • Add the editorView rendering mode with incremental text/token/model synchronization and measured layout/hit testing.
  • Move supported text, selections, carets, current-line highlights, whitespace, indentation guides, line numbers, folding-control paint, and decorations to the GPU; retain DOM input and unsupported surfaces.
  • Detect the application-owned package at desktop startup, including ASAR builds. Hide the editorView choice when absent and validate saved editorView settings to off.
  • Restore DOM rendering and report the error if package import or initialization fails, without rewriting user settings.
  • Keep OSS/Monaco builds independent of the npm package through a host-owned type contract.

Distro follow-up

The distro must add @vscode/editor-view: 0.0.1 to its npm manifest and lockfile. No source dependency or product.json opt-in flag is needed. Web/server builds are not enabled by this change.

Setup and behavior are documented in src/vs/editor/browser/viewParts/editorViewGpu/README.md. The editor-view package itself is not changed by this PR.

Validation

  • Monaco compile check and staged-file hygiene pass.
  • 60 targeted browser tests plus six package-detection tests pass.
  • Full client type-check passed with the npm package physically absent.
  • Electron smoke checks verified installed WebGPU rendering, absent-package DOM rendering with no package requests, and missing-WASM error reporting with DOM recovery.
  • Detection verified against a real Electron ASAR archive.
  • DOM/GPU comparisons for tabs, Unicode and selection passed; largest measured pixel difference was 0.057%.

Review scope

This draft includes the full existing editor-view integration branch, not only the optional-package follow-up. The default remains off; the existing on/off behavior is retained.

Alexandru Dima (alexdima) and others added 25 commits July 4, 2026 20:35
Adds an opt-in integration of the @vscode/editor-view (Rust/WASM) GPU renderer,
gated by reusing editor.experimentalGpuAcceleration with a third value
'editorView' ('off'/'on' are unchanged, so nothing runs unless toggled).

- editorOptions.ts / monaco.d.ts: widen the enum to 'off' | 'on' | 'editorView'
  (default stays 'off') with an enum description.
- viewParts/editorViewGpu/editorViewGpu.ts: new ViewPart that lazily imports the
  package (the @vscode/diff loading pattern), mounts a pointer-events:none canvas
  over the editor, builds the renderer config from fontInfo + theme colors, and
  mirrors the visible lines, syncing on edit/scroll/theme/config.
- view.ts: construct EditorViewGpu only when the setting === 'editorView', append
  its canvas, and skip the DOM ViewLines.renderText work in that mode (the canvas
  is the sole text renderer) while still consuming the render flag.
- eslint.config.js: allow '@vscode/editor-view' as an editor-layer import (type
  import; loaded at runtime via resolveAmdNodeModulePath), like @vscode/diff.

Read-only proof of concept; input/selection/cursor/tokens are future work.

Co-authored-by: Copilot <[email protected]>
The GPU vs DOM rendering strategy is only decided while constructing the
View, so toggling editor.experimentalGpuAcceleration had no effect until
the editor was recreated. Rebuild the view when the option changes,
preserving cursor, selection, scroll and focus by cycling the model
through the existing detach/attach paths.

Co-authored-by: Copilot <[email protected]>
The editorViewGpu ViewPart didn't forward the editor's content-left or the
model's tab size to @vscode/editor-view, so the GPU renderer used its own
defaults (gutterWidth 60, tabSize 4) and its text/line-numbers were offset from
the DOM editor. Plumb `layoutInfo.contentLeft` -> gutterWidth and the model's
tabSize in `_buildConfig` so the two align. Surfaced by the DOM-vs-GPU compare
harness in @vscode/editor-view.

Co-authored-by: Copilot <[email protected]>
… into editorView GPU renderer

Mirror more of the DOM gutter into the @vscode/editor-view renderer so the
compare harness matches pixel-for-pixel: pass editorLineNumber.foreground and
activeForeground, track the primary cursor's view line via onCursorStateChanged,
and pass the exact line-numbers right edge (lineNumbersLeft + lineNumbersWidth)
so digits right-align like the DOM.

Co-authored-by: Copilot <[email protected]>
…crementally

The editorViewGpu ViewPart previously forwarded only line text (so the GPU view
rendered a single foreground color) and reshipped the whole document via
setLines on every change. This:

- Forwards per-line tokens: each getViewLineData(...).tokens presentation
  (offset, ColorId resolved through TokenizationRegistry's color map, and
  bold/italic/underline/strikethrough) becomes an EditorView TokenInput, so
  syntax-highlighted content renders.
- Makes model sync incremental via a new renderer-free planner
  (EditorViewModelSync): view events map to minimal deltas — per-line setTokens
  for background tokenization, replaceLines splices for inserts/deletes/edits.
  Structure is tracked eagerly as events arrive; content is read lazily at
  present time in final coordinates, mirroring the DOM RenderedLinesCollection.
  A full setLines reload is reserved for flush / line-mapping / theme-color-map
  changes. Adds onLineMappingChanged and onTokensColorsChanged handlers.
- Adds unit tests for the planner (13 cases incl. a randomized reference
  cross-check over interleaved insert/delete/change/token edits).

Co-authored-by: Copilot <[email protected]>
The GPU renderer's margin used a placeholder darkened background, showing a
gray band in light themes where the DOM margin stays white. Send the theme's
editorGutter.background (falling back to editorBackground, matching VS Code's
own default) as the new gutterBackground config field so the margin matches
the DOM view in every theme.

Co-authored-by: Copilot <[email protected]>
The static ViewGpuContext.atlas getter throws when the GPU device hasn't
resolved yet, so ViewGpuContext.atlas?.clear() could not no-op: the
exception is raised inside the getter, before optional chaining sees a
value. A color-theme change during startup (WorkbenchThemeService init) or
a device-pixel-ratio change before the device resolved therefore threw.

Guard the backing field instead (ViewGpuContext._atlas?.clear()) at the
DPR-change and theme-change sites. The render-path callers
(canRender/canRenderDetailed) still use the throwing getter, which is
correct since they only run after the device resolves.

Add a small test pinning the getter's throwing contract.

Co-authored-by: Copilot <[email protected]>
…ent-line

Introduce EDITOR_VIEW_GPU_CAPABILITIES in the editorViewGpu ViewPart, a static
descriptor of which editor surfaces the @vscode/editor-view (Rust/WASM) renderer
draws. view.ts consults it and, for each GPU-owned surface, no longer constructs,
mounts or ticks the corresponding DOM view part (the parallel-DOM cost this
renderer exists to remove). Load-bearing DOM stays regardless (EditContext, widget
hosts, scrollbar, and non-painting ViewLines for measurement). With only text
owned this is a behavioral no-op; flipping a flag is a one-line migration.

Flip selection + currentLine on and feed the renderer the data it needs: the full
selection set, the cursor lines and selection-empty state, focus (onFocusChanged),
and the focus-dependent selection / current-line colors, mirroring
selections.css and currentLineHighlight.ts (renderLineHighlight none/line/gutter/
all, fill vs 2px border, renderLineHighlightOnlyWhenFocus, and the
lineHighlightBorder gate via theme.value.defines).

Co-authored-by: Copilot <[email protected]>
Flip the `cursor` capability so the DOM ViewCursors view part is no
longer constructed/mounted/ticked in editorView mode, and drive the
Rust renderer's caret instead.

_cursorConfig() resolves the caret style (effectiveCursorStyle), width
(min(cursorWidth, typicalHalfwidthCharacterWidth), 0 -> 2px), height, and
per-caret foreground/background from the theme -- editorCursor.* for a
single caret, the editorMultiCursor.* palette for multiple, with the
background falling back to the foreground's opposite() exactly like the
cursor theming participant. Caret positions are tracked from
onCursorStateChanged (primary first) and mapped to the renderer's 0-based
coordinates.

Blink is host-owned: _updateBlinking() runs a 500ms WindowIntervalTimer
(mirroring ViewCursors.BLINK_INTERVAL) that toggles the caret and
repaints; the caret is shown solid on focus/cursor moves then blinks, and
hidden entirely when unfocused. smooth/phase/expand are approximated as
flat blink for now (default blink is exact). Re-armed from
onFocusChanged/onCursorStateChanged/onConfigurationChanged and disposed
with the ViewPart.

Co-authored-by: Copilot <[email protected]>
Mirror VS Code's roundedSelection option in the editorView GPU host\nconfig and disable it in high-contrast themes, matching the DOM overlay\nbehavior.\n\nCo-authored-by: Copilot <[email protected]>
…erer

In `editorView` mode, ViewLines no longer re-enters the DOM `renderText(...)` to
keep horizontal scrolling correct. A textless `renderTextInEditorView` keeps only
the scroll-extent (`scrollWidth`) and horizontal-reveal bookkeeping, sourcing real
line/column pixel widths from the Rust renderer through a new
`IEditorViewLineWidthProvider` (implemented by `EditorViewGpu` via
`EditorView.maxLineWidth` / `columnOffset`), instead of a monospace
`typicalHalfwidthCharacterWidth` estimate.

- Restore the DOM's whole-document-visible `_maxLineWidth = 0` reset so the
  horizontal extent can shrink (it is otherwise monotonic).
- Skip width/reveal bookkeeping until the renderer is ready rather than falling
  back to the JS estimate; the reveal compute distinguishes "not ready" (keep the
  request and retry) from "nothing to reveal" (consume it) so a reveal issued
  during startup isn't dropped.
- Unify the DOM and editorView horizontal-reveal paths onto one
  `_computeScrollLeftToRevealCore` skeleton (delegating only the per-line extent
  and RTL test) so they can't drift.
- Make `_syncModel` cheap to call several times per frame (the measurement getters
  do): add an O(1) `EditorViewModelSync.hasPendingChanges` guard so redundant
  calls skip `takePlan`'s allocations. Covered by a unit test.

Co-authored-by: Copilot <[email protected]>
…nderer

The Rust renderer numbered every soft-wrap continuation row because it only
knew the view-row index. Only the host owns the view-to-model mapping, so
`_buildLine` now sets a per-view-line `gutterLabel` via a new `_getGutterLabel`
that mirrors `LineNumbersOverlay._getLineRenderLineNumber`: it returns '' for a
continuation row (the view line's model column != 1), otherwise the model line
number per the `lineNumbers` option (off/on/relative/interval/custom). The
renderer draws the label verbatim (blank continuations, model number on the
first row), matching the DOM gutter.

Co-authored-by: Copilot <[email protected]>
Forward the editor whitespace mode, theme color, font-selected marker glyphs, line limit, and wrapped-line metadata to @vscode/editor-view. Gate the DOM whitespace overlay independently so other decorations remain active.

Co-authored-by: Copilot <[email protected]>
Forward visible-line guide counts, theme colors, indentation geometry, and wrapping limits to @vscode/editor-view. Capability-gate the DOM indentation overlay independently from other decorations.

Co-authored-by: Copilot <[email protected]>
Pass the verified monospace space advance to @vscode/editor-view so fresh huge-line widths avoid synchronous canvas measurement during scrolling.

Co-authored-by: Copilot <[email protected]>
Forward the view model's exact active guide range and level, including bracket-guide suppression, together with paired active theme colors.

Co-authored-by: Copilot <[email protected]>
Pass the editor's scroll/content width to the external renderer so current-line highlights remain correct on long horizontally scrolled lines.

Co-authored-by: Copilot <[email protected]>
Generalize the GPU line hit-test provider so the external editorView renderer can resolve click and drag coordinates without DOM text. Reuse renderer-backed widths and visible positions for related editor queries.

Co-authored-by: Copilot <[email protected]>
Resolve computed decoration paint once, synchronize retained document snapshots only on semantic changes, and preserve pointer-transparent DOM fallback for unsupported styles.

Co-authored-by: Copilot <[email protected]>
Resolve representable computed borders into ordered GPU paint while retaining whole-decoration DOM fallback for unsupported and composite CSS effects.

Co-authored-by: Copilot <[email protected]>
Classify folding decorations for the retained GPU model and render their gutter pixels through @vscode/editor-view. Keep transparent DOM controls mounted so existing hover, tooltip, and click behavior remains intact.

Co-authored-by: Copilot <[email protected]>
Detect the application-owned package at desktop startup and expose the editorView choice only when installed. Keep OSS builds independent of npm types and restore DOM rendering if initialization fails.

Document distro setup and cover availability, validation, and detection failures.

Co-authored-by: Copilot <[email protected]>
Copilot AI balanced review requested due to automatic review settings September 16, 2026 20:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The renderer currently loses several supported editor surfaces and misrenders large documents, view zones, auxiliary windows, and high-contrast selections.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 High severity · 10 Medium severity

Open (13)
What changed in this PR

Adds an optional desktop Rust/WASM GPU editor renderer with package detection, runtime fallback, synchronization, and tests.

Changes:

  • Adds editor-view rendering, layout, hit testing, decorations, and model synchronization.
  • Adds optional-package detection and dynamic setting availability.
  • Adds renderer-focused unit tests and documentation.
File Description
src/​vs/​platform/​product/​test/​node/​editorView.test.ts Tests package detection.
src/​vs/​platform/​product/​node/​editorView.ts Detects the optional renderer.
src/​vs/​monaco.d.ts Exposes the new mode.
src/​vs/​editor/​test/​browser/​viewParts/​editorViewGpu/​editorViewModelSync.test.ts Tests model synchronization.
src/​vs/​editor/​test/​browser/​viewParts/​editorViewGpu/​editorViewDecorations.test.ts Tests decoration conversion.
src/​vs/​editor/​test/​browser/​gpu/​viewGpuContext.test.ts Tests atlas access behavior.
src/​vs/​editor/​test/​browser/​config/​editorConfiguration.test.ts Tests option availability.
src/​vs/​editor/​contrib/​folding/​browser/​foldingDecorations.ts Relocates folding color registration.
src/​vs/​editor/​contrib/​folding/​browser/​folding.css Hides GPU-owned controls.
src/​vs/​editor/​common/​core/​editorColorRegistry.ts Registers folding-control color.
src/​vs/​editor/​common/​config/​editorOptions.ts Adds the editorView option.
src/​vs/​editor/​common/​config/​editorConfigurationSchema.ts Refreshes the dynamic schema.
src/​vs/​editor/​browser/​widget/​codeEditor/​codeEditorWidget.ts Recreates views when modes change.
src/​vs/​editor/​browser/​viewParts/​viewLines/​viewLines.ts Adds GPU measurement bookkeeping.
src/​vs/​editor/​browser/​viewParts/​editorViewGpu/​README.md Documents distro integration.
src/​vs/​editor/​browser/​viewParts/​editorViewGpu/​editorViewTypes.ts Defines the host contract.
src/​vs/​editor/​browser/​viewParts/​editorViewGpu/​editorViewModelSync.ts Plans incremental synchronization.
src/​vs/​editor/​browser/​viewParts/​editorViewGpu/​editorViewGpu.ts Implements the renderer integration.
src/​vs/​editor/​browser/​viewParts/​editorViewGpu/​editorViewDecorations.ts Resolves GPU-supported decorations.
src/​vs/​editor/​browser/​viewParts/​decorations/​decorations.ts Adds selective DOM fallback.
src/​vs/​editor/​browser/​view.ts Integrates GPU-owned view surfaces.
src/​vs/​editor/​browser/​gpu/​viewGpuContext.ts Safely clears an optional atlas.
src/​vs/​editor/​browser/​controller/​mouseTarget.ts Generalizes GPU hit testing.
src/​vs/​editor/​browser/​controller/​mouseHandler.ts Defines the hit-test contract.
src/​vs/​editor/​browser/​config/​editorConfiguration.ts Revalidates renderer availability.
src/​vs/​code/​electron-main/​main.ts Detects availability at startup.
src/​vs/​base/​common/​product.ts Adds detected product metadata.
eslint.config.js Allows the optional module reference.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +41 to +43
const document = _editorRoot.ownerDocument;
this._container = document.createElement('div');
this._container.className = 'view-overlays';
export class EditorViewGpu extends ViewPart implements IEditorViewLineWidthProvider, IViewLineHitTestProvider {

/** Guard against pathological documents while this is a proof of concept. */
private static readonly MAX_LINES = 20_000;
Comment on lines +1037 to +1038
public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return true;
}
services.set(ILogService, logService);

productService.hasEditorView = isEditorViewInstalled(Boolean(productService.commit), logService);
const contentOverlays: DynamicViewOverlay[] = [];
if (!gpu?.currentLine) { contentOverlays.push(new CurrentLineHighlightOverlay(this._context)); }
if (!gpu?.selection) { contentOverlays.push(new SelectionsOverlay(this._context)); }
if (!gpu?.indentGuides) { contentOverlays.push(new IndentGuidesOverlay(this._context)); }
Comment on lines +272 to +275
fontFamily: fontInfo.fontFamily,
fontSize: fontInfo.fontSize,
lineHeight: fontInfo.lineHeight,
fontLigatures,
Comment on lines +310 to +311
// 0-based view line of the primary cursor, drawn with the active color.
activeLine: this._activeLineNumber - 1,
}

return {
selectionBackground: this._packColor(selection, 0),
Comment on lines +528 to +531
const lineData = this._context.viewModel.getViewLineData(viewLineNumber);
return {
text: lineData.content,
tokens: this._buildTokens(lineData.tokens, colorMap, defaultForeground),
return '';
}
const modelLineNumber = modelPosition.lineNumber;
const lineNumbers = this._context.configuration.options.get(EditorOption.lineNumbers);
Preserve concrete GPU option types during isolated declaration generation and keep implementation classes out of the public API. Regenerate Monaco declarations and retain GPU hit testing through the editor distro tree shaker.

Use the Electron test runner's supported module import while retaining ASAR-aware filesystem access, and document the additional validation gates.

Co-authored-by: Copilot <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants