Skip to content

Commit 7fd10c2

Browse files
authored
Merge pull request continuedev#4161 from ferenci84/diff_cache
Implement caching mechanism for diff snippets based on file save timestamp
2 parents 62e2e96 + f70f002 commit 7fd10c2

File tree

6 files changed

+142
-63
lines changed

6 files changed

+142
-63
lines changed

core/autocomplete/snippets/getAllSnippets.ts

+39-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
AutocompleteSnippetType,
1212
} from "./types";
1313

14+
const IDE_SNIPPETS_ENABLED = false; // ideSnippets is not used, so it's temporarily disabled
15+
1416
export interface SnippetPayload {
1517
rootPathSnippets: AutocompleteCodeSnippet[];
1618
importDefinitionSnippets: AutocompleteCodeSnippet[];
@@ -29,6 +31,27 @@ function racePromise<T>(promise: Promise<T[]>): Promise<T[]> {
2931
return Promise.race([promise, timeoutPromise]);
3032
}
3133

34+
class DiffSnippetsCache {
35+
private cache: Map<number, any> = new Map();
36+
private lastTimestamp: number = 0;
37+
38+
public set<T>(timestamp: number, value: T): T {
39+
// Clear old cache entry if exists
40+
if (this.lastTimestamp !== timestamp) {
41+
this.cache.clear();
42+
}
43+
this.lastTimestamp = timestamp;
44+
this.cache.set(timestamp, value);
45+
return value;
46+
}
47+
48+
public get(timestamp: number): any | undefined {
49+
return this.cache.get(timestamp);
50+
}
51+
}
52+
53+
const diffSnippetsCache = new DiffSnippetsCache();
54+
3255
// Some IDEs might have special ways of finding snippets (e.g. JetBrains and VS Code have different "LSP-equivalent" systems,
3356
// or they might separately track recently edited ranges)
3457
async function getIdeSnippets(
@@ -90,19 +113,31 @@ const getClipboardSnippets = async (
90113
const getDiffSnippets = async (
91114
ide: IDE,
92115
): Promise<AutocompleteDiffSnippet[]> => {
116+
const currentTimestamp = ide.getLastFileSaveTimestamp ?
117+
ide.getLastFileSaveTimestamp() :
118+
Math.floor(Date.now() / 10000) * 10000; // Defaults to update once in every 10 seconds
119+
120+
// Check cache first
121+
const cached = diffSnippetsCache.get(currentTimestamp) as AutocompleteDiffSnippet[];
122+
123+
if (cached) {
124+
return cached;
125+
}
126+
93127
let diff: string[] = [];
94128
try {
95129
diff = await ide.getDiff(true);
96130
} catch (e) {
97131
console.error("Error getting diff for autocomplete", e);
98132
}
99133

100-
return diff.map((item) => {
134+
return diffSnippetsCache.set(currentTimestamp, diff.map((item) => {
101135
return {
102136
content: item,
103137
type: AutocompleteSnippetType.Diff,
104138
};
105-
});
139+
}));
140+
106141
};
107142

108143
export const getAllSnippets = async ({
@@ -127,10 +162,8 @@ export const getAllSnippets = async ({
127162
clipboardSnippets,
128163
] = await Promise.all([
129164
racePromise(contextRetrievalService.getRootPathSnippets(helper)),
130-
racePromise(
131-
contextRetrievalService.getSnippetsFromImportDefinitions(helper),
132-
),
133-
racePromise(getIdeSnippets(helper, ide, getDefinitionsFromLsp)),
165+
racePromise(contextRetrievalService.getSnippetsFromImportDefinitions(helper)),
166+
IDE_SNIPPETS_ENABLED ? racePromise(getIdeSnippets(helper, ide, getDefinitionsFromLsp)) : [],
134167
racePromise(getDiffSnippets(ide)),
135168
racePromise(getClipboardSnippets(ide)),
136169
]);

core/index.d.ts

+26-22
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ export interface IndexingProgressUpdate {
4343
desc: string;
4444
shouldClearIndexes?: boolean;
4545
status:
46-
| "loading"
47-
| "indexing"
48-
| "done"
49-
| "failed"
50-
| "paused"
51-
| "disabled"
52-
| "cancelled";
46+
| "loading"
47+
| "indexing"
48+
| "done"
49+
| "failed"
50+
| "paused"
51+
| "disabled"
52+
| "cancelled";
5353
debugInfo?: string;
5454
}
5555

@@ -667,12 +667,16 @@ export interface IDE {
667667
getCurrentFile(): Promise<
668668
| undefined
669669
| {
670-
isUntitled: boolean;
671-
path: string;
672-
contents: string;
673-
}
670+
isUntitled: boolean;
671+
path: string;
672+
contents: string;
673+
}
674674
>;
675675

676+
getLastFileSaveTimestamp?(): number;
677+
678+
updateLastFileSaveTimestamp?(): void;
679+
676680
getPinnedFiles(): Promise<string[]>;
677681

678682
getSearchResults(query: string): Promise<string>;
@@ -849,11 +853,11 @@ export interface CustomCommand {
849853
export interface Prediction {
850854
type: "content";
851855
content:
852-
| string
853-
| {
854-
type: "text";
855-
text: string;
856-
}[];
856+
| string
857+
| {
858+
type: "text";
859+
text: string;
860+
}[];
857861
}
858862

859863
export interface ToolExtras {
@@ -1182,9 +1186,9 @@ export interface Config {
11821186
embeddingsProvider?: EmbeddingsProviderDescription | ILLM;
11831187
/** The model that Continue will use for tab autocompletions. */
11841188
tabAutocompleteModel?:
1185-
| CustomLLM
1186-
| ModelDescription
1187-
| (CustomLLM | ModelDescription)[];
1189+
| CustomLLM
1190+
| ModelDescription
1191+
| (CustomLLM | ModelDescription)[];
11881192
/** Options for tab autocomplete */
11891193
tabAutocompleteOptions?: Partial<TabAutocompleteOptions>;
11901194
/** UI styles customization */
@@ -1275,9 +1279,9 @@ export type PackageDetailsSuccess = PackageDetails & {
12751279
export type PackageDocsResult = {
12761280
packageInfo: ParsedPackageInfo;
12771281
} & (
1278-
| { error: string; details?: never }
1279-
| { details: PackageDetailsSuccess; error?: never }
1280-
);
1282+
| { error: string; details?: never }
1283+
| { details: PackageDetailsSuccess; error?: never }
1284+
);
12811285

12821286
export interface TerminalOptions {
12831287
reuseTerminal?: boolean;

extensions/vscode/src/VsCodeIde.ts

+9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { SecretStorage } from "./stubs/SecretStorage";
3333
class VsCodeIde implements IDE {
3434
ideUtils: VsCodeIdeUtils;
3535
secretStorage: SecretStorage;
36+
private lastFileSaveTimestamp: number = Date.now();
3637

3738
constructor(
3839
private readonly vscodeWebviewProtocolPromise: Promise<VsCodeWebviewProtocol>,
@@ -42,6 +43,14 @@ class VsCodeIde implements IDE {
4243
this.secretStorage = new SecretStorage(context);
4344
}
4445

46+
public updateLastFileSaveTimestamp(): void {
47+
this.lastFileSaveTimestamp = Date.now();
48+
}
49+
50+
public getLastFileSaveTimestamp(): number {
51+
return this.lastFileSaveTimestamp;
52+
}
53+
4554
async readSecrets(keys: string[]): Promise<Record<string, string>> {
4655
const secretValuePromises = keys.map((key) => this.secretStorage.get(key));
4756
const secretValues = await Promise.all(secretValuePromises);

extensions/vscode/src/autocomplete/lsp.ts

+26-22
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function gotoInputKey(input: GotoInput) {
3333
return `${input.name}${input.uri.toString()}${input.line}${input.character}`;
3434
}
3535

36-
const MAX_CACHE_SIZE = 50;
36+
const MAX_CACHE_SIZE = 500;
3737
const gotoCache = new Map<string, RangeInFile[]>();
3838

3939
export async function executeGotoProvider(
@@ -151,28 +151,32 @@ async function crawlTypes(
151151
identifierNodes.forEach((node) => searchedLabels.add(node.text));
152152

153153
// Use LSP to get the definitions of those types
154-
const definitions = await Promise.all(
155-
identifierNodes.map(async (node) => {
156-
const [typeDef] = await executeGotoProvider({
157-
uri: vscode.Uri.parse(rif.filepath),
158-
// TODO: tree-sitter is zero-indexed, but there seems to be an off-by-one
159-
// error at least with the .ts parser sometimes
160-
line:
161-
rif.range.start.line +
162-
Math.min(node.startPosition.row, astLineCount - 1),
163-
character: rif.range.start.character + node.startPosition.column,
164-
name: "vscode.executeDefinitionProvider",
165-
});
154+
const definitions = [];
155+
156+
for (const node of identifierNodes) {
157+
const [typeDef] = await executeGotoProvider({
158+
uri: vscode.Uri.parse(rif.filepath),
159+
// TODO: tree-sitter is zero-indexed, but there seems to be an off-by-one
160+
// error at least with the .ts parser sometimes
161+
line:
162+
rif.range.start.line +
163+
Math.min(node.startPosition.row, astLineCount - 1),
164+
character: rif.range.start.character + node.startPosition.column,
165+
name: "vscode.executeDefinitionProvider",
166+
});
167+
168+
if (!typeDef) {
169+
definitions.push(undefined);
170+
continue;
171+
}
166172

167-
if (!typeDef) {
168-
return undefined;
169-
}
170-
return {
171-
...typeDef,
172-
contents: await ide.readRangeInFile(typeDef.filepath, typeDef.range),
173-
};
174-
}),
175-
);
173+
const contents = await ide.readRangeInFile(typeDef.filepath, typeDef.range);
174+
175+
definitions.push({
176+
...typeDef,
177+
contents,
178+
});
179+
}
176180

177181
// TODO: Filter out if not in our code?
178182

extensions/vscode/src/extension/VsCodeExtension.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ export class VsCodeExtension {
267267
});
268268

269269
vscode.workspace.onDidSaveTextDocument(async (event) => {
270+
this.ide.updateLastFileSaveTimestamp();
270271
this.core.invoke("files/changed", {
271272
uris: [event.uri.toString()],
272273
});
@@ -346,8 +347,7 @@ export class VsCodeExtension {
346347

347348
// Register a content provider for the readonly virtual documents
348349
const documentContentProvider = new (class
349-
implements vscode.TextDocumentContentProvider
350-
{
350+
implements vscode.TextDocumentContentProvider {
351351
// emitter and its event
352352
onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
353353
onDidChange = this.onDidChangeEmitter.event;

extensions/vscode/src/util/ideUtils.ts

+40-11
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,27 @@ export class VsCodeIdeUtils {
394394
}
395395
}
396396

397+
private _getRepositories(): Repository[] | undefined {
398+
const extension =
399+
vscode.extensions.getExtension<GitExtension>("vscode.git");
400+
if (
401+
typeof extension === "undefined" ||
402+
!extension.isActive ||
403+
typeof vscode.workspace.workspaceFolders === "undefined"
404+
) {
405+
return undefined;
406+
}
407+
408+
try {
409+
const git = extension.exports.getAPI(1);
410+
return git.repositories;
411+
} catch (e) {
412+
this._repoWasNone = true;
413+
console.warn("Git not found: ", e);
414+
return undefined;
415+
}
416+
}
417+
397418
private _repoWasNone: boolean = false;
398419
private repoCache: Map<string, Repository> = new Map();
399420
private static secondsToWaitForGitToLoad =
@@ -475,20 +496,28 @@ export class VsCodeIdeUtils {
475496
async getDiff(includeUnstaged: boolean): Promise<string[]> {
476497
const diffs: string[] = [];
477498

478-
for (const dir of this.getWorkspaceDirectories()) {
479-
const repo = await this.getRepo(dir);
480-
if (!repo) {
481-
continue;
482-
}
499+
const repos = this._getRepositories();
500+
501+
try {
502+
if (repos) {
503+
for (const repo of repos) {
504+
505+
const staged = await repo.diff(true);
483506

484-
const staged = await repo.diff(true);
485-
diffs.push(staged);
486-
if (includeUnstaged) {
487-
const unstaged = await repo.diff(false);
488-
diffs.push(unstaged);
507+
diffs.push(staged);
508+
if (includeUnstaged) {
509+
const unstaged = await repo.diff(false);
510+
diffs.push(unstaged);
511+
}
512+
}
489513
}
514+
515+
return diffs.flatMap((diff) => this.splitDiff(diff));
516+
517+
} catch (e) {
518+
console.error(e);
519+
return [];
490520
}
491521

492-
return diffs.flatMap((diff) => this.splitDiff(diff));
493522
}
494523
}

0 commit comments

Comments
 (0)