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 inSchemasinsrc/vs/base/common/network.ts) — used forEditorInput.resourceidentity.Direct editor input: Callers create
ImageCarouselEditorInputwith a collection and open it directly viaIEditorService.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.
How to open the carousel
From code (generic)
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()returnsfalse— image data is in-memory only.Collection ID = chat session identity:
sessionResource + '_carousel'for stable dedup viaEditorInput.matches().Preview-gated:
chat.imageCarousel.enabled(defaultfalse, taggedpreview). When off, clicks fall through toopenResource().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 fromvs/base/browser/domwith@namereferences for declarative DOM — no imperativedocument.createElementcalls.Bottom bar: Caption and thumbnail sections are wrapped in a
div.bottom-barflex 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 onclearInput()).Focus border suppressed: The slideshow container uses
outline: none !importanton:focus/:focus-visibleto 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
keydownlistener withstopPropagation()— not Action2 keybindings, because the modal editor'sKEY_DOWNhandler blocksworkbench.*commands not in its allowlist. The editor overridesfocus()to forward focus to the slideshow container so arrow keys work immediately without clicking.
Zoom
Zoom state is ZoomScale = number | 'fit' held in _zoomScale.
| Gesture | Effect |
|---|---|
| Click | Zoom 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 pinch | Zoom (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'→ addsscale-to-fitclass, clearsimg.style.zoom, removes.zoomedfrom containernumeric → sets
img.style.zoom, adds.zoomedon the container (enablingoverflow: autofor panning with themed scrollbars), preserves scroll center usingdx/dyratio math, addspixelatedclass 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).