
# Commands and menus

A command is a named function. Registering one makes it callable by id — from a keybinding,
another extension, or a menu item your manifest declares. This page covers registering and
running commands, where codelet draws menus, and the `when` clauses that scope them.

## Registering and running

```ts
activate(context, vscode) {
  context.subscriptions.push(
    vscode.commands.registerCommand("todo.add", (title: string) => {
      // …
    }),
  );
},
```

`vscode.commands.registerCommand(id, callback, thisArg?)` returns a `Disposable` — push it onto
`context.subscriptions` so stopping the extension unregisters it. Run a command, yours or
another extension's, with `vscode.commands.executeCommand(id, ...args)`:

```ts
await vscode.commands.executeCommand("todo.add", "Buy milk");
```

`vscode.commands.getCommands(filterInternal?)` lists every registered id.
`registerTextEditorCommand` is the same registration for a command about the file showing: it
does nothing without an active editor, and its edits land through `TextEditor.edit` as one
transaction.

## What puts a command in the palette

Two things, together: a `contributes.commands` declaration, and a registered handler with the
same id.

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

`category` prefixes the title in the palette (`To Do: Add To Do`); `icon` is used wherever the
command is drawn as a button rather than a row. A command declared but never registered — or
registered but never declared — doesn't show up at all: the palette needs both halves.

A command that takes arguments has nothing to receive them from the palette, so it should stay
**undeclared** and be named with a leading `_` — `getCommands(true)` filters those out, following
the same convention VS Code uses for its own internal commands:

```ts
vscode.commands.registerCommand("_todo.open", (id: string) => {
  /* … */
});
```

`contributes.menus.commandPalette` is the way to keep a _declared_ command out of the palette —
for one that's only ever meant to run from a button, a key, or another extension:

```ts
menus: { commandPalette: [{ command: "todo.done", when: "false" }] },
```

A command no item names there is offered, as in VS Code. A command that's named is offered
wherever any of its items' `when` holds — so two items are two ways of being reachable. Read when
the palette opens, against the same keys every other menu is.

## Menus

Beyond the palette, codelet draws a command in eleven places, keyed by `contributes.menus`. Each
runs a command with a different argument, because each is answering a different question about
where the reader clicked.

```ts
menus: {
  "editor/title": [
    { command: "markdown.showPreview", when: "editorLangId == markdown", group: "navigation" },
  ],
  "explorer/context": [
    { command: "markdown.showPreview", when: "resourceLangId == markdown" },
  ],
},
```

| Place                       | Draws                                                        | `when` keys                                                                                                                                     | Command receives                                             |
| --------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `editor/title`              | Button(s) at the right of the tab bar, over the file showing | `editorLangId`, `resourceLangId`, `resourceFilename`, `resourceExtname`, `resourceScheme`, or `activeWebviewPanelId` for an extension's own tab | Nothing                                                      |
| `view/title`                | Button(s) over a contributed view's own rows                 | `view`                                                                                                                                          | Nothing                                                      |
| `explorer/context`          | Row menu of the file tree, under Rename/Delete               | `resourceLangId`, `resourceFilename`, `resourceExtname`, `resourceScheme`, `explorerResourceIsFolder`                                           | The row's `Uri` (the root's, for a click below the last row) |
| `view/item/context`         | Row menu of a contributed tree                               | `view`, `viewItem` (the row's `contextValue`), plus the four `resource*` keys if the row set `resourceUri`                                      | The element the `TreeDataProvider` returned                  |
| `editor/title/context`      | Tab's own context menu, under its three Closes               | `resourceLangId`, `resourceFilename`, `resourceExtname`, `resourceScheme`                                                                       | The tab's `Uri`                                              |
| `editor/context`            | Right-click in the document itself                           | `editorLangId`, `resourceLangId`, `resourceFilename`, `resourceExtname`, `resourceScheme`                                                       | The showing file's `Uri`                                     |
| `scm/title`                 | Over a source control's own heading                          | `scmProvider`                                                                                                                                   | The `SourceControl`                                          |
| `scm/inputBox`              | Beside a source control's commit box                         | `scmProvider`                                                                                                                                   | The `SourceControl`                                          |
| `scm/resourceGroup/context` | On a resource group's heading                                | `scmProvider`, `scmResourceGroup`, `scmResourceGroupState`                                                                                      | The `SourceControlResourceGroup`                             |
| `scm/resourceState/context` | On a row in a resource group                                 | `scmProvider`, `scmResourceGroup`, `scmResourceGroupState`, `scmResourceState`                                                                  | The `SourceControlResourceState`                             |
| `globalActivity`            | Row of the gear at the foot of the activity bar              | The window's own keys alone — no resource                                                                                                       | Nothing                                                      |
| `commandPalette`            | Nothing — only says whether a command is offered there       | Same as the window state at the moment the palette opens                                                                                        | —                                                            |

`editorLangId` is only set where the file in question is also the one showing — `editor/title`
and `editor/context` get it, the three context menus over a row or a tab that need not be showing
don't. The four `scm/*` places are drawn by an extension's own pane (`codelet/extensions/scm`)
rather than by the workbench's chrome directly; read [`codelet.scm.actions`](/api/integrations)
for how a source control provider reaches them.

`globalActivity` is the odd one out: it is about the workbench rather than about anything in it,
so there is no resource to write a `when` against and nothing handed to the command. It is where
VSCode keeps Settings and where codelet's settings extension puts its own row, above the theme
control the workbench draws there itself — the place for a gesture that is the reader's rather
than the project's.

Write the handler to take the argument and fall back to the file showing, so one command answers
a button, a row, and a tab that isn't even open:

```ts
vscode.commands.registerCommand("markdown.showPreview", async (resource?: Uri) => {
  const document = resource
    ? await vscode.workspace.openTextDocument(resource)
    : vscode.window.activeTextEditor?.document;
  // …
});
```

### `group` and order

In the two title bars — `editor/title` and `view/title` — `"navigation"` is the row of buttons
itself; any other group name is the `…` overflow menu at the end of them. A context menu has no
such split: every group renders, and the group name is only what sorts them.
`group@order` (`"navigation@1"`) sorts within one group either way.

## `when` clauses

An expression over context keys: a bare key, `!key`, `key == "value"`, `key != "value"`, joined
with `&&` and `||`. Anything the grammar doesn't reach — parentheses, `in` — reads as false, as
does a key that was never set.

Beyond the menu-specific keys in the table above, two describe the window itself:

| Key                    | Value                                                                           |
| ---------------------- | ------------------------------------------------------------------------------- |
| `workbenchState`       | `"empty"` until the tree showing holds a file anywhere, `"folder"` once it does |
| `workspaceFolderCount` | `"0"` or `"1"`, matching `workbenchState`                                       |

Everything else comes from `setContext`, which any extension can call to name a key of its own
and drive its own views or menu items with it:

```ts
await vscode.commands.executeCommand("setContext", "todo.hasItems", items.length > 0);
```

```ts
views: { todo: [{ id: "todo.list", name: "To Do", when: "todo.hasItems" }] },
```

A container is its views: every view whose `when` fails takes its icon or tab strip entry with
it, so `setContext` is also how a whole activity bar icon appears and disappears.

## Worked example

A button on the tab bar, scoped to one language, that opens a panel — and a button back on that
panel's own tab bar to return to the file. This is exactly what `codelet/extensions/markdown`
does; see [Markdown](/extensions/markdown) for the shipped version.

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

const COMMAND = "notes.preview";
const BACK = "notes.showSource";
const VIEW_TYPE = "notes.preview";

export const notesPreview = defineExtension({
  manifest: {
    name: "notes-preview",
    contributes: {
      commands: [
        { command: COMMAND, title: "Preview Notes", icon: "preview" },
        { command: BACK, title: "Show Source", icon: "file" },
      ],
      menus: {
        "editor/title": [
          { command: COMMAND, when: "editorLangId == notes", group: "navigation" },
          { command: BACK, when: `activeWebviewPanelId == ${VIEW_TYPE}`, group: "navigation" },
        ],
      },
    },
  },
  activate(context, vscode) {
    let panel: ReturnType<typeof vscode.window.createWebviewPanel> | undefined;
    let shownUri: Uri | undefined;

    context.subscriptions.push(
      vscode.commands.registerCommand(COMMAND, async () => {
        const document = vscode.window.activeTextEditor?.document;
        if (!document) return;
        shownUri = document.uri;
        panel ??= vscode.window.createWebviewPanel(
          VIEW_TYPE,
          "Notes Preview",
          vscode.ViewColumn.Beside,
        );
        panel.webview.html = `<pre>${document.getText()}</pre>`;
        panel.reveal();
      }),
      vscode.commands.registerCommand(BACK, async () => {
        if (shownUri) await vscode.window.showTextDocument(shownUri);
      }),
    );
  },
});
```

Two menu items, one command each, and no shared state beyond which file was last previewed —
`activeWebviewPanelId` is what lets the second button find its way home without either command
being handed an argument.

::codelet-playground{mode="workbench" active="README.md" extensions="markdown-preview" height="420"}

```md [README.md]
# Notes

Click the preview icon at the right of the tab bar, then the source icon on the tab it opens.
```

::
