Skip to content
Draft
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
4 changes: 2 additions & 2 deletions core/autocomplete/context/ContextRetrievalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export class ContextRetrievalService {
const { imports } = fileInfo;
// Look for imports of any symbols around the current range
const textAroundCursor =
helper.fullPrefix.split("\n").slice(-5).join("\n") +
helper.fullSuffix.split("\n").slice(0, 3).join("\n");
helper.fullPrefixLines.slice(-5).join("\n") +
helper.fullSuffixLines.slice(0, 3).join("\n");
const symbols = Array.from(getSymbolsForSnippet(textAroundCursor)).filter(
(symbol) => !helper.lang.topLevelKeywords.includes(symbol),
);
Expand Down
33 changes: 20 additions & 13 deletions core/autocomplete/context/ImportDefinitionsService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IDE, RangeInFileWithContents } from "../..";
import { PrecalculatedLruCache } from "../../util/LruCache";
import { readRangeInFile } from "../../util/rangeInFile";
import {
getFullLanguageName,
getParserForFile,
Expand All @@ -21,15 +22,16 @@ export class ImportDefinitionsService {
);

constructor(private readonly ide: IDE) {
ide.onDidChangeActiveTextEditor((filepath) => {
this.cache
.initKey(filepath)
.catch((e) =>
console.warn(
`Failed to initialize ImportDefinitionService: ${e.message}`,
),
);
});
console.log("new import definitions service");
// ide.onDidChangeActiveTextEditor((filepath) => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commenting out the onDidChangeActiveTextEditor handler stops warming this cache when the user switches files, so import definition lookups now return undefined instead of the cached definitions.

Prompt for AI agents
Address the following comment on core/autocomplete/context/ImportDefinitionsService.ts at line 26:

<comment>Commenting out the onDidChangeActiveTextEditor handler stops warming this cache when the user switches files, so import definition lookups now return undefined instead of the cached definitions.</comment>

<file context>
@@ -21,15 +22,16 @@ export class ImportDefinitionsService {
-        );
-    });
+    console.log(&quot;new import definitions service&quot;);
+    //  ide.onDidChangeActiveTextEditor((filepath) =&gt; {
+    //   this.cache
+    //     .initKey(filepath)
</file context>
Fix with Cubic

// this.cache
// .initKey(filepath)
// .catch((e) =>
// console.warn(
// `Failed to initialize ImportDefinitionService: ${e.message}`,
// ),
// );
// });
}

get(filepath: string): FileInfo | undefined {
Expand Down Expand Up @@ -58,6 +60,9 @@ export class ImportDefinitionsService {
if (!foundInDir) {
return null;
} else {
console.log(
`read file - Import definition service _getFileInfo - ${filepath}`,
);
fileContents = await this.ide.readFile(filepath);
}
} catch (err) {
Expand Down Expand Up @@ -101,10 +106,12 @@ export class ImportDefinitionsService {
},
});
fileInfo.imports[match.captures[0].node.text] = await Promise.all(
defs.map(async (def) => ({
...def,
contents: await this.ide.readRangeInFile(def.filepath, def.range),
})),
defs.map(async (def) => {
return {
...def,
contents: await readRangeInFile(this.ide, def.filepath, def.range),
};
}),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { LRUCache } from "lru-cache";
import Parser from "web-tree-sitter";

import { IDE } from "../../..";
import { readRangeInFile } from "../../../util/rangeInFile";
import {
getFullLanguageName,
getQueryForFile,
Expand All @@ -16,6 +17,7 @@ import {
} from "../../snippets/types";
import { AutocompleteSnippetDeprecated } from "../../types";
import { AstPath } from "../../util/ast";
import { AutocompleteReadCache } from "../../util/AutocompleteReadCache";
import { ImportDefinitionsService } from "../ImportDefinitionsService";

// function getSyntaxTreeString(
Expand Down Expand Up @@ -133,6 +135,9 @@ export class RootPathContextService {
character: endPosition.column,
},
});
const readCache = AutocompleteReadCache.getInstance(
this.ide.readFile.bind(this.ide),
);
const newSnippets = await Promise.all(
definitions
.filter((definition) => {
Expand All @@ -142,10 +147,12 @@ export class RootPathContextService {

return !isIgnoredPath;
})
.map(async (def) => ({
...def,
contents: await this.ide.readRangeInFile(def.filepath, def.range),
})),
.map(async (def) => {
return {
...def,
contents: await readRangeInFile(this.ide, def.filepath, def.range),
};
}),
);

return newSnippets;
Expand Down
80 changes: 0 additions & 80 deletions core/autocomplete/context/static-context/tree-sitter-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,86 +78,6 @@ export async function extractTopLevelDecls(
return query.matches(ast.rootNode);
}

export async function extractTopLevelDeclsWithFormatting(
currentFile: string,
givenParser?: Parser,
) {
const ast = await getAst(currentFile, await fs.readFile(currentFile, "utf8"));
if (!ast) {
throw new Error(`failed to get ast for file ${currentFile}`);
}
let language;
if (givenParser) {
language = givenParser.getLanguage();
} else {
language = getFullLanguageName(currentFile);
}

const query = await getQueryForFile(
currentFile,
`static-context-queries/relevant-headers-queries/${language}-get-toplevel-headers.scm`,
);
if (!query) {
throw new Error(
`failed to get query for file ${currentFile} and language ${language}`,
);
}
const matches = query.matches(ast.rootNode);

const results = [];

for (const match of matches) {
const item: {
declaration: string;
nodeType: string;
name: string;
declaredType: string;
returnType?: string;
} = {
declaration: "",
nodeType: "",
name: "",
declaredType: "",
};

for (const { name, node } of match.captures) {
if (name === "top.var.decl") {
item.nodeType = "variable";
item.declaration = node.text;

// Attempt to get the declared type (e.g., const x: string = ...)
const typeNode = node.descendantsOfType("type_annotation")[0];
if (typeNode) {
item.declaredType = typeNode.text.replace(/^:\s*/, "");
}
} else if (name === "top.var.name" || name === "top.fn.name") {
item.name = node.text;
} else if (name === "top.fn.decl") {
item.nodeType = "function";
item.declaration = node.text;

// Get the return type (e.g., function foo(): string)
const returnTypeNode = node.childForFieldName("return_type");
if (returnTypeNode) {
item.returnType = returnTypeNode.text.replace(/^:\s*/, "");
}

// Get declaredType if needed (TypeScript style)
const nameNode = node.childForFieldName("name");
if (nameNode && nameNode.nextSibling?.type === "type_annotation") {
item.declaredType = nameNode.nextSibling.text.replace(/^:\s*/, "");
}
}
}

if (item.name && item.declaration) {
results.push(item);
}
}

return results;
}

export function extractFunctionTypeFromDecl(match: Parser.QueryMatch): string {
let paramsNode: Parser.SyntaxNode | undefined = undefined;
let returnNode: Parser.SyntaxNode | undefined = undefined;
Expand Down
3 changes: 3 additions & 0 deletions core/autocomplete/snippets/getAllSnippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ const getSnippetsFromRecentlyOpenedFiles = async (
// Create a promise that resolves to a snippet or null
const readPromise = new Promise<AutocompleteCodeSnippet | null>(
(resolve) => {
console.log(
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This debug console.log runs on every file read from the recently opened cache, so autocomplete will emit a log per file on each invocation; this adds synchronous logging overhead and floods the console, undermining the performance improvements you're targeting. Please drop or gate the log before merging.

Prompt for AI agents
Address the following comment on core/autocomplete/snippets/getAllSnippets.ts at line 131:

<comment>This debug console.log runs on every file read from the recently opened cache, so autocomplete will emit a log per file on each invocation; this adds synchronous logging overhead and floods the console, undermining the performance improvements you&#39;re targeting. Please drop or gate the log before merging.</comment>

<file context>
@@ -128,6 +128,9 @@ const getSnippetsFromRecentlyOpenedFiles = async (
       // Create a promise that resolves to a snippet or null
       const readPromise = new Promise&lt;AutocompleteCodeSnippet | null&gt;(
         (resolve) =&gt; {
+          console.log(
+            `read file - getAllSnippets getSnippetsFromRecentlyOpenedFiles - ${fileUri}`,
+          );
</file context>
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new console.log will fire for every recently opened file read, spamming the console and adding overhead in production usages. Please remove it or gate it behind the existing logging/instrumentation mechanism.

Prompt for AI agents
Address the following comment on core/autocomplete/snippets/getAllSnippets.ts at line 131:

<comment>This new console.log will fire for every recently opened file read, spamming the console and adding overhead in production usages. Please remove it or gate it behind the existing logging/instrumentation mechanism.</comment>

<file context>
@@ -128,6 +128,9 @@ const getSnippetsFromRecentlyOpenedFiles = async (
       // Create a promise that resolves to a snippet or null
       const readPromise = new Promise&lt;AutocompleteCodeSnippet | null&gt;(
         (resolve) =&gt; {
+          console.log(
+            `read file - getAllSnippets getSnippetsFromRecentlyOpenedFiles - ${fileUri}`,
+          );
</file context>
Fix with Cubic

`read file - getAllSnippets getSnippetsFromRecentlyOpenedFiles - ${fileUri}`,
);
ide
.readFile(fileUri)
.then((fileContent) => {
Expand Down
51 changes: 30 additions & 21 deletions core/autocomplete/templating/constructPrefixSuffix.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { IDE } from "../..";
import { getRangeInString } from "../../util/ranges";
import { languageForFilepath } from "../constants/AutocompleteLanguageInfo";
import { AutocompleteInput } from "../util/types";
Expand All @@ -9,35 +8,45 @@ import { AutocompleteInput } from "../util/types";
*/
export async function constructInitialPrefixSuffix(
input: AutocompleteInput,
ide: IDE,
lines: string[],
): Promise<{
prefix: string;
suffix: string;
prefixLines: string[];
suffixLines: string[];
}> {
const lang = languageForFilepath(input.filepath);

const fileContents =
input.manuallyPassFileContents ?? (await ide.readFile(input.filepath));
const fileLines = fileContents.split("\n");
let prefix =
getRangeInString(fileContents, {
start: { line: 0, character: 0 },
end: input.selectedCompletionInfo?.range.start ?? input.pos,
}) + (input.selectedCompletionInfo?.text ?? "");
const prefixLines = getRangeInString(lines, {
start: { line: 0, character: 0 },
end: input.selectedCompletionInfo?.range.start ?? input.pos,
});
const selectedCompletionInfoText = input.selectedCompletionInfo?.text ?? "";
const selectedCompletionInfoLines = selectedCompletionInfoText.split("\n");
let i = 0;
for (const line of selectedCompletionInfoLines) {
if (i === 0) {
prefixLines[prefixLines.length - 1] =
prefixLines[prefixLines.length - 1] + line;
} else {
prefixLines.push(line);
}
i++;
}

let prefix: string;
if (input.injectDetails) {
const lines = prefix.split("\n");
prefix = `${lines.slice(0, -1).join("\n")}\n${
lang.singleLineComment
} ${input.injectDetails
const lastLine = prefixLines.pop();
const detailsLines = input.injectDetails
.split("\n")
.join(`\n${lang.singleLineComment} `)}\n${lines[lines.length - 1]}`;
.map((line) => `${lang.singleLineComment} ${line}`);
prefixLines.push(...detailsLines);
if (lastLine !== undefined) {
prefixLines.push(lastLine);
}
}

const suffix = getRangeInString(fileContents, {
const suffixLines = getRangeInString(lines, {
start: input.pos,
end: { line: fileLines.length - 1, character: Number.MAX_SAFE_INTEGER },
end: { line: lines.length - 1, character: Number.MAX_SAFE_INTEGER },
});

return { prefix, suffix };
return { prefixLines, suffixLines };
}
65 changes: 65 additions & 0 deletions core/autocomplete/util/AutocompleteReadCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { LRUCache } from "lru-cache";
import * as URI from "uri-js";

const MAX_READ_CACHE_SIZE = 100;
type ReadFn = (uri: string) => Promise<string>;
export class AutocompleteReadCache {
private static _instance: AutocompleteReadCache | null = null;
private cache: LRUCache<string, string>;

constructor(private readonly readFile: ReadFn) {
this.cache = new LRUCache({
max: MAX_READ_CACHE_SIZE,
ttl: 5 * 60 * 1000, // 5 minutes in milliseconds
updateAgeOnGet: true,
});
}

static getInstance(readFile: ReadFn): AutocompleteReadCache {
if (!AutocompleteReadCache._instance) {
AutocompleteReadCache._instance = new AutocompleteReadCache(readFile);
}
return AutocompleteReadCache._instance;
}

get(uri: string): string | undefined {
return this.cache.get(URI.normalize(uri));
}

set(uri: string, contents: string): void {
this.cache.set(URI.normalize(uri), contents);
}

delete(uri: string): boolean {
return this.cache.delete(URI.normalize(uri));
}

has(uri: string): boolean {
return this.cache.has(URI.normalize(uri));
}

clear(): void {
this.cache.clear();
}

size(): number {
return this.cache.size;
}

async getOrRead(uri: string): Promise<string> {
const normalizedUri = URI.normalize(uri);
const cached = this.cache.get(normalizedUri);
if (cached !== undefined) {
return cached;
}
console.log(`read file - Autocomplete read cache - ${normalizedUri}`);

const contents = await this.readFile(normalizedUri);
this.cache.set(normalizedUri, contents);
return contents;
}

invalidate(uri: string): void {
this.cache.delete(URI.normalize(uri));
}
}
Loading
Loading