Editing and tabs
Tabs and breadcrumbs, saving, cursors, read-only files, diff view, and the editor's own gestures
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.
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 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:
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
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 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.