Skip to main content

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

ParameterTypeRequiredDefaultDescription
originalstringYesThe original (left/old) text content
modifiedstringYesThe modified (right/new) text content
titlestringNo"Diff"Title shown in the tab bar
languagestringNoFile extension for syntax highlighting (e.g. "dart", "js", "py")
readOnlybooleanNotrueWhether the modified side is editable
onSaveCommandstringNoCommand 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
}
FieldTypeDescription
args.contentstringThe 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",
});
});