Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/imageCarousel/AGENTS.md
13397 views

Image Carousel

A generic workbench editor for viewing collections of images in a carousel/slideshow UI. Opens as a modal editor pane with navigation arrows, a caption, and a thumbnail strip.

Architecture

The image carousel is a self-contained workbench contribution that follows the custom editor pattern:

  • URI scheme: vscode-image-carousel (registered in Schemas in src/vs/base/common/network.ts) — used for EditorInput.resource identity.

  • Direct editor input: Callers create ImageCarouselEditorInput with a collection and open it directly via IEditorService.openEditor().

  • Image extraction: Chat integration builds a sections-based collection from chat request/response items. A section can include user-attached request images alongside response-derived images (tool invocations and inline references). For paired request/response items, the section title prefers the user's chat request message; pending requests with image attachments form their own section.

From code (generic)

const collection: IImageCarouselCollection = { id, title, sections: [{ title: '', images: [...] }] }; const input = new ImageCarouselEditorInput(collection, startIndex); await editorService.openEditor(input, { pinned: true }, MODAL_GROUP);

From chat (via click handler)

Clicking an image attachment pill in chat (when chat.imageCarousel.enabled is true) executes the workbench.action.chat.openImageInCarousel command, which collects request attachment images together with response-derived images for the current chat session and opens them in the carousel. MIME types are resolved via getMediaMime() from src/vs/base/common/mime.ts.

Key design decisions

Editor & lifecycle

  • Modal editor: Opens in MODAL_GROUP (-4) as an overlay.

  • Not restorable: canSerialize() returns false — image data is in-memory only.

  • Collection ID = chat session identity: sessionResource + '_carousel' for stable dedup via EditorInput.matches().

  • Preview-gated: chat.imageCarousel.enabled (default false, tagged preview). When off, clicks fall through to openResource().

  • Exact image matching: Finds the clicked image within the constructed collection by URI first, then falls back to byte equality when needed.

DOM & rendering

  • DOM construction: Uses the h() helper from vs/base/browser/dom with @name references for declarative DOM — no imperative document.createElement calls.

  • Bottom bar: Caption and thumbnail sections are wrapped in a div.bottom-bar flex column below the image area.

  • Stable DOM skeleton: Builds DOM once per setInput(), updates only changing parts to avoid flash on navigation.

  • Blob URL lifecycle: Main image URLs tracked in _imageDisposables (revoked on nav), thumbnails in _contentDisposables (revoked on clearInput()).

  • Focus border suppressed: The slideshow container uses outline: none !important on :focus / :focus-visible to override the workbench's global [tabindex="0"] focus styles.

Keyboard & focus

  • Keyboard parity: Uses registerOpenEditorListeners (click, double-click, Enter, Space) matching other attachment widgets.

  • Arrow key navigation: Handled via DOM keydown listener with stopPropagation() — not Action2 keybindings, because the modal editor's KEY_DOWN handler blocks workbench.* commands not in its allowlist. The editor overrides focus() to forward focus to the slideshow container so arrow keys work immediately without clicking.

Zoom

Zoom state is ZoomScale = number | 'fit' held in _zoomScale.

GestureEffect
ClickZoom in one level
Alt+click (Mac) / Ctrl+click (Win/Linux)Zoom out one level
Ctrl+scroll (Win/Linux) or Alt+scroll (Mac)Continuous zoom (~7.5% per tick)
Trackpad pinchZoom (reported as wheel + e.ctrlKey)

Predefined zoom levels: 10%, 20%, 30%, …, 100%, 150%, 200%, 300%, 500%, 700%, 1000%, 1500%, 2000%. Click cycles through these levels.

_applyZoom(scale) is the central method:

  • 'fit' → adds scale-to-fit class, clears img.style.zoom, removes .zoomed from container

  • numeric → sets img.style.zoom, adds .zoomed on the container (enabling overflow: auto for panning with themed scrollbars), preserves scroll center using dx/dy ratio math, adds pixelated class at ≥ 3× zoom

Zoom always resets to 'fit' when navigating to a different image.

Cursor changes to zoom-out when the zoom-out modifier key is held (via .zoom-out class on the container).