Codelet logoCodelet

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.

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:

GestureKeyCommand
Format DocumentShift-Alt-Feditor.action.formatDocument
Format Selectionnoneeditor.action.formatSelection
Rename SymbolF2editor.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.