Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js';
import { $, append, EventHelper } from '../../../../../base/browser/dom.js';
import { $, append, EventHelper, addDisposableListener, EventType, hide, setVisibility } from '../../../../../base/browser/dom.js';
import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js';
import { KeyCode } from '../../../../../base/common/keyCodes.js';
import { localize } from '../../../../../nls.js';
import { AgentSessionSection, IAgentSession, IAgentSessionSection, IAgentSessionsModel, IMarshalledAgentSessionContext, isAgentSession, isAgentSessionSection } from './agentSessionsModel.js';
import { AgentSessionListItem, AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsDragAndDrop, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionSectionRenderer, AgentSessionsSorter, IAgentSessionsFilter, IAgentSessionsSorterOptions } from './agentSessionsViewer.js';
import { AgentSessionApprovalModel } from './agentSessionApprovalModel.js';
Expand Down Expand Up @@ -71,6 +74,8 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
private sessionsContainer: HTMLElement | undefined;
get element(): HTMLElement | undefined { return this.sessionsContainer; }

private emptyFilterMessage: HTMLElement | undefined;

private sessionsList: WorkbenchCompressibleAsyncDataTree<IAgentSessionsModel, AgentSessionListItem, FuzzyScore> | undefined;
private sessionsListFindIsOpen = false;

Expand Down Expand Up @@ -106,7 +111,7 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
this.focusedAgentSessionTypeContextKey = ChatContextKeys.agentSessionType.bindTo(this.contextKeyService);
this.hasMultipleAgentSessionsSelectedContextKey = ChatContextKeys.hasMultipleAgentSessionsSelected.bindTo(this.contextKeyService);

this.createList(this.container);
this.create(this.container);

this.registerListeners();
}
Expand Down Expand Up @@ -140,9 +145,35 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
}
}

private createList(container: HTMLElement): void {
private create(container: HTMLElement): void {
this.sessionsContainer = append(container, $('.agent-sessions-viewer'));

this.createEmptyFilterMessage(this.sessionsContainer);
this.createList(this.sessionsContainer);
}

private createEmptyFilterMessage(container: HTMLElement): void {
this.emptyFilterMessage = append(container, $('.agent-sessions-empty-filter-message'));
hide(this.emptyFilterMessage);

const span = append(this.emptyFilterMessage, $('span'));
span.textContent = localize('agentSessions.noFilterResults', "No matching sessions.");

const link = append(this.emptyFilterMessage, $('span.reset-filter-link'));
link.textContent = localize('agentSessions.clearFilters', "Clear Filter");
link.tabIndex = 0;
link.setAttribute('role', 'button');
this._register(addDisposableListener(link, EventType.CLICK, () => this.options.filter.reset()));
this._register(addDisposableListener(link, EventType.KEY_DOWN, (e) => {
const event = new StandardKeyboardEvent(e);
if (event.keyCode === KeyCode.Enter || event.keyCode === KeyCode.Space) {
event.preventDefault();
this.options.filter.reset();
}
}));
}

private createList(container: HTMLElement): void {
const collapseByDefault = (element: unknown) => {
if (isAgentSessionSection(element)) {
if (element.section === AgentSessionSection.More && !this.options.filter.getExcludes().read) {
Expand All @@ -168,16 +199,17 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
const sorter = new AgentSessionsSorter(this.options);
const approvalModel = this.options.enableApprovalRow ? this._register(this.instantiationService.createInstance(AgentSessionApprovalModel)) : undefined;
const sessionRenderer = this._register(this.instantiationService.createInstance(AgentSessionRenderer, this.options, approvalModel));
const sessionFilter = this._register(new AgentSessionsDataSource(this.options.filter, sorter));
const list = this.sessionsList = this._register(this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree,
'AgentSessionsView',
this.sessionsContainer,
container,
new AgentSessionsListDelegate(approvalModel),
new AgentSessionsCompressionDelegate(),
[
sessionRenderer,
this.instantiationService.createInstance(AgentSessionSectionRenderer),
],
new AgentSessionsDataSource(this.options.filter, sorter),
sessionFilter,
{
accessibilityProvider: new AgentSessionsAccessibilityProvider(),
dnd: this.instantiationService.createInstance(AgentSessionsDragAndDrop),
Expand All @@ -202,6 +234,10 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
}
}));

this._register(sessionFilter.onDidGetChildren(count => {
this.updateEmptyFilterMessage(count);
}));

const model = this.agentSessionsService.model;

this._register(this.options.filter.onDidChange(async () => {
Expand Down Expand Up @@ -251,6 +287,20 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
}));
}

private updateEmptyFilterMessage(visibleChildren: number): void {
if (!this.emptyFilterMessage || !this.sessionsList) {
return;
}

const model = this.agentSessionsService.model;
const hasSessionsInModel = model.sessions.length > 0;
const hasVisibleChildren = visibleChildren > 0;
const isFilterActive = !this.options.filter.isDefault();

const showMessage = hasSessionsInModel && !hasVisibleChildren && isFilterActive;
setVisibility(showMessage, this.emptyFilterMessage);
}

private hasTodaySessions(): boolean {
const startOfToday = new Date().setHours(0, 0, 0, 0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export class AgentSessionsFilter extends Disposable implements Required<IAgentSe
});
}
run(): void {
that.storeExcludes({ ...DEFAULT_EXCLUDES });
that.reset();
}
}));
}
Expand Down Expand Up @@ -332,4 +332,8 @@ export class AgentSessionsFilter extends Disposable implements Required<IAgentSe
notifyResults(count: number): void {
this.options.notifyResults?.(count);
}

reset(): void {
this.storeExcludes({ ...DEFAULT_EXCLUDES });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -689,16 +689,31 @@ export interface IAgentSessionsFilter {
* Get the current filter excludes for display in the UI.
*/
getExcludes(): IAgentSessionsFilterExcludes;

/**
* Whether the filter is at its default state (no custom filters applied).
*/
isDefault(): boolean;

/**
* Reset the filter to its default state.
*/
reset(): void;
}

export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsModel, AgentSessionListItem> {
export class AgentSessionsDataSource extends Disposable implements IAsyncDataSource<IAgentSessionsModel, AgentSessionListItem> {

private static readonly CAPPED_SESSIONS_LIMIT = 3;

private readonly _onDidGetChildren = this._register(new Emitter<number>());
readonly onDidGetChildren: Event<number> = this._onDidGetChildren.event;

constructor(
private readonly filter: IAgentSessionsFilter | undefined,
private readonly sorter: ITreeSorter<IAgentSession>,
) { }
) {
super();
}

hasChildren(element: IAgentSessionsModel | AgentSessionListItem): boolean {

Expand Down Expand Up @@ -739,6 +754,7 @@ export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsM

// Callback results count
this.filter?.notifyResults?.(filteredSessions.length);
this._onDidGetChildren.fire(filteredSessions.length);

// Group sessions into sections if enabled
if (this.filter?.groupResults?.()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,27 @@
.monaco-list:not(:focus) .monaco-list-row.selected .agent-session-section-label {
color: var(--vscode-list-inactiveSelectionForeground);
}

.agent-sessions-empty-filter-message {
padding: 8px 12px;
font-size: 12px;
color: var(--vscode-descriptionForeground);

.reset-filter-link {
margin-left: 4px;
color: var(--vscode-textLink-foreground);
cursor: pointer;
text-decoration: none;

&:hover {
text-decoration: underline;
}

&:focus-visible {
text-decoration: underline;
outline: 1px solid var(--vscode-focusBorder);
outline-offset: 1px;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ suite('sessionDateFromNow', () => {

suite('AgentSessionsDataSource', () => {

ensureNoDisposablesAreLeakedInTestSuite();
const disposables = ensureNoDisposablesAreLeakedInTestSuite();

const ONE_DAY = 24 * 60 * 60 * 1000;
const WEEK_THRESHOLD = 7 * ONE_DAY; // 7 days
Expand Down Expand Up @@ -152,7 +152,9 @@ suite('AgentSessionsDataSource', () => {
onDidChange: Event.None,
groupResults: () => options.groupBy,
exclude: options.exclude ?? (() => false),
getExcludes: () => ({ providers: [], states: [], archived: false, read: options.excludeRead ?? false })
getExcludes: () => ({ providers: [], states: [], archived: false, read: options.excludeRead ?? false }),
isDefault: () => true,
reset: () => { },
};
}

Expand Down Expand Up @@ -182,7 +184,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: undefined });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -202,7 +204,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -223,7 +225,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -244,7 +246,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -263,7 +265,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -281,7 +283,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -299,7 +301,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -319,7 +321,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand Down Expand Up @@ -353,7 +355,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand Down Expand Up @@ -382,7 +384,7 @@ suite('AgentSessionsDataSource', () => {
test('empty sessions returns empty result', () => {
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel([]);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -399,7 +401,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -422,7 +424,7 @@ suite('AgentSessionsDataSource', () => {

const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Date });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand Down Expand Up @@ -456,7 +458,7 @@ suite('AgentSessionsDataSource', () => {
excludeRead: true // Filtering to show only unread sessions
});
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand All @@ -481,7 +483,7 @@ suite('AgentSessionsDataSource', () => {
excludeRead: false // Not filtering to unread only
});
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const dataSource = disposables.add(new AgentSessionsDataSource(filter, sorter));

const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
Expand Down
Loading