
# Source control

`scm` adds a Source Control pane to the activity bar: a group of changed files per provider, a
commit message box above them, and a click on a row that opens what changed.

```ts
import { Workbench, FileSystem } from "codelet/workbench";
import { scm } from "codelet/extensions/scm";

const workbench = new Workbench({
  parent: document.getElementById("app")!,
  fs: new FileSystem({ "/src/index.ts": "export const hi = 1;\n" }),
  extensions: [scm()],
});
```

::warning
**This is the pane, and only the pane.** It brings no git and no provider — what fills it is a
`SourceControl` some other extension registers against `vscode.scm`, the workbench's own
namespace. A workbench that mounts `scm` and no provider gets no icon in the activity bar at
all: the container is only there while something is tracking, so nothing shows until something
does. Register a provider and the icon, the pane and the badge all arrive together.
::

One provider ships: [`codelet/extensions/github`](/extensions/github#changes-since-you-opened-it)
keeps the tarball it unpacked as a base revision, so mounting the two together is a real,
read-only source control over any repository on GitHub — no git anywhere.

## Registering a provider

Write against [`vscode.scm`](/api/integrations#source-control) — VS Code's own namespace and
objects, member for member, so a provider written for real VS Code compiles here unmodified.

```ts
import { defineExtension } from "codelet/extensions";

export const git = defineExtension({
  manifest: { name: "git", displayName: "Git" },
  activate(context, vscode) {
    const control = vscode.scm.createSourceControl("git", "Git", vscode.Uri.file("/"));
    control.acceptInputCommand = { command: "git.commit", title: "Commit" };
    control.inputBox.placeholder = "Message (⌘Enter to commit)";
    control.statusBarCommands = [{ command: "git.checkout", title: "$(scm) main" }];

    const changes = control.createResourceGroup("working", "Changes");
    changes.resourceStates = [
      {
        resourceUri: vscode.Uri.file("/src/index.ts"),
        command: {
          command: "vscode.diff",
          title: "Open Changes",
          arguments: [
            vscode.Uri.parse("git:/HEAD/src/index.ts"),
            vscode.Uri.file("/src/index.ts"),
            "index.ts (Working Tree)",
          ],
        },
        decorations: {
          tooltip: "Modified",
          iconPath: new vscode.ThemeIcon(
            "scm",
            new vscode.ThemeColor("gitDecoration.modifiedResourceForeground"),
          ),
        },
      },
    ];

    context.subscriptions.push(control);
  },
});
```

Writing a member is what repaints the pane: assign `group.resourceStates` and the rows redraw,
set `control.count` and the badge on the activity bar icon changes. There's no second call to
make — the same as VS Code's own workbench.

## What the pane draws

| Member                          | Where it lands                                                              |
| ------------------------------- | --------------------------------------------------------------------------- |
| `label`                         | the heading over that provider's groups                                     |
| `inputBox`                      | the message box, `Mod-Enter` accepts                                        |
| `commitTemplate`                | written into the box when it changes and the box is empty                   |
| `acceptInputCommand`            | the button under the box, drawn from its `title`                            |
| `statusBarCommands`             | real status bar items, each a button running its command                    |
| `count`                         | the badge on the activity bar icon — every group's rows added up if unset   |
| `group.label` / `hideWhenEmpty` | a foldable section, and whether an empty one draws at all                   |
| `state.resourceUri`             | a file icon, its name, and its folder dimmed after it                       |
| `state.command`                 | what a click on the row runs                                                |
| `state.decorations`             | `tooltip`, `strikeThrough`, `faded`, and a `ThemeIcon` whose colour is used |

`decorations.light` / `decorations.dark` override the base for the theme showing. Colour lands
on one letter at the end of the row and nowhere else — names stay plain foreground text, folders
are dimmed, and a whole row painted in colour would leave nothing standing out. That letter is
your `FileDecorationProvider`'s badge, the same one the explorer draws beside that file:

```ts
vscode.window.registerFileDecorationProvider({
  onDidChangeFileDecorations: changed.event,
  provideFileDecoration: (uri) =>
    new vscode.FileDecoration(
      "M",
      "Modified",
      new vscode.ThemeColor("gitDecoration.modifiedResourceForeground"),
    ),
});
```

Register no decoration provider and a row falls back to the `ThemeIcon` on
`state.decorations` instead.

### Buttons

Four places, read from your manifest: `scm/title` over the provider's heading,
`scm/inputBox` beside the message box, `scm/resourceGroup/context` on a group, and
`scm/resourceState/context` on a row.

```ts
contributes: {
  commands: [{ command: "git.stage", title: "Stage Changes", icon: "add" }],
  menus: {
    "scm/resourceState/context": [
      { command: "git.stage", group: "inline", when: "scmProvider == git && scmResourceGroup == working" },
    ],
    commandPalette: [{ command: "git.stage", when: "false" }],
  },
}
```

`when` reads VS Code's own keys — `scmProvider` is the source control's id, `scmResourceGroup` a
group's id, `scmResourceGroupState` and `scmResourceState` whatever `contextValue` you set on the
group or the row. A command's `icon` is the button; without one, its `title` is.

## The diff view and the quick-diff gutter

These belong to the workbench, not to this extension — they work as soon as anything registers a
source control, with or without `scm` mounted.

**`vscode.diff`** opens a tab with the two documents side by side, highlighted for the file's own
language, read-only, with unchanged runs collapsed. Either side may be a real file or a document
one of your own providers serves — which is where a `HEAD` version comes from:

```ts
await vscode.commands.executeCommand(
  "vscode.diff",
  vscode.Uri.parse("git:/HEAD/src/index.ts"), // left
  vscode.Uri.file("/src/index.ts"), // right
  "index.ts (Working Tree)", // title, optional
);
```

Without a title the tab's bar shows the two addresses instead. A document a provider serves is
read once while its tab is open — close it and run the command again for a fresh comparison.

**The quick-diff gutter** paints a bar beside every changed line of the file you're editing,
sourced from `SourceControl.quickDiffProvider`:

```ts
control.quickDiffProvider = {
  provideOriginalResource: (uri) => vscode.Uri.parse(`git:/HEAD${uri.path}`),
};
```

The first registered source control that answers a `Uri` for the file showing is the one the
gutter is drawn against. No provider, or a provider that answers nothing for that file, means no
gutter — there's no bar guessing at a change nothing said was one.

## What's not here

- **`scm.inputBox`**, VS Code's deprecated global for "whichever source control was made last".
  `SourceControl.inputBox` is the real one, and the only one this reads.
- **git itself.** This is the pane, `vscode.diff` and the gutter — what knows about commits and
  history is a provider's to bring. The one that ships reads a tarball, which is a base revision
  with no history under it: read-only, and clear about it.

:read-more{to="/api/integrations"}
:read-more{to="/guide/editing"}
