diff --git a/core/autocomplete/context/ContextRetrievalService.ts b/core/autocomplete/context/ContextRetrievalService.ts index e7e9e60a467..dece63c3d4c 100644 --- a/core/autocomplete/context/ContextRetrievalService.ts +++ b/core/autocomplete/context/ContextRetrievalService.ts @@ -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), ); diff --git a/core/autocomplete/context/ImportDefinitionsService.ts b/core/autocomplete/context/ImportDefinitionsService.ts index 0115b443fd4..9a28404398c 100644 --- a/core/autocomplete/context/ImportDefinitionsService.ts +++ b/core/autocomplete/context/ImportDefinitionsService.ts @@ -1,5 +1,6 @@ import { IDE, RangeInFileWithContents } from "../.."; import { PrecalculatedLruCache } from "../../util/LruCache"; +import { readRangeInFile } from "../../util/rangeInFile"; import { getFullLanguageName, getParserForFile, @@ -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) => { + // this.cache + // .initKey(filepath) + // .catch((e) => + // console.warn( + // `Failed to initialize ImportDefinitionService: ${e.message}`, + // ), + // ); + // }); } get(filepath: string): FileInfo | undefined { @@ -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) { @@ -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), + }; + }), ); } diff --git a/core/autocomplete/context/root-path-context/RootPathContextService.ts b/core/autocomplete/context/root-path-context/RootPathContextService.ts index 7a29f8a8fc7..23c302177b4 100644 --- a/core/autocomplete/context/root-path-context/RootPathContextService.ts +++ b/core/autocomplete/context/root-path-context/RootPathContextService.ts @@ -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, @@ -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( @@ -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) => { @@ -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; diff --git a/core/autocomplete/context/static-context/tree-sitter-utils.ts b/core/autocomplete/context/static-context/tree-sitter-utils.ts index 6e751f54783..e10e7ee11e6 100644 --- a/core/autocomplete/context/static-context/tree-sitter-utils.ts +++ b/core/autocomplete/context/static-context/tree-sitter-utils.ts @@ -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; diff --git a/core/autocomplete/snippets/getAllSnippets.ts b/core/autocomplete/snippets/getAllSnippets.ts index 079127abc44..51a682a32f8 100644 --- a/core/autocomplete/snippets/getAllSnippets.ts +++ b/core/autocomplete/snippets/getAllSnippets.ts @@ -128,6 +128,9 @@ const getSnippetsFromRecentlyOpenedFiles = async ( // Create a promise that resolves to a snippet or null const readPromise = new Promise( (resolve) => { + console.log( + `read file - getAllSnippets getSnippetsFromRecentlyOpenedFiles - ${fileUri}`, + ); ide .readFile(fileUri) .then((fileContent) => { diff --git a/core/autocomplete/templating/constructPrefixSuffix.ts b/core/autocomplete/templating/constructPrefixSuffix.ts index 2bf6fb067af..9b2d75b67a4 100644 --- a/core/autocomplete/templating/constructPrefixSuffix.ts +++ b/core/autocomplete/templating/constructPrefixSuffix.ts @@ -1,4 +1,3 @@ -import { IDE } from "../.."; import { getRangeInString } from "../../util/ranges"; import { languageForFilepath } from "../constants/AutocompleteLanguageInfo"; import { AutocompleteInput } from "../util/types"; @@ -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 }; } diff --git a/core/autocomplete/util/AutocompleteReadCache.ts b/core/autocomplete/util/AutocompleteReadCache.ts new file mode 100644 index 00000000000..716cd6a796e --- /dev/null +++ b/core/autocomplete/util/AutocompleteReadCache.ts @@ -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; +export class AutocompleteReadCache { + private static _instance: AutocompleteReadCache | null = null; + private cache: LRUCache; + + 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 { + 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)); + } +} diff --git a/core/autocomplete/util/HelperVars.ts b/core/autocomplete/util/HelperVars.ts index af0043687a3..1c64b0bcd70 100644 --- a/core/autocomplete/util/HelperVars.ts +++ b/core/autocomplete/util/HelperVars.ts @@ -1,9 +1,5 @@ import { IDE, TabAutocompleteOptions } from "../.."; -import { - countTokens, - pruneLinesFromBottom, - pruneLinesFromTop, -} from "../../llm/countTokens"; +import { pruneFromBottom, pruneFromTop } from "../../llm/countTokens"; import { AutocompleteLanguageInfo, languageForFilepath, @@ -26,6 +22,8 @@ export class HelperVars { private _fileLines: string[] | undefined; private _fullPrefix: string | undefined; private _fullSuffix: string | undefined; + private _fullSuffixLines: string[] | undefined; + private _fullPrefixLines: string[] | undefined; private _prunedPrefix: string | undefined; private _prunedSuffix: string | undefined; @@ -45,6 +43,7 @@ export class HelperVars { } this.workspaceUris = await this.ide.getWorkspaceDirs(); + console.log(`read file - HelperVars init - ${this.filepath}`); this._fileContents = this.input.manuallyPassFileContents ?? (await this.ide.readFile(this.filepath)); @@ -52,14 +51,24 @@ export class HelperVars { this._fileLines = this._fileContents.split("\n"); // Construct full prefix/suffix (a few edge cases handled in here) - const { prefix: fullPrefix, suffix: fullSuffix } = - await constructInitialPrefixSuffix(this.input, this.ide); + const { prefixLines, suffixLines } = await constructInitialPrefixSuffix( + this.input, + this._fileLines, + ); + this._fullPrefixLines = prefixLines; + const fullPrefix = prefixLines.join("\n"); this._fullPrefix = fullPrefix; + + this._fullSuffixLines = suffixLines; + const fullSuffix = suffixLines.join("\n"); this._fullSuffix = fullSuffix; - const { prunedPrefix, prunedSuffix } = this.prunePrefixSuffix(); - this._prunedPrefix = prunedPrefix; - this._prunedSuffix = prunedSuffix; + const { prunedPrefix, prunedSuffix } = this.prunePrefixSuffix( + prefixLines, + suffixLines, + ); + this._prunedPrefix = prunedPrefix.join("\n"); + this._prunedSuffix = prunedSuffix.join("\n"); try { const ast = await getAst(this.filepath, fullPrefix + fullSuffix); @@ -82,23 +91,24 @@ export class HelperVars { return instance; } - prunePrefixSuffix() { + prunePrefixSuffix(prefixLines: string[], suffixLines: string[]) { // Construct basic prefix const maxPrefixTokens = this.options.maxPromptTokens * this.options.prefixPercentage; - const prunedPrefix = pruneLinesFromTop( - this.fullPrefix, + + const { pruned: prunedPrefix, totalTokens: prefixTokens } = pruneFromTop( + prefixLines, maxPrefixTokens, this.modelName, ); // Construct suffix const maxSuffixTokens = Math.min( - this.options.maxPromptTokens - countTokens(prunedPrefix, this.modelName), + this.options.maxPromptTokens - prefixTokens, this.options.maxSuffixPercentage * this.options.maxPromptTokens, ); - const prunedSuffix = pruneLinesFromBottom( - this.fullSuffix, + const { pruned: prunedSuffix } = pruneFromBottom( + suffixLines, maxSuffixTokens, this.modelName, ); @@ -131,6 +141,24 @@ export class HelperVars { return this._fileContents; } + get fullSuffixLines(): string[] { + if (this._fullSuffixLines === undefined) { + throw new Error( + "HelperVars must be initialized before accessing fullSuffixLines", + ); + } + return this._fullSuffixLines; + } + + get fullPrefixLines(): string[] { + if (this._fullPrefixLines === undefined) { + throw new Error( + "HelperVars must be initialized before accessing fullPrefixLines", + ); + } + return this._fullPrefixLines; + } + get fileLines(): string[] { if (this._fileLines === undefined) { throw new Error( diff --git a/core/config/types.ts b/core/config/types.ts index d9f58aca6e2..f7b7fa6581d 100644 --- a/core/config/types.ts +++ b/core/config/types.ts @@ -696,8 +696,6 @@ declare global { readFile(filepath: string): Promise; - readRangeInFile(filepath: string, range: Range): Promise; - showLines( filepath: string, startLine: number, @@ -746,9 +744,6 @@ declare global { getSignatureHelp(location: Location): Promise; getReferences(location: Location): Promise; getDocumentSymbols(textDocumentIdentifier: string): Promise; - - // Callbacks - onDidChangeActiveTextEditor(callback: (filepath: string) => void): void; } // Slash Commands diff --git a/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts b/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts index e062e8ff3e4..5ad6ccdd224 100644 --- a/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts +++ b/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts @@ -157,45 +157,6 @@ export default class RerankerRetrievalPipeline extends BaseRetrievalPipeline { let results = await this._retrieveInitial(args); results = await this._rerank(args.query, results); - // // // Expand top reranked results - // const expanded = await this._expandRankedResults(results); - // results.push(...expanded); - - // // De-duplicate - // results = deduplicateChunks(results); - - // // Rerank again - // results = await this._rerank(input, results); - - // TODO: stitch together results - return results; } } - -// Source: expansion with code graph -// consider doing this after reranking? Or just having a lower reranking threshold -// This is VS Code only until we use PSI for JetBrains or build our own general solution -// TODO: Need to pass in the expandSnippet function as a function argument -// because this import causes `tsc` to fail -// if ((await extras.ide.getIdeInfo()).ideType === "vscode") { -// const { expandSnippet } = await import( -// "../../../extensions/vscode/src/util/expandSnippet" -// ); -// let expansionResults = ( -// await Promise.all( -// extras.selectedCode.map(async (rif) => { -// return expandSnippet( -// rif.filepath, -// rif.range.start.line, -// rif.range.end.line, -// extras.ide, -// ); -// }), -// ) -// ).flat() as Chunk[]; -// retrievalResults.push(...expansionResults); -// } - -// Source: Open file exact match -// Source: Class/function name exact match diff --git a/core/index.d.ts b/core/index.d.ts index c5e106b4239..3d47e06700a 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -830,8 +830,6 @@ export interface IDE { readFile(fileUri: string): Promise; - readRangeInFile(fileUri: string, range: Range): Promise; - showLines(fileUri: string, startLine: number, endLine: number): Promise; getOpenFiles(): Promise; @@ -884,9 +882,6 @@ export interface IDE { getSignatureHelp(location: Location): Promise; // TODO: add to jetbrains getReferences(location: Location): Promise; getDocumentSymbols(textDocumentIdentifier: string): Promise; - - // Callbacks - onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void; } // Slash Commands diff --git a/core/llm/countTokens.ts b/core/llm/countTokens.ts index 0749fbdfb83..d63c881ca11 100644 --- a/core/llm/countTokens.ts +++ b/core/llm/countTokens.ts @@ -273,6 +273,18 @@ function pruneLinesFromTop( modelName: string, ): string { const lines = prompt.split("\n"); + const { pruned } = pruneFromTop(lines, maxTokens, modelName); + return pruned.join("\n"); +} + +function pruneFromTop( + lines: string[], + maxTokens: number, + modelName: string, +): { + pruned: string[]; + totalTokens: number; +} { // Preprocess tokens for all lines and cache them. const lineTokens = lines.map((line) => countTokens(line, modelName)); let totalTokens = lineTokens.reduce((sum, tokens) => sum + tokens, 0); @@ -293,7 +305,11 @@ function pruneLinesFromTop( start++; } - return lines.slice(start).join("\n"); + const pruned = lines.slice(start); + return { + pruned, + totalTokens, + }; } function pruneLinesFromBottom( @@ -302,6 +318,18 @@ function pruneLinesFromBottom( modelName: string, ): string { const lines = prompt.split("\n"); + const { pruned } = pruneFromBottom(lines, maxTokens, modelName); + return pruned.join("\n"); +} + +function pruneFromBottom( + lines: string[], + maxTokens: number, + modelName: string, +): { + pruned: string[]; + totalTokens: number; +} { const lineTokens = lines.map((line) => countTokens(line, modelName)); let totalTokens = lineTokens.reduce((sum, tokens) => sum + tokens, 0); let end = lines.length; @@ -320,7 +348,11 @@ function pruneLinesFromBottom( } } - return lines.slice(0, end).join("\n"); + const pruned = lines.slice(0, end); + return { + pruned, + totalTokens, + }; } function pruneStringFromBottom( @@ -550,6 +582,8 @@ export { countTokens, countTokensAsync, extractToolSequence, + pruneFromBottom, + pruneFromTop, pruneLinesFromBottom, pruneLinesFromTop, pruneRawPromptFromTop, diff --git a/core/nextEdit/NextEditProvider.ts b/core/nextEdit/NextEditProvider.ts index 1150f554544..7c1dda3031f 100644 --- a/core/nextEdit/NextEditProvider.ts +++ b/core/nextEdit/NextEditProvider.ts @@ -232,6 +232,9 @@ export class NextEditProvider { this.previousCompletions = []; if (this.previousRequest) { + console.log( + `read file - NextEditProvider deleteChain - ${this.previousRequest.filepath}`, + ); const fileContent = ( await this.ide.readFile(this.previousRequest.filepath) ).toString(); diff --git a/core/protocol/ide.ts b/core/protocol/ide.ts index d8b86ff960a..9226a447b1f 100644 --- a/core/protocol/ide.ts +++ b/core/protocol/ide.ts @@ -8,7 +8,6 @@ import type { IndexTag, Location, Problem, - Range, RangeInFile, SignatureHelp, TerminalOptions, @@ -50,7 +49,6 @@ export type ToIdeFromWebviewOrCoreProtocol = { ]; getPinnedFiles: [undefined, string[]]; showLines: [{ filepath: string; startLine: number; endLine: number }, void]; - readRangeInFile: [{ filepath: string; range: Range }, string]; getDiff: [{ includeUnstaged: boolean }, string[]]; getTerminalContents: [undefined, string]; getDebugLocals: [{ threadIndex: number }, string]; diff --git a/core/protocol/messenger/messageIde.ts b/core/protocol/messenger/messageIde.ts index 795b483c460..9590e42ebec 100644 --- a/core/protocol/messenger/messageIde.ts +++ b/core/protocol/messenger/messageIde.ts @@ -11,7 +11,6 @@ import type { IndexTag, Location, Problem, - Range, RangeInFile, SignatureHelp, TerminalOptions, @@ -64,10 +63,6 @@ export class MessageIde implements IDE { return this.request("getDocumentSymbols", { textDocumentIdentifier }); } - onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void { - this.on("didChangeActiveTextEditor", (data) => callback(data.filepath)); - } - getIdeSettings(): Promise { return this.request("getIdeSettings", undefined); } @@ -117,10 +112,6 @@ export class MessageIde implements IDE { return this.request("getIdeInfo", undefined); } - readRangeInFile(filepath: string, range: Range): Promise { - return this.request("readRangeInFile", { filepath, range }); - } - isTelemetryEnabled(): Promise { return this.request("isTelemetryEnabled", undefined); } diff --git a/core/protocol/messenger/reverseMessageIde.ts b/core/protocol/messenger/reverseMessageIde.ts index 9ce82db54a9..c6c24e65dfe 100644 --- a/core/protocol/messenger/reverseMessageIde.ts +++ b/core/protocol/messenger/reverseMessageIde.ts @@ -77,10 +77,6 @@ export class ReverseMessageIde { return this.ide.getIdeInfo(); }); - this.on("readRangeInFile", (data) => { - return this.ide.readRangeInFile(data.filepath, data.range); - }); - this.on("isTelemetryEnabled", () => { return this.ide.isTelemetryEnabled(); }); diff --git a/core/tools/implementations/readFileRange.integration.vitest.ts b/core/tools/implementations/readFileRange.integration.vitest.ts index 945d03e4141..e0e051829c6 100644 --- a/core/tools/implementations/readFileRange.integration.vitest.ts +++ b/core/tools/implementations/readFileRange.integration.vitest.ts @@ -168,7 +168,7 @@ test("readFileRangeImpl handles normal ranges correctly", async () => { vi.mocked(throwIfFileExceedsHalfOfContext).mockResolvedValue(undefined); const mockIde = { - readRangeInFile: vi.fn().mockResolvedValue("line2\nline3\nline4"), + readFile: vi.fn().mockResolvedValue("line2\nline3\nline4"), }; const mockExtras = { @@ -197,7 +197,7 @@ test("readFileRangeImpl handles normal ranges correctly", async () => { }); // Verify correct 0-based conversion - expect(mockIde.readRangeInFile).toHaveBeenCalledWith("file:///test.txt", { + expect(mockIde.readFile).toHaveBeenCalledWith("file:///test.txt", { start: { line: 1, character: 0 }, // 2 - 1 end: { line: 3, character: Number.MAX_SAFE_INTEGER }, // 4 - 1 }); diff --git a/core/tools/implementations/readFileRange.ts b/core/tools/implementations/readFileRange.ts index 7b5f47338e0..9607cb0c056 100644 --- a/core/tools/implementations/readFileRange.ts +++ b/core/tools/implementations/readFileRange.ts @@ -3,9 +3,10 @@ import { getUriPathBasename } from "../../util/uri"; import { ToolImpl } from "."; import { throwIfFileIsSecurityConcern } from "../../indexing/ignore"; +import { ContinueError, ContinueErrorReason } from "../../util/errors"; +import { readRangeInFile } from "../../util/rangeInFile"; import { getNumberArg, getStringArg } from "../parseArgs"; import { throwIfFileExceedsHalfOfContext } from "./readFileLimit"; -import { ContinueError, ContinueErrorReason } from "../../util/errors"; export const readFileRangeImpl: ToolImpl = async (args, extras) => { const filepath = getStringArg(args, "filepath"); @@ -44,8 +45,8 @@ export const readFileRangeImpl: ToolImpl = async (args, extras) => { // Security check on the resolved display path throwIfFileIsSecurityConcern(resolvedPath.displayPath); - // Use the IDE's readRangeInFile method with 0-based range (IDE expects 0-based internally) - const content = await extras.ide.readRangeInFile(resolvedPath.uri, { + // readRangeInFile expects 0-based range indexes + const content = await readRangeInFile(extras.ide, resolvedPath.uri, { start: { line: startLine - 1, // Convert from 1-based to 0-based character: 0, diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts index 67a70374a35..0a722805031 100644 --- a/core/util/filesystem.ts +++ b/core/util/filesystem.ts @@ -11,7 +11,6 @@ import { IndexTag, Location, Problem, - Range, RangeInFile, SignatureHelp, TerminalOptions, @@ -61,10 +60,6 @@ class FileSystemIde implements IDE { return Promise.resolve([]); } - onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void { - return; - } - isWorkspaceRemote(): Promise { return Promise.resolve(false); } @@ -141,10 +136,6 @@ class FileSystemIde implements IDE { }); } - readRangeInFile(fileUri: string, range: Range): Promise { - return Promise.resolve(""); - } - isTelemetryEnabled(): Promise { return Promise.resolve(true); } diff --git a/core/util/rangeInFile.ts b/core/util/rangeInFile.ts new file mode 100644 index 00000000000..26175fa2e79 --- /dev/null +++ b/core/util/rangeInFile.ts @@ -0,0 +1,14 @@ +import { IDE, Range } from ".."; + +export function getRangeFromFileContents(contents: string, range: Range) { + const lines = contents.split("\n"); + return `${lines.slice(range.start.line, range.end.line).join("\n")}\n${lines[ + range.end.line < lines.length - 1 ? range.end.line : lines.length - 1 + ].slice(0, range.end.character)}`; +} + +export async function readRangeInFile(ide: IDE, uri: string, range: Range) { + console.log("READ RANGE IN FILE"); + const contents = await ide.readFile(uri); + return getRangeFromFileContents(contents, range); +} diff --git a/core/util/ranges.ts b/core/util/ranges.ts index a9bf5e97242..1d45da3dc6b 100644 --- a/core/util/ranges.ts +++ b/core/util/ranges.ts @@ -1,15 +1,13 @@ import { Position, Range } from "../index.js"; -export function getRangeInString(content: string, range: Range): string { - const lines = content.split("\n"); - +export function getRangeInString(lines: string[], range: Range): string[] { if (range.start.line === range.end.line) { - return ( + return [ lines[range.start.line]?.substring( range.start.character, range.end.character, - ) ?? "" - ); + ) ?? "", + ]; } const firstLine = @@ -21,7 +19,7 @@ export function getRangeInString(content: string, range: Range): string { const lastLine = lines[range.end.line]?.substring(0, range.end.character) ?? ""; - return [firstLine, ...middleLines, lastLine].join("\n"); + return [firstLine, ...middleLines, lastLine]; } export function intersection(a: Range, b: Range): Range | null { diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/constants/MessageTypes.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/constants/MessageTypes.kt index 417f12cdbd3..91eb5d524d9 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/constants/MessageTypes.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/constants/MessageTypes.kt @@ -3,7 +3,6 @@ package com.github.continuedev.continueintellijextension.constants class MessageTypes { companion object { val IDE_MESSAGE_TYPES = listOf( - "readRangeInFile", "isTelemetryEnabled", "getUniqueId", "getDiff", diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IdeProtocolClient.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IdeProtocolClient.kt index 0bc20471034..958a8a79cab 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IdeProtocolClient.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IdeProtocolClient.kt @@ -166,15 +166,6 @@ class IdeProtocolClient( respond(isEnabled) } - "readRangeInFile" -> { - val params = Gson().fromJson( - dataElement.toString(), - ReadRangeInFileParams::class.java - ) - val contents = ide.readRangeInFile(params.filepath, params.range) - respond(contents) - } - "getWorkspaceDirs" -> { val dirs = ide.getWorkspaceDirs() respond(dirs) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IntelliJIde.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IntelliJIde.kt index 97230ebdb87..5f07a62d312 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IntelliJIde.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/IntelliJIde.kt @@ -348,24 +348,6 @@ class IntelliJIDE( override suspend fun readFile(filepath: String): String = fileUtils.readFile(filepath) - override suspend fun readRangeInFile(filepath: String, range: Range): String { - val fullContents = readFile(filepath) - val lines = fullContents.lines() - val startLine = range.start.line - val startCharacter = range.start.character - val endLine = range.end.line - val endCharacter = range.end.character - - val firstLine = lines.getOrNull(startLine)?.substring(startCharacter) ?: "" - val lastLine = lines.getOrNull(endLine)?.substring(0, endCharacter) ?: "" - val betweenLines = if (endLine - startLine > 1) { - lines.subList(startLine + 1, endLine).joinToString("\n") - } else { - "" - } - - return listOf(firstLine, betweenLines, lastLine).filter { it.isNotEmpty() }.joinToString("\n") - } override suspend fun showLines(filepath: String, startLine: Int, endLine: Int) { setFileOpen(filepath, true) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/protocol/ide.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/protocol/ide.kt index bc199b35a57..a07e8933528 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/protocol/ide.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/protocol/ide.kt @@ -44,12 +44,6 @@ data class ShowLinesParams( val endLine: Int ) -data class ReadRangeInFileParams( - val filepath: String, - val range: Range -) - - data class GetDiffParams(val includeUnstaged: Boolean) data class GetBranchParams(val dir: String) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/types.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/types.kt index d937b0add6b..d23756634af 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/types.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/types.kt @@ -202,9 +202,7 @@ interface IDE { suspend fun saveFile(filepath: String) suspend fun readFile(filepath: String): String - - suspend fun readRangeInFile(filepath: String, range: Range): String - + suspend fun showLines( filepath: String, startLine: Int, diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts index ab5e8270266..df73943294d 100644 --- a/extensions/vscode/src/VsCodeIde.ts +++ b/extensions/vscode/src/VsCodeIde.ts @@ -1,7 +1,6 @@ import * as child_process from "node:child_process"; import { exec } from "node:child_process"; -import { Range } from "core"; import { EXTENSION_NAME } from "core/control-plane/env"; import { DEFAULT_IGNORES, defaultIgnoresGlob } from "core/indexing/ignore"; import * as URI from "uri-js"; @@ -137,14 +136,6 @@ class VsCodeIde implements IDE { return result; } - onDidChangeActiveTextEditor(callback: (uri: string) => void): void { - vscode.window.onDidChangeActiveTextEditor((editor) => { - if (editor) { - callback(editor.document.uri.toString()); - } - }); - } - showToast: IDE["showToast"] = async (...params) => { const [type, message, ...otherParams] = params; const { showErrorMessage, showWarningMessage, showInformationMessage } = @@ -209,16 +200,6 @@ class VsCodeIde implements IDE { }); } - readRangeInFile(fileUri: string, range: Range): Promise { - return this.ideUtils.readRangeInFile( - vscode.Uri.parse(fileUri), - new vscode.Range( - new vscode.Position(range.start.line, range.start.character), - new vscode.Position(range.end.line, range.end.character), - ), - ); - } - async getFileStats(files: string[]): Promise { const pathToLastModified: FileStatsMap = {}; await Promise.all( diff --git a/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts b/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts index d1f5b8ef266..7baec11b7bd 100644 --- a/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts +++ b/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts @@ -3,30 +3,34 @@ import { AutocompleteCodeSnippet, AutocompleteSnippetType, } from "core/autocomplete/snippets/types"; +import { AutocompleteReadCache } from "core/autocomplete/util/AutocompleteReadCache"; import { isSecurityConcern } from "core/indexing/ignore"; import { PosthogFeatureFlag, Telemetry } from "core/util/posthog"; import { LRUCache } from "lru-cache"; import * as vscode from "vscode"; +interface RecentlyVisitedLine { + line: number; + timestamp: number; +} + +type RecentlyVisitedRangeSnippet = AutocompleteCodeSnippet & { + timestamp: number; +}; + /** * Service to keep track of recently visited ranges in files. */ export class RecentlyVisitedRangesService { - private cache: LRUCache< - string, - Array - >; + private cache: LRUCache>; // Default value, we override in initWithPostHog private numSurroundingLines = 20; private maxRecentFiles = 3; - private maxSnippetsPerFile = 3; + private maxSnippetsPerFile = 3; // TODO - might have less if private isEnabled = true; constructor(private readonly ide: IDE) { - this.cache = new LRUCache< - string, - Array - >({ + this.cache = new LRUCache>({ max: this.maxRecentFiles, }); @@ -39,59 +43,54 @@ export class RecentlyVisitedRangesService { PosthogFeatureFlag.RecentlyVisitedRangesNumSurroundingLines, ); - if (recentlyVisitedRangesNumSurroundingLines) { + if (true) { this.isEnabled = true; this.numSurroundingLines = recentlyVisitedRangesNumSurroundingLines; + vscode.window.onDidChangeTextEditorSelection( + this.cacheCurrentSelectionContext, + ); + } else { + this.isEnabled = false; } - - vscode.window.onDidChangeTextEditorSelection( - this.cacheCurrentSelectionContext, - ); } + private securityChecks = new Map(); + private cacheCurrentSelectionContext = async ( event: vscode.TextEditorSelectionChangeEvent, ) => { - const fsPath = event.textEditor.document.fileName; - if (isSecurityConcern(fsPath)) { + let securityCheck = this.securityChecks.get( + event.textEditor.document.fileName, + ); + if (securityCheck === null) { + securityCheck = isSecurityConcern(event.textEditor.document.fileName); + this.securityChecks.set( + event.textEditor.document.fileName, + securityCheck, + ); + } + if (securityCheck) { return; } + const filepath = event.textEditor.document.uri.toString(); - const line = event.selections[0].active.line; - const startLine = Math.max(0, line - this.numSurroundingLines); - const endLine = Math.min( - line + this.numSurroundingLines, - event.textEditor.document.lineCount - 1, - ); - try { - const fileContents = await this.ide.readFile(filepath); - const lines = fileContents.split("\n"); - const relevantLines = lines - .slice(startLine, endLine + 1) - .join("\n") - .trim(); - - const snippet: AutocompleteCodeSnippet & { timestamp: number } = { - filepath, - content: relevantLines, - type: AutocompleteSnippetType.Code, - timestamp: Date.now(), - }; - - const existing = this.cache.get(filepath) || []; - const newSnippets = [...existing, snippet] - .sort((a, b) => b.timestamp - a.timestamp) - .slice(0, this.maxSnippetsPerFile); - - this.cache.set(filepath, newSnippets); - } catch (err) { - console.error( - "Error caching recently visited ranges for autocomplete: ", - err, - ); + // Exclude extension output panels + if (filepath.includes("output:extension-output")) { + // Just continue output: "output:extension-output-Continue.continue" return; } + + const existing = this.cache.get(filepath) || []; + if (existing.length >= this.maxSnippetsPerFile) { + existing.pop(); + } + existing.unshift({ + timestamp: Date.now(), + line: event.selections[0].active.line, + }); + + this.cache.set(filepath, existing); }; /** @@ -99,35 +98,65 @@ export class RecentlyVisitedRangesService { * Excludes snippets from the currently active file. * @returns Array of code snippets from recently visited files */ - public getSnippets(): AutocompleteCodeSnippet[] { + public async getSnippets(): Promise { if (!this.isEnabled) { return []; } - const currentFilepath = + const currentFileUri = vscode.window.activeTextEditor?.document.uri.toString(); - let allSnippets: Array = - []; - - // Get most recent snippets from each file in cache - for (const filepath of Array.from(this.cache.keys())) { - const snippets = (this.cache.get(filepath) || []) - .sort((a, b) => b.timestamp - a.timestamp) - .slice(0, this.maxSnippetsPerFile); - allSnippets = [...allSnippets, ...snippets]; + + const files = Array.from(this.cache.keys()).filter( + (f) => f !== currentFileUri, + ); + const readCache = AutocompleteReadCache.getInstance( + this.ide.readFile.bind(this.ide), + ); + const fileReads = await Promise.allSettled( + files.map(async (uri) => { + const contents = await readCache.getOrRead(uri); + const lines = contents.split("\n"); + return { + uri, + lines, + }; + }), + ); + + const successfulReads = fileReads + .filter((fr) => fr.status === "fulfilled") + .map((fr) => fr.value); + + const snippets: RecentlyVisitedRangeSnippet[] = []; + for (const file of successfulReads) { + const recentVisits = this.cache.get(file.uri); + if (recentVisits) { + for (const recentVisit of recentVisits) { + const startLine = Math.max( + 0, + recentVisit.line - this.numSurroundingLines, + ); + const endLine = Math.min( + recentVisit.line + this.numSurroundingLines, + file.lines.length - 1, + ); + const relevantLines = file.lines + .slice(startLine, endLine + 1) + .join("\n") + .trim(); + + const snippet: RecentlyVisitedRangeSnippet = { + filepath: file.uri, + content: relevantLines, + type: AutocompleteSnippetType.Code, + timestamp: recentVisit.timestamp, + }; + snippets.push(snippet); + } + } } - return allSnippets - .filter( - (s) => - !currentFilepath || - (s.filepath !== currentFilepath && - // Exclude Continue's own output as it makes it super-hard for users to test the autocomplete feature - // while looking at the prompts in the Continue's output - !s.filepath.startsWith( - "output:extension-output-Continue.continue", - )), - ) + return snippets .sort((a, b) => b.timestamp - a.timestamp) .map(({ timestamp, ...snippet }) => snippet); } diff --git a/extensions/vscode/src/autocomplete/completionProvider.ts b/extensions/vscode/src/autocomplete/completionProvider.ts index 498f971d05d..44905db5b69 100644 --- a/extensions/vscode/src/autocomplete/completionProvider.ts +++ b/extensions/vscode/src/autocomplete/completionProvider.ts @@ -276,7 +276,8 @@ export class ContinueCompletionProvider // const completionId = uuidv4(); const filepath = document.uri.toString(); - const recentlyVisitedRanges = this.recentlyVisitedRanges.getSnippets(); + const recentlyVisitedRanges = + await this.recentlyVisitedRanges.getSnippets(); let recentlyEditedRanges = await this.recentlyEditedTracker.getRecentlyEditedRanges(); @@ -372,7 +373,8 @@ export class ContinueCompletionProvider if (!this.usingFullFileDiff) { this.prefetchQueue.process({ ...ctx, - recentlyVisitedRanges: this.recentlyVisitedRanges.getSnippets(), + recentlyVisitedRanges: + await this.recentlyVisitedRanges.getSnippets(), recentlyEditedRanges: await this.recentlyEditedTracker.getRecentlyEditedRanges(), }); @@ -393,7 +395,8 @@ export class ContinueCompletionProvider if (!this.usingFullFileDiff) { this.prefetchQueue.process({ ...ctx, - recentlyVisitedRanges: this.recentlyVisitedRanges.getSnippets(), + recentlyVisitedRanges: + await this.recentlyVisitedRanges.getSnippets(), recentlyEditedRanges: await this.recentlyEditedTracker.getRecentlyEditedRanges(), }); diff --git a/extensions/vscode/src/autocomplete/lsp.ts b/extensions/vscode/src/autocomplete/lsp.ts index 32e8b03f534..93c86a7f9a2 100644 --- a/extensions/vscode/src/autocomplete/lsp.ts +++ b/extensions/vscode/src/autocomplete/lsp.ts @@ -21,6 +21,7 @@ import type { RangeInFileWithContents, SignatureHelp, } from "core"; +import { readRangeInFile } from "core/util/rangeInFile"; import type Parser from "web-tree-sitter"; type GotoProviderName = @@ -144,19 +145,14 @@ function findTypeIdentifiers(node: Parser.SyntaxNode): Parser.SyntaxNode[] { } async function crawlTypes( - rif: RangeInFile | RangeInFileWithContents, + rif: RangeInFileWithContents, ide: IDE, depth: number = 1, results: RangeInFileWithContents[] = [], searchedLabels: Set = new Set(), ): Promise { - // Get the file contents if not already attached - const contents = isRifWithContents(rif) - ? rif.contents - : await ide.readFile(rif.filepath); - // Parse AST - const ast = await getAst(rif.filepath, contents); + const ast = await getAst(rif.filepath, rif.contents); if (!ast) { return results; } @@ -190,7 +186,14 @@ async function crawlTypes( continue; } - const contents = await ide.readRangeInFile(typeDef.filepath, typeDef.range); + console.log( + `read file - lsp crawlTypes readRangeInFile - ${typeDef.filepath}`, + ); + const contents = await readRangeInFile( + ide, + typeDef.filepath, + typeDef.range, + ); definitions.push({ ...typeDef, @@ -247,7 +250,10 @@ export async function getDefinitionsForNode( // Don't display a function of more than 15 lines // We can of course do something smarter here eventually - let funcText = await ide.readRangeInFile(funDef.filepath, funDef.range); + console.log( + `read file - lsp getDefinitionsForNode call_expression - ${funDef.filepath}`, + ); + let funcText = await readRangeInFile(ide, funDef.filepath, funDef.range); if (funcText.split("\n").length > 15) { let truncated = false; const funRootAst = await getAst(funDef.filepath, funcText); @@ -309,7 +315,11 @@ export async function getDefinitionsForNode( if (!classDef) { break; } - const contents = await ide.readRangeInFile( + console.log( + `read file - lsp getDefinitionsForNode new_expression - ${classDef.filepath}`, + ); + const contents = await readRangeInFile( + ide, classDef.filepath, classDef.range, ); @@ -347,9 +357,12 @@ export async function getDefinitionsForNode( rif.range = range; if (!isRifWithContents(rif)) { + console.log( + `read file - lsp getDefinitionsForNode final mapping - ${rif.filepath}`, + ); return { ...rif, - contents: await ide.readRangeInFile(rif.filepath, rif.range), + contents: await readRangeInFile(ide, rif.filepath, rif.range), }; } return rif; diff --git a/extensions/vscode/src/autocomplete/recentlyEdited.ts b/extensions/vscode/src/autocomplete/recentlyEdited.ts index fc4eab51731..802c6efab90 100644 --- a/extensions/vscode/src/autocomplete/recentlyEdited.ts +++ b/extensions/vscode/src/autocomplete/recentlyEdited.ts @@ -1,4 +1,3 @@ -import { RangeInFileWithContents } from "core"; import { getSymbolsForSnippet } from "core/autocomplete/context/ranking"; import { RecentlyEditedRange } from "core/autocomplete/util/types"; import * as vscode from "vscode"; @@ -114,6 +113,9 @@ export class RecentlyEditedTracker { private async _getContentsForRange( entry: Omit, ): Promise { + console.log( + `read file - recentlyEdited _getContentsForRange - ${entry.uri}`, + ); const content = await this.ideUtils.readFile(entry.uri); if (content === null) { return ""; @@ -133,35 +135,4 @@ export class RecentlyEditedTracker { }; }); } - - public async getRecentlyEditedDocuments(): Promise< - RangeInFileWithContents[] - > { - const results = await Promise.all( - this.recentlyEditedDocuments.map(async (entry) => { - try { - const contents = await vscode.workspace.fs - .readFile(entry.uri) - .then((content) => content.toString()); - const lines = contents.split("\n"); - - return { - filepath: entry.uri.toString(), - contents, - range: { - start: { line: 0, character: 0 }, - end: { - line: lines.length - 1, - character: lines[lines.length - 1].length, - }, - }, - }; - } catch (e) { - return null; - } - }), - ); - - return results.filter((result) => result !== null) as any; - } } diff --git a/extensions/vscode/src/extension/VsCodeExtension.ts b/extensions/vscode/src/extension/VsCodeExtension.ts index 5bd039cd64a..649e5ca4876 100644 --- a/extensions/vscode/src/extension/VsCodeExtension.ts +++ b/extensions/vscode/src/extension/VsCodeExtension.ts @@ -634,8 +634,12 @@ export class VsCodeExtension { ); context.subscriptions.push(linkProvider); - this.ide.onDidChangeActiveTextEditor((filepath) => { - void this.core.invoke("files/opened", { uris: [filepath] }); + vscode.window.onDidChangeActiveTextEditor((editor) => { + if (editor) { + this.core.invoke("files/opened", { + uris: [editor.document.uri.toString()], + }); + } }); // initializes openedFileLruCache with files that are already open when the extension is activated diff --git a/extensions/vscode/src/extension/VsCodeMessenger.ts b/extensions/vscode/src/extension/VsCodeMessenger.ts index fd71d9354c6..9514b90e686 100644 --- a/extensions/vscode/src/extension/VsCodeMessenger.ts +++ b/extensions/vscode/src/extension/VsCodeMessenger.ts @@ -673,19 +673,6 @@ export class VsCodeMessenger { // None right now /** BOTH CORE AND WEBVIEW **/ - this.onWebviewOrCore("readRangeInFile", async (msg) => { - return await vscode.workspace - .openTextDocument(msg.data.filepath) - .then((document) => { - const start = new vscode.Position(0, 0); - const end = new vscode.Position(5, 0); - const range = new vscode.Range(start, end); - - const contents = document.getText(range); - return contents; - }); - }); - this.onWebviewOrCore("getIdeSettings", async (msg) => { return ide.getIdeSettings(); }); diff --git a/extensions/vscode/src/util/editLoggingUtils.ts b/extensions/vscode/src/util/editLoggingUtils.ts index 467ab4f0f19..e94ac23c229 100644 --- a/extensions/vscode/src/util/editLoggingUtils.ts +++ b/extensions/vscode/src/util/editLoggingUtils.ts @@ -70,7 +70,7 @@ export const handleTextDocumentChange = async ( recentlyEditedRanges = await completionProvider.recentlyEditedTracker.getRecentlyEditedRanges(); recentlyVisitedRanges = - completionProvider.recentlyVisitedRanges.getSnippets(); + await completionProvider.recentlyVisitedRanges.getSnippets(); } return { diff --git a/extensions/vscode/src/util/expandSnippet.ts b/extensions/vscode/src/util/expandSnippet.ts deleted file mode 100644 index bd253cf7eaf..00000000000 --- a/extensions/vscode/src/util/expandSnippet.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Chunk, IDE } from "core"; -import { languageForFilepath } from "core/autocomplete/constants/AutocompleteLanguageInfo"; -import { DEFAULT_IGNORE_DIRS } from "core/indexing/ignore"; -import { deduplicateArray } from "core/util"; -import { getParserForFile } from "core/util/treeSitter"; -import * as vscode from "vscode"; - -import { getDefinitionsForNode } from "../autocomplete/lsp"; - -import type { SyntaxNode } from "web-tree-sitter"; - -export async function expandSnippet( - fileUri: string, - startLine: number, - endLine: number, - ide: IDE, -): Promise { - const parser = await getParserForFile(fileUri); - if (!parser) { - return []; - } - - const fullFileContents = await ide.readFile(fileUri); - const root: SyntaxNode = parser.parse(fullFileContents).rootNode; - - // Find all nodes contained in the range - const containedInRange: SyntaxNode[] = []; - const toExplore: SyntaxNode[] = [root]; - while (toExplore.length > 0) { - const node = toExplore.pop()!; - for (const child of node.namedChildren) { - if ( - child.startPosition.row >= startLine && - child.endPosition.row <= endLine - ) { - // Fully contained in range - containedInRange.push(child); - toExplore.push(child); - } else if ( - child.startPosition.row >= startLine || - child.endPosition.row <= endLine - ) { - // Overlaps, children may be contained in range - toExplore.push(child); - } - } - } - - // Find all call expressions - const callExpressions = containedInRange.filter( - (node) => node.type === "call_expression", - ); - let callExpressionDefinitions = ( - await Promise.all( - callExpressions.map(async (node) => { - return getDefinitionsForNode( - vscode.Uri.parse(fileUri), - node, - ide, - languageForFilepath(fileUri), - ); - }), - ) - ).flat(); - - // De-duplicate the definitions - callExpressionDefinitions = deduplicateArray( - callExpressionDefinitions, - (a, b) => { - return ( - a.filepath === b.filepath && - a.range.start.line === b.range.start.line && - a.range.end.line === b.range.end.line && - a.range.start.character === b.range.start.character && - a.range.end.character === b.range.end.character - ); - }, - ); - - // Filter out definitions already in selected range - callExpressionDefinitions = callExpressionDefinitions.filter((def) => { - return !( - def.filepath === fileUri && - def.range.start.line >= startLine && - def.range.end.line <= endLine - ); - }); - - // Filter out defintions not under workspace directories - const workspaceDirectories = await ide.getWorkspaceDirs(); - callExpressionDefinitions = callExpressionDefinitions.filter((def) => { - return ( - workspaceDirectories.some((dir) => def.filepath.startsWith(dir)) && - !DEFAULT_IGNORE_DIRS.some( - (dir) => - def.filepath.includes(`/${dir}/`) || - def.filepath.includes(`\\${dir}\\`), - ) - ); - }); - - const chunks = await Promise.all( - callExpressionDefinitions.map(async (def) => { - return { - filepath: def.filepath, - startLine: def.range.start.line, - endLine: def.range.end.line, - digest: "", - index: 0, - content: await ide.readRangeInFile(def.filepath, def.range), - }; - }), - ); - return chunks; -}