@@ -17,11 +17,13 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
1717import { HasSpeechProvider , ISpeechService , SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService' ;
1818import { CancellationTokenSource } from 'vs/base/common/cancellation' ;
1919import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController' ;
20- import { getWindow , h , reset , runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom' ;
20+ import * as dom from 'vs/base/browser/dom' ;
2121import { IDimension } from 'vs/editor/common/core/dimension' ;
2222import { EditorOption } from 'vs/editor/common/config/editorOptions' ;
2323import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions' ;
2424import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding' ;
25+ import { Emitter , Event } from 'vs/base/common/event' ;
26+ import { DisposableStore } from 'vs/base/common/lifecycle' ;
2527
2628const CTX_QUICK_CHAT_IN_PROGRESS = new RawContextKey < boolean > ( 'inlineChat.quickChatInProgress' , false ) ;
2729
@@ -107,17 +109,27 @@ class QuickVoiceWidget implements IContentWidget {
107109 readonly suppressMouseDown = true ;
108110
109111 private readonly _domNode = document . createElement ( 'div' ) ;
110- private readonly _elements = h ( '.inline-chat-quick-voice@main' , [
111- h ( 'span@mic' ) ,
112- h ( 'span.message@message' ) ,
112+ private readonly _elements = dom . h ( '.inline-chat-quick-voice@main' , [
113+ dom . h ( 'span@mic' ) ,
114+ dom . h ( 'span.message@message' ) ,
113115 ] ) ;
114116
117+ private _focusTracker : dom . IFocusTracker | undefined ;
118+
119+ private readonly _onDidBlur = new Emitter < void > ( ) ;
120+ readonly onDidBlur : Event < void > = this . _onDidBlur . event ;
121+
115122 constructor ( private readonly _editor : ICodeEditor ) {
116123 this . _domNode . appendChild ( this . _elements . root ) ;
117124 this . _domNode . style . zIndex = '1000' ;
118125 this . _domNode . tabIndex = - 1 ;
119126 this . _domNode . style . outline = 'none' ;
120- reset ( this . _elements . mic , renderIcon ( Codicon . micFilled ) ) ;
127+ dom . reset ( this . _elements . mic , renderIcon ( Codicon . micFilled ) ) ;
128+ }
129+
130+ dispose ( ) : void {
131+ this . _focusTracker ?. dispose ( ) ;
132+ this . _onDidBlur . dispose ( ) ;
121133 }
122134
123135 getId ( ) : string {
@@ -150,24 +162,33 @@ class QuickVoiceWidget implements IContentWidget {
150162 return null ;
151163 }
152164
165+ afterRender ( ) : void {
166+ this . _domNode . focus ( ) ;
167+ this . _focusTracker ?. dispose ( ) ;
168+ this . _focusTracker = dom . trackFocus ( this . _domNode ) ;
169+ this . _focusTracker . onDidBlur ( ( ) => this . _onDidBlur . fire ( ) ) ;
170+ }
171+
153172 // ---
154173
155174 updateInput ( input : string | undefined , isDefinite : boolean ) : void {
156175 this . _elements . message . classList . toggle ( 'preview' , ! isDefinite ) ;
157176 this . _elements . message . textContent = input ?? '' ;
158177 }
159178
160- focus ( ) : void {
161- this . _domNode . focus ( ) ;
179+ show ( ) {
180+ this . _editor . addContentWidget ( this ) ;
162181 }
163182
164183 active ( ) : void {
165184 this . _elements . main . classList . add ( 'recording' ) ;
166185 }
167186
168- reset ( ) : void {
187+ hide ( ) {
169188 this . _elements . main . classList . remove ( 'recording' ) ;
170189 this . updateInput ( undefined , true ) ;
190+ this . _editor . removeContentWidget ( this ) ;
191+ this . _focusTracker ?. dispose ( ) ;
171192 }
172193}
173194
@@ -179,6 +200,7 @@ export class InlineChatQuickVoice implements IEditorContribution {
179200 return editor . getContribution < InlineChatQuickVoice > ( InlineChatQuickVoice . ID ) ;
180201 }
181202
203+ private readonly _store = new DisposableStore ( ) ;
182204 private readonly _ctxQuickChatInProgress : IContextKey < boolean > ;
183205 private readonly _widget : QuickVoiceWidget ;
184206 private _finishCallback ?: ( abort : boolean ) => void ;
@@ -188,24 +210,25 @@ export class InlineChatQuickVoice implements IEditorContribution {
188210 @ISpeechService private readonly _speechService : ISpeechService ,
189211 @IContextKeyService contextKeyService : IContextKeyService ,
190212 ) {
191- this . _widget = new QuickVoiceWidget ( this . _editor ) ;
213+ this . _widget = this . _store . add ( new QuickVoiceWidget ( this . _editor ) ) ;
214+ this . _widget . onDidBlur ( ( ) => this . _finishCallback ?.( true ) , undefined , this . _store ) ;
192215 this . _ctxQuickChatInProgress = CTX_QUICK_CHAT_IN_PROGRESS . bindTo ( contextKeyService ) ;
193216 }
194217
195218 dispose ( ) : void {
196219 this . _finishCallback ?.( true ) ;
220+ this . _ctxQuickChatInProgress . reset ( ) ;
221+ this . _store . dispose ( ) ;
197222 }
198223
199224 start ( ) {
200225
226+ this . _finishCallback ?.( true ) ;
227+
201228 const cts = new CancellationTokenSource ( ) ;
202- this . _editor . addContentWidget ( this . _widget ) ;
229+ this . _widget . show ( ) ;
203230 this . _ctxQuickChatInProgress . set ( true ) ;
204231
205- runAtThisOrScheduleAtNextAnimationFrame ( getWindow ( this . _widget . getDomNode ( ) ) , ( ) => {
206- this . _widget . focus ( ) ; // requires RAF because...
207- } ) ;
208-
209232 let message : string | undefined ;
210233 const session = this . _speechService . createSpeechToTextSession ( cts . token ) ;
211234 const listener = session . onDidChange ( e => {
@@ -233,8 +256,7 @@ export class InlineChatQuickVoice implements IEditorContribution {
233256 const done = ( abort : boolean ) => {
234257 cts . dispose ( true ) ;
235258 listener . dispose ( ) ;
236- this . _widget . reset ( ) ;
237- this . _editor . removeContentWidget ( this . _widget ) ;
259+ this . _widget . hide ( ) ;
238260 this . _ctxQuickChatInProgress . reset ( ) ;
239261
240262 if ( ! abort && message ) {
@@ -252,5 +274,4 @@ export class InlineChatQuickVoice implements IEditorContribution {
252274 cancel ( ) : void {
253275 this . _finishCallback ?.( true ) ;
254276 }
255-
256277}
0 commit comments