Skip to content

Jacob/enhancement/nextedit perf #6661

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 48 additions & 53 deletions extensions/vscode/src/activation/NextEditWindowManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ import * as vscode from "vscode";
import { DiffChar, DiffLine } from "core";
import { CodeRenderer } from "core/codeRenderer/CodeRenderer";
import { myersCharDiff } from "core/diff/myers";
import {
NEXT_EDIT_EDITABLE_REGION_BOTTOM_MARGIN,
NEXT_EDIT_EDITABLE_REGION_TOP_MARGIN,
} from "core/nextEdit/constants";
import { getOffsetPositionAtLastNewLine } from "core/nextEdit/diff/diff";
import { NextEditLoggingService } from "core/nextEdit/NextEditLoggingService";
import { getThemeString } from "../util/getTheme";
Expand Down Expand Up @@ -117,6 +113,9 @@ export class NextEditWindowManager {
// Track for logging purposes.
private loggingService: NextEditLoggingService;
private mostRecentCompletionId: string | null = null;
// Helps us skip redundant calculations. No need for cleanup because this always gets reassigned with new values at showNextEditWindow, and we don't reuse windows.
private editableRegionStartLine: number = 0;
private editableRegionEndLine: number = 0;

// Disposables
private disposables: vscode.Disposable[] = [];
Expand Down Expand Up @@ -261,6 +260,7 @@ export class NextEditWindowManager {
editor: vscode.TextEditor,
currCursorPos: vscode.Position,
editableRegionStartLine: number,
editableRegionEndLine: number,
oldEditRangeSlice: string,
newEditRangeSlice: string,
diffLines: DiffLine[],
Expand All @@ -272,11 +272,15 @@ export class NextEditWindowManager {
// Clear any existing decorations first (very important to prevent overlapping).
await this.hideAllNextEditWindows();

this.editableRegionStartLine = editableRegionStartLine;
this.editableRegionEndLine = editableRegionEndLine;

// Store the current tooltip text for accepting later.
this.currentTooltipText = newEditRangeSlice;

// How far away is the current line from the start of the editable region?
const lineOffsetAtCursorPos = currCursorPos.line - editableRegionStartLine;
const lineOffsetAtCursorPos =
currCursorPos.line - this.editableRegionStartLine;

// How long is the line at the current cursor position?
const lineContentAtCursorPos =
Expand All @@ -291,7 +295,7 @@ export class NextEditWindowManager {
// Calculate the actual line number in the editor by adding the startPos offset
// to the line number from the diff calculation.
this.finalCursorPos = new vscode.Position(
editableRegionStartLine + offset.line,
this.editableRegionStartLine + offset.line,
offset.character,
);

Expand All @@ -301,13 +305,13 @@ export class NextEditWindowManager {
currCursorPos,
oldEditRangeSlice,
newEditRangeSlice,
editableRegionStartLine,
this.editableRegionStartLine,
diffLines,
);

const diffChars = myersCharDiff(oldEditRangeSlice, newEditRangeSlice);

this.renderDeletes(editor, editableRegionStartLine, diffChars);
this.renderDeletions(editor, diffChars);

// Reserve tab and esc to either accept or reject the displayed next edit contents.
await NextEditWindowManager.reserveTabAndEsc();
Expand All @@ -317,6 +321,8 @@ export class NextEditWindowManager {
* Hide all tooltips in all editors.
*/
public async hideAllNextEditWindows() {
await NextEditWindowManager.freeTabAndEsc();

if (this.currentDecoration) {
vscode.window.visibleTextEditors.forEach((editor) => {
editor.setDecorations(this.currentDecoration!, []);
Expand All @@ -339,19 +345,18 @@ export class NextEditWindowManager {
// Clear the current tooltip text.
this.currentTooltipText = null;
}

await NextEditWindowManager.freeTabAndEsc();
}

public async hideAllNextEditWindowsAndResetCompletionId() {
this.hideAllNextEditWindows();
await this.hideAllNextEditWindows();

// Log with accept = false.
await vscode.commands.executeCommand(
"continue.logNextEditOutcomeReject",
this.mostRecentCompletionId,
this.loggingService,
);

this.mostRecentCompletionId = null;
}

Expand All @@ -369,6 +374,10 @@ export class NextEditWindowManager {
const position = editor.selection.active;

let success = false;

// Hide windows first for a snappier feel.
await this.hideAllNextEditWindows();

if (this.textApplier) {
success = await this.textApplier.applyText(
editor,
Expand All @@ -377,20 +386,15 @@ export class NextEditWindowManager {
this.finalCursorPos,
);
} else {
// Define the editable region.
const editableRegionStartLine = Math.max(
0,
position.line - NEXT_EDIT_EDITABLE_REGION_TOP_MARGIN,
);
const editableRegionEndLine = Math.min(
editor.document.lineCount - 1,
position.line + NEXT_EDIT_EDITABLE_REGION_BOTTOM_MARGIN,
);
const startPos = new vscode.Position(editableRegionStartLine, 0);
const endPosChar = editor.document.lineAt(editableRegionEndLine).text
// Define the range to replace.
const startPos = new vscode.Position(this.editableRegionStartLine, 0);
const endPosChar = editor.document.lineAt(this.editableRegionEndLine).text
.length;

const endPos = new vscode.Position(editableRegionEndLine, endPosChar);
const endPos = new vscode.Position(
this.editableRegionEndLine,
endPosChar,
);
const editRange = new vscode.Range(startPos, endPos);

success = await editor.edit((editBuilder) => {
Expand All @@ -405,23 +409,19 @@ export class NextEditWindowManager {
.update("editor.inlineSuggest.enabled", false, true);
}

if (success) {
if (success && this.finalCursorPos) {
// Move cursor to the final position if available.
if (this.finalCursorPos) {
editor.selection = new vscode.Selection(
this.finalCursorPos,
this.finalCursorPos,
);

// Reenable inline suggestions after we move the cursor.
await vscode.workspace
.getConfiguration()
.update("editor.inlineSuggest.enabled", true, true);

await this.hideAllNextEditWindows();
}
editor.selection = new vscode.Selection(
this.finalCursorPos,
this.finalCursorPos,
);
}

// Reenable inline suggestions.
await vscode.workspace
.getConfiguration()
.update("editor.inlineSuggest.enabled", true, true);

// Log with accept = true.
await vscode.commands.executeCommand(
"continue.logNextEditOutcomeAccept",
Expand Down Expand Up @@ -454,7 +454,7 @@ export class NextEditWindowManager {
*/
private setupListeners() {
// Theme change listener.
vscode.workspace.onDidChangeConfiguration((e) => {
vscode.workspace.onDidChangeConfiguration(async (e) => {
if (
e.affectsConfiguration("workbench.colorTheme") ||
e.affectsConfiguration("editor.fontSize") ||
Expand All @@ -467,7 +467,7 @@ export class NextEditWindowManager {
e.affectsConfiguration("workbench.preferredHighContrastLightColorTheme")
) {
this.theme = getThemeString();
this.codeRenderer.setTheme(this.theme);
await this.codeRenderer.setTheme(this.theme);
console.log(
"Theme updated:",
this.theme ? "Theme exists" : "Theme is undefined",
Expand All @@ -479,9 +479,9 @@ export class NextEditWindowManager {
});

// Listen for active color theme changes.
vscode.window.onDidChangeActiveColorTheme(() => {
vscode.window.onDidChangeActiveColorTheme(async () => {
this.theme = getThemeString();
this.codeRenderer.setTheme(this.theme);
await this.codeRenderer.setTheme(this.theme);
console.log(
"Active theme changed:",
this.theme ? "Theme exists" : "Theme is undefined",
Expand All @@ -495,10 +495,11 @@ export class NextEditWindowManager {
this.activeEditor &&
!vscode.window.visibleTextEditors.includes(this.activeEditor)
) {
if (this.mostRecentCompletionId)
if (this.mostRecentCompletionId) {
this.loggingService.cancelRejectionTimeout(
this.mostRecentCompletionId,
);
}
await this.hideAllNextEditWindows();
}
});
Expand All @@ -508,10 +509,11 @@ export class NextEditWindowManager {
// If the selection changed in our active editor, hide the tooltip.
if (this.activeEditor && e.textEditor === this.activeEditor) {
// If the cursor moved because of something other than accepting next edit, stop logging it.
if (!this.accepted && this.mostRecentCompletionId)
if (!this.accepted && this.mostRecentCompletionId) {
this.loggingService.cancelRejectionTimeout(
this.mostRecentCompletionId,
);
}
await this.hideAllNextEditWindows();
}
});
Expand Down Expand Up @@ -747,7 +749,6 @@ export class NextEditWindowManager {
}

// Store the decoration and editor.
await this.hideAllNextEditWindows();
this.currentDecoration = decoration; // TODO: This might be redundant.
this.disposables.push(decoration);
this.activeEditor = editor;
Expand Down Expand Up @@ -786,13 +787,7 @@ export class NextEditWindowManager {
);
}

private renderDeletes(
editor: vscode.TextEditor,
editableRegionStartLine: number,
// oldEditRangeSlice: string,
// newEditRangeSlice: string,
oldDiffChars: DiffChar[],
) {
private renderDeletions(editor: vscode.TextEditor, oldDiffChars: DiffChar[]) {
const charsToDelete: vscode.DecorationOptions[] = [];

// const diffChars = myersCharDiff(oldEditRangeSlice, newEditRangeSlice);
Expand All @@ -803,11 +798,11 @@ export class NextEditWindowManager {
charsToDelete.push({
range: new vscode.Range(
new vscode.Position(
editableRegionStartLine + diff.oldLineIndex!,
this.editableRegionStartLine + diff.oldLineIndex!,
diff.oldCharIndexInLine!,
),
new vscode.Position(
editableRegionStartLine + diff.oldLineIndex!,
this.editableRegionStartLine + diff.oldLineIndex!,
diff.oldCharIndexInLine! + diff.char.length,
),
),
Expand Down
1 change: 1 addition & 0 deletions extensions/vscode/src/autocomplete/completionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ export class ContinueCompletionProvider
editor,
currCursorPos,
editableRegionStartLine,
editableRegionEndLine,
oldEditRangeSlice,
newEditRangeSlice,
diffLines,
Expand Down
Loading