@@ -12,9 +12,11 @@ import { Emitter, Event } from '../../../../../base/common/event.js';
1212import { Disposable , DisposableStore } from '../../../../../base/common/lifecycle.js' ;
1313import { localize } from '../../../../../nls.js' ;
1414import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js' ;
15+ import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js' ;
1516import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js' ;
1617import { WorkbenchList } from '../../../../../platform/list/browser/listService.js' ;
1718import { IChatTodoListService , IChatTodo } from '../../common/chatTodoListService.js' ;
19+ import { ChatContextKeys } from '../../common/chatContextKeys.js' ;
1820import { TodoListToolDescriptionFieldSettingId } from '../../common/tools/manageTodoListTool.js' ;
1921
2022class TodoListDelegate implements IListVirtualDelegate < IChatTodo > {
@@ -125,7 +127,7 @@ export class ChatTodoListWidget extends Disposable {
125127
126128 private _isExpanded : boolean = false ;
127129 private _userManuallyExpanded : boolean = false ;
128- private expandoElement ! : HTMLElement ;
130+ private expandoButton ! : Button ;
129131 private todoListContainer ! : HTMLElement ;
130132 private clearButtonContainer ! : HTMLElement ;
131133 private clearButton ! : Button ;
@@ -135,11 +137,19 @@ export class ChatTodoListWidget extends Disposable {
135137 constructor (
136138 @IChatTodoListService private readonly chatTodoListService : IChatTodoListService ,
137139 @IConfigurationService private readonly configurationService : IConfigurationService ,
138- @IInstantiationService private readonly instantiationService : IInstantiationService
140+ @IInstantiationService private readonly instantiationService : IInstantiationService ,
141+ @IContextKeyService private readonly contextKeyService : IContextKeyService
139142 ) {
140143 super ( ) ;
141144
142145 this . domNode = this . createChatTodoWidget ( ) ;
146+
147+ // Listen to context key changes to update clear button state when request state changes
148+ this . _register ( this . contextKeyService . onDidChangeContext ( e => {
149+ if ( e . affectsSome ( new Set ( [ ChatContextKeys . requestInProgress . key ] ) ) ) {
150+ this . updateClearButtonState ( ) ;
151+ }
152+ } ) ) ;
143153 }
144154
145155 public get height ( ) : number {
@@ -150,11 +160,12 @@ export class ChatTodoListWidget extends Disposable {
150160 const container = dom . $ ( '.chat-todo-list-widget' ) ;
151161 container . style . display = 'none' ;
152162
153- this . expandoElement = dom . $ ( '.todo-list-expand' ) ;
154- this . expandoElement . setAttribute ( 'role' , 'button' ) ;
155- this . expandoElement . setAttribute ( 'aria-expanded' , 'true' ) ;
156- this . expandoElement . setAttribute ( 'tabindex' , '0' ) ;
157- this . expandoElement . setAttribute ( 'aria-controls' , 'todo-list-container' ) ;
163+ const expandoContainer = dom . $ ( '.todo-list-expand' ) ;
164+ this . expandoButton = this . _register ( new Button ( expandoContainer , {
165+ supportIcons : true
166+ } ) ) ;
167+ this . expandoButton . element . setAttribute ( 'aria-expanded' , String ( this . _isExpanded ) ) ;
168+ this . expandoButton . element . setAttribute ( 'aria-controls' , 'todo-list-container' ) ;
158169
159170 // Create title section to group icon and title
160171 const titleSection = dom . $ ( '.todo-list-title-section' ) ;
@@ -174,37 +185,28 @@ export class ChatTodoListWidget extends Disposable {
174185 titleSection . appendChild ( expandIcon ) ;
175186 titleSection . appendChild ( titleElement ) ;
176187
177- this . expandoElement . appendChild ( titleSection ) ;
178- this . expandoElement . appendChild ( this . clearButtonContainer ) ;
188+ this . expandoButton . element . appendChild ( titleSection ) ;
189+ this . expandoButton . element . appendChild ( this . clearButtonContainer ) ;
179190
180191 this . todoListContainer = dom . $ ( '.todo-list-container' ) ;
181192 this . todoListContainer . style . display = this . _isExpanded ? 'block' : 'none' ;
182193 this . todoListContainer . id = 'todo-list-container' ;
183194 this . todoListContainer . setAttribute ( 'role' , 'list' ) ;
184195 this . todoListContainer . setAttribute ( 'aria-labelledby' , 'todo-list-title' ) ;
185196
186- container . appendChild ( this . expandoElement ) ;
197+ container . appendChild ( expandoContainer ) ;
187198 container . appendChild ( this . todoListContainer ) ;
188199
189- this . _register ( dom . addDisposableListener ( this . expandoElement , 'click' , ( ) => {
200+ this . _register ( this . expandoButton . onDidClick ( ( ) => {
190201 this . toggleExpanded ( ) ;
191202 } ) ) ;
192203
193- this . _register ( dom . addDisposableListener ( this . expandoElement , 'keydown' , ( e ) => {
194- if ( e . key === 'Enter' || e . key === ' ' ) {
195- e . preventDefault ( ) ;
196- this . toggleExpanded ( ) ;
197- }
198- } ) ) ;
199-
200204 return container ;
201205 }
202206
203207 private createClearButton ( ) : void {
204208 this . clearButton = new Button ( this . clearButtonContainer , {
205209 supportIcons : true ,
206- title : localize ( 'chat.todoList.clearButton' , 'Clear all todos' ) ,
207- ariaLabel : localize ( 'chat.todoList.clearButton.ariaLabel' , 'Clear all todos' )
208210 } ) ;
209211 this . clearButton . element . tabIndex = 0 ;
210212 this . clearButton . icon = Codicon . clearAll ;
@@ -262,7 +264,7 @@ export class ChatTodoListWidget extends Disposable {
262264 }
263265
264266 private renderTodoList ( todoList : IChatTodo [ ] ) : void {
265- const titleElement = this . expandoElement . querySelector ( '.todo-list-title' ) as HTMLElement ;
267+ const titleElement = this . expandoButton . element . querySelector ( '.todo-list-title' ) as HTMLElement ;
266268 if ( titleElement ) {
267269 this . updateTitleElement ( titleElement , todoList ) ;
268270 }
@@ -307,13 +309,16 @@ export class ChatTodoListWidget extends Disposable {
307309 const hasInProgressTask = todoList . some ( todo => todo . status === 'in-progress' ) ;
308310 const hasCompletedTask = todoList . some ( todo => todo . status === 'completed' ) ;
309311
312+ // Update clear button state based on request progress
313+ this . updateClearButtonState ( ) ;
314+
310315 // Only auto-collapse if there are in-progress or completed tasks AND user hasn't manually expanded
311316 if ( ( hasInProgressTask || hasCompletedTask ) && this . _isExpanded && ! this . _userManuallyExpanded ) {
312317 this . _isExpanded = false ;
313- this . expandoElement . setAttribute ( 'aria-expanded' , 'false' ) ;
318+ this . expandoButton . element . setAttribute ( 'aria-expanded' , 'false' ) ;
314319 this . todoListContainer . style . display = 'none' ;
315320
316- const expandIcon = this . expandoElement . querySelector ( '.expand-icon' ) as HTMLElement ;
321+ const expandIcon = this . expandoButton . element . querySelector ( '.expand-icon' ) as HTMLElement ;
317322 if ( expandIcon ) {
318323 expandIcon . classList . remove ( 'codicon-chevron-down' ) ;
319324 expandIcon . classList . add ( 'codicon-chevron-right' ) ;
@@ -328,7 +333,7 @@ export class ChatTodoListWidget extends Disposable {
328333 this . _isExpanded = ! this . _isExpanded ;
329334 this . _userManuallyExpanded = true ;
330335
331- const expandIcon = this . expandoElement . querySelector ( '.expand-icon' ) as HTMLElement ;
336+ const expandIcon = this . expandoButton . element . querySelector ( '.expand-icon' ) as HTMLElement ;
332337 if ( expandIcon ) {
333338 expandIcon . classList . toggle ( 'codicon-chevron-down' , this . _isExpanded ) ;
334339 expandIcon . classList . toggle ( 'codicon-chevron-right' , ! this . _isExpanded ) ;
@@ -338,7 +343,7 @@ export class ChatTodoListWidget extends Disposable {
338343
339344 if ( this . _currentSessionId ) {
340345 const todoList = this . chatTodoListService . getTodos ( this . _currentSessionId ) ;
341- const titleElement = this . expandoElement . querySelector ( '.todo-list-title' ) as HTMLElement ;
346+ const titleElement = this . expandoButton . element . querySelector ( '.todo-list-title' ) as HTMLElement ;
342347 if ( titleElement ) {
343348 this . updateTitleElement ( titleElement , todoList ) ;
344349 }
@@ -357,6 +362,26 @@ export class ChatTodoListWidget extends Disposable {
357362 this . _onDidChangeHeight . fire ( ) ;
358363 }
359364
365+ private updateClearButtonState ( ) : void {
366+ if ( ! this . _currentSessionId ) {
367+ return ;
368+ }
369+
370+ const todoList = this . chatTodoListService . getTodos ( this . _currentSessionId ) ;
371+ const hasInProgressTask = todoList . some ( todo => todo . status === 'in-progress' ) ;
372+ const isRequestInProgress = ChatContextKeys . requestInProgress . getValue ( this . contextKeyService ) ?? false ;
373+ const shouldDisable = isRequestInProgress && hasInProgressTask ;
374+
375+ this . clearButton . enabled = ! shouldDisable ;
376+
377+ // Update tooltip based on state
378+ if ( shouldDisable ) {
379+ this . clearButton . setTitle ( localize ( 'chat.todoList.clearButton.disabled' , 'Cannot clear todos while a task is in progress' ) ) ;
380+ } else {
381+ this . clearButton . setTitle ( localize ( 'chat.todoList.clearButton' , 'Clear all todos' ) ) ;
382+ }
383+ }
384+
360385 private updateTitleElement ( titleElement : HTMLElement , todoList : IChatTodo [ ] ) : void {
361386 titleElement . textContent = '' ;
362387
@@ -371,8 +396,8 @@ export class ChatTodoListWidget extends Disposable {
371396 const expandButtonLabel = this . _isExpanded
372397 ? localize ( 'chat.todoList.collapseButton' , 'Collapse Todos' )
373398 : localize ( 'chat.todoList.expandButton' , 'Expand Todos' ) ;
374- this . expandoElement . setAttribute ( 'aria-label' , expandButtonLabel ) ;
375- this . expandoElement . setAttribute ( 'aria-expanded' , this . _isExpanded ? 'true' : 'false' ) ;
399+ this . expandoButton . element . setAttribute ( 'aria-label' , expandButtonLabel ) ;
400+ this . expandoButton . element . setAttribute ( 'aria-expanded' , this . _isExpanded ? 'true' : 'false' ) ;
376401
377402 if ( this . _isExpanded ) {
378403 const titleText = dom . $ ( 'span' ) ;
0 commit comments