Skip to content

Conversation

@masachika-kamada
Copy link

@masachika-kamada masachika-kamada commented Dec 30, 2025

Fixes #285418
Fixes #281258

Summary

This PR adds the ability to resize the Chat Sessions sidebar when using the "Side by Side" orientation.

Changes

  • Added a resize sash to the sessions sidebar container
  • Implemented drag-to-resize functionality with min (150px) and max (600px) constraints
  • Sidebar width is persisted in user profile storage (chat.sessionsPanel.width)
  • Double-click on the sash resets the width to default (300px)
  • Sash is hidden in stacked orientation mode
  • Works correctly for both left and right panel positions

Testing

  1. Open Copilot Chat view
  2. Enable "Side by Side" orientation in chat settings
  3. Hover over the border between sessions list and chat area
  4. Drag to resize the sessions sidebar
  5. Verify the width is persisted after reloading

Demo

vscode.mp4
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
Copilot AI review requested due to automatic review settings December 30, 2025 18:05
@vs-code-engineering
Copy link

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

@bpasero

Matched files:

  • src/vs/workbench/contrib/chat/browser/chatViewPane.ts
  • src/vs/workbench/contrib/chat/browser/media/chatViewPane.css
Copy link
Contributor

Copilot AI left a 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);
Copy link

Copilot AI Dec 30, 2025

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.

Suggested change
const maxSidebarWidth = Math.min(this.sessionsSidebarWidth, width - 200);
const minChatAreaWidth = 200;
const maxSidebarWidth = Math.min(this.sessionsSidebarWidth, width - minChatAreaWidth);
Copilot uses AI. Check for mistakes.
Comment on lines +439 to +517
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);
}
}));
}
Copy link

Copilot AI Dec 30, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +439 to +517
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);
}
}));
}
Copy link

Copilot AI Dec 30, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to 177
.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;
}
Copy link

Copilot AI Dec 30, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +479 to +480
this._register(addDisposableListener(getWindow(sashElement).document, EventType.MOUSE_MOVE, onMouseMove));
this._register(addDisposableListener(getWindow(sashElement).document, EventType.MOUSE_UP, onMouseUp));
Copy link

Copilot AI Dec 30, 2025

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants