-
Notifications
You must be signed in to change notification settings - Fork 37.2k
feat(chat): Allow resizing the Chat Sessions list width in 'Side by Side' orientation #285451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(chat): Allow resizing the Chat Sessions list width in 'Side by Side' orientation #285451
Conversation
Fixes microsoft#285418 - Add resizable sash to sessions sidebar when in side-by-side orientation - Store sidebar width in user profile storage - Double-click sash to reset to default width (300px) - Min width: 150px, Max width: 600px
📬 CODENOTIFYThe following users are being notified based on files changed in this PR: @bpaseroMatched files:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds drag-to-resize functionality for the Chat Sessions sidebar when using the "Side by Side" orientation. The implementation includes width persistence, double-click reset, and respects minimum/maximum width constraints.
Key Changes:
- Custom sash implementation with mouse drag handlers for resizing the sessions sidebar
- Width persistence to user profile storage with validation
- CSS styling for the custom sash element with hover effects and orientation-specific positioning
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/vs/workbench/contrib/chat/browser/chatViewPane.ts | Implements custom resize sash with mouse event handlers, storage persistence logic, and width clamping; adds wrapper container for chat controls |
| src/vs/workbench/contrib/chat/browser/media/chatViewPane.css | Adds styles for custom sash element including positioning, hover states, and visibility rules for different orientations |
| // Show as sidebar with resizable width | ||
| if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) { | ||
| // Clamp sidebar width to available space | ||
| const maxSidebarWidth = Math.min(this.sessionsSidebarWidth, width - 200); |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded value '200' for minimum chat area width should be defined as a named constant (e.g., MIN_CHAT_AREA_WIDTH) to improve code clarity and maintainability. This magic number represents a critical constraint for the resize behavior but its purpose is not immediately clear from the code.
| const maxSidebarWidth = Math.min(this.sessionsSidebarWidth, width - 200); | |
| const minChatAreaWidth = 200; | |
| const maxSidebarWidth = Math.min(this.sessionsSidebarWidth, width - minChatAreaWidth); |
| private setupSessionsSash(sashElement: HTMLElement): void { | ||
| let startX = 0; | ||
| let startWidth = 0; | ||
| let dragPosition: Position | undefined; | ||
|
|
||
| const onMouseMove = (e: MouseEvent) => { | ||
| if (dragPosition === undefined) { | ||
| return; | ||
| } | ||
|
|
||
| const delta = dragPosition === Position.RIGHT | ||
| ? startX - e.clientX // When sessions on right, dragging left increases width | ||
| : e.clientX - startX; // When sessions on left, dragging right increases width | ||
|
|
||
| const newWidth = Math.max( | ||
| ChatViewPane.SESSIONS_SIDEBAR_MIN_WIDTH, | ||
| Math.min(ChatViewPane.SESSIONS_SIDEBAR_MAX_WIDTH, startWidth + delta) | ||
| ); | ||
|
|
||
| this.sessionsSidebarWidth = newWidth; | ||
| if (this.lastDimensions) { | ||
| this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); | ||
| } | ||
| }; | ||
|
|
||
| const onMouseUp = () => { | ||
| dragPosition = undefined; | ||
| document.body.style.cursor = ''; | ||
| sashElement.classList.remove('active'); | ||
|
|
||
| // Save the width to storage | ||
| this.storageService.store( | ||
| ChatViewPane.SESSIONS_SIDEBAR_WIDTH_STORAGE_KEY, | ||
| this.sessionsSidebarWidth, | ||
| StorageScope.PROFILE, | ||
| StorageTarget.USER | ||
| ); | ||
| }; | ||
|
|
||
| // Register document-level listeners with proper disposal | ||
| this._register(addDisposableListener(getWindow(sashElement).document, EventType.MOUSE_MOVE, onMouseMove)); | ||
| this._register(addDisposableListener(getWindow(sashElement).document, EventType.MOUSE_UP, onMouseUp)); | ||
|
|
||
| this._register(addDisposableListener(sashElement, EventType.MOUSE_DOWN, (e: MouseEvent) => { | ||
| if (this.sessionsViewerOrientation !== AgentSessionsViewerOrientation.SideBySide) { | ||
| return; // Only allow resize in side-by-side mode | ||
| } | ||
|
|
||
| e.preventDefault(); | ||
| startX = e.clientX; | ||
| startWidth = this.sessionsSidebarWidth; | ||
|
|
||
| // Capture the position at drag start to handle layout changes during drag | ||
| const { position } = this.getViewPositionAndLocation(); | ||
| dragPosition = position; | ||
|
|
||
| document.body.style.cursor = 'ew-resize'; | ||
| sashElement.classList.add('active'); | ||
| })); | ||
|
|
||
| // Double-click to reset to default width | ||
| this._register(addDisposableListener(sashElement, EventType.DBLCLICK, () => { | ||
| if (this.sessionsViewerOrientation !== AgentSessionsViewerOrientation.SideBySide) { | ||
| return; | ||
| } | ||
|
|
||
| this.sessionsSidebarWidth = ChatViewPane.SESSIONS_SIDEBAR_DEFAULT_WIDTH; | ||
| this.storageService.store( | ||
| ChatViewPane.SESSIONS_SIDEBAR_WIDTH_STORAGE_KEY, | ||
| this.sessionsSidebarWidth, | ||
| StorageScope.PROFILE, | ||
| StorageTarget.USER | ||
| ); | ||
|
|
||
| if (this.lastDimensions) { | ||
| this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); | ||
| } | ||
| })); | ||
| } |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This custom sash implementation should be replaced with the standard Sash component from 'vs/base/browser/ui/sash/sash'. The existing Sash component provides built-in support for keyboard navigation, touch events, hover states, accessibility features, and consistent behavior across the application. Using the standard component would simplify this code significantly and ensure consistency with other resizable UI elements in VS Code.
Example usage can be found in chatQuick.ts and simpleFindWidget.ts where Sash is instantiated with a layout provider and event listeners for onDidStart, onDidChange, onDidEnd, and onDidReset events.
| private setupSessionsSash(sashElement: HTMLElement): void { | ||
| let startX = 0; | ||
| let startWidth = 0; | ||
| let dragPosition: Position | undefined; | ||
|
|
||
| const onMouseMove = (e: MouseEvent) => { | ||
| if (dragPosition === undefined) { | ||
| return; | ||
| } | ||
|
|
||
| const delta = dragPosition === Position.RIGHT | ||
| ? startX - e.clientX // When sessions on right, dragging left increases width | ||
| : e.clientX - startX; // When sessions on left, dragging right increases width | ||
|
|
||
| const newWidth = Math.max( | ||
| ChatViewPane.SESSIONS_SIDEBAR_MIN_WIDTH, | ||
| Math.min(ChatViewPane.SESSIONS_SIDEBAR_MAX_WIDTH, startWidth + delta) | ||
| ); | ||
|
|
||
| this.sessionsSidebarWidth = newWidth; | ||
| if (this.lastDimensions) { | ||
| this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); | ||
| } | ||
| }; | ||
|
|
||
| const onMouseUp = () => { | ||
| dragPosition = undefined; | ||
| document.body.style.cursor = ''; | ||
| sashElement.classList.remove('active'); | ||
|
|
||
| // Save the width to storage | ||
| this.storageService.store( | ||
| ChatViewPane.SESSIONS_SIDEBAR_WIDTH_STORAGE_KEY, | ||
| this.sessionsSidebarWidth, | ||
| StorageScope.PROFILE, | ||
| StorageTarget.USER | ||
| ); | ||
| }; | ||
|
|
||
| // Register document-level listeners with proper disposal | ||
| this._register(addDisposableListener(getWindow(sashElement).document, EventType.MOUSE_MOVE, onMouseMove)); | ||
| this._register(addDisposableListener(getWindow(sashElement).document, EventType.MOUSE_UP, onMouseUp)); | ||
|
|
||
| this._register(addDisposableListener(sashElement, EventType.MOUSE_DOWN, (e: MouseEvent) => { | ||
| if (this.sessionsViewerOrientation !== AgentSessionsViewerOrientation.SideBySide) { | ||
| return; // Only allow resize in side-by-side mode | ||
| } | ||
|
|
||
| e.preventDefault(); | ||
| startX = e.clientX; | ||
| startWidth = this.sessionsSidebarWidth; | ||
|
|
||
| // Capture the position at drag start to handle layout changes during drag | ||
| const { position } = this.getViewPositionAndLocation(); | ||
| dragPosition = position; | ||
|
|
||
| document.body.style.cursor = 'ew-resize'; | ||
| sashElement.classList.add('active'); | ||
| })); | ||
|
|
||
| // Double-click to reset to default width | ||
| this._register(addDisposableListener(sashElement, EventType.DBLCLICK, () => { | ||
| if (this.sessionsViewerOrientation !== AgentSessionsViewerOrientation.SideBySide) { | ||
| return; | ||
| } | ||
|
|
||
| this.sessionsSidebarWidth = ChatViewPane.SESSIONS_SIDEBAR_DEFAULT_WIDTH; | ||
| this.storageService.store( | ||
| ChatViewPane.SESSIONS_SIDEBAR_WIDTH_STORAGE_KEY, | ||
| this.sessionsSidebarWidth, | ||
| StorageScope.PROFILE, | ||
| StorageTarget.USER | ||
| ); | ||
|
|
||
| if (this.lastDimensions) { | ||
| this.layoutBody(this.lastDimensions.height, this.lastDimensions.width); | ||
| } | ||
| })); | ||
| } |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom sash implementation lacks keyboard accessibility. Users who rely on keyboard navigation cannot resize the sessions sidebar. The standard Sash component provides proper keyboard support out of the box, which is essential for accessibility compliance.
| .agent-sessions-sash { | ||
| right: 0; | ||
| cursor: ew-resize; | ||
| border-right: 1px solid var(--vscode-panel-border); | ||
| } | ||
|
|
||
| .agent-sessions-sash:hover, | ||
| .agent-sessions-sash.active { | ||
| border-right-color: var(--vscode-sash-hoverBorder); | ||
| } | ||
| } | ||
|
|
||
| &.chat-view-position-right { | ||
| flex-direction: row-reverse; | ||
|
|
||
| .agent-sessions-container { | ||
| border-left: none; /* border handled by sash */ | ||
| position: relative; | ||
| flex-shrink: 0; | ||
| } | ||
|
|
||
| .agent-sessions-sash { | ||
| left: 0; | ||
| cursor: ew-resize; | ||
| border-left: 1px solid var(--vscode-panel-border); | ||
| } | ||
|
|
||
| .agent-sessions-sash:hover, | ||
| .agent-sessions-sash.active { | ||
| border-left-color: var(--vscode-sash-hoverBorder); | ||
| } | ||
| } | ||
|
|
||
| .agent-sessions-container { | ||
| height: 100%; | ||
| overflow: hidden; | ||
| } | ||
|
|
||
| .agent-sessions-sash { | ||
| position: absolute; | ||
| top: 0; | ||
| bottom: 0; | ||
| width: 4px; | ||
| z-index: 10; | ||
| transition: border-color 0.1s ease-out; | ||
| } |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The manual implementation of sash styling (hover states, cursors, borders) duplicates functionality that already exists in the standard Sash component and its associated CSS. This increases maintenance burden and may lead to inconsistencies with other sashes in the application that use the standard implementation.
| this._register(addDisposableListener(getWindow(sashElement).document, EventType.MOUSE_MOVE, onMouseMove)); | ||
| this._register(addDisposableListener(getWindow(sashElement).document, EventType.MOUSE_UP, onMouseUp)); |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mousemove and mouseup event listeners are registered on the document level permanently, but they only do meaningful work when dragPosition is defined. This means these handlers are invoked on every mouse movement in the entire window, even when not dragging the sash. This is inefficient and could impact performance.
A more efficient pattern would be to register these listeners only during an active drag (in the mousedown handler) and remove them in the mouseup handler, similar to how the standard Sash component handles events.
Fixes #285418
Fixes #281258
Summary
This PR adds the ability to resize the Chat Sessions sidebar when using the "Side by Side" orientation.
Changes
chat.sessionsPanel.width)Testing
Demo
vscode.mp4