diff --git a/.continueignore b/.continueignore index fcbe84fa0d..4950608f1c 100644 --- a/.continueignore +++ b/.continueignore @@ -1,4 +1,7 @@ -**/*.run.xml -archive/**/* -extensions/vscode/models/**/* -docs/docs/languages \ No newline at end of file +\*_/_.run.xml +docs/docs/languages +.changes/ +.idea/ +.vscode/ +.archive/ +**/*.scm \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index b497624cc1..5d9bf33594 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ -extensions/vscode/continue_rc_schema.json \ No newline at end of file +extensions/vscode/continue_rc_schema.json +**/.continueignore \ No newline at end of file diff --git a/binary/test/binary.test.ts b/binary/test/binary.test.ts index 87549e1550..5db4ff52ff 100644 --- a/binary/test/binary.test.ts +++ b/binary/test/binary.test.ts @@ -73,7 +73,11 @@ describe("Test Suite", () => { ); } - const ide = new FileSystemIde(); + const testDir = path.join(__dirname, "..", ".test"); + if (!fs.existsSync(testDir)) { + fs.mkdirSync(testDir); + } + const ide = new FileSystemIde(testDir); const reverseIde = new ReverseMessageIde(messenger.on.bind(messenger), ide); // Wait for core to set itself up diff --git a/core/autocomplete/completionProvider.ts b/core/autocomplete/completionProvider.ts index de235aca5c..ef73d9b58d 100644 --- a/core/autocomplete/completionProvider.ts +++ b/core/autocomplete/completionProvider.ts @@ -4,7 +4,7 @@ import OpenAI from "openai"; import path from "path"; import { v4 as uuidv4 } from "uuid"; import { RangeInFileWithContents } from "../commands/util.js"; -import { ConfigHandler } from "../config/handler.js"; +import { IConfigHandler } from "../config/IConfigHandler.js"; import { TRIAL_FIM_MODEL } from "../config/onboarding.js"; import { streamLines } from "../diff/util.js"; import { @@ -145,7 +145,7 @@ export class CompletionProvider { private static lastUUID: string | undefined = undefined; constructor( - private readonly configHandler: ConfigHandler, + private readonly configHandler: IConfigHandler, private readonly ide: IDE, private readonly getLlm: () => Promise, private readonly _onError: (e: any) => void, diff --git a/core/config/handler.ts b/core/config/ConfigHandler.ts similarity index 97% rename from core/config/handler.ts rename to core/config/ConfigHandler.ts index 419d1a01cf..1d9a8c958f 100644 --- a/core/config/handler.ts +++ b/core/config/ConfigHandler.ts @@ -8,9 +8,10 @@ import { ILLM, } from "../index.js"; import { Telemetry } from "../util/posthog.js"; +import { IConfigHandler } from "./IConfigHandler.js"; import { finalToBrowserConfig, loadFullConfigNode } from "./load.js"; -export class ConfigHandler { +export class ConfigHandler implements IConfigHandler { private savedConfig: ContinueConfig | undefined; private savedBrowserConfig?: BrowserSerializedContinueConfig; private additionalContextProviders: IContextProvider[] = []; diff --git a/core/config/IConfigHandler.ts b/core/config/IConfigHandler.ts new file mode 100644 index 0000000000..8177e7e03e --- /dev/null +++ b/core/config/IConfigHandler.ts @@ -0,0 +1,17 @@ +import { + BrowserSerializedContinueConfig, + ContinueConfig, + IContextProvider, + IdeSettings, + ILLM, +} from "../index.js"; + +export interface IConfigHandler { + updateIdeSettings(ideSettings: IdeSettings): void; + onConfigUpdate(listener: (newConfig: ContinueConfig) => void): void; + reloadConfig(): Promise; + getSerializedConfig(): Promise; + loadConfig(): Promise; + llmFromTitle(title?: string): Promise; + registerCustomContextProvider(contextProvider: IContextProvider): void; +} diff --git a/core/context/retrieval/fullTextSearch.ts b/core/context/retrieval/fullTextSearch.ts index 5040374979..3e22db3021 100644 --- a/core/context/retrieval/fullTextSearch.ts +++ b/core/context/retrieval/fullTextSearch.ts @@ -1,5 +1,6 @@ import { BranchAndDir, Chunk } from "../../index.js"; import { FullTextSearchCodebaseIndex } from "../../indexing/FullTextSearch.js"; + export async function retrieveFts( query: string, n: number, diff --git a/core/context/retrieval/pipelines/BaseRetrievalPipeline.ts b/core/context/retrieval/pipelines/BaseRetrievalPipeline.ts new file mode 100644 index 0000000000..b993aea07e --- /dev/null +++ b/core/context/retrieval/pipelines/BaseRetrievalPipeline.ts @@ -0,0 +1,59 @@ +import { + BranchAndDir, + Chunk, + EmbeddingsProvider, + IDE, + Reranker, +} from "../../.."; +import { LanceDbIndex } from "../../../indexing/LanceDbIndex"; +import { retrieveFts } from "../fullTextSearch"; + +export interface RetrievalPipelineOptions { + ide: IDE; + embeddingsProvider: EmbeddingsProvider; + reranker: Reranker | undefined; + + input: string; + nRetrieve: number; + nFinal: number; + tags: BranchAndDir[]; + filterDirectory?: string; +} + +export interface IRetrievalPipeline { + run(options: RetrievalPipelineOptions): Promise; +} + +export default class BaseRetrievalPipeline implements IRetrievalPipeline { + private lanceDbIndex: LanceDbIndex; + constructor(protected readonly options: RetrievalPipelineOptions) { + this.lanceDbIndex = new LanceDbIndex(options.embeddingsProvider, (path) => + options.ide.readFile(path), + ); + } + + protected async retrieveFts(input: string, n: number): Promise { + return retrieveFts( + input, + n, + this.options.tags, + this.options.filterDirectory, + ); + } + + protected async retrieveEmbeddings( + input: string, + n: number, + ): Promise { + return this.lanceDbIndex.retrieve( + input, + n, + this.options.tags, + this.options.filterDirectory, + ); + } + + run(): Promise { + throw new Error("Not implemented"); + } +} diff --git a/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts b/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts new file mode 100644 index 0000000000..7ad1819d5c --- /dev/null +++ b/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts @@ -0,0 +1,26 @@ +import { Chunk } from "../../.."; +import { deduplicateChunks } from "../util"; +import BaseRetrievalPipeline from "./BaseRetrievalPipeline"; + +export default class NoRerankerRetrievalPipeline extends BaseRetrievalPipeline { + async run(): Promise { + const { input } = this.options; + + // Get all retrieval results + const retrievalResults: Chunk[] = []; + + // Full-text search + const ftsResults = await this.retrieveFts(input, this.options.nFinal / 2); + retrievalResults.push(...ftsResults); + + // Embeddings + const embeddingResults = await this.retrieveEmbeddings( + input, + this.options.nFinal / 2, + ); + retrievalResults.push(...embeddingResults); + + const finalResults: Chunk[] = deduplicateChunks(retrievalResults); + return finalResults; + } +} diff --git a/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts b/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts new file mode 100644 index 0000000000..47b0f1f9d0 --- /dev/null +++ b/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts @@ -0,0 +1,125 @@ +import { Chunk } from "../../.."; +import { RETRIEVAL_PARAMS } from "../../../util/parameters"; +import { deduplicateChunks } from "../util"; +import BaseRetrievalPipeline from "./BaseRetrievalPipeline"; + +export default class RerankerRetrievalPipeline extends BaseRetrievalPipeline { + private async _retrieveInitial(): Promise { + const { input, nRetrieve } = this.options; + + // Get all retrieval results + const retrievalResults: Chunk[] = []; + + // Full-text search + const ftsResults = await this.retrieveFts(input, nRetrieve / 2); + retrievalResults.push(...ftsResults); + + // Embeddings + const embeddingResults = await this.retrieveEmbeddings(input, nRetrieve); + retrievalResults.push( + ...embeddingResults.slice(0, nRetrieve - ftsResults.length), + ); + + const results: Chunk[] = deduplicateChunks(retrievalResults); + return results; + } + + private async _rerank(input: string, chunks: Chunk[]): Promise { + if (!this.options.reranker) { + throw new Error("No reranker provided"); + } + + let scores: number[] = await this.options.reranker.rerank(input, chunks); + + // Filter out low-scoring results + let results = chunks; + // let results = chunks.filter( + // (_, i) => scores[i] >= RETRIEVAL_PARAMS.rerankThreshold, + // ); + // scores = scores.filter( + // (score) => score >= RETRIEVAL_PARAMS.rerankThreshold, + // ); + + results.sort( + (a, b) => scores[results.indexOf(a)] - scores[results.indexOf(b)], + ); + results = results.slice(-this.options.nFinal); + return results; + } + + private async _expandWithEmbeddings(chunks: Chunk[]): Promise { + const topResults = chunks.slice( + -RETRIEVAL_PARAMS.nResultsToExpandWithEmbeddings, + ); + + const expanded = await Promise.all( + topResults.map(async (chunk, i) => { + const results = await this.retrieveEmbeddings( + chunk.content, + RETRIEVAL_PARAMS.nEmbeddingsExpandTo, + ); + return results; + }), + ); + return expanded.flat(); + } + + private async _expandRankedResults(chunks: Chunk[]): Promise { + let results: Chunk[] = []; + + const embeddingsResults = await this._expandWithEmbeddings(chunks); + results.push(...embeddingsResults); + + return results; + } + + async run(): Promise { + // Retrieve initial results + let results = await this._retrieveInitial(); + + // Rerank + const { input } = this.options; + results = await this._rerank(input, 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/context/retrieval/retrieval.ts b/core/context/retrieval/retrieval.ts index f3218685bc..1138078d22 100644 --- a/core/context/retrieval/retrieval.ts +++ b/core/context/retrieval/retrieval.ts @@ -1,24 +1,14 @@ import { BranchAndDir, - Chunk, ContextItem, ContextProviderExtras, } from "../../index.js"; -import { LanceDbIndex } from "../../indexing/LanceDbIndex.js"; -import { deduplicateArray, getRelativePath } from "../../util/index.js"; +import { getRelativePath } from "../../util/index.js"; import { RETRIEVAL_PARAMS } from "../../util/parameters.js"; -import { retrieveFts } from "./fullTextSearch.js"; - -function deduplicateChunks(chunks: Chunk[]): Chunk[] { - return deduplicateArray(chunks, (a, b) => { - return ( - a.filepath === b.filepath && - a.startLine === b.startLine && - a.endLine === b.endLine - ); - }); -} +import { RetrievalPipelineOptions } from "./pipelines/BaseRetrievalPipeline.js"; +import NoRerankerRetrievalPipeline from "./pipelines/NoRerankerRetrievalPipeline.js"; +import RerankerRetrievalPipeline from "./pipelines/RerankerRetrievalPipeline.js"; export async function retrieveContextItemsFromEmbeddings( extras: ContextProviderExtras, @@ -66,80 +56,21 @@ export async function retrieveContextItemsFromEmbeddings( branch: branches[i], })); - // Get all retrieval results - const retrievalResults: Chunk[] = []; - - // Source: Full-text search - const ftsResults = await retrieveFts( - extras.fullInput, - nRetrieve / 2, - tags, - filterDirectory, - ); - retrievalResults.push(...ftsResults); - - // 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 - - // Source: Embeddings - const lanceDbIndex = new LanceDbIndex(extras.embeddingsProvider, (path) => - extras.ide.readFile(path), - ); - const vecResults = await lanceDbIndex.retrieve( - extras.fullInput, + const pipelineType = useReranking + ? RerankerRetrievalPipeline + : NoRerankerRetrievalPipeline; + const pipelineOptions: RetrievalPipelineOptions = { + nFinal, nRetrieve, tags, + embeddingsProvider: extras.embeddingsProvider, + reranker: extras.reranker, filterDirectory, - ); - retrievalResults.push(...vecResults); - - // De-duplicate - let results: Chunk[] = deduplicateChunks(retrievalResults); - - // Re-rank - if (useReranking && extras.reranker) { - let scores: number[] = await extras.reranker.rerank( - extras.fullInput, - results, - ); - - // Filter out low-scoring results - results = results.filter( - (_, i) => scores[i] >= RETRIEVAL_PARAMS.rerankThreshold, - ); - scores = scores.filter( - (score) => score >= RETRIEVAL_PARAMS.rerankThreshold, - ); - - results.sort( - (a, b) => scores[results.indexOf(a)] - scores[results.indexOf(b)], - ); - results = results.slice(-nFinal); - } + ide: extras.ide, + input: extras.fullInput, + }; + const pipeline = new pipelineType(pipelineOptions); + const results = await pipeline.run(); if (results.length === 0) { throw new Error( diff --git a/core/context/retrieval/util.ts b/core/context/retrieval/util.ts new file mode 100644 index 0000000000..e783a2ca03 --- /dev/null +++ b/core/context/retrieval/util.ts @@ -0,0 +1,12 @@ +import { Chunk } from "../.."; +import { deduplicateArray } from "../../util"; + +export function deduplicateChunks(chunks: Chunk[]): Chunk[] { + return deduplicateArray(chunks, (a, b) => { + return ( + a.filepath === b.filepath && + a.startLine === b.startLine && + a.endLine === b.endLine + ); + }); +} diff --git a/core/core.ts b/core/core.ts index 9dcfc13ea5..6e6b8b997e 100644 --- a/core/core.ts +++ b/core/core.ts @@ -6,7 +6,8 @@ import type { SiteIndexingConfig, } from "."; import { CompletionProvider } from "./autocomplete/completionProvider.js"; -import { ConfigHandler } from "./config/handler.js"; +import { ConfigHandler } from "./config/ConfigHandler.js"; +import { IConfigHandler } from "./config/IConfigHandler"; import { setupApiKeysMode, setupFreeTrialMode, @@ -16,9 +17,9 @@ import { import { createNewPromptFile } from "./config/promptFile.js"; import { addModel, addOpenAIKey, deleteModel } from "./config/util.js"; import { ContinueServerClient } from "./continueServer/stubs/client.js"; +import { CodebaseIndexer, PauseToken } from "./indexing/CodebaseIndexer.js"; import { DocsService } from "./indexing/docs/DocsService"; import TransformersJsEmbeddingsProvider from "./indexing/embeddings/TransformersJsEmbeddingsProvider.js"; -import { CodebaseIndexer, PauseToken } from "./indexing/indexCodebase.js"; import Ollama from "./llm/llms/Ollama.js"; import type { FromCoreProtocol, ToCoreProtocol } from "./protocol"; import { GlobalContext } from "./util/GlobalContext.js"; @@ -33,7 +34,7 @@ import { streamDiffLines } from "./util/verticalEdit.js"; export class Core { // implements IMessenger - configHandler: ConfigHandler; + configHandler: IConfigHandler; codebaseIndexerPromise: Promise; completionProvider: CompletionProvider; continueServerClientPromise: Promise; @@ -286,7 +287,7 @@ export class Core { }); async function* llmStreamChat( - configHandler: ConfigHandler, + configHandler: IConfigHandler, abortedMessageIds: Set, msg: Message, ) { @@ -321,7 +322,7 @@ export class Core { ); async function* llmStreamComplete( - configHandler: ConfigHandler, + configHandler: IConfigHandler, abortedMessageIds: Set, msg: Message, @@ -387,7 +388,7 @@ export class Core { }); async function* runNodeJsSlashCommand( - configHandler: ConfigHandler, + configHandler: IConfigHandler, abortedMessageIds: Set, msg: Message, messenger: IMessenger, @@ -476,7 +477,7 @@ export class Core { }); async function* streamDiffLinesGenerator( - configHandler: ConfigHandler, + configHandler: IConfigHandler, abortedMessageIds: Set, msg: Message, ) { diff --git a/core/indexing/CodeSnippetsIndex.ts b/core/indexing/CodeSnippetsIndex.ts index d773b1dd31..a5ec68f8bf 100644 --- a/core/indexing/CodeSnippetsIndex.ts +++ b/core/indexing/CodeSnippetsIndex.ts @@ -116,7 +116,7 @@ export class CodeSnippetsCodebaseIndex implements CodebaseIndex { } yield { - desc: `Indexing ${compute.path}`, + desc: `Indexing ${getBasename(compute.path)}`, progress: i / results.compute.length, status: "indexing", }; diff --git a/core/indexing/indexCodebase.ts b/core/indexing/CodebaseIndexer.ts similarity index 98% rename from core/indexing/indexCodebase.ts rename to core/indexing/CodebaseIndexer.ts index a0ceeb3cb2..5b90e0775f 100644 --- a/core/indexing/indexCodebase.ts +++ b/core/indexing/CodebaseIndexer.ts @@ -1,4 +1,4 @@ -import { ConfigHandler } from "../config/handler.js"; +import { IConfigHandler } from "../config/IConfigHandler.js"; import { IContinueServerClient } from "../continueServer/interface.js"; import { IDE, IndexTag, IndexingProgressUpdate } from "../index.js"; import { CodeSnippetsCodebaseIndex } from "./CodeSnippetsIndex.js"; @@ -23,7 +23,7 @@ export class PauseToken { export class CodebaseIndexer { constructor( - private readonly configHandler: ConfigHandler, + private readonly configHandler: IConfigHandler, private readonly ide: IDE, private readonly pauseToken: PauseToken, private readonly continueServerClient: IContinueServerClient, diff --git a/core/indexing/FullTextSearch.ts b/core/indexing/FullTextSearch.ts index 1e60d2c6c5..c640eae203 100644 --- a/core/indexing/FullTextSearch.ts +++ b/core/indexing/FullTextSearch.ts @@ -4,6 +4,7 @@ import { IndexTag, IndexingProgressUpdate, } from "../index.js"; +import { getBasename } from "../util/index.js"; import { RETRIEVAL_PARAMS } from "../util/parameters.js"; import { ChunkCodebaseIndex } from "./chunk/ChunkCodebaseIndex.js"; import { DatabaseConnection, SqliteDb, tagToString } from "./refreshIndex.js"; @@ -71,7 +72,7 @@ export class FullTextSearchCodebaseIndex implements CodebaseIndex { yield { progress: i / results.compute.length, - desc: `Indexing ${item.path}`, + desc: `Indexing ${getBasename(item.path)}`, status: "indexing", }; markComplete([item], IndexResultType.Compute); diff --git a/core/indexing/ignore.ts b/core/indexing/ignore.ts index caa391d930..8881f6b5e5 100644 --- a/core/indexing/ignore.ts +++ b/core/indexing/ignore.ts @@ -90,6 +90,7 @@ export const DEFAULT_IGNORE_DIRS = [ ".gradle", ".cache", "gems", + "vendor", ]; export const defaultIgnoreDir = ignore().add(DEFAULT_IGNORE_DIRS); diff --git a/core/jest.config.js b/core/jest.config.js index 035fe21397..1ec7c44f8a 100644 --- a/core/jest.config.js +++ b/core/jest.config.js @@ -19,4 +19,5 @@ export default { __dirname: path.dirname(fileURLToPath(import.meta.url)), __filename: path.resolve(fileURLToPath(import.meta.url)), }, + globalSetup: "/jest.global-setup.ts", }; diff --git a/core/jest.global-setup.ts b/core/jest.global-setup.ts new file mode 100644 index 0000000000..1c4fe12e8f --- /dev/null +++ b/core/jest.global-setup.ts @@ -0,0 +1,6 @@ +// jest.global-setup.js +import path from "path"; + +export default async function () { + process.env.CONTINUE_GLOBAL_DIR = path.join(__dirname, ".continue-test"); +} diff --git a/core/test/.gitignore b/core/test/.gitignore new file mode 100644 index 0000000000..227f3fc4fa --- /dev/null +++ b/core/test/.gitignore @@ -0,0 +1 @@ +scratch.test.ts \ No newline at end of file diff --git a/core/test/_testEnv.test.ts b/core/test/_testEnv.test.ts new file mode 100644 index 0000000000..3ebddd64b1 --- /dev/null +++ b/core/test/_testEnv.test.ts @@ -0,0 +1,6 @@ +describe("Test environment", () => { + test("should have CONTINUE_GLOBAL_DIR env var set to .continue-test", () => { + expect(process.env.CONTINUE_GLOBAL_DIR).toBeDefined(); + expect(process.env.CONTINUE_GLOBAL_DIR)?.toMatch(/\.continue-test$/); + }); +}); diff --git a/core/test/context/retrieval/RetrievalPipeline.test.ts b/core/test/context/retrieval/RetrievalPipeline.test.ts new file mode 100644 index 0000000000..cb707d39a0 --- /dev/null +++ b/core/test/context/retrieval/RetrievalPipeline.test.ts @@ -0,0 +1,20 @@ +import { IRetrievalPipeline } from "../../../context/retrieval/pipelines/BaseRetrievalPipeline"; + +function testRetrievalPipeline(pipeline: IRetrievalPipeline) { + test("should successfully run"); +} + +// describe("Retrieval Pipelines", () => { +// const ide = new FileSystemIde(); +// const options: RetrievalPipelineOptions; + +// test("NoRerankerRetrievalPipeline", () => { +// const pipeline = new NoRerankerRetrievalPipeline(options); +// testRetrievalPipeline(pipeline); +// }); + +// test("NoRerankerRetrievalPipeline", () => { +// const pipeline = new NoRerankerRetrievalPipeline(options); +// testRetrievalPipeline(pipeline); +// }); +// }); diff --git a/core/test/indexing/CodebaseIndexer.test.ts b/core/test/indexing/CodebaseIndexer.test.ts new file mode 100644 index 0000000000..db4c5107e4 --- /dev/null +++ b/core/test/indexing/CodebaseIndexer.test.ts @@ -0,0 +1,174 @@ +import fs from "node:fs"; +import path from "node:path"; +import { ConfigHandler } from "../../config/ConfigHandler"; +import { ContinueServerClient } from "../../continueServer/stubs/client"; +import { CodebaseIndexer, PauseToken } from "../../indexing/CodebaseIndexer"; +import { LanceDbIndex } from "../../indexing/LanceDbIndex"; +import TransformersJsEmbeddingsProvider from "../../indexing/embeddings/TransformersJsEmbeddingsProvider"; +import FileSystemIde from "../../util/filesystem"; +import { + getIndexFolderPath, + getIndexSqlitePath, + getLanceDbPath, +} from "../../util/paths"; +import { + addToTestDir, + setUpTestDir, + tearDownTestDir, + TEST_DIR, +} from "../testUtils/testDir"; + +const TEST_TS = `\ +function main() { + console.log("Hello, world!"); +} + +class Foo { + constructor(public bar: string) {} +} +`; + +const TEST_PY = `\ +def main(): + print("Hello, world!") + +class Foo: + def __init__(self, bar: str): + self.bar = bar +`; + +const TEST_RS = `\ +fn main() { + println!("Hello, world!"); +} + +struct Foo { + bar: String, +} +`; + +// These are more like integration tests, whereas we should separately test +// the individual CodebaseIndex classes +describe.skip("CodebaseIndexer", () => { + const ide = new FileSystemIde(TEST_DIR); + const ideSettingsPromise = ide.getIdeSettings(); + const configHandler = new ConfigHandler( + ide, + ideSettingsPromise, + async (text) => {}, + ); + const pauseToken = new PauseToken(false); + const continueServerClient = new ContinueServerClient(undefined, undefined); + const codebaseIndexer = new CodebaseIndexer( + configHandler, + ide, + pauseToken, + continueServerClient, + ); + const lancedbIndex = new LanceDbIndex( + new TransformersJsEmbeddingsProvider(), + ide.readFile.bind(ide), + continueServerClient, + ); + + beforeAll(async () => { + setUpTestDir(); + }); + + afterAll(async () => { + tearDownTestDir(); + }); + + test("should index test folder without problem", async () => { + addToTestDir([ + ["test.ts", TEST_TS], + ["py/main.py", TEST_PY], + ]); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + const updates = []; + for await (const update of codebaseIndexer.refresh( + [TEST_DIR], + abortSignal, + )) { + updates.push(update); + } + + expect(updates.length).toBeGreaterThan(0); + }); + + test("should have created index folder with all necessary files", async () => { + expect(fs.existsSync(getIndexFolderPath())).toBe(true); + expect(fs.existsSync(getIndexSqlitePath())).toBe(true); + expect(fs.existsSync(getLanceDbPath())).toBe(true); + }); + + test("should be able to query lancedb index", async () => { + const chunks = await lancedbIndex.retrieve( + "What is the main function doing?", + 10, + await ide.getTags(lancedbIndex.artifactId), + undefined, + ); + + expect(chunks.length).toBe(2); + // Check that the main function from both files is returned + expect(chunks.some((chunk) => chunk.filepath.endsWith("test.ts"))).toBe( + true, + ); + expect(chunks.some((chunk) => chunk.filepath.endsWith("main.py"))).toBe( + true, + ); + }); + + test("should successfully re-index after adding a file", async () => { + addToTestDir([["main.rs", TEST_RS]]); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + const updates = []; + for await (const update of codebaseIndexer.refresh( + [TEST_DIR], + abortSignal, + )) { + updates.push(update); + } + expect(updates.length).toBeGreaterThan(0); + // Check that the new file was indexed + const chunks = await lancedbIndex.retrieve( + "What is the main function doing?", + 3, + await ide.getTags(lancedbIndex.artifactId), + undefined, + ); + expect(chunks.length).toBe(3); + expect(chunks.some((chunk) => chunk.filepath.endsWith("main.rs"))).toBe( + true, + ); + }); + + test("should successfully re-index after deleting a file", async () => { + fs.rmSync(path.join(TEST_DIR, "main.rs")); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + const updates = []; + for await (const update of codebaseIndexer.refresh( + [TEST_DIR], + abortSignal, + )) { + updates.push(update); + } + expect(updates.length).toBeGreaterThan(0); + // Check that the deleted file was removed from the index + const chunks = await lancedbIndex.retrieve( + "What is the main function doing?", + 10, + await ide.getTags(lancedbIndex.artifactId), + undefined, + ); + expect(chunks.length).toBe(2); + expect(chunks.every((chunk) => !chunk.filepath.endsWith("main.rs"))).toBe( + true, + ); + }); +}); diff --git a/core/test/testUtils/testDir.ts b/core/test/testUtils/testDir.ts new file mode 100644 index 0000000000..33a636d08f --- /dev/null +++ b/core/test/testUtils/testDir.ts @@ -0,0 +1,27 @@ +import fs from "fs"; +import path from "path"; + +export const TEST_DIR = path.join(__dirname, "testDir"); + +export function setUpTestDir() { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true }); + } + fs.mkdirSync(TEST_DIR); +} + +export function tearDownTestDir() { + fs.rmSync(TEST_DIR, { recursive: true }); +} + +export function addToTestDir(paths: (string | string[])[]) { + for (const p of paths) { + if (Array.isArray(p)) { + fs.writeFileSync(path.join(TEST_DIR, p[0]), p[1]); + } else if (p.endsWith("/")) { + fs.mkdirSync(path.join(TEST_DIR, p), { recursive: true }); + } else { + fs.writeFileSync(path.join(TEST_DIR, p), ""); + } + } +} diff --git a/core/test/util/deduplicateArray.test.ts b/core/test/util/deduplicateArray.test.ts new file mode 100644 index 0000000000..f26b2c782e --- /dev/null +++ b/core/test/util/deduplicateArray.test.ts @@ -0,0 +1,82 @@ +import { deduplicateArray } from "../../util"; + +describe("deduplicateArray", () => { + it("should return an empty array when given an empty array", () => { + const result = deduplicateArray([], (a, b) => a === b); + expect(result).toEqual([]); + }); + + it("should return the same array when there are no duplicates", () => { + const input = [1, 2, 3, 4, 5]; + const result = deduplicateArray(input, (a, b) => a === b); + expect(result).toEqual(input); + }); + + it("should remove duplicates based on the equality function", () => { + const input = [1, 2, 2, 3, 4, 4, 5]; + const result = deduplicateArray(input, (a, b) => a === b); + expect(result).toEqual([1, 2, 3, 4, 5]); + }); + + it("should work with objects using custom equality function", () => { + const input = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 1, name: "Alice" }, + { id: 3, name: "Charlie" }, + ]; + const result = deduplicateArray(input, (a, b) => a.id === b.id); + expect(result).toEqual([ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 3, name: "Charlie" }, + ]); + }); + + it("should preserve the order of items", () => { + const input = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]; + const result = deduplicateArray(input, (a, b) => a === b); + expect(result).toEqual([3, 1, 4, 5, 9, 2, 6]); + }); + + it("should work with strings", () => { + const input = ["apple", "banana", "apple", "cherry", "banana", "date"]; + const result = deduplicateArray(input, (a, b) => a === b); + expect(result).toEqual(["apple", "banana", "cherry", "date"]); + }); + + it("should handle arrays with all duplicate elements", () => { + const input = [1, 1, 1, 1, 1]; + const result = deduplicateArray(input, (a, b) => a === b); + expect(result).toEqual([1]); + }); + + it("should work with custom equality function for complex objects", () => { + const input = [ + { x: 1, y: 2 }, + { x: 2, y: 1 }, + { x: 1, y: 2 }, + { x: 3, y: 4 }, + ]; + const result = deduplicateArray( + input, + (a, b) => a.x === b.x && a.y === b.y, + ); + expect(result).toEqual([ + { x: 1, y: 2 }, + { x: 2, y: 1 }, + { x: 3, y: 4 }, + ]); + }); + + it("should handle large arrays efficiently", () => { + const input = Array(10000) + .fill(0) + .map((_, i) => i % 100); + const start = performance.now(); + const result = deduplicateArray(input, (a, b) => a === b); + const end = performance.now(); + expect(result).toHaveLength(100); + expect(end - start).toBeLessThan(1000); // Ensure it completes in less than 1 second + }); +}); diff --git a/core/test/walkDir.test.ts b/core/test/walkDir.test.ts index 3fad566722..93abbe9119 100644 --- a/core/test/walkDir.test.ts +++ b/core/test/walkDir.test.ts @@ -1,22 +1,13 @@ -import fs from "fs"; import path from "path"; import { walkDir, WalkerOptions } from "../indexing/walkDir"; import FileSystemIde from "../util/filesystem"; -const ide = new FileSystemIde(); - -const TEST_DIR = path.join(__dirname, "testDir"); - -function buildTestDir(paths: (string | string[])[]) { - for (const p of paths) { - if (Array.isArray(p)) { - fs.writeFileSync(path.join(TEST_DIR, p[0]), p[1]); - } else if (p.endsWith("/")) { - fs.mkdirSync(path.join(TEST_DIR, p), { recursive: true }); - } else { - fs.writeFileSync(path.join(TEST_DIR, p), ""); - } - } -} +import { + addToTestDir, + setUpTestDir, + tearDownTestDir, + TEST_DIR, +} from "./testUtils/testDir"; +const ide = new FileSystemIde(TEST_DIR); async function walkTestDir( options?: WalkerOptions, @@ -44,14 +35,11 @@ async function expectPaths( describe("walkDir", () => { beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true }); - } - fs.mkdirSync(TEST_DIR); + setUpTestDir(); }); afterEach(() => { - fs.rmSync(TEST_DIR, { recursive: true }); + tearDownTestDir(); }); test("should return nothing for empty dir", async () => { @@ -61,20 +49,20 @@ describe("walkDir", () => { test("should return all files in flat dir", async () => { const files = ["a.txt", "b.py", "c.ts"]; - buildTestDir(files); + addToTestDir(files); const result = await walkTestDir(); expect(result).toEqual(files); }); test("should ignore ignored files in flat dir", async () => { const files = [[".gitignore", "*.py"], "a.txt", "c.ts", "b.py"]; - buildTestDir(files); - await expectPaths(["a.txt", "c.ts", ".gitignore"], ["b.py"]); + addToTestDir(files); + await expectPaths(["a.txt", "c.ts"], ["b.py"]); }); test("should handle negation in flat folder", async () => { const files = [[".gitignore", "**/*\n!*.py"], "a.txt", "c.ts", "b.py"]; - buildTestDir(files); + addToTestDir(files); await expectPaths(["b.py"], [".gitignore", "a.txt", "c.ts"]); }); @@ -89,7 +77,7 @@ describe("walkDir", () => { "d/g/", "d/g/h.ts", ]; - buildTestDir(files); + addToTestDir(files); await expectPaths( files.filter((files) => !files.endsWith("/")), [], @@ -108,16 +96,16 @@ describe("walkDir", () => { "d/g/h.ts", ["d/.gitignore", "*.py"], ]; - buildTestDir(files); + addToTestDir(files); await expectPaths( - ["a.txt", "b.py", "c.ts", "d/e.txt", "d/g/h.ts", "d/.gitignore"], + ["a.txt", "b.py", "c.ts", "d/e.txt", "d/g/h.ts"], ["d/f.py"], ); }); test("should handle leading slash in gitignore", async () => { const files = [[".gitignore", "/no.txt"], "a.txt", "b.py", "no.txt"]; - buildTestDir(files); + addToTestDir(files); await expectPaths(["a.txt", "b.py"], ["no.txt"]); }); @@ -131,7 +119,7 @@ describe("walkDir", () => { "c/e.py", ["c/.gitignore", "*.py"], ]; - buildTestDir(files); + addToTestDir(files); await expectPaths(["a.py"], ["b.txt", "c/e.py", "c/d.txt"]); }); @@ -146,7 +134,7 @@ describe("walkDir", () => { "d/f.py", "d/g.ts", ]; - buildTestDir(files); + addToTestDir(files); await expectPaths( ["c.ts", "d/g.ts"], ["a.txt", "b.py", "d/e.txt", "d/f.py"], @@ -162,7 +150,7 @@ describe("walkDir", () => { "ignored_dir/c/", "ignored_dir/c/d.py", ]; - buildTestDir(files); + addToTestDir(files); await expectPaths(["a.txt"], ["ignored_dir/b.txt", "ignored_dir/c/d.py"]); }); @@ -178,7 +166,7 @@ describe("walkDir", () => { "temp/", "temp/c.txt", ]; - buildTestDir(files); + addToTestDir(files); await expectPaths( ["important.what", "subdir/root_only.txt"], ["a.what", "root_only.txt", "subdir/b.what", "temp/c.txt"], @@ -194,11 +182,8 @@ describe("walkDir", () => { "c.ts", "d.js", ]; - buildTestDir(files); - await expectPaths( - ["a.txt", "d.js", ".gitignore", ".continueignore"], - ["b.py", "c.ts"], - ); + addToTestDir(files); + await expectPaths(["a.txt", "d.js"], ["b.py", "c.ts"]); }); test("should return dirs and only dirs in onlyDirs mode", async () => { @@ -212,7 +197,7 @@ describe("walkDir", () => { "d/g/", "d/g/h.ts", ]; - buildTestDir(files); + addToTestDir(files); await expectPaths( ["d", "d/g"], ["a.txt", "b.py", "c.ts", "d/e.txt", "d/f.py", "d/g/h.ts"], @@ -222,7 +207,7 @@ describe("walkDir", () => { test("should return valid paths in absolute path mode", async () => { const files = ["a.txt", "b/", "b/c.txt"]; - buildTestDir(files); + addToTestDir(files); await expectPaths( [path.join(TEST_DIR, "a.txt"), path.join(TEST_DIR, "b", "c.txt")], [], @@ -232,25 +217,6 @@ describe("walkDir", () => { ); }); - test("should walk continue repo without getting any files of the default ignore types", async () => { - const results = await walkDir(path.join(__dirname, "..", ".."), ide, { - ignoreFiles: [".gitignore", ".continueignore"], - }); - expect(results.length).toBeGreaterThan(0); - expect(results.some((file) => file.includes("/node_modules/"))).toBe(false); - expect(results.some((file) => file.includes("/.git/"))).toBe(false); - expect( - results.some( - (file) => - file.endsWith(".gitignore") || - file.endsWith(".continueignore") || - file.endsWith("package-lock.json"), - ), - ).toBe(false); - // At some point we will cross this number, but in case we leap past it suddenly I think we'd want to investigate why - expect(results.length).toBeLessThan(1500); - }); - test("should skip .git and node_modules folders", async () => { const files = [ "a.txt", @@ -265,7 +231,7 @@ describe("walkDir", () => { "src/", "src/index.ts", ]; - buildTestDir(files); + addToTestDir(files); await expectPaths( ["a.txt", "src/index.ts"], [ @@ -276,4 +242,35 @@ describe("walkDir", () => { ], ); }); + + test("should walk continue repo without getting any files of the default ignore types", async () => { + const results = await walkDir(path.join(__dirname, "..", ".."), ide, { + ignoreFiles: [".gitignore", ".continueignore"], + }); + expect(results.length).toBeGreaterThan(0); + expect(results.some((file) => file.includes("/node_modules/"))).toBe(false); + expect(results.some((file) => file.includes("/.git/"))).toBe(false); + expect( + results.some( + (file) => + file.endsWith(".gitignore") || + file.endsWith(".continueignore") || + file.endsWith("package-lock.json"), + ), + ).toBe(false); + // At some point we will cross this number, but in case we leap past it suddenly I think we'd want to investigate why + expect(results.length).toBeLessThan(1500); + }); + + test("should walk continue/extensions/vscode without getting any files in the .continueignore", async () => { + const vscodePath = path.join(__dirname, "..", "..", "extensions", "vscode"); + const results = await walkDir(vscodePath, ide, { + ignoreFiles: [".gitignore", ".continueignore"], + }); + expect(results.length).toBeGreaterThan(0); + expect(results.some((file) => file.includes("/textmate-syntaxes/"))).toBe( + false, + ); + expect(results.some((file) => file.includes(".tmLanguage"))).toBe(false); + }); }); diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts index cde6332b68..b9d35cdb92 100644 --- a/core/util/filesystem.ts +++ b/core/util/filesystem.ts @@ -17,11 +17,7 @@ import { import { getContinueGlobalPath } from "./paths.js"; class FileSystemIde implements IDE { - static workspaceDir = "/tmp/continue"; - - constructor() { - fs.mkdirSync(FileSystemIde.workspaceDir, { recursive: true }); - } + constructor(private readonly workspaceDir: string) {} pathSep(): Promise { return Promise.resolve(path.sep); } @@ -141,14 +137,7 @@ class FileSystemIde implements IDE { } getWorkspaceDirs(): Promise { - return new Promise((resolve, reject) => { - fs.mkdtemp(FileSystemIde.workspaceDir, (err, folder) => { - if (err) { - reject(err); - } - resolve([folder]); - }); - }); + return Promise.resolve([this.workspaceDir]); } listFolders(): Promise { diff --git a/core/util/parameters.ts b/core/util/parameters.ts index 8909af6f4b..13b38626d0 100644 --- a/core/util/parameters.ts +++ b/core/util/parameters.ts @@ -27,7 +27,9 @@ export const DO_NOT_COUNT_REJECTED_BEFORE = 250; export const RETRIEVAL_PARAMS = { rerankThreshold: 0.3, - nFinal: 10, - nRetrieve: 20, + nFinal: 20, + nRetrieve: 50, bm25Threshold: -2.5, + nResultsToExpandWithEmbeddings: 5, + nEmbeddingsExpandTo: 5, }; diff --git a/extensions/vscode/.continueignore b/extensions/vscode/.continueignore index 7fd2a214af..2a5d96ea4b 100644 --- a/extensions/vscode/.continueignore +++ b/extensions/vscode/.continueignore @@ -1,3 +1,6 @@ media **/*.tmLanguage -textmate-syntaxes \ No newline at end of file +models/**/* +builtin-themes/ +**/textmate-syntaxes/ +**/*.scm \ No newline at end of file diff --git a/extensions/vscode/package-lock.json b/extensions/vscode/package-lock.json index 6873f1d753..92cd86f32d 100644 --- a/extensions/vscode/package-lock.json +++ b/extensions/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.9.172", + "version": "0.9.174", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "continue", - "version": "0.9.172", + "version": "0.9.174", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extensions/vscode/src/autocomplete/completionProvider.ts b/extensions/vscode/src/autocomplete/completionProvider.ts index e76effba85..3befe838e0 100644 --- a/extensions/vscode/src/autocomplete/completionProvider.ts +++ b/extensions/vscode/src/autocomplete/completionProvider.ts @@ -4,13 +4,18 @@ import { CompletionProvider, type AutocompleteInput, } from "core/autocomplete/completionProvider"; -import type { ConfigHandler } from "core/config/handler"; +import { IConfigHandler } from "core/config/IConfigHandler"; import { v4 as uuidv4 } from "uuid"; import * as vscode from "vscode"; import type { TabAutocompleteModel } from "../util/loadAutocompleteModel"; import { getDefinitionsFromLsp } from "./lsp"; import { RecentlyEditedTracker } from "./recentlyEdited"; -import { StatusBarStatus, setupStatusBar, stopStatusBarLoading, getStatusBarStatus } from "./statusBar"; +import { + StatusBarStatus, + getStatusBarStatus, + setupStatusBar, + stopStatusBarLoading, +} from "./statusBar"; interface VsCodeCompletionInput { document: vscode.TextDocument; @@ -43,7 +48,7 @@ export class ContinueCompletionProvider private recentlyEditedTracker = new RecentlyEditedTracker(); constructor( - private readonly configHandler: ConfigHandler, + private readonly configHandler: IConfigHandler, private readonly ide: IDE, private readonly tabAutocompleteModel: TabAutocompleteModel, ) { @@ -73,7 +78,8 @@ export class ContinueCompletionProvider token: vscode.CancellationToken, //@ts-ignore ): ProviderResult { - const enableTabAutocomplete = getStatusBarStatus() === StatusBarStatus.Enabled; + const enableTabAutocomplete = + getStatusBarStatus() === StatusBarStatus.Enabled; if (token.isCancellationRequested || !enableTabAutocomplete) { return null; } diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts index e95a99dd11..b89703b974 100644 --- a/extensions/vscode/src/commands.ts +++ b/extensions/vscode/src/commands.ts @@ -6,7 +6,7 @@ import * as vscode from "vscode"; import { ContextMenuConfig, IDE } from "core"; import { CompletionProvider } from "core/autocomplete/completionProvider"; -import { ConfigHandler } from "core/config/handler"; +import { IConfigHandler } from "core/config/IConfigHandler"; import { ContinueServerClient } from "core/continueServer/stubs/client"; import { GlobalContext } from "core/util/GlobalContext"; import { @@ -158,7 +158,7 @@ const commandsMap: ( ide: IDE, extensionContext: vscode.ExtensionContext, sidebar: ContinueGUIWebviewViewProvider, - configHandler: ConfigHandler, + configHandler: IConfigHandler, diffManager: DiffManager, verticalDiffManager: VerticalPerLineDiffManager, continueServerClientPromise: Promise, @@ -597,8 +597,8 @@ const commandsMap: ( currentStatus === StatusBarStatus.Paused ? StatusBarStatus.Enabled : currentStatus === StatusBarStatus.Disabled - ? StatusBarStatus.Paused - : StatusBarStatus.Disabled; + ? StatusBarStatus.Paused + : StatusBarStatus.Disabled; } else { // Toggle between Disabled and Enabled targetStatus = @@ -684,7 +684,7 @@ export function registerAllCommands( ide: IDE, extensionContext: vscode.ExtensionContext, sidebar: ContinueGUIWebviewViewProvider, - configHandler: ConfigHandler, + configHandler: IConfigHandler, diffManager: DiffManager, verticalDiffManager: VerticalPerLineDiffManager, continueServerClientPromise: Promise, diff --git a/extensions/vscode/src/debugPanel.ts b/extensions/vscode/src/debugPanel.ts index 41d2f26ea5..2c22158c6f 100644 --- a/extensions/vscode/src/debugPanel.ts +++ b/extensions/vscode/src/debugPanel.ts @@ -1,5 +1,5 @@ import type { FileEdit } from "core"; -import type { ConfigHandler } from "core/config/handler"; +import { IConfigHandler } from "core/config/IConfigHandler"; import * as vscode from "vscode"; import { getTheme } from "./util/getTheme"; import { getExtensionVersion } from "./util/util"; @@ -46,7 +46,7 @@ export class ContinueGUIWebviewViewProvider } constructor( - private readonly configHandlerPromise: Promise, + private readonly configHandlerPromise: Promise, private readonly windowId: string, private readonly extensionContext: vscode.ExtensionContext, ) { diff --git a/extensions/vscode/src/diff/verticalPerLine/manager.ts b/extensions/vscode/src/diff/verticalPerLine/manager.ts index 337bdb442f..f6c4eafb43 100644 --- a/extensions/vscode/src/diff/verticalPerLine/manager.ts +++ b/extensions/vscode/src/diff/verticalPerLine/manager.ts @@ -1,4 +1,4 @@ -import type { ConfigHandler } from "core/config/handler"; +import { IConfigHandler } from "core/config/IConfigHandler"; import { pruneLinesFromBottom, pruneLinesFromTop } from "core/llm/countTokens"; import { getMarkdownLanguageTagForFile } from "core/util"; import { streamDiffLines } from "core/util/verticalEdit"; @@ -21,7 +21,7 @@ export class VerticalPerLineDiffManager { private userChangeListener: vscode.Disposable | undefined; - constructor(private readonly configHandler: ConfigHandler) { + constructor(private readonly configHandler: IConfigHandler) { this.userChangeListener = undefined; } diff --git a/extensions/vscode/src/extension/VsCodeExtension.ts b/extensions/vscode/src/extension/VsCodeExtension.ts index 608b982bec..557f6b17d6 100644 --- a/extensions/vscode/src/extension/VsCodeExtension.ts +++ b/extensions/vscode/src/extension/VsCodeExtension.ts @@ -1,5 +1,5 @@ import { IContextProvider } from "core"; -import { ConfigHandler } from "core/config/handler"; +import { IConfigHandler } from "core/config/IConfigHandler"; import { Core } from "core/core"; import { FromCoreProtocol, ToCoreProtocol } from "core/protocol"; import { InProcessMessenger } from "core/util/messenger"; @@ -30,7 +30,7 @@ import { CONTINUE_WORKSPACE_KEY } from "../util/workspaceConfig"; export class VsCodeExtension { // Currently some of these are public so they can be used in testing (test/test-suites) - private configHandler: ConfigHandler; + private configHandler: IConfigHandler; private extensionContext: vscode.ExtensionContext; private ide: VsCodeIde; private tabAutocompleteModel: TabAutocompleteModel; @@ -66,7 +66,7 @@ export class VsCodeExtension { }, ); let resolveConfigHandler: any = undefined; - const configHandlerPromise = new Promise((resolve) => { + const configHandlerPromise = new Promise((resolve) => { resolveConfigHandler = resolve; }); this.sidebar = new ContinueGUIWebviewViewProvider( diff --git a/extensions/vscode/src/extension/VsCodeMessenger.ts b/extensions/vscode/src/extension/VsCodeMessenger.ts index 23873a923e..8cd14dfae0 100644 --- a/extensions/vscode/src/extension/VsCodeMessenger.ts +++ b/extensions/vscode/src/extension/VsCodeMessenger.ts @@ -1,4 +1,4 @@ -import { ConfigHandler } from "core/config/handler"; +import { IConfigHandler } from "core/config/IConfigHandler"; import { FromCoreProtocol, ToCoreProtocol } from "core/protocol"; import { ToWebviewFromCoreProtocol } from "core/protocol/coreWebview"; import { ToIdeFromWebviewOrCoreProtocol } from "core/protocol/ide"; @@ -70,7 +70,7 @@ export class VsCodeMessenger { private readonly webviewProtocol: VsCodeWebviewProtocol, private readonly ide: VsCodeIde, private readonly verticalDiffManagerPromise: Promise, - private readonly configHandlerPromise: Promise, + private readonly configHandlerPromise: Promise, ) { /** WEBVIEW ONLY LISTENERS **/ this.onWebview("showFile", (msg) => { diff --git a/extensions/vscode/src/ideProtocol.ts b/extensions/vscode/src/ideProtocol.ts index ebb210cf8c..c284a33563 100644 --- a/extensions/vscode/src/ideProtocol.ts +++ b/extensions/vscode/src/ideProtocol.ts @@ -15,7 +15,6 @@ import type { Thread, } from "core"; import { Range } from "core"; -import { defaultIgnoreDir, defaultIgnoreFile } from "core/indexing/ignore"; import { walkDir } from "core/indexing/walkDir"; import { editConfigJson, @@ -505,15 +504,7 @@ class VsCodeIde implements IDE { } async listDir(dir: string): Promise<[string, FileType][]> { - const files = await vscode.workspace.fs.readDirectory(uriFromFilePath(dir)); - const results = files.filter( - ([name, type]) => - !( - (type === vscode.FileType.File && defaultIgnoreFile.ignores(name)) || - (type === vscode.FileType.Directory && defaultIgnoreDir.ignores(name)) - ), - ) as any; - return results; + return vscode.workspace.fs.readDirectory(uriFromFilePath(dir)) as any; } getIdeSettingsSync(): IdeSettings { diff --git a/extensions/vscode/src/quickEdit/QuickEdit.ts b/extensions/vscode/src/quickEdit/QuickEdit.ts index 5e598f51f2..e5c4e69330 100644 --- a/extensions/vscode/src/quickEdit/QuickEdit.ts +++ b/extensions/vscode/src/quickEdit/QuickEdit.ts @@ -1,5 +1,5 @@ import { IDE } from "core"; -import { ConfigHandler } from "core/config/handler"; +import { IConfigHandler } from "core/config/IConfigHandler"; import { fetchwithRequestOptions } from "core/util/fetchWithOptions"; import * as vscode from "vscode"; import { VerticalPerLineDiffManager } from "../diff/verticalPerLine/manager"; @@ -17,7 +17,7 @@ interface QuickEditFlowStuff { export class QuickEdit { constructor( private readonly verticalDiffManager: VerticalPerLineDiffManager, - private readonly configHandler: ConfigHandler, + private readonly configHandler: IConfigHandler, private readonly webviewProtocol: VsCodeWebviewProtocol, private readonly ide: IDE, private readonly context: vscode.ExtensionContext, diff --git a/extensions/vscode/src/util/loadAutocompleteModel.ts b/extensions/vscode/src/util/loadAutocompleteModel.ts index c4498c74ad..db56c7e31c 100644 --- a/extensions/vscode/src/util/loadAutocompleteModel.ts +++ b/extensions/vscode/src/util/loadAutocompleteModel.ts @@ -1,5 +1,5 @@ import type { ILLM } from "core"; -import type { ConfigHandler } from "core/config/handler"; +import { IConfigHandler } from "core/config/IConfigHandler"; import Ollama from "core/llm/llms/Ollama"; import { GlobalContext } from "core/util/GlobalContext"; import * as vscode from "vscode"; @@ -13,9 +13,9 @@ export class TabAutocompleteModel { private shownOllamaWarning = false; private shownDeepseekWarning = false; - private configHandler: ConfigHandler; + private configHandler: IConfigHandler; - constructor(configHandler: ConfigHandler) { + constructor(configHandler: IConfigHandler) { this.configHandler = configHandler; }