Skip to content

Commit 837f4dd

Browse files
authored
Merge pull request #7268 from continuedev/jacob/con-3594
fix: tab and esc are properly reserved
2 parents 5c11b12 + fc0dea6 commit 837f4dd

File tree

3 files changed

+1079
-24
lines changed

3 files changed

+1079
-24
lines changed

extensions/vscode/package-lock.json

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/vscode/src/activation/NextEditWindowManager.ts

Lines changed: 101 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)