
# Anatomy of an extension

An extension is a `manifest` plus an `activate` function, built with `defineExtension` from
`codelet/extensions`. This page covers what goes in each half, when `activate` runs, and what
it's handed.

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

export const todo = defineExtension({
  manifest: {
    name: "todo",
    contributes: {
      commands: [{ command: "todo.add", title: "Add To Do" }],
    },
  },
  activate(context, vscode) {
    context.subscriptions.push(
      vscode.commands.registerCommand("todo.add", () => {
        /* … */
      }),
    );
  },
});
```

`defineExtension` does nothing at runtime beyond returning what you pass it. It exists so
`activate`'s three parameters are typed against your manifest — a `views` block naming
`type: "component"` gives you a `codelet.window.registerComponentView` that knows about it, for
example. Nothing here is required to run; skip it and the object still works, just untyped.

## The manifest

`manifest` is plain data — no function to call, nothing to run — and that's the point. It's what
a server reads to build the activity bar, the panel, the palette and every view's heading without
executing a line of your extension. `activate` is the other half, and it only ever runs in a
browser.

```ts
import { renderWorkbench } from "codelet/workbench/server";
import { Workbench } from "codelet/workbench";
import { EXTENSIONS } from "./extensions.ts";

// On the server: reads every manifest, runs nothing.
const html = renderWorkbench({ extensions: EXTENSIONS });

// In the browser: same list, and now activate() runs for each one.
const workbench = new Workbench({ parent, extensions: EXTENSIONS });
```

::warning
Pass the exact same `extensions` array, in the same order, to both calls. The server's markup and
the client's first render have to agree, or hydration rebuilds the shell instead of taking it
over.
::

A manifest's top-level fields are its identity:

| Field         | Type                  | Default     | Description                                             |
| ------------- | --------------------- | ----------- | ------------------------------------------------------- |
| `name`        | `string`              | —           | The extension's id.                                     |
| `displayName` | `string`              | `name`      | Shown in place of `name` in the Extensions view.        |
| `description` | `string`              | —           | One line under the name.                                |
| `icon`        | `string`              | —           | A URL, drawn beside the name in the Extensions view.    |
| `homepage`    | `string`              | —           | Where this is written up.                               |
| `workspace`   | `"showing" \| "home"` | `"showing"` | Which tree this extension reads and writes — see below. |

Most extensions never set `workspace`. `"showing"` follows the reader: if they open a second
workspace over their own, your extension follows them into it. `"home"` pins an extension to the
reader's own tree regardless of what's opened over it — for something that has to keep mirroring
the reader's files no matter what's showing, the way `codelet/extensions/remote` does.

Everything else lives under `contributes`, and every point in it is optional.

### `viewsContainers`

An icon in the activity bar, a tab in the panel, or a pane in the secondary side bar.

```ts
contributes: {
  viewsContainers: {
    activitybar: [{ id: "todo", title: "To Do", icon: "check" }],
  },
},
```

| Field   | Type       | Default | Description                                                          |
| ------- | ---------- | ------- | -------------------------------------------------------------------- |
| `id`    | `string`   | —       | Referenced by `views`, keyed to this id.                             |
| `title` | `string`   | —       | The container's heading.                                             |
| `icon`  | `IconName` | —       | One of codelet's own icon names.                                     |
| `order` | `number`   | —       | Where it sits among its peers. Lower is nearer the top or the start. |
| `end`   | `boolean`  | `false` | Activity bar only: the foot of the bar, beside the theme control.    |

`activitybar`, `panel` and `secondary` are the three places a container can go; declaring one in
one place shows it only there.

### `views`

Rows inside a container, keyed by the container's id.

```ts
views: {
  todo: [{ id: "todo.list", name: "To Do", filter: { placeholder: "Filter" } }],
},
```

`id`, `name` and `type` (`"tree"` by default, or `"webview"` / `"component"`) are the shape;
`filter`, `select`, `tagged` and `colored` are the chrome a view can ask for. All of it, and how
to fill a view with rows, a frame or a component, is [Views](/api/views).

### `viewsWelcome`

Markdown shown in place of a view's rows while it has none.

```ts
viewsWelcome: [{ view: "todo.list", contents: "No items yet.\n\n[Add one](command:todo.add)" }],
```

`[text](command:id)` runs a command on click. Covered fully, including `when` and `group`, on
[Views](/api/views).

### `commands`

What the command palette lists.

```ts
commands: [{ command: "todo.add", title: "Add To Do", icon: "check" }],
```

A declared command shows in the palette once its extension also calls
`vscode.commands.registerCommand` with the same id. The full story — including commands that take
arguments, which stay undeclared — is [Commands and menus](/api/commands).

### `menus`

Commands placed somewhere other than the palette: a button on the tab bar, an entry in a context
menu, a row over a view's own contents.

```ts
menus: {
  "editor/title": [{ command: "todo.add", when: "editorLangId == markdown", group: "navigation" }],
},
```

Every place codelet draws one, the `when` keys available there, and the argument a command gets
when it runs from one, is [Commands and menus](/api/commands).

### `customEditors`

A file shown as something other than text.

```ts
customEditors: [
  {
    viewType: "media.image",
    displayName: "Image Preview",
    selector: [{ filenamePattern: "**/*.png" }],
  },
],
```

| Field         | Type                            | Description                               |
| ------------- | ------------------------------- | ----------------------------------------- |
| `viewType`    | `string`                        | Passed to `registerCustomEditorProvider`. |
| `displayName` | `string`                        | Shown in "Reopen Editor With…".           |
| `selector`    | `{ filenamePattern: string }[]` | Globs matched against the path.           |

The first declaration whose selector matches a file wins. More on custom editors and webview
panels is on [Editors](/api/editors).

### `terminal.profiles`

A shell "New Terminal" can open.

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

Answered by calling `window.registerTerminalProfileProvider` for the same `id` in `activate`.
This only does anything if the workbench also carries a terminal extension
(`codelet/extensions/terminal`) to draw the tab in — see [Tasks, source control and terminals](/api/integrations).

### `configuration`

The settings this extension reads, and what they are by default.

```ts
contributes: {
  configuration: {
    title: "Markdown",
    properties: { "markdown.preview.theme": { type: "string", default: "auto" } },
  },
},
```

`properties` is keyed by the full dotted name. `default` is the only member codelet reads back —
see [Settings your extension declares](#settings-your-extension-declares) below.

### `languages`

A language codelet doesn't already know.

```ts
contributes: {
  languages: [{ id: "svelte", extensions: [".svelte"], aliases: ["Svelte"] }],
},
```

| Field        | Type        | Description                                                          |
| ------------ | ----------- | -------------------------------------------------------------------- |
| `id`         | `string`    | What `TextDocument.languageId` and a selector's `language` say.      |
| `extensions` | `string[]?` | File extensions, dot included. Only the last one in a name is read.  |
| `filenames`  | `string[]?` | Whole names matched before any extension — `Gemfile`, `Cargo.lock`.  |
| `aliases`    | `string[]?` | The first is the display label; the rest are for matching elsewhere. |

A manifest naming a language codelet already has adds its names and aliases to that one, rather
than declaring a second language with the same id.

### `directories`

Directories made in the tree before anything has filled them.

```ts
contributes: { directories: ["/workspace"] },
```

Applied where a workbench is built — both `new Workbench()` and `renderWorkbench()` — rather than
in `activate`, so a folder whose contents arrive later (over a socket, say) is a real folded row
from the first paint instead of appearing once something has connected. A path that already has
something in it is left alone.

## codelet's own additions

`filter`, `select`, `tagged` and `colored` on a view; `type: "component"`; `secondary` and `end`
on a container; and `directories` are codelet's own additions to VS Code's contribution schema.
VS Code ignores manifest keys it doesn't recognize, so a manifest using any of these stays a
valid VS Code extension — it just contributes nothing extra there.

## Activation

`activate(context, vscode, codelet)` runs once per extension, and only in a browser — never
during `renderWorkbench()` on a server, and never twice for the same extension unless it was
stopped and started again.

`context.subscriptions` is an array you push anything disposable onto: event listeners, tree
views, status bar items, registered commands, output channels. Everything pushed there is
disposed together the moment the extension is stopped — from the built-in Extensions view, or a
call to `codelet.extensions.setActive(id, false)`. Starting it again builds a fresh
`ExtensionContext` and runs `activate` from the top.

```ts
activate(context, vscode) {
  const item = vscode.window.createStatusBarItem();
  item.show();
  context.subscriptions.push(item);
},
deactivate() {
  // Runs immediately before subscriptions are disposed. For cleanup that isn't itself
  // disposable — closing a socket, say.
},
```

`deactivate()` is optional, and runs right before that teardown.

## The extension context

`context` carries state that belongs to the workbench rather than to one activation — stop an
extension and start it again, and it reads back what it wrote.

```ts
await context.globalState.update("lastOpened", path);
context.globalState.get<string>("lastOpened");

await context.workspaceState.update("expanded", ids); // scoped to the tree you're in
await context.secrets.store("token", value); // never written down in the clear
const token = await context.secrets.get("token");
```

`globalState` and `workspaceState` are `Memento`s — `get`, `update`, `keys()` — and JSON,
surviving a reload in `localStorage` unless the host keeps them elsewhere. `workspaceState` is
per tree: only the reader's own tree is written down, since a workspace another extension opened
is a different tree under the same name on the next visit.

`extensionMode` is always `ExtensionMode.Production`, and `extension` is the same `ExtensionInfo`
`vscode.extensions.getExtension` would answer for this extension, `isActive` live on it.

::warning
**Write a token to `secrets`, never to `globalState`.** What `secrets` guarantees depends on the
host: by default codelet keeps secrets in memory and forgets them when the tab closes, because a
page can't encrypt anything on its own — writing one to `globalState` would put it in the clear
under an API that says otherwise. A host that hands the workbench a sealed store makes secrets
survive a reload under the reader's own passkey; either way, `secrets` is where the guarantee
lives.
::

::warning
**Don't read a secret during `activate`.** On a host that seals them, the first question any
extension asks can prompt the reader for a passkey — while the page is still loading, for
something nobody has looked at yet. Read one when the reader reaches for the thing that needs it:
a command they ran, a tab they opened.
::

## Settings your extension declares

`contributes.configuration` declares a setting and its default; `workspace.getConfiguration`
reads it back, layered under whatever the reader has written over it.

```ts
manifest: {
  contributes: {
    configuration: {
      properties: { "markdown.preview.theme": { type: "string", default: "auto" } },
    },
  },
},
async activate(context, vscode) {
  const config = vscode.workspace.getConfiguration();
  const theme = config.get<string>("markdown.preview.theme");
  await config.update("markdown.preview.theme", "dark");
},
```

Defaults are read with the rest of the manifest's static contributions, not at activation, so a
key means what its manifest says whether or not the extension that declared it is currently
running. The full `WorkspaceConfiguration` API — sections, `inspect()`, change events — is
[Workspace](/api/workspace).
