@@ -39,6 +39,7 @@ import './media/chatQuestionCarousel.css';
3939
4040const PREVIOUS_QUESTION_ACTION_ID = 'workbench.action.chat.previousQuestion' ;
4141const NEXT_QUESTION_ACTION_ID = 'workbench.action.chat.nextQuestion' ;
42+ const QUESTION_TITLE_TRUNCATION_THRESHOLD = 200 ;
4243export interface IChatQuestionCarouselOptions {
4344 onSubmit : ( answers : Map < string , IChatQuestionAnswerValue > | undefined ) => void ;
4445 shouldAutoFocus ?: boolean ;
@@ -72,6 +73,10 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
7273 private _skipAllButton : Button | undefined ;
7374
7475 private _isSkipped = false ;
76+ private _isQuestionTextExpanded = false ;
77+ private _expandButton : Button | undefined ;
78+ private _renderQuestionTitle : ( ( expanded : boolean ) => void ) | undefined ;
79+ private _questionTitleIsLong = false ;
7580
7681 private readonly _textInputBoxes : Map < string , InputBox > = new Map ( ) ;
7782 private readonly _singleSelectItems : Map < string , { items : HTMLElement [ ] ; selectedIndex : number ; optionIndices : number [ ] } > = new Map ( ) ;
@@ -167,6 +172,14 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
167172 collapseButton . element . setAttribute ( 'aria-label' , collapseToggleTitle ) ;
168173 this . _collapseButton = collapseButton ;
169174
175+ // Expand button for showing/hiding full question text
176+ const expandButton = interactiveStore . add ( new Button ( this . _headerActionsContainer , { ...defaultButtonStyles , secondary : true , supportIcons : true } ) ) ;
177+ expandButton . element . classList . add ( 'chat-question-collapse-toggle' ) ;
178+ this . _expandButton = expandButton ;
179+ this . _isQuestionTextExpanded = false ;
180+ this . updateExpandButton ( ) ;
181+ interactiveStore . add ( expandButton . onDidClick ( ( ) => this . toggleQuestionTextExpanded ( ) ) ) ;
182+
170183 // Close/skip button (X) - placed in header row, only shown when allowSkip is true
171184 if ( carousel . allowSkip ) {
172185 this . _closeButtonContainer = dom . $ ( '.chat-question-close-container' ) ;
@@ -312,6 +325,31 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
312325 }
313326 }
314327
328+ private toggleQuestionTextExpanded ( ) : void {
329+ this . _isQuestionTextExpanded = ! this . _isQuestionTextExpanded ;
330+ if ( this . _renderQuestionTitle ) {
331+ this . _renderQuestionTitle ( this . _isQuestionTextExpanded ) ;
332+ }
333+ this . updateExpandButton ( ) ;
334+ this . _onDidChangeHeight . fire ( ) ;
335+ }
336+
337+ private updateExpandButton ( ) : void {
338+ if ( ! this . _expandButton ) {
339+ return ;
340+ }
341+ const expanded = this . _isQuestionTextExpanded ;
342+ const label = expanded
343+ ? localize ( 'chat.questionCarousel.collapseQuestionText' , "Collapse Question Text" )
344+ : localize ( 'chat.questionCarousel.expandQuestionText' , "Expand Question Text" ) ;
345+ this . _expandButton . label = expanded
346+ ? `$(${ Codicon . screenNormal . id } )`
347+ : `$(${ Codicon . screenFull . id } )` ;
348+ this . _expandButton . element . setAttribute ( 'aria-label' , label ) ;
349+ this . _expandButton . element . setAttribute ( 'aria-expanded' , String ( expanded ) ) ;
350+ this . _expandButton . setTitle ( label ) ;
351+ }
352+
315353 /**
316354 * Navigates the carousel by the given delta.
317355 * @param delta Negative for previous, positive for next
@@ -425,6 +463,7 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
425463 this . _closeButtonContainer = undefined ;
426464 this . _focusTerminalButtonContainer = undefined ;
427465 this . _collapseButton = undefined ;
466+ this . _expandButton = undefined ;
428467 this . _footerRow = undefined ;
429468 this . _stepIndicator = undefined ;
430469 this . _submitHint = undefined ;
@@ -664,6 +703,12 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
664703 // Clear previous content
665704 dom . clearNode ( this . _questionContainer ) ;
666705
706+ // Reset expand state for new question
707+ this . _isQuestionTextExpanded = false ;
708+ this . _renderQuestionTitle = undefined ;
709+ this . _questionTitleIsLong = false ;
710+ this . updateExpandButton ( ) ;
711+
667712 const question = this . carousel . questions [ this . _currentIndex ] ;
668713 if ( ! question ) {
669714 return ;
@@ -687,20 +732,39 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent
687732 const title = dom . $ ( '.chat-question-title' ) ;
688733 const messageContent = this . getQuestionText ( questionText ) ;
689734 title . setAttribute ( 'aria-label' , messageContent ) ;
690- questionRenderStore . add ( this . _hoverService . setupDelayedHover ( title , { content : messageContent } ) ) ;
691735
692- const titleText = question . required
693- ? new MarkdownString ( `${ isMarkdownString ( questionText ) ? questionText . value : questionText } *` )
694- : ( isMarkdownString ( questionText ) ? MarkdownString . lift ( questionText ) : new MarkdownString ( questionText ) ) ;
695- const renderedTitle = questionRenderStore . add ( this . _markdownRendererService . render ( titleText ) ) ;
696- title . appendChild ( renderedTitle . element ) ;
736+ const isLong = messageContent . length > QUESTION_TITLE_TRUNCATION_THRESHOLD ;
737+ const renderTitle = ( expanded : boolean ) => {
738+ dom . clearNode ( title ) ;
739+ const rawValue = isMarkdownString ( questionText ) ? questionText . value : questionText ;
740+ const displayValue = ( isLong && ! expanded )
741+ ? rawValue . slice ( 0 , QUESTION_TITLE_TRUNCATION_THRESHOLD ) . replace ( / \s + \S * $ / , '' ) + '…'
742+ : rawValue ;
743+ const suffixed = question . required ? `${ displayValue } *` : displayValue ;
744+ const md = isMarkdownString ( questionText )
745+ ? new MarkdownString ( suffixed , { isTrusted : questionText . isTrusted , supportThemeIcons : questionText . supportThemeIcons } )
746+ : new MarkdownString ( suffixed ) ;
747+ const rendered = questionRenderStore . add ( this . _markdownRendererService . render ( md ) ) ;
748+ title . appendChild ( rendered . element ) ;
749+ } ;
750+
751+ renderTitle ( this . _isQuestionTextExpanded ) ;
752+ this . _renderQuestionTitle = renderTitle ;
753+ this . _questionTitleIsLong = isLong ;
697754 titleRow . appendChild ( title ) ;
755+ } else {
756+ this . _renderQuestionTitle = undefined ;
757+ this . _questionTitleIsLong = false ;
698758 }
699759
700760 headerRow . appendChild ( titleRow ) ;
701761
702762 if ( this . _headerActionsContainer ) {
703763 dom . clearNode ( this . _headerActionsContainer ) ;
764+ if ( this . _expandButton ) {
765+ this . _headerActionsContainer . appendChild ( this . _expandButton . element ) ;
766+ dom . setVisibility ( this . _questionTitleIsLong , this . _expandButton . element ) ;
767+ }
704768 if ( this . _focusTerminalButtonContainer ) {
705769 this . _headerActionsContainer . appendChild ( this . _focusTerminalButtonContainer ) ;
706770 }
0 commit comments