Diff Editor API
The diff editor API lets extensions open a side-by-side or inline comparison of two texts. This is useful for git diffs, file comparisons, code review, and any scenario where you need to show changes between two versions.
Opening a Diff Editor
import { editor } from "@srcnexus/ext-sdk";
const result = await editor.openDiff({
original: "old content here...",
modified: "new content here...",
title: "main.dart (HEAD vs working)",
language: "dart",
readOnly: false,
onSaveCommand: "myExtension.applyChanges",
});
console.log(result.tabKey); // "diff:1234567890"
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
original | string | Yes | — | The original (left/old) text content |
modified | string | Yes | — | The modified (right/new) text content |
title | string | No | "Diff" | Title shown in the tab bar |
language | string | No | — | File extension for syntax highlighting (e.g. "dart", "js", "py") |
readOnly | boolean | No | true | Whether the modified side is editable |
onSaveCommand | string | No | — | Command ID executed with modified content when user saves |
Return Value
{
tabKey: string;
}
The tabKey uniquely identifies the opened diff tab (e.g. "diff:1234567890").
View Modes
The diff editor supports two view modes:
- Inline (unified) — Default. Shows changes in a single column with additions and deletions interleaved. Best for mobile portrait mode.
- Side-by-side — Shows original on the left and modified on the right. When activated, the app switches to landscape orientation for adequate screen space.
Users toggle between modes using a toolbar button in the diff tab.
Editable Diffs
When readOnly: false, the user can edit the modified (right) side of the diff. When combined with onSaveCommand, the specified command is executed when the user taps save.
onSaveCommand Payload
The command receives a single args object with the following shape:
{
content: string; // The full modified-side text after the user's edits
}
| Field | Type | Description |
|---|---|---|
args.content | string | The complete text from the modified (right) side of the diff editor, including any edits the user made |
Example
import { commands, workspace, window as win } from "@srcnexus/ext-sdk";
const fileUri = "...";
commands.registerCommand("myExtension.applyChanges", async (args) => {
const modifiedContent = args.content; // full modified text as a string
console.log("Content length:", modifiedContent.length);
// Write the modified content back to the file
await workspace.fs.write(fileUri, modifiedContent);
await win.showToast("Changes applied!");
});
CodeMirror Extensions
CodeMirror extensions registered via extensions.registerCodeMirrorExtension() work in the diff editor too. No special handling is needed — the same window.CM module sharing and window.editor.extensions API apply to both the normal and diff editors.
Detecting Diff Mode from a CodeMirror Extension
A CodeMirror extension (inline JS or file-based) can check whether it's running inside the diff editor or the normal editor by reading the window.__DIFF_MODE__ flag:
// Inside a CodeMirror extension's JS code
const isDiffEditor = window.__DIFF_MODE__ === true;
if (isDiffEditor) {
// Diff editor — e.g. skip features that don't make sense in diff view
return [];
}
// Normal editor — return your extensions as usual
return [myExtension()];
The flag is an immutable boolean set by App before the page loads. It is true in the diff editor and undefined in the normal editor.
Use cases
- Skip extensions that don't apply to diffs (e.g. auto-complete, linting)
- Add diff-specific decorations (e.g. highlight conflict markers)
- Adjust behavior — an extension might want read-only mode in diffs but editable in normal editor
Examples
Git Diff
import { commands, workspace, editor } from "@srcnexus/ext-sdk";
commands.registerCommand("git.showFileDiff", async () => {
const file = await workspace.getActiveFile();
if (!file?.uri) return;
const currentContent = await workspace.fs.read(file.uri);
const oldContent = await getGitContent(file.uri, "HEAD");
await editor.openDiff({
original: oldContent,
modified: currentContent,
title: `${file.name} (HEAD ↔ working)`,
language: file.name.split(".").pop(),
readOnly: false,
onSaveCommand: "git.stageFile",
});
});
Compare Two Files
import { commands, workspace, editor, window as win } from "@srcnexus/ext-sdk";
commands.registerCommand("myExtension.compareFiles", async () => {
const file1 = await workspace.getActiveFile();
if (!file1?.uri) return;
const files = await workspace.fs.list(file1.uri.replace(/\/[^/]+$/, ""));
const file2 = await win.showPicker({
title: "Select file to compare with",
items: files
.filter((f) => !f.isDirectory)
.map((f) => ({ label: f.name, detail: f.uri })),
});
if (!file2) return;
const content1 = await workspace.fs.read(file1.uri);
const content2 = await workspace.fs.read(file2.detail);
await editor.openDiff({
original: content1,
modified: content2,
title: `${file1.name} ↔ ${file2.label}`,
});
});
AI Code Review
import { commands, workspace, editor } from "@srcnexus/ext-sdk";
commands.registerCommand("ai.reviewCode", async () => {
const file = await workspace.getActiveFile();
if (!file?.uri) return;
const original = await workspace.fs.read(file.uri);
const improved = await getAIImprovedCode(original); // your AI API call
await editor.openDiff({
original: original,
modified: improved,
title: `${file.name} — AI Review`,
language: file.name.split(".").pop(),
readOnly: false,
onSaveCommand: "ai.applyReview",
});
});