Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/@types/vscode.proposed.chatParticipantPrivate.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,15 @@ declare module 'vscode' {
*/
readonly editedFileEvents?: ChatRequestEditedFileEvent[];

/**
* The identifier of the language model that was used for this request, if known.
*/
readonly modelId?: string;

/**
* @hidden
*/
constructor(prompt: string, command: string | undefined, references: ChatPromptReference[], participant: string, toolReferences: ChatLanguageModelToolReference[], editedFileEvents: ChatRequestEditedFileEvent[] | undefined, id: string | undefined);
constructor(prompt: string, command: string | undefined, references: ChatPromptReference[], participant: string, toolReferences: ChatLanguageModelToolReference[], editedFileEvents: ChatRequestEditedFileEvent[] | undefined, id: string | undefined, modelId: string | undefined);
}

export class ChatResponseTurn2 {
Expand Down Expand Up @@ -262,8 +267,6 @@ declare module 'vscode' {

export interface LanguageModelToolInvocationOptions<T> {
chatRequestId?: string;
/** @deprecated Use {@link chatSessionResource} instead */
chatSessionId?: string;
chatSessionResource?: Uri;
chatInteractionId?: string;
terminalCommand?: string;
Expand All @@ -289,8 +292,6 @@ declare module 'vscode' {
*/
input: T;
chatRequestId?: string;
/** @deprecated Use {@link chatSessionResource} instead */
chatSessionId?: string;
chatSessionResource?: Uri;
chatInteractionId?: string;
/**
Expand Down
49 changes: 41 additions & 8 deletions src/@types/vscode.proposed.chatSessionsProvider.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ declare module 'vscode' {
/**
* Registers a new {@link ChatSessionItemProvider chat session item provider}.
*
* To use this, also make sure to also add `chatSessions` contribution in the `package.json`.
* @deprecated Use {@linkcode createChatSessionItemController} instead.
*
* To use this, also make sure to add `chatSessions` contribution in the `package.json`.
*
* @param chatSessionType The type of chat session the provider is for.
* @param provider The provider to register.
Expand All @@ -46,12 +48,21 @@ declare module 'vscode' {

/**
* Creates a new {@link ChatSessionItemController chat session item controller} with the given unique identifier.
*
* To use this, also make sure to add `chatSessions` contribution in the `package.json`.
*
* @param chatSessionType The type of chat session the provider is for.
* @param refreshHandler The controller's {@link ChatSessionItemController.refreshHandler refresh handler}.
*
* @returns A new controller instance that can be used to manage chat session items for the given chat session type.
*/
export function createChatSessionItemController(id: string, refreshHandler: (token: CancellationToken) => Thenable<void>): ChatSessionItemController;
export function createChatSessionItemController(chatSessionType: string, refreshHandler: ChatSessionItemControllerRefreshHandler): ChatSessionItemController;
}

/**
* Provides a list of information about chat sessions.
*
* @deprecated Use {@linkcode ChatSessionItemController} instead.
*/
export interface ChatSessionItemProvider {
/**
Expand All @@ -77,7 +88,12 @@ declare module 'vscode' {
}

/**
* Provides a list of information about chat sessions.
* Extension callback invoked to refresh the collection of chat session items for a {@linkcode ChatSessionItemController}.
*/
export type ChatSessionItemControllerRefreshHandler = (token: CancellationToken) => Thenable<void>;

/**
* Manages chat sessions for a specific chat session type
*/
export interface ChatSessionItemController {
readonly id: string;
Expand All @@ -93,7 +109,7 @@ declare module 'vscode' {
readonly items: ChatSessionItemCollection;

/**
* Creates a new managed chat session item that be added to the collection.
* Creates a new managed chat session item that can be added to the collection.
*/
createChatSessionItem(resource: Uri, label: string): ChatSessionItem;

Expand All @@ -102,7 +118,7 @@ declare module 'vscode' {
*
* This is also called on first load to get the initial set of items.
*/
readonly refreshHandler: (token: CancellationToken) => Thenable<void>;
readonly refreshHandler: ChatSessionItemControllerRefreshHandler;

/**
* Fired when an item's archived state changes.
Expand All @@ -121,7 +137,8 @@ declare module 'vscode' {

/**
* Replaces the items stored by the collection.
* @param items Items to store.
*
* @param items Items to store. If two items have the same resource URI, the last one will be used.
*/
replace(items: readonly ChatSessionItem[]): void;

Expand All @@ -136,31 +153,42 @@ declare module 'vscode' {
/**
* Adds the chat session item to the collection. If an item with the same resource URI already
* exists, it'll be replaced.
*
* @param item Item to add.
*/
add(item: ChatSessionItem): void;

/**
* Removes a single chat session item from the collection.
*
* @param resource Item resource to delete.
*/
delete(resource: Uri): void;

/**
* Efficiently gets a chat session item by resource, if it exists, in the collection.
*
* @param resource Item resource to get.
*
* @returns The found item or undefined if it does not exist.
*/
get(resource: Uri): ChatSessionItem | undefined;
}

/**
* A chat session shown in the UI.
*
* This should be created by calling a {@link ChatSessionItemController.createChatSessionItem createChatSessionItem}
* method on the controller. The item can then be added to the controller's {@link ChatSessionItemController.items items collection}
* to show it in the UI.
*/
export interface ChatSessionItem {
/**
* The resource associated with the chat session.
*
* This is uniquely identifies the chat session and is used to open the chat session.
* This uniquely identifies the chat session and is used to open the chat session.
*/
resource: Uri;
readonly resource: Uri;

/**
* Human readable name of the session shown in the UI
Expand Down Expand Up @@ -436,6 +464,11 @@ declare module 'vscode' {
export interface ChatSessionContext {
readonly chatSessionItem: ChatSessionItem; // Maps to URI of chat session editor (could be 'untitled-1', etc..)
readonly isUntitled: boolean;
/**
* The initial option selections for the session, provided with the first request.
* Contains the options the user selected (or defaults) before the session was created.
*/
readonly initialSessionOptions?: ReadonlyArray<{ optionId: string; value: string | ChatSessionProviderOptionItem }>;
}

export interface ChatSessionCapabilities {
Expand Down
84 changes: 82 additions & 2 deletions webviews/editorWebview/test/overview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,34 @@
import { default as assert } from 'assert';
import * as React from 'react';
import { cleanup, render } from 'react-testing-library';
import { createSandbox, SinonSandbox } from 'sinon';
import { createSandbox, SinonSandbox, SinonStub } from 'sinon';

import { PRContext, default as PullRequestContext } from '../../common/context';
import { Overview } from '../overview';
import { PullRequestBuilder } from './builder/pullRequest';

describe('Overview', function () {
let sinon: SinonSandbox;
let observerCallback: IntersectionObserverCallback;
let mockIntersectionObserver: SinonStub;

beforeEach(function () {
sinon = createSandbox();

// Mock IntersectionObserver
mockIntersectionObserver = sinon.stub().callsFake((callback: IntersectionObserverCallback) => {
observerCallback = callback;
return {
observe: sinon.stub(),
disconnect: sinon.stub(),
unobserve: sinon.stub(),
takeRecords: sinon.stub().returns([]),
root: null,
rootMargin: '',
thresholds: [0],
};
});
global.IntersectionObserver = mockIntersectionObserver;
});

afterEach(function () {
Expand Down Expand Up @@ -55,8 +72,71 @@ describe('Overview', function () {
assert(!titleElement.classList.contains('sticky'));

// Sticky header should exist but not be visible initially
const stickyHeader = out.container.querySelector('.sticky-header');
let stickyHeader = out.container.querySelector('.sticky-header');
assert(stickyHeader);
assert(!stickyHeader.classList.contains('visible'));

// Simulate scrolling - title element is no longer intersecting (not visible)
assert(observerCallback, 'IntersectionObserver callback should be set');
const mockRect: DOMRectReadOnly = {
x: 0,
y: 0,
width: 0,
height: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
toJSON: () => ({}),
};
const mockObserver: IntersectionObserver = {
observe: () => {},
disconnect: () => {},
unobserve: () => {},
takeRecords: () => [],
root: null,
rootMargin: '',
thresholds: [0],
};
observerCallback(
[
{
isIntersecting: false,
target: titleElement,
boundingClientRect: mockRect,
intersectionRatio: 0,
intersectionRect: mockRect,
rootBounds: null,
time: Date.now(),
},
],
mockObserver,
);

// After scrolling, sticky header should become visible
stickyHeader = out.container.querySelector('.sticky-header');
assert(stickyHeader);
assert(stickyHeader.classList.contains('visible'), 'Sticky header should have visible class when title is not intersecting');

// Simulate scrolling back to top - title element is intersecting again (visible)
observerCallback(
[
{
isIntersecting: true,
target: titleElement,
boundingClientRect: mockRect,
intersectionRatio: 1,
intersectionRect: mockRect,
rootBounds: null,
time: Date.now(),
},
],
mockObserver,
);

// After scrolling back to top, sticky header should be hidden again
stickyHeader = out.container.querySelector('.sticky-header');
assert(stickyHeader);
assert(!stickyHeader.classList.contains('visible'), 'Sticky header should not have visible class when title is intersecting');
});
});