@@ -122,6 +122,11 @@ export class NextEditWindowManager {
122122 private editableRegionStartLine : number = 0 ;
123123 private editableRegionEndLine : number = 0 ;
124124
125+ // State tracking for key reservation.
126+ // By default it is set to free, and is only set to reserved when the transition is done.
127+ private keyReservationState : "free" | "reserved" | "transitioning" = "free" ;
128+ private latestOperationId = 0 ;
129+
125130 // Disposables
126131 private disposables : vscode . Disposable [ ] = [ ] ;
127132
@@ -170,20 +175,71 @@ export class NextEditWindowManager {
170175 this . loggingService = NextEditLoggingService . getInstance ( ) ;
171176 }
172177
178+ // This is an implementation of last-action-wins.
179+ // For each action that fires setKeyReservation, it keeps its own operationId while incrementing latestOperationId.
180+ // When an action completes, checking for operationId === latestOperationId will determine which one came last.
181+ private async setKeyReservation ( reserve : boolean ) : Promise < void > {
182+ // Increment and capture this operation's ID.
183+ const operationId = ++ this . latestOperationId ;
184+
185+ // Return early when already in desired state.
186+ if (
187+ ( reserve && this . keyReservationState === "reserved" ) ||
188+ ( ! reserve && this . keyReservationState === "free" )
189+ ) {
190+ return ;
191+ }
192+
193+ try {
194+ await this . performKeyReservation ( reserve ) ;
195+
196+ // Only update state if we're still the latest operation.
197+ if ( operationId === this . latestOperationId ) {
198+ this . keyReservationState = reserve ? "reserved" : "free" ;
199+ }
200+ } catch ( err ) {
201+ console . error ( `Failed to set nextEditWindowActive to ${ reserve } : ${ err } ` ) ;
202+
203+ // Only reset to free if we're still the latest operation.
204+ if ( operationId === this . latestOperationId ) {
205+ this . keyReservationState = "free" ;
206+ }
207+ throw err ;
208+ }
209+ }
210+
211+ public async resetKeyReservation ( ) : Promise < void > {
212+ // Reset internal tracking.
213+ this . keyReservationState = "free" ;
214+ this . latestOperationId = 0 ;
215+
216+ // Ensure VS Code context matches.
217+ try {
218+ await this . performKeyReservation ( false ) ;
219+ } catch ( err ) {
220+ console . error ( `Failed to reset nextEditWindowActive context: ${ err } ` ) ;
221+ }
222+ }
223+
224+ private async performKeyReservation ( reserve : boolean ) : Promise < void > {
225+ try {
226+ await vscode . commands . executeCommand (
227+ "setContext" ,
228+ "nextEditWindowActive" ,
229+ reserve ,
230+ ) ;
231+ } catch ( err ) {
232+ console . error ( `Failed to set nextEditWindowActive to ${ reserve } : ${ err } ` ) ;
233+ throw err ;
234+ }
235+ }
236+
173237 public static async reserveTabAndEsc ( ) {
174- await vscode . commands . executeCommand (
175- "setContext" ,
176- "nextEditWindowActive" ,
177- true ,
178- ) ;
238+ await NextEditWindowManager . getInstance ( ) . setKeyReservation ( true ) ;
179239 }
180240
181241 public static async freeTabAndEsc ( ) {
182- await vscode . commands . executeCommand (
183- "setContext" ,
184- "nextEditWindowActive" ,
185- false ,
186- ) ;
242+ await NextEditWindowManager . getInstance ( ) . setKeyReservation ( false ) ;
187243 }
188244
189245 /**
@@ -202,7 +258,8 @@ export class NextEditWindowManager {
202258
203259 // Set nextEditWindowActive to false to free esc and tab,
204260 // letting them return to their original behaviors.
205- await NextEditWindowManager . freeTabAndEsc ( ) ;
261+ await this . resetKeyReservation ( ) ;
262+ // await NextEditWindowManager.freeTabAndEsc();
206263
207264 // Register HIDE_TOOLTIP_COMMAND and ACCEPT_NEXT_EDIT_COMMAND with their corresponding callbacks.
208265 this . registerCommandSafely ( HIDE_NEXT_EDIT_SUGGESTION_COMMAND , async ( ) => {
@@ -353,29 +410,48 @@ export class NextEditWindowManager {
353410
354411 // Create and apply decoration with the text.
355412 if ( newEditRangeSlice !== "" ) {
356- await this . renderWindow (
357- editor ,
358- currCursorPos ,
359- oldEditRangeSlice ,
360- newEditRangeSlice ,
361- this . editableRegionStartLine ,
362- diffLines ,
363- ) ;
413+ try {
414+ await this . renderWindow (
415+ editor ,
416+ currCursorPos ,
417+ oldEditRangeSlice ,
418+ newEditRangeSlice ,
419+ this . editableRegionStartLine ,
420+ diffLines ,
421+ ) ;
422+ } catch ( error ) {
423+ console . error ( "Failed to render window:" , error ) ;
424+ // Clean up and reset state.
425+ await this . hideAllNextEditWindows ( ) ;
426+ return ;
427+ }
364428 }
365429
366430 const diffChars = myersCharDiff ( oldEditRangeSlice , newEditRangeSlice ) ;
367431
368432 this . renderDeletions ( editor , diffChars ) ;
369433
370434 // Reserve tab and esc to either accept or reject the displayed next edit contents.
371- await NextEditWindowManager . reserveTabAndEsc ( ) ;
435+ try {
436+ await NextEditWindowManager . reserveTabAndEsc ( ) ;
437+ } catch ( err ) {
438+ console . error (
439+ `Error reserving Tab/Esc after showing decorations: ${ err } ` ,
440+ ) ;
441+ await this . hideAllNextEditWindows ( ) ;
442+ return ;
443+ }
372444 }
373445
374446 /**
375447 * Hide all tooltips in all editors.
376448 */
377449 public async hideAllNextEditWindows ( ) {
378- await NextEditWindowManager . freeTabAndEsc ( ) ;
450+ try {
451+ await NextEditWindowManager . freeTabAndEsc ( ) ;
452+ } catch ( err ) {
453+ console . error ( `Error freeing Tab/Esc while hiding: ${ err } ` ) ;
454+ }
379455
380456 if ( this . currentDecoration ) {
381457 vscode . window . visibleTextEditors . forEach ( ( editor ) => {
@@ -502,6 +578,10 @@ export class NextEditWindowManager {
502578 * Dispose of the NextEditWindowManager.
503579 */
504580 public dispose ( ) {
581+ void this . resetKeyReservation ( ) . catch ( ( err ) =>
582+ console . error ( `Failed to reset keys on dispose: ${ err } ` ) ,
583+ ) ;
584+
505585 // Dispose current decoration.
506586 if ( this . currentDecoration ) {
507587 this . currentDecoration . dispose ( ) ;
0 commit comments