
# Tasks, source control and terminals

Three subsystems share one shape. `vscode` is the producing half — the calls any extension uses
to say "here is a task", "here is what changed", "here is a shell you can open." `codelet` is the
consuming half: a small, separate namespace that reads back what was registered, used only by an
extension that draws a pane for it. Writing a task provider, a source control or a terminal
profile needs nothing from `codelet` at all; the built-in Tasks pane, Source Control pane and
terminal each use it to become the thing that shows what you registered.

## Tasks

```ts
vscode.tasks.registerTaskProvider("greet", {
  provideTasks: () => [
    new vscode.Task(
      { type: "greet" },
      vscode.TaskScope.Workspace,
      "Say hi",
      "greet",
      new vscode.CustomExecution(async () => {
        const shell = codelet.terminal.profiles().at(-1);
        const pty = (await shell?.provide())?.options.pty;
        if (!pty) throw new Error("Nothing here can run a shell.");
        return {
          onDidWrite: pty.onDidWrite,
          onDidClose: pty.onDidClose,
          open: (dimensions) => {
            pty.open(dimensions);
            pty.handleInput?.("echo hi\n");
          },
          close: () => pty.close(),
        };
      }),
    ),
  ],
  resolveTask: (task) => task,
});
```

A `Task` is a `TaskDefinition` (your own `type` plus whatever else identifies one of yours), a
`TaskScope` or `WorkspaceFolder` saying whose it is, a name, a `source` string shown beside it,
and an optional `execution`. `TaskGroup.Build`, `.Test`, `.Clean` and `.Rebuild` are there for a
task to say what it is for. `CustomExecution` is the only execution codelet answers — a page has
nothing to spawn a shell command or a program with, so the callback hands back a `Pseudoterminal`
instead of naming one. In practice that means running a task is typing a command line into a
shell another extension contributed: the example above reaches for whatever
`codelet.terminal.profiles()` last offered and writes the line into it once the tab opens.

`vscode.tasks.executeTask(task)` runs one and resolves with a `TaskExecution` — `.terminate()`
ends it by closing its terminal, the same as the reader closing the tab. `fetchTasks(filter?)`
asks every registered provider and returns what they offer; `taskExecutions` is what is
currently running, and `onDidStartTask` / `onDidEndTask` fire around each.

`codelet/extensions/npm` reads a workspace's `package.json` scripts as tasks this way, typing
`npm run <script>` (or whichever manager the lockfile implies) into a shell and reporting when
the script's own line finishes rather than when the tab closes. `codelet/extensions/tasks` is the
activity bar pane that lists whatever every provider offers and runs one on a click — it reads
`vscode.tasks` the same way any other extension would, with no `codelet` namespace of its own.

## Source control

```ts
const control = vscode.scm.createSourceControl("git", "Git", vscode.Uri.file("/"));
control.inputBox.placeholder = "Message (⌘Enter to commit)";
control.acceptInputCommand = { command: "git.commit", title: "Commit" };

const changes = control.createResourceGroup("changes", "Changes");
changes.resourceStates = [
  {
    resourceUri: vscode.Uri.file("/src/index.ts"),
    decorations: {
      tooltip: "Modified",
      iconPath: new vscode.ThemeIcon(
        "scm",
        new vscode.ThemeColor("gitDecoration.modifiedResourceForeground"),
      ),
    },
  },
];

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

`createSourceControl(id, label, rootUri?)` returns a `SourceControl`: the input box above the
rows, one or more `SourceControlResourceGroup`s each holding `SourceControlResourceState`s, a
`count` for the activity bar badge, and `statusBarCommands` for a branch name in the footer.
Writing any of it — `group.resourceStates = [...]`, `control.count = 3` — repaints whatever is
drawing it, with nothing further to call.

Nothing draws a source control by itself. `codelet.scm.sources()` is the read side — every
registered `SourceControl` with its groups attached — and `onDidChangeSources` is when to ask
again; `codelet.scm.actions(menu, control, group?, state?)` resolves the buttons contributed to
one of the four `scm/*` menus (`scm/title`, `scm/inputBox`, `scm/resourceGroup/context`,
`scm/resourceState/context`) for whatever is being drawn. `codelet/extensions/scm`
([Scm](/extensions/scm)) is the pane that calls both — mount it for a registered
source control to show anywhere at all.

`quickDiffProvider` is here: answer it with where a file's unedited text comes from and the
editor paints a quick diff gutter beside the lines that changed, the same three bars VS Code
draws. The one member missing is `scm.inputBox`, VS Code's own deprecated global for the box of
whichever source control was made last — `SourceControl.inputBox` is the box.

## Terminals

```ts
vscode.window.registerTerminalProfileProvider("my-shell", {
  provideTerminalProfile: () => new vscode.TerminalProfile({ name: "My Shell", pty: makeShell() }),
});
```

paired with a manifest declaring it:

```ts
contributes: { terminal: { profiles: [{ id: "my-shell", title: "My Shell" }] } },
```

is what a normal extension needs — a `Pseudoterminal` you implement (`onDidWrite`, `open`,
`close`, `handleInput`), answered for the id your manifest named. `window.createTerminal(options)`
makes a `Terminal` out of a `Pseudoterminal` you already hold, for a shell that is yours from the
start rather than opened by the reader: `sendText`, `show`, `hide`, `dispose`, and
`onDidCloseTerminal` for when it ends, whichever end that was.

The consuming half is for the extension that draws terminal _tabs_ — one ships,
`codelet/extensions/terminal` ([Terminal](/extensions/terminal)) — and nothing else needs
it. `codelet.terminal.profiles()` and `.terminals()` are what a manifest declared and what
`createTerminal` made; `.tabs()` is a different list again, what the terminal extension is
actually showing, published through `.present(tabs)` and read back by anything else that cares
what tab is open. `.expect()` / `.expecting()` let another extension hold the panel open for tabs
that are still arriving rather than let the terminal open one of its own first —
`codelet/extensions/live` uses this for a guest whose shared shells are still crossing the wire.
`.focus(id)` and `onDidShowTerminal` / `onDidHideTerminal` round out which tab is active and
whether the panel itself is open.

### Worked example

A `Pseudoterminal` that echoes what is typed, registered as a profile:

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

export const echo = defineExtension({
  manifest: {
    name: "echo-terminal",
    contributes: { terminal: { profiles: [{ id: "echo", title: "Echo" }] } },
  },

  activate(context, vscode) {
    context.subscriptions.push(
      vscode.window.registerTerminalProfileProvider("echo", {
        provideTerminalProfile() {
          const written = new vscode.EventEmitter<string>();
          let line = "";
          return new vscode.TerminalProfile({
            name: "Echo",
            pty: {
              onDidWrite: written.event,
              open: () => written.fire("Type something.\r\n$ "),
              close: () => written.dispose(),
              handleInput: (data) => {
                if (data === "\r") {
                  written.fire(`\r\n${line}\r\n$ `);
                  line = "";
                } else if (data === "\u007f") {
                  line = line.slice(0, -1);
                  written.fire("\b \b");
                } else {
                  line += data;
                  written.fire(data);
                }
              },
            },
          });
        },
      }),
    );
  },
});
```

Mount it alongside `codelet/extensions/terminal` and "Echo" shows up beside every other shell in
the New Terminal picker. Below is `codelet/extensions/terminal` itself running just-bash, a real
`Pseudoterminal` over the workbench's own files — the same interface the example above
implements:

::codelet-playground{mode="workbench" extensions="terminal" view="terminal" active="src/index.ts" height="420"}

```ts [src/index.ts]
export const greet = (name: string) => `hi ${name}`;
```

::
