
# Editing and tabs

Once a file is open it gets a tab across the top and a breadcrumb trail underneath. This page
covers what happens between opening a file and closing it: the tab strip, saving, restoring
where the reader was, and the gestures the editor itself offers — find and replace, formatting,
and the right-click menu.

## Tabs

A single click in the explorer, or on a search result, opens a file into the **preview tab** —
shown in italic. The next single click, anywhere, replaces it, so browsing the tree leaves no
trail of tabs behind. A tab is promoted to a normal, pinned one by double-clicking it, clicking
a tab that's already open, dragging it, or typing in the document — anything deliberate enough
that browsing wasn't the point.

Drag a tab to reorder the strip; it moves live as the pointer crosses each neighbour. Close a
tab with its `×`, or right-click it for **Close**, **Close Others** and **Close to the Right**,
followed by anything an extension contributed. **Close Editor** and **Close All Editors** are
also in the command palette.

```ts
new Workbench({
  parent,
  fs,
  open: ["/README.md", "/src/index.ts"],
  active: "/src/index.ts",
  onOpen: (open, active) => console.log("tabs:", open, "active:", active),
});
```

`open` seeds the tabs a workbench mounts with, `active` picks which one has focus, and `onOpen`
reports the full list and the active one whenever either changes — see
[Workbench](/guide/workbench) for the rest of the constructor's options.

## Breadcrumbs

The row under the tabs is the active file's path, one button per segment. Click a segment to
reveal that directory in the explorer — the last one reveals the file itself. Hovering the row
shows a **Copy Path** button at its end, left out where there's no Clipboard API to back it. A
tab whose path is a scheme rather than a tree file — a diff, or a document an extension serves —
shows plain text there instead of clickable crumbs.

## Saving and unsaved changes

Every keystroke writes straight to `fs`: there's no buffer, so the tree always matches what the
editor shows. `Mod-S` has nothing left to write — it's the moment a host should persist the file
elsewhere, through `onSave`, not the write itself:

```ts
new Workbench({
  parent,
  fs,
  onSave: (path, text) =>
    fetch("/api/save", { method: "POST", body: JSON.stringify({ path, text }) }),
});
```

`onSave` reports the text left after anything an extension did on save (formatting, say), not
the string that was there when `Mod-S` was pressed, and it fires even when nothing had changed.
A tab with unsaved changes carries a dot in place of its close button until it's saved.

Closing a tab with unsaved changes asks first — one dialog, whether it's the `×`, the tab menu,
or **Close All Editors**, and batched once for a whole set of files rather than once per tab:

> Do you want to save the changes you made to `index.ts`?
> Your changes will be lost if you don't save them.
>
> **Save** · Don't Save · Cancel

**Don't Save** discards back to what the file held before this round of edits began, not to the
last time it was saved. Deleting a dirty file skips the dialog — there's nothing left to
preserve once it's gone.

## Restoring where the reader was

```ts
new Workbench({
  parent,
  fs,
  cursors: { "/src/index.ts": { from: [3, 1] } },
  onCursor: (path, cursor) => console.log(path, "is looking at", cursor),
});
```

`cursors` seeds each path's selection, read once when that tab opens; `onCursor` reports every
move. A `Cursor` is `{ from: Place, to?: Place }` and a `Place` is `[line, column?]` — a caret has
no `to`. Persist what `onCursor` reports and hand it back as `cursors` on the next mount to put
the reader back where they left off.

## Read-only and locked files

A tab refuses edits for one of two reasons: it's a document a provider serves rather than a file
of the tree, and that provider doesn't write back — or it's a file an extension has locked with
`codelet.workspace.setReadonly`. Either way the tab wears a lock icon (its tooltip adds
"(read-only)"), the status bar says **Read-only** beside a lock icon while it's showing, and
`Mod-S` does nothing.

## Reopen Editor With…

Where a manifest's own editor is showing a file — an image, say — or a file is bytes nothing
claimed, **View: Reopen Editor With…** in the command palette offers a way to see the raw text
instead: two rows, the claiming editor's name (or "Binary File") and "Text Editor". Running it
again switches back. The same command is the way to pick a different editor for a file more than
one manifest could claim.

## Diff view

`vscode.diff(left, right, title?)` opens two documents side by side in a tab, whichever URIs
they name — [`codelet/extensions/scm`](/extensions/scm) uses it to show a changed file against
its last committed version. Both sides are read-only; the bar above them carries the title, or
the two addresses if none was given.

Beside every changed line in the editor itself, a source control with a quick diff provider adds
a bar in the gutter: added, modified, or a wedge where lines were removed. There's no accept or
reject there and no inline preview of what changed — the diff tab is where you read the change,
the gutter is only where you notice one happened.

## Find and replace

`Mod-F` opens the find widget over the active document, seeded with the current selection where
there is one; `Mod-H` (`Cmd-Alt-F` on a Mac) opens it with the replace row showing too. Escape
closes it.

The widget has three toggles — **Match Case**, **Match Whole Word**, **Use Regular
Expression** — and a count of matches, or **No results** / **Invalid pattern**. `Enter` in the
query field steps to the next match, `Shift-Enter` to the previous; `Enter` in the replace field
replaces the current one, and `Mod-Alt-Enter` replaces every match in the document. Read-only
documents get the find half without the replace row.

## Formatting, rename and quick fixes

Four gestures, each offered only where an extension answers for the active file and it isn't
read-only:

| Gesture          | Key           | Command                         |
| ---------------- | ------------- | ------------------------------- |
| Format Document  | `Shift-Alt-F` | `editor.action.formatDocument`  |
| Format Selection | none          | `editor.action.formatSelection` |
| Rename Symbol    | `F2`          | `editor.action.rename`          |
| Quick Fix…       | `Mod-.`       | `editor.action.quickFix`        |

Format Selection needs a selection and has no key of its own — the editor's right-click menu is
the only door to it. The rest also appear in the command palette while the active file answers
for them. A quick-fix bulb appears at the end of the caret's line, around 200ms after it settles,
wherever a diagnostic covers that spot; clicking it opens the same picker `Mod-.` does.

## The editor context menu

Right-click in the document for Cut, Copy and Paste — each needs a selection or a readable
clipboard, and is left out rather than shown disabled where it has nothing to act on. Below a
rule: **Go to Definition**, **Go to Declaration**, **Go to Type Definition**, **Go to
Implementations** and **Go to References**, one row per capability a language actually answers,
shown only over a word. Below another: Format Document, Format Selection, Rename Symbol and
Quick Fix…, on the same terms as the table above. Right-click moves the caret to the pointer
first, unless the pointer is already inside the current selection. Whatever an extension
contributed to `editor/context` lands at the end.

## Try it

Open both files, switch between them, and try `Mod-F` or a right-click in the text.

::codelet-playground{mode="workbench" active="src/index.ts" height="440"}

```ts [src/index.ts]
export function add(a: number, b: number) {
  return a + b;
}

console.log(add(2, 3));
```

```md [notes.md]
# Notes

Open `src/index.ts` too, then try Mod-F, or right-click in either file.
```

::
