Skip to content

Commit 8926d41

Browse files
Merge pull request #6963 from continuedev/jacob/con-3141
feat: cache model responses for near-instant suggestions
2 parents 5502729 + 732ff51 commit 8926d41

25 files changed

+3475
-956
lines changed

core/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ export class Core {
580580
const outcome = await this.nextEditProvider.provideInlineCompletionItems(
581581
msg.data,
582582
undefined,
583-
{ withChain: false },
583+
{ withChain: false, usingFullFileDiff: true },
584584
);
585585
return outcome ? [outcome.completion, outcome.originalEditableRange] : [];
586586
});

core/nextEdit/NextEditEditableRegionCalculator.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ import Parser from "web-tree-sitter";
22
import { Chunk, IDE, ILLM, Position, Range, RangeInFile } from "..";
33
import { getAst } from "../autocomplete/util/ast";
44
import { DocumentHistoryTracker } from "./DocumentHistoryTracker";
5+
import {
6+
NEXT_EDIT_EDITABLE_REGION_BOTTOM_MARGIN,
7+
NEXT_EDIT_EDITABLE_REGION_TOP_MARGIN,
8+
} from "./constants";
59

610
export enum EditableRegionStrategy {
711
Naive = "naive",
12+
Sliding = "sliding",
813
Rerank = "rerank",
914
StaticRerank = "staticRerank",
1015
Static = "static",
@@ -17,6 +22,8 @@ export async function getNextEditableRegion(
1722
switch (strategy) {
1823
case EditableRegionStrategy.Naive:
1924
return naiveJump(ctx);
25+
case EditableRegionStrategy.Sliding:
26+
return slidingJump(ctx);
2027
case EditableRegionStrategy.Rerank:
2128
return await rerankJump(ctx);
2229
case EditableRegionStrategy.StaticRerank:
@@ -51,6 +58,63 @@ function naiveJump(ctx: any): RangeInFile[] | null {
5158
];
5259
}
5360

61+
// Sliding splits the file using into sliding window.
62+
function slidingJump(ctx: any): RangeInFile[] | null {
63+
const { fileLines, filepath } = ctx;
64+
if (!fileLines || !filepath) {
65+
console.warn("Missing required context for naive jump");
66+
return null;
67+
}
68+
69+
const windowSize =
70+
NEXT_EDIT_EDITABLE_REGION_TOP_MARGIN +
71+
NEXT_EDIT_EDITABLE_REGION_BOTTOM_MARGIN +
72+
1; // 1 for current line;
73+
74+
if (fileLines.length <= windowSize) {
75+
return [
76+
{
77+
filepath,
78+
range: {
79+
start: { line: 0, character: 0 },
80+
end: {
81+
line: fileLines.length - 1,
82+
character: fileLines[fileLines.length - 1].length,
83+
},
84+
},
85+
},
86+
];
87+
}
88+
89+
const slidingStep = Math.max(1, Math.floor(windowSize / 2));
90+
const ranges: RangeInFile[] = [];
91+
92+
for (
93+
let startLine = 0;
94+
startLine < fileLines.length;
95+
startLine += slidingStep
96+
) {
97+
const endLine = Math.min(startLine + windowSize - 1, fileLines.length - 1);
98+
99+
ranges.push({
100+
filepath,
101+
range: {
102+
start: { line: startLine, character: 0 },
103+
end: {
104+
line: endLine,
105+
character: fileLines[endLine].length,
106+
},
107+
},
108+
});
109+
110+
if (endLine >= fileLines.length - 1) {
111+
break;
112+
}
113+
}
114+
115+
return ranges;
116+
}
117+
54118
// A rerank jump splits the current file into chunks.
55119
// Then it uses a rerank model to get the most relevant chunks and their positions.
56120
async function rerankJump(ctx: {
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { RangeInFile } from "..";
2+
import { NextEditProvider } from "./NextEditProvider";
3+
import { NextEditOutcome } from "./types";
4+
5+
interface ProcessedItem {
6+
location: RangeInFile;
7+
outcome: NextEditOutcome; // Result from the model
8+
}
9+
10+
export class PrefetchQueue {
11+
private static instance: PrefetchQueue | null = null;
12+
13+
private unprocessedQueue: RangeInFile[] = [];
14+
private processedQueue: ProcessedItem[] = [];
15+
private prefetchLimit: number;
16+
private abortController: AbortController;
17+
18+
private usingFullFileDiff: boolean = true;
19+
20+
private constructor(prefetchLimit: number = 3) {
21+
this.prefetchLimit = prefetchLimit;
22+
this.abortController = new AbortController();
23+
}
24+
25+
public static getInstance(prefetchLimit: number = 3): PrefetchQueue {
26+
if (!PrefetchQueue.instance) {
27+
PrefetchQueue.instance = new PrefetchQueue(prefetchLimit);
28+
}
29+
30+
return PrefetchQueue.instance;
31+
}
32+
33+
initialize(usingFullFileDiff: boolean) {
34+
this.usingFullFileDiff = usingFullFileDiff;
35+
}
36+
37+
// Queue management methods
38+
enqueueUnprocessed(location: RangeInFile): void {
39+
this.unprocessedQueue.push(location);
40+
}
41+
42+
private dequeueUnprocessed(): RangeInFile | undefined {
43+
return this.unprocessedQueue.shift();
44+
}
45+
46+
enqueueProcessed(item: ProcessedItem): void {
47+
this.processedQueue.push(item);
48+
}
49+
50+
dequeueProcessed(): ProcessedItem | undefined {
51+
return this.processedQueue.shift();
52+
}
53+
54+
// Process items from unprocessed queue
55+
async process(ctx: any): Promise<void> {
56+
while (
57+
this.unprocessedQueue.length > 0 &&
58+
this.processedQueue.length < this.prefetchLimit &&
59+
!this.abortController.signal.aborted
60+
) {
61+
const location = this.dequeueUnprocessed();
62+
if (!location) break;
63+
64+
try {
65+
const outcome =
66+
await NextEditProvider.getInstance().provideInlineCompletionItemsWithChain(
67+
ctx,
68+
location,
69+
this.abortController.signal,
70+
this.usingFullFileDiff,
71+
);
72+
73+
if (!outcome) continue;
74+
75+
this.enqueueProcessed({
76+
location,
77+
outcome,
78+
});
79+
} catch (error) {
80+
if (!this.abortController.signal.aborted) {
81+
// Handle error
82+
console.error("Error processing item:", error);
83+
}
84+
// If aborted, we just stop processing
85+
break;
86+
}
87+
}
88+
}
89+
90+
// Abort all operations
91+
abort(): void {
92+
this.abortController.abort();
93+
this.clear();
94+
95+
// Create a new AbortController for future operations
96+
this.abortController = new AbortController();
97+
}
98+
99+
// Clear all queues
100+
clear(): void {
101+
this.unprocessedQueue = [];
102+
this.processedQueue = [];
103+
}
104+
105+
// Additional helper methods
106+
get unprocessedCount(): number {
107+
return this.unprocessedQueue.length;
108+
}
109+
110+
get processedCount(): number {
111+
return this.processedQueue.length;
112+
}
113+
114+
peekProcessed(): ProcessedItem | undefined {
115+
return this.processedQueue[0];
116+
}
117+
118+
setPreetchLimit(limit: number): void {
119+
this.prefetchLimit = limit;
120+
}
121+
}

0 commit comments

Comments
 (0)