
# Prompts, notifications and UI

Everything an extension can put in front of the reader that is not a view or an editor: a
question, a message, a line in the status bar, a progress spinner, an output channel. All of it
hangs off `vscode.window` and `vscode.env`, handed to `activate` the same way the rest of the
namespace is.

## Asking a question

`showQuickPick` puts a list of choices to the reader and resolves with the one they took:

```ts
const choice = await vscode.window.showQuickPick(
  [
    { label: "Small", description: "2 spaces" },
    { label: "Large", description: "4 spaces" },
  ],
  { placeHolder: "Indent size" },
);
```

Escape or a click outside resolves `undefined`. Pass `canPickMany: true` for a tick per row and
the whole selection as the answer:

```ts
const chosen = await vscode.window.showQuickPick(items, {
  canPickMany: true,
  placeHolder: "Which files to include",
});
```

`showInputBox` asks for a line of text. `validateInput` runs on every keystroke; returning a
string keeps Enter from doing anything until the field says something else:

```ts
const name = await vscode.window.showInputBox({
  prompt: "Branch name",
  placeHolder: "feature/…",
  validateInput: (value) => (/^[\w./-]+$/.test(value) ? undefined : "No spaces or punctuation."),
});
```

Nothing typed and Enter pressed answers `""`; escape answers `undefined` — worth telling apart
if an empty value means something.

`showWorkspaceFolderPick` asks about the one folder there is:

```ts
const folder = await vscode.window.showWorkspaceFolderPick({ placeHolder: "Confirm the folder" });
```

## Holding a prompt open

`showQuickPick` and `showInputBox` are one call each: they show, wait for an answer, and hide.
`createQuickPick()` and `createInputBox()` give you the same field without any of that — you own
its whole lifecycle and write to it as things change:

```ts
const picker = vscode.window.createQuickPick<vscode.QuickPickItem>();
picker.placeholder = "Search files…";
picker.busy = true;

picker.onDidChangeValue(async (query) => {
  picker.busy = true;
  picker.items = await search(query); // fills the list as results come back
  picker.busy = false;
});

picker.onDidAccept(() => {
  const [item] = picker.selectedItems;
  if (item) open(item);
  picker.hide();
});

picker.show();
```

Reach for these over the one-shot calls whenever the list depends on what has been typed, whenever
you need a spinner while something loads, or whenever you want buttons on the field itself
(`buttons`, `onDidTriggerButton`) or on a row (`QuickPickItem.buttons`,
`onDidTriggerItemButton`). `createInputBox()` is the same idea with no list under it — `value`,
`password`, `prompt`, `validationMessage` written directly instead of read through options.
`onDidHide` fires however the field closes, and `dispose()` when you are done with it for good.

## Messages

Three calls, one per severity, each returning the button the reader clicked:

```ts
const answer = await vscode.window.showWarningMessage(
  "Discard 3 unsaved files?",
  { modal: true, detail: "This cannot be undone." },
  "Discard",
);
if (answer === "Discard") discard();
```

Without `items`, the call still resolves — `undefined` if the reader dismisses it, whatever
string they clicked otherwise. Without `{ modal: true }` a message is a toast in the corner the
reader can read past; with it, a dialog over the whole shell they have to answer before doing
anything else. Reserve modal for something that cannot be undone — `detail` is its second line
and only shows in a modal. Messages queue one at a time, oldest first, and if the extension that
raised one is stopped before it is answered, the promise resolves `undefined` rather than hanging.

## The status bar

```ts
const item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
item.text = "$(check) All saved";
item.tooltip = "Every file is written to disk";
item.command = "myExtension.showDetails";
item.show();
context.subscriptions.push(item);
```

Higher `priority` sits further left within its group (`Left` or `Right`). `text` reads `$(name)`
as one of codelet's icons, so `"$(sync~spin) Syncing…"` is a spinning icon before the words.
`tooltip` and `color` (a CSS string or a `ThemeColor`) are both honoured; `backgroundColor` takes
only `statusBarItem.errorBackground` or `statusBarItem.warningBackground`. `command` runs on
click — a string id, or a `Command` object if you need to pass arguments. `show()`, `hide()` and
`dispose()` are yours to call; nothing shows an item you have not called `show()` on.

For a message that should say itself once and go, `setStatusBarMessage(text, hideAfterTimeout?)`
is a status bar item and a timer in one call, returning a `Disposable` that takes it down early.

## Progress

```ts
await vscode.window.withProgress(
  { location: vscode.ProgressLocation.Window, title: "Publishing" },
  async (progress) => {
    progress.report({ message: "Uploading assets", increment: 20 });
    await uploadAssets();
    progress.report({ message: "Writing manifest", increment: 80 });
    await writeManifest();
  },
);
```

`ProgressLocation.Window` is the only location — a line in the status bar, replaced by whatever
the task last reported. `increment` is a percentage added to what came before; report none and
the line shows a spinner with no bar, which is the honest shape for work that cannot say how far
along it is. There is no cancellation: the task runs to completion or throws, and nothing here
gives the reader a button to stop it early.

## Output channels

```ts
const output = vscode.window.createOutputChannel("My Extension", { log: true });
output.info("Starting up");
output.warn("Config missing a value, using the default");
output.error(new Error("Could not reach the server"));
```

`createOutputChannel(name)` alone gives you an `OutputChannel`: `append`, `appendLine`,
`replace`, `clear`, `show`, `hide`, `dispose`. Passing `{ log: true }` gives you a
`LogOutputChannel` with `trace`, `debug`, `info`, `warn` and `error` instead — `trace` lands as
`debug`, there being no fifth row to draw it at. A channel's lines show in the Logs panel
([Logs](/extensions/logs)) when a workbench mounts it, and print to the console when
none is mounted — an extension does not need to know which.

## The theme

```ts
if (vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark) {
  // …
}

context.subscriptions.push(vscode.window.onDidChangeActiveColorTheme(() => rebuildWebview()));
```

`activeColorTheme` carries one member, `kind` — `ColorThemeKind.Light` or `.Dark` (`.HighContrast`
and `.HighContrastLight` are declared for an extension that compares against them, but neither is
ever answered). A webview is a whole document that styles itself, and it is rebuilt whenever the
theme changes rather than told about it — this event is how an extension notices and does the
same to anything else it drew that read the theme once.

## Icons and colors

`new vscode.ThemeIcon(name)` names one of codelet's own glyphs rather than drawing one —
`ThemeIcon.File` and `ThemeIcon.Folder` are built in, and a second argument colors it with a
`ThemeColor`. A name outside the set draws nothing, the same way an unknown id does in VS Code.
codelet ships a few dozen glyphs of its own — `files`, `search`, `folder`, `gear`, `trash`,
`star`, `scm`, `terminal`, `chat`, `run`, `refresh`, `check`, `error`, `warning`, `info`, `add`,
`edit`, `copy`, `download`, `lock`, `plug`, and more — plus a set of VS Code's own codicon names
aliased onto the same shapes (`settings` → gear, `source-control` → scm, `play` → run,
`star-full` → star, and so on). There is no published list to import; the fastest way to see what
is available is to look at the workbench's own chrome — every glyph in the activity bar, the
status bar and a tree row is one of these names, `$(name)` in a status bar item's `text` included.

`new vscode.ThemeColor(id)` names a color from a fixed table rather than VS Code's full theme —
an id outside it paints nothing. What is in the table:

| Id                                                | What it colors                         |
| ------------------------------------------------- | -------------------------------------- |
| `gitDecoration.addedResourceForeground`           | An added file's badge and name.        |
| `gitDecoration.modifiedResourceForeground`        | A modified file's badge and name.      |
| `gitDecoration.deletedResourceForeground`         | A deleted file's badge and name.       |
| `gitDecoration.renamedResourceForeground`         | A renamed file's badge and name.       |
| `gitDecoration.untrackedResourceForeground`       | An untracked file's badge and name.    |
| `gitDecoration.ignoredResourceForeground`         | An ignored file's badge and name.      |
| `gitDecoration.conflictingResourceForeground`     | A conflicting file's badge and name.   |
| `list.deemphasizedForeground`                     | Muted row text.                        |
| `list.errorForeground` / `list.warningForeground` | Row text at that severity.             |
| `statusBarItem.errorBackground`                   | `StatusBarItem.backgroundColor`.       |
| `statusBarItem.warningBackground`                 | `StatusBarItem.backgroundColor`.       |
| `problemsErrorIcon.foreground`                    | An error glyph.                        |
| `problemsWarningIcon.foreground`                  | A warning glyph.                       |
| `problemsInfoIcon.foreground`                     | An info glyph.                         |
| `editorGutter.addedBackground`                    | The quick diff gutter's added bar.     |
| `editorGutter.modifiedBackground`                 | The quick diff gutter's modified bar.  |
| `editorGutter.deletedBackground`                  | The quick diff gutter's deleted wedge. |

## The clipboard and external links

```ts
await vscode.env.clipboard.writeText(url);
const copied = await vscode.env.clipboard.readText();

const opened = await vscode.env.openExternal(vscode.Uri.parse("https://example.com"));
if (!opened) {
  // a popup the browser blocked, with no gesture behind it
}
```

`env.clipboard` wraps `navigator.clipboard`; a browser that offers none, or a read the reader has
not allowed, rejects rather than answering with nothing. `env.openExternal` opens a new tab and
resolves whether it actually opened — `false` is a blocked popup, worth telling the reader about
rather than swallowing.
