Skip to content

Add support for custom diff editors (proposed API) #298924

@hediet

Description

@hediet

Plan for #138525

Problem

Today, custom editor providers (CustomEditorProvider, CustomTextEditorProvider) can define how files are rendered and edited in a single editor. When a diff is opened for a file type handled by a custom editor, VS Code shows two side-by-side custom editor webviews wrapped in a standard DiffEditorInput. This works but has limitations:

  • No custom diff rendering — the extension cannot provide a unified diff experience (e.g., overlaid image diffs, side-by-side binary comparisons with highlights, or structured document diffs)
  • Two separate webviews — wasteful for cases where a single view could render a better diff
  • No access to both documents simultaneously — each webview only sees one side

Proposal

Extend the existing custom editor provider interfaces with an optional resolveCustomDiffEditor method. When present, VS Code opens a single webview for diffs, passing both documents to the extension. The extension renders the diff however it sees fit.

This reuses the existing customEditors contribution point and CustomDocument model — no new contribution point needed.

Proposed API

// For CustomDocument-based providers
interface CustomReadonlyEditorProvider<T extends CustomDocument = CustomDocument> {
    // existing
    openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): Thenable<T> | T;
    resolveCustomEditor(document: T, webviewPanel: WebviewPanel, token: CancellationToken): Thenable<void> | void;

    // NEW — optional
    resolveCustomDiffEditor?(originalDocument: T, modifiedDocument: T, webviewPanel: WebviewPanel, token: CancellationToken): Thenable<void> | void;
}

// Editing support inherited from CustomEditorProvider<T> — save/revert/undo/redo apply to the modified document

// For TextDocument-based providers
interface CustomTextEditorProvider {
    // existing
    resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel, token: CancellationToken): Thenable<void> | void;

    // NEW — optional
    resolveCustomTextDiffEditor?(originalDocument: TextDocument, modifiedDocument: TextDocument, webviewPanel: WebviewPanel, token: CancellationToken): Thenable<void> | void;
}

How it works

  1. Extension registers a CustomEditorProvider with resolveCustomDiffEditor implemented
  2. User opens a diff for a file matching the editor's selector
  3. Framework calls openCustomDocument(uri) for both the original and modified URIs — same function, same document model, ref-counted as usual via CustomEditorModelManager
  4. Framework creates a single webview and calls resolveCustomDiffEditor(originalDoc, modifiedDoc, webviewPanel)
  5. Extension renders a unified diff view in the webview
  6. Save/revert/undo/redo on the modified document work through the existing CustomDocument infrastructure (onDidChangeCustomDocument, saveCustomDocument, etc.)
  7. Extensions that do not implement resolveCustomDiffEditor fall back to the current behavior (two side-by-side custom editor webviews)

Key design decisions

  • No new contribution point — the existing customEditors selector already matches the file type for diffs
  • Reuses openCustomDocument — both sides get documents from the same openCustomDocument call; if the document is already open in a regular editor, the ref-counted model is shared
  • Single webview — the diff editor gets one WebviewPanel; the extension controls the entire diff rendering surface
  • Editing on modified side — dirty state, save, revert, undo/redo all work through the existing CustomDocument model on the modified document; the original side is read-only by convention
  • Proposed API — behind customDiffEditorProvider enablement flag

Implementation Plan

Phase 1: Proposed API Surface

  1. Create src/vscode-dts/vscode.proposed.customDiffEditorProvider.d.ts with the interfaces above
  2. Regenerate extensionsApiProposals.ts (run compile-api-proposal-names gulp task)

Phase 2: Protocol Layer

  1. Extend ExtHostCustomEditorsShape in extHost.protocol.ts — add:
    $resolveCustomDiffEditor(
        originalResource: UriComponents,
        modifiedResource: UriComponents,
        newWebviewHandle: WebviewHandle,
        viewType: string,
        initData: { title: string; contentOptions: IWebviewContentOptions; options: IWebviewPanelOptions; active: boolean },
        position: EditorGroupColumn,
        cancellation: CancellationToken
    ): Promise<void>;
  2. Extend $registerCustomEditorProvider / $registerTextEditorProvider to pass supportsDiffEditor: boolean via capabilities
  3. Implement $resolveCustomDiffEditor in extHostCustomEditors.ts — create one webview panel, retrieve both documents, call the provider's resolveCustomDiffEditor
  4. Detect diff-capable providers during registration — check for resolveCustomDiffEditor method on provider, pass supportsDiffEditor flag to main thread

Phase 3: Custom Diff Editor Input & Pane

Following the NotebookDiffEditorInput pattern:

  1. Create CustomDiffEditorInput in src/vs/workbench/contrib/customEditor/browser/customDiffEditorInput.ts
    • Extends DiffEditorInput
    • Holds original: CustomEditorInput, modified: CustomEditorInput, plus a single IOverlayWebview
    • editorId returns this.modified.viewType (routes to the correct pane)
    • resolve() acquires models for both sides, resolves the webview
  2. Create CustomDiffEditor pane in src/vs/workbench/contrib/customEditor/browser/customDiffEditor.ts
    • Extends EditorPane, renders the single webview
    • setInput() calls input.resolve(), claims the webview, manages layout
  3. Register pane in customEditor.contribution.ts:
    • EditorPaneDescriptor.create(CustomDiffEditor, ...) bound to CustomDiffEditorInput
    • Register serializer for CustomDiffEditorInput

Phase 4: Main Thread Wiring

  1. Extend CustomEditorCapabilities in customEditor.ts with supportsDiffEditor?: boolean
  2. Modify createDiffEditorInput factory in customEditors.ts:
    • If supportsDiffEditor is true → create CustomDiffEditorInput (single webview)
    • If false → existing behavior (two CustomEditorInputs in standard DiffEditorInput)
  3. Extend registerEditorProvider in mainThreadCustomEditors.ts:
    • Register a webview resolver matching CustomDiffEditorInput
    • resolveWebview: create models for both sides, call $resolveCustomDiffEditor on ext host

Phase 5: API Gate

  1. In extHost.api.impl.ts, call checkProposedApiEnabled(extension, 'customDiffEditorProvider') when a diff-capable provider is detected

Files to change

File Action
src/vscode-dts/vscode.proposed.customDiffEditorProvider.d.ts NEW — proposed API types
src/vs/platform/extensions/common/extensionsApiProposals.ts Auto-regenerated
src/vs/workbench/api/common/extHost.protocol.ts Extend ExtHostCustomEditorsShape and MainThreadCustomEditorsShape
src/vs/workbench/api/common/extHostCustomEditors.ts Implement $resolveCustomDiffEditor, detect diff providers
src/vs/workbench/api/common/extHost.api.impl.ts Proposed API gate
src/vs/workbench/api/browser/mainThreadCustomEditors.ts Diff webview resolution
src/vs/workbench/contrib/customEditor/common/customEditor.ts Extend CustomEditorCapabilities
src/vs/workbench/contrib/customEditor/browser/customEditors.ts Modify diff factory
src/vs/workbench/contrib/customEditor/browser/customDiffEditorInput.ts NEW — diff input class
src/vs/workbench/contrib/customEditor/browser/customDiffEditor.ts NEW — diff editor pane
src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts Register pane + serializer
src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts Add diff serializer

Verification

  • Compilation: Zero TypeScript errors via VS Code - Build task
  • Layering: npm run valid-layers-check passes
  • Existing tests: src/vs/workbench/contrib/customEditor/test/ — no regressions
  • Manual test: Extension registering resolveCustomDiffEditor → single webview diff opens, save/revert works, omitting the method falls back to side-by-side

Metadata

Metadata

Assignees

Labels

api-proposalcustom-editorsCustom editor API (webview based editors)feature-requestRequest for new features or functionalityinsiders-releasedPatch has been released in VS Code Insidersverification-neededVerification of issue is requested

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions