editor: integrate optional editor-view GPU renderer - #336479
Draft
Alexandru Dima (alexdima) wants to merge 26 commits into
Draft
Alexandru Dima (alexdima) wants to merge 26 commits into
Alexandru Dima (alexdima) wants to merge 26 commits into
Conversation
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]>
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 started reviewing on behalf of
Alexandru Dima (alexdima)
September 16, 2026 20:30
View session
Contributor
There was a problem hiding this comment.
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
Open (13)
Create probe elements in the main DOM realm · New Fall back before truncating documents at 20,000 lines · New Account for view-zone vertical offsets · New Use the actual built-state for ASAR detection · New Do not suppress bracket-pair guides · New Retain line-number decorations · New Do not claim mixed foreground decorations · New Use the editor's window for device pixel ratio · New Forward all editor font metrics to the renderer · New Highlight the labeled row of a wrapped line · New Send the high-contrast selection foreground · New Preserve inline decorations when replacing DOM text · New Refresh cursor-dependent line-number labels · New
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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Integrate the experimental
@vscode/editor-viewRust/WASM renderer as an optional desktop distro dependency, alongside the existing DOM and TypeScript GPU renderers.Distro follow-up
The distro must add
@vscode/editor-view: 0.0.1to 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
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.