From 5c1b4d77eaff9d32339ded670162869fdfbd3f62 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Sat, 7 Dec 2024 19:34:14 -0800 Subject: [PATCH 01/84] remove getcontinueglobaluri --- core/util/paths.ts | 12 ------------ core/util/uri.ts | 0 2 files changed, 12 deletions(-) create mode 100644 core/util/uri.ts diff --git a/core/util/paths.ts b/core/util/paths.ts index 3ff5251e62..48877ea4d8 100644 --- a/core/util/paths.ts +++ b/core/util/paths.ts @@ -1,6 +1,5 @@ import * as fs from "fs"; import * as os from "os"; -import { pathToFileURL } from "url"; import * as path from "path"; import * as JSONC from "comment-json"; import dotenv from "dotenv"; @@ -41,9 +40,6 @@ export function getGlobalContinueIgnorePath(): string { return continueIgnorePath; } -/* - Deprecated, replace with getContinueGlobalUri where possible -*/ export function getContinueGlobalPath(): string { // This is ~/.continue on mac/linux const continuePath = CONTINUE_GLOBAL_DIR; @@ -53,10 +49,6 @@ export function getContinueGlobalPath(): string { return continuePath; } -export function getContinueGlobalUri(): string { - return pathToFileURL(CONTINUE_GLOBAL_DIR).href; -} - export function getSessionsFolderPath(): string { const sessionsPath = path.join(getContinueGlobalPath(), "sessions"); if (!fs.existsSync(sessionsPath)) { @@ -101,10 +93,6 @@ export function getConfigJsonPath(ideType: IdeType = "vscode"): string { return p; } -export function getConfigJsonUri(): string { - return getContinueGlobalUri() + "/config.json"; -} - export function getConfigTsPath(): string { const p = path.join(getContinueGlobalPath(), "config.ts"); if (!fs.existsSync(p)) { diff --git a/core/util/uri.ts b/core/util/uri.ts new file mode 100644 index 0000000000..e69de29bb2 From 0062c0439a522dfa3991c8e3dd545cb2f9318362 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Sat, 7 Dec 2024 21:08:58 -0800 Subject: [PATCH 02/84] file change events --- core/config/getSystemPromptDotFile.ts | 6 +- core/core.ts | 76 +++++++++++++++++-- core/promptFiles/v2/createNewPromptFile.ts | 15 ++-- core/protocol/core.ts | 7 +- core/protocol/passThrough.ts | 1 - core/util/GlobalContext.ts | 1 + core/util/uri.ts | 3 + .../ContinuePluginStartupActivity.kt | 31 ++++---- .../toolWindow/ContinueBrowser.kt | 1 - extensions/vscode/e2e/tests/GUI.test.ts | 3 +- extensions/vscode/src/commands.ts | 1 - .../vscode/src/extension/VsCodeExtension.ts | 53 ++----------- extensions/vscode/src/util/arePathsEqual.ts | 11 --- extensions/vscode/src/util/ideUtils.ts | 8 +- extensions/vscode/src/util/vscode.ts | 14 ---- 15 files changed, 116 insertions(+), 115 deletions(-) delete mode 100644 extensions/vscode/src/util/arePathsEqual.ts diff --git a/core/config/getSystemPromptDotFile.ts b/core/config/getSystemPromptDotFile.ts index 03690dd47e..e5bd708e80 100644 --- a/core/config/getSystemPromptDotFile.ts +++ b/core/config/getSystemPromptDotFile.ts @@ -1,14 +1,14 @@ import { IDE } from ".."; - +import * as uri from "../util/uri"; export const SYSTEM_PROMPT_DOT_FILE = ".continuerules"; export async function getSystemPromptDotFile(ide: IDE): Promise { const dirs = await ide.getWorkspaceDirs(); let prompts: string[] = []; - const pathSep = await ide.pathSep(); + // const pathSep = await ide.pathSep(); for (const dir of dirs) { - const dotFile = `${dir}${pathSep}${SYSTEM_PROMPT_DOT_FILE}`; + const dotFile = uri.join(dir, SYSTEM_PROMPT_DOT_FILE); if (await ide.fileExists(dotFile)) { try { const content = await ide.readFile(dotFile); diff --git a/core/core.ts b/core/core.ts index ddfacf5b9f..c8deeb3737 100644 --- a/core/core.ts +++ b/core/core.ts @@ -30,7 +30,11 @@ import { DevDataSqliteDb } from "./util/devdataSqlite"; import { fetchwithRequestOptions } from "./util/fetchWithOptions"; import { GlobalContext } from "./util/GlobalContext"; import historyManager from "./util/history"; -import { editConfigJson, setupInitialDotContinueDirectory } from "./util/paths"; +import { + editConfigJson, + getConfigJsonPath, + setupInitialDotContinueDirectory, +} from "./util/paths"; import { Telemetry } from "./util/posthog"; import { getSymbolsForManyFiles } from "./util/treeSitter"; import { TTS } from "./util/tts"; @@ -38,6 +42,8 @@ import { TTS } from "./util/tts"; import type { ContextItemId, IDE, IndexingProgressUpdate } from "."; import type { FromCoreProtocol, ToCoreProtocol } from "./protocol"; import type { IMessenger, Message } from "./util/messenger"; +import { fileURLToPath } from "url"; +import { SYSTEM_PROMPT_DOT_FILE } from "./config/getSystemPromptDotFile"; export class Core { // implements IMessenger @@ -702,11 +708,6 @@ export class Core { const dirs = data?.dirs ?? (await this.ide.getWorkspaceDirs()); await this.refreshCodebaseIndex(dirs); }); - on("index/forceReIndexFiles", async ({ data }) => { - if (data?.files?.length) { - await this.refreshCodebaseIndexFiles(data.files); - } - }); on("index/setPaused", (msg) => { this.globalContext.update("indexingPaused", msg.data); this.indexingPauseToken.paused = msg.data; @@ -722,6 +723,69 @@ export class Core { } }); + // File changes + // TODO - remove remaining logic for these from IDEs where possible + on("files/changed", async ({ data }) => { + if (data?.uris?.length) { + for (const uri of data.uris) { + // Listen for file changes in the workspace + const filePath = fileURLToPath(uri); + if (filePath === getConfigJsonPath()) { + // Trigger a toast notification to provide UI feedback that config + // has been updated + const showToast = + this.globalContext.get("showConfigUpdateToast") ?? true; + if (showToast) { + void this.ide.showToast("info", "Config updated"); + + // URI TODO - this is a small regression - add core -> toast functionality that handles responses to update global context here + // vscode.window + // .showInformationMessage("Config updated", "Don't show again") + // .then((selection) => { + // if (selection === "Don't show again") { + // this.globalContext.update("showConfigUpdateToast", false); + // } + // }); + } + } + + if ( + uri.endsWith(".continuerc.json") || + uri.endsWith(".prompt") || + uri.endsWith(SYSTEM_PROMPT_DOT_FILE) + ) { + await this.configHandler.reloadConfig(); + } else if ( + uri.endsWith(".continueignore") || + uri.endsWith(".gitignore") + ) { + // Reindex the workspaces + this.invoke("index/forceReIndex", undefined); + } else { + // Reindex the file + await this.refreshCodebaseIndexFiles([uri]); + } + } + } + }); + + on("files/created", async ({ data }) => { + if (data?.uris?.length) { + await this.refreshCodebaseIndexFiles(data.uris); + } + }); + + on("files/deleted", async ({ data }) => { + if (data?.uris?.length) { + await this.refreshCodebaseIndexFiles(data.uris); + } + }); + on("files/opened", async ({ data }) => { + if (data?.uris?.length) { + // Do something on files opened + } + }); + // Docs, etc. indexing on("indexing/reindex", async (msg) => { if (msg.data.type === "docs") { diff --git a/core/promptFiles/v2/createNewPromptFile.ts b/core/promptFiles/v2/createNewPromptFile.ts index 0a8adc25d9..ac71035103 100644 --- a/core/promptFiles/v2/createNewPromptFile.ts +++ b/core/promptFiles/v2/createNewPromptFile.ts @@ -1,6 +1,6 @@ import { IDE } from "../.."; import { GlobalContext } from "../../util/GlobalContext"; -import { getPathModuleForIde } from "../../util/pathModule"; +// import { getPathModuleForIde } from "../../util/pathModule"; const FIRST_TIME_DEFAULT_PROMPT_FILE = `# This is an example ".prompt" file # It is used to define and reuse prompts within Continue @@ -54,15 +54,12 @@ export async function createNewPromptFileV2( // Find the first available filename let counter = 0; - let promptFilePath: string; + let promptFileUri: string; do { const suffix = counter === 0 ? "" : `-${counter}`; - promptFilePath = pathModule.join( - baseDir, - `new-prompt-file${suffix}.prompt`, - ); + promptFileUri = pathModule.join(baseDir, `new-prompt-file${suffix}.prompt`); counter++; - } while (await ide.fileExists(promptFilePath)); + } while (await ide.fileExists(promptFileUri)); const globalContext = new GlobalContext(); const PROMPT_FILE = @@ -72,6 +69,6 @@ export async function createNewPromptFileV2( globalContext.update("hasAlreadyCreatedAPromptFile", true); - await ide.writeFile(promptFilePath, PROMPT_FILE); - await ide.openFile(promptFilePath); + await ide.writeFile(promptFileUri, PROMPT_FILE); + await ide.openFile(promptFileUri); } diff --git a/core/protocol/core.ts b/core/protocol/core.ts index 369470c599..9a7d85510d 100644 --- a/core/protocol/core.ts +++ b/core/protocol/core.ts @@ -159,7 +159,6 @@ export type ToCoreFromIdeOrWebviewProtocol = { undefined | { dirs?: string[]; shouldClearIndexes?: boolean }, void, ]; - "index/forceReIndexFiles": [undefined | { files?: string[] }, void]; "index/indexingProgressBarInitialized": [undefined, void]; completeOnboarding: [ { @@ -168,6 +167,12 @@ export type ToCoreFromIdeOrWebviewProtocol = { void, ]; + // File changes + "files/changed": [{ uris?: string[] }, void]; + "files/opened": [{ uris?: string[] }, void]; + "files/created": [{ uris?: string[] }, void]; + "files/deleted": [{ uris?: string[] }, void]; + // Docs etc. Indexing. TODO move codebase to this "indexing/reindex": [{ type: string; id: string }, void]; "indexing/abort": [{ type: string; id: string }, void]; diff --git a/core/protocol/passThrough.ts b/core/protocol/passThrough.ts index d5299614ff..ddc911b1b9 100644 --- a/core/protocol/passThrough.ts +++ b/core/protocol/passThrough.ts @@ -44,7 +44,6 @@ export const WEBVIEW_TO_CORE_PASS_THROUGH: (keyof ToCoreFromWebviewProtocol)[] = // Codebase "index/setPaused", "index/forceReIndex", - "index/forceReIndexFiles", "index/indexingProgressBarInitialized", // Docs, etc. "indexing/reindex", diff --git a/core/util/GlobalContext.ts b/core/util/GlobalContext.ts index 7804460c6e..41daa8de25 100644 --- a/core/util/GlobalContext.ts +++ b/core/util/GlobalContext.ts @@ -17,6 +17,7 @@ export type GlobalContextType = { curEmbeddingsProviderId: EmbeddingsProvider["id"]; hasDismissedConfigTsNoticeJetBrains: boolean; hasAlreadyCreatedAPromptFile: boolean; + showConfigUpdateToast: boolean; }; /** diff --git a/core/util/uri.ts b/core/util/uri.ts index e69de29bb2..26b30fd691 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -0,0 +1,3 @@ +export function join(uri: string, segment: string) { + return uri.replace(/\/*$/, "") + "/" + segment.replace(/^\/*/, ""); +} diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt index eecbc958cd..1338e7bd44 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt @@ -156,21 +156,24 @@ class ContinuePluginStartupActivity : StartupActivity, DumbAware { // Handle file changes and deletions - reindex connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener { override fun after(events: List) { - // Collect all relevant paths for deletions - val deletedPaths = events.filterIsInstance() - .map { event -> event.file.path.split("/").dropLast(1).joinToString("/") } - - // Collect all relevant paths for content changes - val changedPaths = events.filterIsInstance() - .map { event -> event.file.path.split("/").dropLast(1).joinToString("/") } - - // Combine both lists of paths for re-indexing - val allPaths = deletedPaths + changedPaths + // Collect all relevant URIs for deletions + val deletedURIs = events.filterIsInstance() + .map { event -> event.file.url } + + // Send "files/deleted" message if there are any deletions + if (deletedURIs.isNotEmpty()) { + val data = mapOf("files" to deletedURIs) + continuePluginService.coreMessenger?.request("files/deleted", data, null) { _ -> } + } - // Create a data map if there are any paths to re-index - if (allPaths.isNotEmpty()) { - val data = mapOf("files" to allPaths) - continuePluginService.coreMessenger?.request("index/forceReIndexFiles", data, null) { _ -> } + // Collect all relevant URIs for content changes + val changedURIs = events.filterIsInstance() + .map { event -> event.file.url } + + // Send "files/changed" message if there are any content changes + if (changedURIs.isNotEmpty()) { + val data = mapOf("files" to changedURIs) + continuePluginService.coreMessenger?.request("files/changed", data, null) { _ -> } } } }) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/toolWindow/ContinueBrowser.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/toolWindow/ContinueBrowser.kt index 8fc73d5d45..8520c42683 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/toolWindow/ContinueBrowser.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/toolWindow/ContinueBrowser.kt @@ -58,7 +58,6 @@ class ContinueBrowser(val project: Project, url: String) { "stats/getTokensPerModel", "index/setPaused", "index/forceReIndex", - "index/forceReIndexFiles", "index/indexingProgressBarInitialized", "completeOnboarding", "addAutocompleteModel", diff --git a/extensions/vscode/e2e/tests/GUI.test.ts b/extensions/vscode/e2e/tests/GUI.test.ts index 6a5e820f87..761964623f 100644 --- a/extensions/vscode/e2e/tests/GUI.test.ts +++ b/extensions/vscode/e2e/tests/GUI.test.ts @@ -7,7 +7,6 @@ import { import { expect } from "chai"; import { GUIActions } from "../actions/GUI.actions"; import { GUISelectors } from "../selectors/GUI.selectors"; -import * as path from "path"; import { TestUtils } from "../TestUtils"; describe("GUI Test", () => { @@ -17,7 +16,7 @@ describe("GUI Test", () => { before(async function () { this.timeout(10000000); - await VSBrowser.instance.openResources(path.join("e2e/test-continue")); + await VSBrowser.instance.openResources("e2e/test-continue"); await GUIActions.openGui(); diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts index 0ae80c0bd2..f23f6dff2f 100644 --- a/extensions/vscode/src/commands.ts +++ b/extensions/vscode/src/commands.ts @@ -33,7 +33,6 @@ import EditDecorationManager from "./quickEdit/EditDecorationManager"; import { QuickEdit, QuickEditShowParams } from "./quickEdit/QuickEditQuickPick"; import { Battery } from "./util/battery"; import { getFullyQualifiedPath } from "./util/util"; -import { uriFromFilePath } from "./util/vscode"; import { VsCodeIde } from "./VsCodeIde"; import type { VsCodeWebviewProtocol } from "./webviewProtocol"; diff --git a/extensions/vscode/src/extension/VsCodeExtension.ts b/extensions/vscode/src/extension/VsCodeExtension.ts index ec1c5f9639..7d3368a760 100644 --- a/extensions/vscode/src/extension/VsCodeExtension.ts +++ b/extensions/vscode/src/extension/VsCodeExtension.ts @@ -29,7 +29,6 @@ import { getControlPlaneSessionInfo, WorkOsAuthProvider, } from "../stubs/WorkOsAuthProvider"; -import { arePathsEqual } from "../util/arePathsEqual"; import { Battery } from "../util/battery"; import { FileSearch } from "../util/FileSearch"; import { TabAutocompleteModel } from "../util/loadAutocompleteModel"; @@ -37,7 +36,6 @@ import { VsCodeIde } from "../VsCodeIde"; import { VsCodeMessenger } from "./VsCodeMessenger"; -import { SYSTEM_PROMPT_DOT_FILE } from "core/config/getSystemPromptDotFile"; import type { VsCodeWebviewProtocol } from "../webviewProtocol"; export class VsCodeExtension { @@ -265,57 +263,20 @@ export class VsCodeExtension { }); vscode.workspace.onDidSaveTextDocument(async (event) => { - // Listen for file changes in the workspace - const filepath = event.uri.fsPath; - - if (arePathsEqual(filepath, getConfigJsonPath())) { - // Trigger a toast notification to provide UI feedback that config - // has been updated - const showToast = context.globalState.get( - "showConfigUpdateToast", - true, - ); - - if (showToast) { - vscode.window - .showInformationMessage("Config updated", "Don't show again") - .then((selection) => { - if (selection === "Don't show again") { - context.globalState.update("showConfigUpdateToast", false); - } - }); - } - } - - if ( - filepath.endsWith(".continuerc.json") || - filepath.endsWith(".prompt") || - filepath.endsWith(SYSTEM_PROMPT_DOT_FILE) - ) { - this.configHandler.reloadConfig(); - } else if ( - filepath.endsWith(".continueignore") || - filepath.endsWith(".gitignore") - ) { - // Reindex the workspaces - this.core.invoke("index/forceReIndex", undefined); - } else { - // Reindex the file - this.core.invoke("index/forceReIndexFiles", { - files: [filepath], - }); - } + this.core.invoke("files/changed", { + uris: [event.uri.toString()] + }) }); vscode.workspace.onDidDeleteFiles(async (event) => { - this.core.invoke("index/forceReIndexFiles", { - files: event.files.map((file) => file.fsPath), + this.core.invoke("files/deleted", { + uris: event.files.map((uri) => uri.toString()), }); }); vscode.workspace.onDidCreateFiles(async (event) => { - this.core.invoke("index/forceReIndexFiles", { - files: event.files.map((file) => file.fsPath), + this.core.invoke("files/created", { + uris: event.files.map((uri) => uri.toString()), }); }); diff --git a/extensions/vscode/src/util/arePathsEqual.ts b/extensions/vscode/src/util/arePathsEqual.ts deleted file mode 100644 index cd10e53996..0000000000 --- a/extensions/vscode/src/util/arePathsEqual.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as os from "os"; - -export function arePathsEqual(path1: string, path2: string): boolean { - if (os.platform() === "win32") { - // On Windows, compare paths case-insensitively - return path1.toLowerCase() === path2.toLowerCase(); - } else { - // On other platforms, compare paths case-sensitively - return path1 === path2; - } -} diff --git a/extensions/vscode/src/util/ideUtils.ts b/extensions/vscode/src/util/ideUtils.ts index cfd6898eb4..1869dc64ff 100644 --- a/extensions/vscode/src/util/ideUtils.ts +++ b/extensions/vscode/src/util/ideUtils.ts @@ -15,11 +15,7 @@ import { showSuggestion as showSuggestionInEditor, } from "../suggestions"; -import { - getUniqueId, - openEditorAndRevealRange, - uriFromFilePath, -} from "./vscode"; +import { getUniqueId, openEditorAndRevealRange } from "./vscode"; import type { FileEdit, RangeInFile, Thread } from "core"; @@ -30,7 +26,7 @@ export class VsCodeIdeUtils { visibleMessages: Set = new Set(); async gotoDefinition( - filepath: string, + uri: string, position: vscode.Position, ): Promise { const locations: vscode.Location[] = await vscode.commands.executeCommand( diff --git a/extensions/vscode/src/util/vscode.ts b/extensions/vscode/src/util/vscode.ts index 16c15bb195..18f084ae22 100644 --- a/extensions/vscode/src/util/vscode.ts +++ b/extensions/vscode/src/util/vscode.ts @@ -141,20 +141,6 @@ export function getPathSep(): string { return isWindowsLocalButNotRemote() ? "/" : path.sep; } -export function uriFromFilePath(filepath: string): vscode.Uri { - let finalPath = filepath; - if (vscode.env.remoteName) { - if (isWindowsLocalButNotRemote()) { - finalPath = windowsToPosix(filepath); - } - return vscode.Uri.parse( - `vscode-remote://${vscode.env.remoteName}${finalPath}`, - ); - } else { - return vscode.Uri.file(finalPath); - } -} - export function getUniqueId() { const id = vscode.env.machineId; if (id === "someValue.machineId") { From 8e1b4588d33920c550f785bfdb92b3d0c6f30d80 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Sun, 8 Dec 2024 18:59:43 -0800 Subject: [PATCH 03/84] path -> URI gui p2 --- core/util/uri.ts | 47 ++++++++++++++++++- .../CodeToEditCard/CodeToEditListItem.tsx | 3 +- gui/src/components/FileIcon.tsx | 16 +++++-- .../components/History/HistoryTableRow.tsx | 8 +--- gui/src/components/mainInput/TipTapEditor.tsx | 4 +- gui/src/components/mainInput/resolveInput.ts | 4 +- gui/src/components/markdown/FilenameLink.tsx | 6 +-- .../StepContainerPreToolbar/FileInfo.tsx | 5 +- gui/src/pages/gui/ToolCallDiv/CreateFile.tsx | 6 +-- .../FunctionSpecificToolCallDiv.tsx | 5 +- gui/src/redux/slices/sessionSlice.ts | 17 +++++-- gui/src/redux/thunks/gatherContext.ts | 4 +- 12 files changed, 91 insertions(+), 34 deletions(-) diff --git a/core/util/uri.ts b/core/util/uri.ts index 26b30fd691..93c4ddd009 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -1,3 +1,48 @@ -export function join(uri: string, segment: string) { +import URI from "uri-js"; + +export function getFullPath(uri: string): string { + try { + return URI.parse(uri).path ?? ""; + } catch (error) { + console.error(`Invalid URI: ${uri}`, error); + return ""; + } +} + +// Whenever working with partial URIs +// Should always first get the path relative to the workspaces +// If a matching workspace is not found, ONLY the file name should be used +export function getRelativePath( + fileUri: string, + workspaceUris: string[], +): string { + const fullPath = getFullPath(fileUri); +} + +export function getLastNPathParts(path: string, n: number): string { + if (n < 1) { + return ""; + } + const pathParts = path.split("/").filter(Boolean); + if (pathParts.length <= n) { + return path; + } + return pathParts.slice(-n).join("/"); +} + +export function getLastNUriRelativePathParts( + workspaceDirs: string[], + uri: string, + n: number, +): string { + const path = getRelativePath(uri, workspaceDirs); + return getLastNPathParts(path, n); +} + +export function getFileName(uri: string): string { + return getPath(); +} + +export function join(uri: string, pathSegment: string) { return uri.replace(/\/*$/, "") + "/" + segment.replace(/^\/*/, ""); } diff --git a/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx b/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx index ed46517e24..f6e4420f41 100644 --- a/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx +++ b/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx @@ -6,9 +6,10 @@ import { } from "@heroicons/react/24/outline"; import { useState } from "react"; import StyledMarkdownPreview from "../markdown/StyledMarkdownPreview"; -import { getLastNPathParts, getMarkdownLanguageTagForFile } from "core/util"; +import { getMarkdownLanguageTagForFile } from "core/util"; import styled from "styled-components"; import { CodeToEdit } from "core"; +import { getLastNPathParts } from "core/util/uri"; export interface CodeToEditListItemProps { code: CodeToEdit; diff --git a/gui/src/components/FileIcon.tsx b/gui/src/components/FileIcon.tsx index 96321348c1..995b59be10 100644 --- a/gui/src/components/FileIcon.tsx +++ b/gui/src/components/FileIcon.tsx @@ -1,4 +1,5 @@ import DOMPurify from "dompurify"; +import { useMemo } from "react"; import { themeIcons } from "seti-file-icons"; export interface FileIconProps { @@ -7,10 +8,15 @@ export interface FileIconProps { width: string; } export default function FileIcon({ filename, height, width }: FileIconProps) { - const filenameParts = filename.includes(" (") - ? filename.split(" ") - : [filename, ""]; - filenameParts.pop(); + const file = useMemo(() => { + if (filename.includes(" (")) { + const path = filename.split(" "); + path.pop(); + return path.join(" "); + } else { + return filename; + } + }, [filename]); const getIcon = themeIcons({ blue: "#268bd2", @@ -27,7 +33,7 @@ export default function FileIcon({ filename, height, width }: FileIconProps) { }); // Sanitize the SVG string before rendering it - const { svg, color } = getIcon(filenameParts.join(" ")); + const { svg, color } = getIcon(file); const sanitizedSVG = DOMPurify.sanitize(svg); return ( diff --git a/gui/src/components/History/HistoryTableRow.tsx b/gui/src/components/History/HistoryTableRow.tsx index 016aae3298..a19e2e0627 100644 --- a/gui/src/components/History/HistoryTableRow.tsx +++ b/gui/src/components/History/HistoryTableRow.tsx @@ -7,11 +7,7 @@ import { Input } from ".."; import useHistory from "../../hooks/useHistory"; import HeaderButtonWithToolTip from "../gui/HeaderButtonWithToolTip"; import { useAppSelector } from "../../redux/hooks"; - -function lastPartOfPath(path: string): string { - const sep = path.includes("/") ? "/" : "\\"; - return path.split(sep).pop() || path; -} +import { getLastNPathParts } from "core/util/uri"; export function HistoryTableRow({ sessionMetadata, @@ -95,7 +91,7 @@ export function HistoryTableRow({
- {lastPartOfPath(sessionMetadata.workspaceDirectory || "")} + {getLastNPathParts(sessionMetadata.workspaceDirectory || "", 1)} {/* Uncomment to show the date */} {/* diff --git a/gui/src/components/mainInput/TipTapEditor.tsx b/gui/src/components/mainInput/TipTapEditor.tsx index bf6a65b854..e26510b739 100644 --- a/gui/src/components/mainInput/TipTapEditor.tsx +++ b/gui/src/components/mainInput/TipTapEditor.tsx @@ -13,7 +13,6 @@ import { RangeInFile, } from "core"; import { modelSupportsImages } from "core/llm/autodetect"; -import { getBasename, getRelativePath } from "core/util"; import { debounce } from "lodash"; import { usePostHog } from "posthog-js/react"; import { @@ -72,6 +71,7 @@ import { selectIsInEditMode, } from "../../redux/slices/sessionSlice"; import { exitEditMode } from "../../redux/thunks"; +import { getFileName, getRelativePath } from "core/util/uri"; const InputBoxDiv = styled.div<{ border?: string }>` resize: none; @@ -733,7 +733,7 @@ function TipTapEditor(props: TipTapEditorProps) { const rif: RangeInFile & { contents: string } = data.rangeInFileWithContents; - const basename = getBasename(rif.filepath); + const basename = getFileName(rif.filepath); const relativePath = getRelativePath( rif.filepath, await ideMessenger.ide.getWorkspaceDirs(), diff --git a/gui/src/components/mainInput/resolveInput.ts b/gui/src/components/mainInput/resolveInput.ts index e3af025000..6caac6c576 100644 --- a/gui/src/components/mainInput/resolveInput.ts +++ b/gui/src/components/mainInput/resolveInput.ts @@ -70,8 +70,8 @@ async function resolveEditorContent({ } else if (p.type === "codeBlock") { if (!p.attrs.item.editing) { let meta = p.attrs.item.description.split(" "); - let relativePath = meta[0] || ""; - let extName = relativePath.split(".").slice(-1)[0]; + let relativeFilepath = meta[0] || ""; + let extName = relativeFilepath.split(".").slice(-1)[0]; const text = "\n\n" + "```" + diff --git a/gui/src/components/markdown/FilenameLink.tsx b/gui/src/components/markdown/FilenameLink.tsx index 39a6aab256..722499c033 100644 --- a/gui/src/components/markdown/FilenameLink.tsx +++ b/gui/src/components/markdown/FilenameLink.tsx @@ -1,8 +1,8 @@ import { RangeInFile } from "core"; import { useContext } from "react"; -import { getBasename } from "core/util"; import { IdeMessengerContext } from "../../context/IdeMessenger"; import FileIcon from "../FileIcon"; +import { getFileName } from "core/util/uri"; interface FilenameLinkProps { rif: RangeInFile; @@ -25,8 +25,8 @@ function FilenameLink({ rif }: FilenameLinkProps) { onClick={onClick} > - - {getBasename(rif.filepath)} + + {getFileName(rif.filepath)} ); diff --git a/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx b/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx index 6d8fc8d74b..3d8a438787 100644 --- a/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx +++ b/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx @@ -1,8 +1,7 @@ -import { ChevronDownIcon } from "@heroicons/react/24/outline"; -import { getBasename } from "core/util"; import FileIcon from "../../FileIcon"; import { useContext } from "react"; import { IdeMessengerContext } from "../../../context/IdeMessenger"; +import { getFileName } from "core/util/uri"; export interface FileInfoProps { filepath: string; @@ -26,7 +25,7 @@ const FileInfo = ({ filepath, range }: FileInfoProps) => { > - {getBasename(filepath)} + {getFileName(filepath)} {range && ` ${range}`}
diff --git a/gui/src/pages/gui/ToolCallDiv/CreateFile.tsx b/gui/src/pages/gui/ToolCallDiv/CreateFile.tsx index dd03800663..bca805f453 100644 --- a/gui/src/pages/gui/ToolCallDiv/CreateFile.tsx +++ b/gui/src/pages/gui/ToolCallDiv/CreateFile.tsx @@ -2,14 +2,14 @@ import { getMarkdownLanguageTagForFile } from "core/util"; import StyledMarkdownPreview from "../../../components/markdown/StyledMarkdownPreview"; interface CreateFileToolCallProps { - filepath: string; + relativeFilepath: string; fileContents: string; } export function CreateFile(props: CreateFileToolCallProps) { - const src = `\`\`\`${getMarkdownLanguageTagForFile(props.filepath ?? "test.txt")} ${props.filepath}\n${props.fileContents ?? ""}\n\`\`\``; + const src = `\`\`\`${getMarkdownLanguageTagForFile(props.relativeFilepath ?? "test.txt")} ${props.relativeFilepath}\n${props.fileContents ?? ""}\n\`\`\``; - return props.filepath ? ( + return props.relativeFilepath ? ( ) : null; } diff --git a/gui/src/pages/gui/ToolCallDiv/FunctionSpecificToolCallDiv.tsx b/gui/src/pages/gui/ToolCallDiv/FunctionSpecificToolCallDiv.tsx index d55055b031..71082844d3 100644 --- a/gui/src/pages/gui/ToolCallDiv/FunctionSpecificToolCallDiv.tsx +++ b/gui/src/pages/gui/ToolCallDiv/FunctionSpecificToolCallDiv.tsx @@ -14,7 +14,10 @@ function FunctionSpecificToolCallDiv({ switch (toolCall.function.name) { case "create_new_file": return ( - + ); case "run_terminal_command": return ( diff --git a/gui/src/redux/slices/sessionSlice.ts b/gui/src/redux/slices/sessionSlice.ts index 871b1e7eb8..52e327de69 100644 --- a/gui/src/redux/slices/sessionSlice.ts +++ b/gui/src/redux/slices/sessionSlice.ts @@ -26,6 +26,7 @@ import { v4 as uuidv4 } from "uuid"; import { RootState } from "../store"; import { streamResponseThunk } from "../thunks/streamResponse"; import { findCurrentToolCall } from "../util"; +import { getFileName, getRelativePath } from "core/util/uri"; // We need this to handle reorderings (e.g. a mid-array deletion) of the messages array. // The proper fix is adding a UUID to all chat messages, but this is the temp workaround. @@ -392,17 +393,19 @@ export const sessionSlice = createSlice({ return { ...item, editing: false }; }); - const base = payload.rangeInFileWithContents.filepath - .split(/[\\/]/) - .pop(); + const relativeFilepath = getRelativePath( + payload.rangeInFileWithContents.filepath, + window.workspacePaths ?? [], + ); + const fileName = getFileName(payload.rangeInFileWithContents.filepath); const lineNums = `(${ payload.rangeInFileWithContents.range.start.line + 1 }-${payload.rangeInFileWithContents.range.end.line + 1})`; contextItems.push({ - name: `${base} ${lineNums}`, - description: payload.rangeInFileWithContents.filepath, + name: `${fileName} ${lineNums}`, + description: relativeFilepath, id: { providerTitle: "code", itemId: uuidv4(), @@ -410,6 +413,10 @@ export const sessionSlice = createSlice({ content: payload.rangeInFileWithContents.contents, editing: true, editable: true, + uri: { + type: "file", + value: payload.rangeInFileWithContents.filepath, + }, }); state.history[state.history.length - 1].contextItems = contextItems; diff --git a/gui/src/redux/thunks/gatherContext.ts b/gui/src/redux/thunks/gatherContext.ts index 596752c8a0..82b9f0cb20 100644 --- a/gui/src/redux/thunks/gatherContext.ts +++ b/gui/src/redux/thunks/gatherContext.ts @@ -6,7 +6,6 @@ import { MessageContent, RangeInFile, } from "core"; -import { getBasename, getRelativePath } from "core/util"; import resolveEditorContent, { hasSlashCommandOrContextProvider, } from "../../components/mainInput/resolveInput"; @@ -14,6 +13,7 @@ import { updateFileSymbolsFromContextItems } from "../../util/symbols"; import { ThunkApiType } from "../store"; import { selectDefaultModel } from "../slices/configSlice"; import { setIsGatheringContext } from "../slices/sessionSlice"; +import { getFileName, getRelativePath } from "core/util/uri"; export const gatherContext = createAsyncThunk< { @@ -85,7 +85,7 @@ export const gatherContext = createAsyncThunk< currentFile.path, await extra.ideMessenger.ide.getWorkspaceDirs(), )}\n${currentFileContents}\n\`\`\``, - name: `Active file: ${getBasename(currentFile.path)}`, + name: `Active file: ${getFileName(currentFile.path)}`, description: currentFile.path, id: { itemId: currentFile.path, From a335b0e22f608295ac1921e470b37d89ae292b0f Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 10 Dec 2024 12:53:40 -0800 Subject: [PATCH 04/84] path -> URI continued --- .../CodeHighlightsContextProvider.ts | 21 +- .../providers/CodeOutlineContextProvider.ts | 21 +- .../providers/CurrentFileContextProvider.ts | 5 +- core/context/providers/FileContextProvider.ts | 10 +- .../providers/FileTreeContextProvider.ts | 4 +- .../providers/FolderContextProvider.ts | 10 +- .../providers/GreptileContextProvider.ts | 349 ++++++++++-------- .../providers/OpenFilesContextProvider.ts | 4 +- .../providers/ProblemsContextProvider.ts | 9 +- .../providers/RepoMapContextProvider.ts | 12 +- core/indexing/walkDir.ts | 5 +- core/util/index.ts | 246 ++++++------ core/util/uri.ts | 75 +++- gui/src/components/mainInput/TipTapEditor.tsx | 4 +- gui/src/components/markdown/FilenameLink.tsx | 4 +- .../StepContainerPreToolbar/FileInfo.tsx | 4 +- gui/src/redux/slices/sessionSlice.ts | 4 +- gui/src/redux/thunks/gatherContext.ts | 4 +- 18 files changed, 431 insertions(+), 360 deletions(-) diff --git a/core/context/providers/CodeHighlightsContextProvider.ts b/core/context/providers/CodeHighlightsContextProvider.ts index 1cae074a49..1b9d017f37 100644 --- a/core/context/providers/CodeHighlightsContextProvider.ts +++ b/core/context/providers/CodeHighlightsContextProvider.ts @@ -3,7 +3,6 @@ import { ContextProviderDescription, ContextProviderExtras, } from "../../index.js"; -import { getBasename } from "../../util/index.js"; import { BaseContextProvider } from "../index.js"; const HIGHLIGHTS_TOKEN_BUDGET = 2000; @@ -26,16 +25,16 @@ class CodeHighlightsContextProvider extends BaseContextProvider { // ); const ide = extras.ide; const openFiles = await ide.getOpenFiles(); - const allFiles: { name: string; absPath: string; content: string }[] = - await Promise.all( - openFiles.map(async (filepath: string) => { - return { - name: getBasename(filepath), - absPath: filepath, - content: `${await ide.readFile(filepath)}`, - }; - }), - ); + // const allFiles: { name: string; absPath: string; content: string }[] = + // await Promise.all( + // openFiles.map(async (filepath: string) => { + // return { + // name: getBasename(filepath), + // absPath: filepath, + // content: `${await ide.readFile(filepath)}`, + // }; + // }), + // ); // const contextSizer = { // fits(content: string): boolean { // return countTokens(content, "") < HIGHLIGHTS_TOKEN_BUDGET; diff --git a/core/context/providers/CodeOutlineContextProvider.ts b/core/context/providers/CodeOutlineContextProvider.ts index 85781446e3..e12fa8231e 100644 --- a/core/context/providers/CodeOutlineContextProvider.ts +++ b/core/context/providers/CodeOutlineContextProvider.ts @@ -3,7 +3,6 @@ import { ContextProviderDescription, ContextProviderExtras, } from "../../index.js"; -import { getBasename } from "../../util/index.js"; import { BaseContextProvider } from "../index.js"; class CodeOutlineContextProvider extends BaseContextProvider { @@ -21,16 +20,16 @@ class CodeOutlineContextProvider extends BaseContextProvider { ): Promise { const ide = extras.ide; const openFiles = await ide.getOpenFiles(); - const allFiles: { name: string; absPath: string; content: string }[] = - await Promise.all( - openFiles.map(async (filepath: string) => { - return { - name: getBasename(filepath), - absPath: filepath, - content: `${await ide.readFile(filepath)}`, - }; - }), - ); + // const allFiles: { name: string; absPath: string; content: string }[] = + // await Promise.all( + // openFiles.map(async (filepath: string) => { + // return { + // name: getBasename(filepath), + // absPath: filepath, + // content: `${await ide.readFile(filepath)}`, + // }; + // }), + // ); // const outlines = await getOutlines( // allFiles // .filter((file) => file.content.length > 0) diff --git a/core/context/providers/CurrentFileContextProvider.ts b/core/context/providers/CurrentFileContextProvider.ts index ccb1b231d0..67593b064c 100644 --- a/core/context/providers/CurrentFileContextProvider.ts +++ b/core/context/providers/CurrentFileContextProvider.ts @@ -4,7 +4,7 @@ import { ContextProviderDescription, ContextProviderExtras, } from "../../"; -import { getBasename } from "../../util/"; +import { getBasename } from "../../util/uri"; class CurrentFileContextProvider extends BaseContextProvider { static description: ContextProviderDescription = { @@ -19,8 +19,7 @@ class CurrentFileContextProvider extends BaseContextProvider { query: string, extras: ContextProviderExtras, ): Promise { - const ide = extras.ide; - const currentFile = await ide.getCurrentFile(); + const currentFile = await extras.ide.getCurrentFile(); if (!currentFile) { return []; } diff --git a/core/context/providers/FileContextProvider.ts b/core/context/providers/FileContextProvider.ts index 26fb9dc735..81f8a573cc 100644 --- a/core/context/providers/FileContextProvider.ts +++ b/core/context/providers/FileContextProvider.ts @@ -8,10 +8,10 @@ import { } from "../../"; import { walkDir } from "../../indexing/walkDir"; import { - getBasename, - getUniqueFilePath, + getUniqueUriPath, + getUriPathBasename, groupByLastNPathParts, -} from "../../util/"; +} from "../../util/uri"; const MAX_SUBMENU_ITEMS = 10_000; @@ -58,8 +58,8 @@ class FileContextProvider extends BaseContextProvider { return files.map((file) => { return { id: file, - title: getBasename(file), - description: getUniqueFilePath(file, fileGroups), + title: getUriPathBasename(file), + description: getUniqueUriPath(file, fileGroups), }; }); } diff --git a/core/context/providers/FileTreeContextProvider.ts b/core/context/providers/FileTreeContextProvider.ts index 52af978236..fbb4b90a8d 100644 --- a/core/context/providers/FileTreeContextProvider.ts +++ b/core/context/providers/FileTreeContextProvider.ts @@ -4,7 +4,7 @@ import { ContextProviderExtras, } from "../../index.js"; import { walkDir } from "../../indexing/walkDir.js"; -import { splitPath } from "../../util/index.js"; +import { getUriPathBasename } from "../../util/uri.js"; import { BaseContextProvider } from "../index.js"; interface Directory { @@ -47,7 +47,7 @@ class FileTreeContextProvider extends BaseContextProvider { const contents = await walkDir(workspaceDir, extras.ide); const subDirTree: Directory = { - name: splitPath(workspaceDir).pop() ?? "", + name: getUriPathBasename(workspaceDir), files: [], directories: [], }; diff --git a/core/context/providers/FolderContextProvider.ts b/core/context/providers/FolderContextProvider.ts index a4b1101fb8..93d728c9a4 100644 --- a/core/context/providers/FolderContextProvider.ts +++ b/core/context/providers/FolderContextProvider.ts @@ -6,10 +6,10 @@ import { LoadSubmenuItemsArgs, } from "../../index.js"; import { - getBasename, + getUniqueUriPath, + getUriPathBasename, groupByLastNPathParts, - getUniqueFilePath, -} from "../../util/index.js"; +} from "../../util/uri.js"; import { BaseContextProvider } from "../index.js"; class FolderContextProvider extends BaseContextProvider { @@ -39,8 +39,8 @@ class FolderContextProvider extends BaseContextProvider { return folders.map((folder) => { return { id: folder, - title: getBasename(folder), - description: getUniqueFilePath(folder, folderGroups), + title: getUriPathBasename(folder), + description: getUniqueUriPath(folder, folderGroups), }; }); } diff --git a/core/context/providers/GreptileContextProvider.ts b/core/context/providers/GreptileContextProvider.ts index 3ae6fa2141..a13e4376f6 100644 --- a/core/context/providers/GreptileContextProvider.ts +++ b/core/context/providers/GreptileContextProvider.ts @@ -1,188 +1,209 @@ - import { execSync } from "child_process"; - import * as fs from "fs"; - import * as path from "path"; +import { execSync } from "child_process"; +import * as fs from "fs"; +import * as path from "path"; import { - ContextItem, - ContextProviderDescription, - ContextProviderExtras, - } from "../../index.js"; - import { BaseContextProvider } from "../index.js"; - - - class GreptileContextProvider extends BaseContextProvider { - static description: ContextProviderDescription = { - title: "greptile", - displayTitle: "Greptile", - description: "Insert query to Greptile", - type: "query", - }; - - async getContextItems( - query: string, - extras: ContextProviderExtras, - ): Promise { - const greptileToken = this.getGreptileToken(); - if (!greptileToken) { - throw new Error("Greptile token not found."); - } + ContextItem, + ContextProviderDescription, + ContextProviderExtras, +} from "../../index.js"; +import { BaseContextProvider } from "../index.js"; - const githubToken = this.getGithubToken(); - if (!githubToken) { - throw new Error("GitHub token not found."); - } - - let absPath = await this.getWorkspaceDir(extras); - if (!absPath) { - throw new Error("Failed to determine the workspace directory."); - } +class GreptileContextProvider extends BaseContextProvider { + static description: ContextProviderDescription = { + title: "greptile", + displayTitle: "Greptile", + description: "Insert query to Greptile", + type: "query", + }; - var remoteUrl = getRemoteUrl(absPath); - remoteUrl = getRemoteUrl(absPath); - const repoName = extractRepoName(remoteUrl); - const branch = getCurrentBranch(absPath); - const remoteType = getRemoteType(remoteUrl); - - if (!remoteType) { - throw new Error("Unable to determine remote type."); - } - - const options = { - method: "POST", - headers: { - Authorization: `Bearer ${greptileToken}`, - "X-GitHub-Token": githubToken, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - messages: [{ id: "", content: query, role: "user" }], - repositories: [{ - remote: remoteType, - branch: branch, - repository: repoName - }], - sessionId: extras.config.userToken || "default-session", - stream: false, - genius: true, - }), - }; - - try { - const response = await extras.fetch("https://api.greptile.com/v2/query", options); - const rawText = await response.text(); - - // Check for HTTP errors - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - // Parse the response as JSON - const json = JSON.parse(rawText); - - return json.sources.map((source: any) => ({ - description: source.filepath, - content: `File: ${source.filepath}\nLines: ${source.linestart}-${source.lineend}\n\n${source.summary}`, - name: (source.filepath.split("/").pop() ?? "").split("\\").pop() ?? "", - })); - } catch (error) { - console.error("Error getting context items from Greptile:", error); - throw new Error("Error getting context items from Greptile"); - } + async getContextItems( + query: string, + extras: ContextProviderExtras, + ): Promise { + const greptileToken = this.getGreptileToken(); + if (!greptileToken) { + throw new Error("Greptile token not found."); } - - private getGreptileToken(): string | undefined { - return this.options.GreptileToken || process.env.GREPTILE_AUTH_TOKEN; + + const githubToken = this.getGithubToken(); + if (!githubToken) { + throw new Error("GitHub token not found."); } - private getGithubToken(): string | undefined { - return this.options.GithubToken || process.env.GITHUB_TOKEN; + let absPath = await this.getWorkspaceDir(extras); + if (!absPath) { + throw new Error("Failed to determine the workspace directory."); } - - private async getWorkspaceDir(extras: ContextProviderExtras): Promise { - try { - const workspaceDirs = await extras.ide.getWorkspaceDirs(); - if (workspaceDirs && workspaceDirs.length > 0) { - return workspaceDirs[0]; - } else { - console.warn("extras.ide.getWorkspaceDirs() returned undefined or empty array."); - } - } catch (err) { - console.warn("Failed to get workspace directories from extras.ide.getWorkspaceDirs():"); - } - - // Fallback to using Git commands - try { - const currentDir = process.cwd(); - if (this.isGitRepository(currentDir)) { - const workspaceDir = execSync("git rev-parse --show-toplevel").toString().trim(); - return workspaceDir; - } else { - console.warn(`Current directory is not a Git repository: ${currentDir}`); - return null; - } - } catch (err) { - console.warn("Failed to get workspace directory using Git commands: "); - return null; - } + + var remoteUrl = getRemoteUrl(absPath); + remoteUrl = getRemoteUrl(absPath); + const repoName = extractRepoName(remoteUrl); + const branch = getCurrentBranch(absPath); + const remoteType = getRemoteType(remoteUrl); + + if (!remoteType) { + throw new Error("Unable to determine remote type."); } - - private isGitRepository(dir: string): boolean { - try { - const gitDir = path.join(dir, ".git"); - return fs.existsSync(gitDir); - } catch (err) { - console.warn("Failed to check if directory is a Git repository:"); - return false; + + const options = { + method: "POST", + headers: { + Authorization: `Bearer ${greptileToken}`, + "X-GitHub-Token": githubToken, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + messages: [{ id: "", content: query, role: "user" }], + repositories: [ + { + remote: remoteType, + branch: branch, + repository: repoName, + }, + ], + sessionId: extras.config.userToken || "default-session", + stream: false, + genius: true, + }), + }; + + try { + const response = await extras.fetch( + "https://api.greptile.com/v2/query", + options, + ); + const rawText = await response.text(); + + // Check for HTTP errors + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } + + // Parse the response as JSON + const json = JSON.parse(rawText); + + return json.sources.map((source: any) => ({ + description: source.filepath, + content: `File: ${source.filepath}\nLines: ${source.linestart}-${source.lineend}\n\n${source.summary}`, + name: (source.filepath.split("/").pop() ?? "").split("\\").pop() ?? "", + })); + } catch (error) { + console.error("Error getting context items from Greptile:", error); + throw new Error("Error getting context items from Greptile"); } } - - // Helper functions - function getRemoteUrl(absPath: string): string { + + private getGreptileToken(): string | undefined { + return this.options.GreptileToken || process.env.GREPTILE_AUTH_TOKEN; + } + + private getGithubToken(): string | undefined { + return this.options.GithubToken || process.env.GITHUB_TOKEN; + } + + private async getWorkspaceDir( + extras: ContextProviderExtras, + ): Promise { try { - const remote = execSync(`git -C ${absPath} remote get-url origin`).toString().trim(); - return remote; + const workspaceDirs = await extras.ide.getWorkspaceDirs(); + if (workspaceDirs && workspaceDirs.length > 0) { + return workspaceDirs[0]; + } else { + console.warn( + "extras.ide.getWorkspaceDirs() returned undefined or empty array.", + ); + } } catch (err) { - console.warn("Failed to get remote URL"); - return ""; + console.warn( + "Failed to get workspace directories from extras.ide.getWorkspaceDirs():", + ); } - } - - function getCurrentBranch(absPath: string): string { + + // Fallback to using Git commands try { - const branch = execSync(`git -C ${absPath} rev-parse --abbrev-ref HEAD`).toString().trim(); - return branch; + const currentDir = process.cwd(); + if (this.isGitRepository(currentDir)) { + const workspaceDir = execSync("git rev-parse --show-toplevel") + .toString() + .trim(); + return workspaceDir; + } else { + console.warn( + `Current directory is not a Git repository: ${currentDir}`, + ); + return null; + } } catch (err) { - console.warn("Failed to get current branch"); - return "master"; // Default to 'master' if the current branch cannot be determined + console.warn("Failed to get workspace directory using Git commands: "); + return null; } } - - function extractRepoName(remote: string): string { - if (remote.startsWith("http://") || remote.startsWith("https://")) { - const parts = remote.split("/"); - if (parts.length >= 2) { - return parts[parts.length - 2] + "/" + parts[parts.length - 1].replace(".git", ""); - } - } else if (remote.startsWith("git@")) { - const parts = remote.split(":"); - if (parts.length >= 2) { - return parts[1].replace(".git", ""); - } + + private isGitRepository(dir: string): boolean { + try { + const gitDir = path.join(dir, ".git"); + return fs.existsSync(gitDir); + } catch (err) { + console.warn("Failed to check if directory is a Git repository:"); + return false; } + } +} + +// Helper functions +function getRemoteUrl(absPath: string): string { + try { + const remote = execSync(`git -C ${absPath} remote get-url origin`) + .toString() + .trim(); + return remote; + } catch (err) { + console.warn("Failed to get remote URL"); return ""; } - - function getRemoteType(remote: string): string { - if (remote.includes("github.com")) { - return "github"; - } else if (remote.includes("gitlab.com")) { - return "gitlab"; - } else if (remote.includes("azure.com")) { - return "azure"; +} + +function getCurrentBranch(absPath: string): string { + try { + const branch = execSync(`git -C ${absPath} rev-parse --abbrev-ref HEAD`) + .toString() + .trim(); + return branch; + } catch (err) { + console.warn("Failed to get current branch"); + return "master"; // Default to 'master' if the current branch cannot be determined + } +} + +function extractRepoName(remote: string): string { + if (remote.startsWith("http://") || remote.startsWith("https://")) { + const parts = remote.split("/"); + if (parts.length >= 2) { + return ( + parts[parts.length - 2] + + "/" + + parts[parts.length - 1].replace(".git", "") + ); + } + } else if (remote.startsWith("git@")) { + const parts = remote.split(":"); + if (parts.length >= 2) { + return parts[1].replace(".git", ""); } - return ""; } - - export default GreptileContextProvider; - \ No newline at end of file + return ""; +} + +function getRemoteType(remote: string): string { + if (remote.includes("github.com")) { + return "github"; + } else if (remote.includes("gitlab.com")) { + return "gitlab"; + } else if (remote.includes("azure.com")) { + return "azure"; + } + return ""; +} + +export default GreptileContextProvider; diff --git a/core/context/providers/OpenFilesContextProvider.ts b/core/context/providers/OpenFilesContextProvider.ts index c00121a96e..8b68332c3f 100644 --- a/core/context/providers/OpenFilesContextProvider.ts +++ b/core/context/providers/OpenFilesContextProvider.ts @@ -3,7 +3,7 @@ import { ContextProviderDescription, ContextProviderExtras, } from "../../index.js"; -import { getRelativePath } from "../../util/index.js"; +import { getBasename, getRelativePath } from "../../util/uri.js"; import { BaseContextProvider } from "../index.js"; class OpenFilesContextProvider extends BaseContextProvider { @@ -32,7 +32,7 @@ class OpenFilesContextProvider extends BaseContextProvider { filepath, workspaceDirs, )}\n${await ide.readFile(filepath)}\n\`\`\``, - name: (filepath.split("/").pop() ?? "").split("\\").pop() ?? "", + name: getBasename(filepath), uri: { type: "file", value: filepath, diff --git a/core/context/providers/ProblemsContextProvider.ts b/core/context/providers/ProblemsContextProvider.ts index d5ffd46d82..b1a0372a5d 100644 --- a/core/context/providers/ProblemsContextProvider.ts +++ b/core/context/providers/ProblemsContextProvider.ts @@ -3,7 +3,7 @@ import { ContextProviderDescription, ContextProviderExtras, } from "../../index.js"; -import { getBasename } from "../../util/index.js"; +import { getUriPathBasename } from "../../util/uri.js"; import { BaseContextProvider } from "../index.js"; class ProblemsContextProvider extends BaseContextProvider { @@ -23,6 +23,7 @@ class ProblemsContextProvider extends BaseContextProvider { const items = await Promise.all( problems.map(async (problem) => { + const fileName = getUriPathBasename(problem.filepath); const content = await ide.readFile(problem.filepath); const lines = content.split("\n"); const rangeContent = lines @@ -34,10 +35,8 @@ class ProblemsContextProvider extends BaseContextProvider { return { description: "Problems in current file", - content: `\`\`\`${getBasename( - problem.filepath, - )}\n${rangeContent}\n\`\`\`\n${problem.message}\n\n`, - name: `Warning in ${getBasename(problem.filepath)}`, + content: `\`\`\`${fileName}\n${rangeContent}\n\`\`\`\n${problem.message}\n\n`, + name: `Warning in ${fileName}`, }; }), ); diff --git a/core/context/providers/RepoMapContextProvider.ts b/core/context/providers/RepoMapContextProvider.ts index 183ff3cb60..8f01eb8ffe 100644 --- a/core/context/providers/RepoMapContextProvider.ts +++ b/core/context/providers/RepoMapContextProvider.ts @@ -6,12 +6,12 @@ import { ContextSubmenuItem, LoadSubmenuItemsArgs, } from "../../"; +import generateRepoMap from "../../util/generateRepoMap"; import { - getBasename, - getUniqueFilePath, + getUniqueUriPath, + getUriPathBasename, groupByLastNPathParts, -} from "../../util"; -import generateRepoMap from "../../util/generateRepoMap"; +} from "../../util/uri"; const ENTIRE_PROJECT_ITEM: ContextSubmenuItem = { id: "entire-codebase", @@ -52,8 +52,8 @@ class RepoMapContextProvider extends BaseContextProvider { ENTIRE_PROJECT_ITEM, ...folders.map((folder) => ({ id: folder, - title: getBasename(folder), - description: getUniqueFilePath(folder, folderGroups), + title: getUriPathBasename(folder), + description: getUniqueUriPath(folder, folderGroups), })), ]; } diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts index 9e40ccb570..47f5a2cac4 100644 --- a/core/indexing/walkDir.ts +++ b/core/indexing/walkDir.ts @@ -97,7 +97,10 @@ class DFSWalker { }, ignoreContexts: [ { - ignore: ignore().add(defaultIgnoreDir).add(defaultIgnoreFile).add(globalIgnoreFile), + ignore: ignore() + .add(defaultIgnoreDir) + .add(defaultIgnoreFile) + .add(globalIgnoreFile), dirname: "", }, ], diff --git a/core/util/index.ts b/core/util/index.ts index 9a896baff5..a5608a5082 100644 --- a/core/util/index.ts +++ b/core/util/index.ts @@ -1,3 +1,5 @@ +import { getLastNUriRelativePathParts } from "./uri"; + export function removeQuotesAndEscapes(output: string): string { output = output.trim(); @@ -68,128 +70,128 @@ export function dedentAndGetCommonWhitespace(s: string): [string, string] { const SEP_REGEX = /[\\/]/; -export function getBasename(filepath: string): string { - return filepath.split(SEP_REGEX).pop() ?? ""; -} - -export function getLastNPathParts(filepath: string, n: number): string { - if (n <= 0) { - return ""; - } - return filepath.split(SEP_REGEX).slice(-n).join("/"); -} - -export function groupByLastNPathParts( - filepaths: string[], - n: number, -): Record { - return filepaths.reduce( - (groups, item) => { - const lastNParts = getLastNPathParts(item, n); - if (!groups[lastNParts]) { - groups[lastNParts] = []; - } - groups[lastNParts].push(item); - return groups; - }, - {} as Record, - ); -} - -export function getUniqueFilePath( - item: string, - itemGroups: Record, -): string { - const lastTwoParts = getLastNPathParts(item, 2); - const group = itemGroups[lastTwoParts]; - - let n = 2; - if (group.length > 1) { - while ( - group.some( - (otherItem) => - otherItem !== item && - getLastNPathParts(otherItem, n) === getLastNPathParts(item, n), - ) - ) { - n++; - } - } - - return getLastNPathParts(item, n); -} - -export function shortestRelativePaths(paths: string[]): string[] { - if (paths.length === 0) { - return []; - } - - const partsLengths = paths.map((x) => x.split(SEP_REGEX).length); - const currentRelativePaths = paths.map(getBasename); - const currentNumParts = paths.map(() => 1); - const isDuplicated = currentRelativePaths.map( - (x, i) => - currentRelativePaths.filter((y, j) => y === x && paths[i] !== paths[j]) - .length > 1, - ); - - while (isDuplicated.some(Boolean)) { - const firstDuplicatedPath = currentRelativePaths.find( - (x, i) => isDuplicated[i], - ); - if (!firstDuplicatedPath) { - break; - } - - currentRelativePaths.forEach((x, i) => { - if (x === firstDuplicatedPath) { - currentNumParts[i] += 1; - currentRelativePaths[i] = getLastNPathParts( - paths[i], - currentNumParts[i], - ); - } - }); - - isDuplicated.forEach((x, i) => { - if (x) { - isDuplicated[i] = - // Once we've used up all the parts, we can't make it longer - currentNumParts[i] < partsLengths[i] && - currentRelativePaths.filter((y) => y === currentRelativePaths[i]) - .length > 1; - } - }); - } - - return currentRelativePaths; -} - -export function splitPath(path: string, withRoot?: string): string[] { - let parts = path.includes("/") ? path.split("/") : path.split("\\"); - if (withRoot !== undefined) { - const rootParts = splitPath(withRoot); - parts = parts.slice(rootParts.length - 1); - } - return parts; -} - -export function getRelativePath( - filepath: string, - workspaceDirs: string[], -): string { - for (const workspaceDir of workspaceDirs) { - const filepathParts = splitPath(filepath); - const workspaceDirParts = splitPath(workspaceDir); - if ( - filepathParts.slice(0, workspaceDirParts.length).join("/") === - workspaceDirParts.join("/") - ) { - return filepathParts.slice(workspaceDirParts.length).join("/"); - } - } - return splitPath(filepath).pop() ?? ""; // If the file is not in any of the workspaces, return the plain filename -} +// export function getBasename(filepath: string): string { +// return filepath.split(SEP_REGEX).pop() ?? ""; +// } + +// export function getLastNPathParts(filepath: string, n: number): string { +// if (n <= 0) { +// return ""; +// } +// return filepath.split(SEP_REGEX).slice(-n).join("/"); +// } + +// export function groupByLastNPathParts( +// filepaths: string[], +// n: number, +// ): Record { +// return filepaths.reduce( +// (groups, item) => { +// const lastNParts = getLastNPathParts(item, n); +// if (!groups[lastNParts]) { +// groups[lastNParts] = []; +// } +// groups[lastNParts].push(item); +// return groups; +// }, +// {} as Record, +// ); +// } + +// export function getUniqueFilePath( +// item: string, +// itemGroups: Record, +// ): string { +// const lastTwoParts = getLastNPathParts(item, 2); +// const group = itemGroups[lastTwoParts]; + +// let n = 2; +// if (group.length > 1) { +// while ( +// group.some( +// (otherItem) => +// otherItem !== item && +// getLastNPathParts(otherItem, n) === getLastNPathParts(item, n), +// ) +// ) { +// n++; +// } +// } + +// return getLastNPathParts(item, n); +// } + +// export function shortestRelativePaths(paths: string[]): string[] { +// if (paths.length === 0) { +// return []; +// } + +// const partsLengths = paths.map((x) => x.split(SEP_REGEX).length); +// const currentRelativePaths = paths.map(getB); +// const currentNumParts = paths.map(() => 1); +// const isDuplicated = currentRelativePaths.map( +// (x, i) => +// currentRelativePaths.filter((y, j) => y === x && paths[i] !== paths[j]) +// .length > 1, +// ); + +// while (isDuplicated.some(Boolean)) { +// const firstDuplicatedPath = currentRelativePaths.find( +// (x, i) => isDuplicated[i], +// ); +// if (!firstDuplicatedPath) { +// break; +// } + +// currentRelativePaths.forEach((x, i) => { +// if (x === firstDuplicatedPath) { +// currentNumParts[i] += 1; +// currentRelativePaths[i] = getLastNUriRelativePathParts( +// paths[i], +// currentNumParts[i], +// ); +// } +// }); + +// isDuplicated.forEach((x, i) => { +// if (x) { +// isDuplicated[i] = +// // Once we've used up all the parts, we can't make it longer +// currentNumParts[i] < partsLengths[i] && +// currentRelativePaths.filter((y) => y === currentRelativePaths[i]) +// .length > 1; +// } +// }); +// } + +// return currentRelativePaths; +// } + +// export function splitPath(path: string, withRoot?: string): string[] { +// let parts = path.includes("/") ? path.split("/") : path.split("\\"); +// if (withRoot !== undefined) { +// const rootParts = splitPath(withRoot); +// parts = parts.slice(rootParts.length - 1); +// } +// return parts; +// } + +// export function getRelativePath( +// filepath: string, +// workspaceDirs: string[], +// ): string { +// for (const workspaceDir of workspaceDirs) { +// const filepathParts = splitPath(filepath); +// const workspaceDirParts = splitPath(workspaceDir); +// if ( +// filepathParts.slice(0, workspaceDirParts.length).join("/") === +// workspaceDirParts.join("/") +// ) { +// return filepathParts.slice(workspaceDirParts.length).join("/"); +// } +// } +// return splitPath(filepath).pop() ?? ""; // If the file is not in any of the workspaces, return the plain filename +// } export function getMarkdownLanguageTagForFile(filepath: string): string { const ext = filepath.split(".").pop(); diff --git a/core/util/uri.ts b/core/util/uri.ts index 93c4ddd009..2b8429acad 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -19,30 +19,79 @@ export function getRelativePath( const fullPath = getFullPath(fileUri); } -export function getLastNPathParts(path: string, n: number): string { - if (n < 1) { - return ""; - } - const pathParts = path.split("/").filter(Boolean); - if (pathParts.length <= n) { - return path; - } - return pathParts.slice(-n).join("/"); -} - export function getLastNUriRelativePathParts( workspaceDirs: string[], uri: string, n: number, ): string { const path = getRelativePath(uri, workspaceDirs); - return getLastNPathParts(path, n); + return getLastN(path, n); } -export function getFileName(uri: string): string { +export function getUriPathBasename(uri: string): string { return getPath(); } export function join(uri: string, pathSegment: string) { return uri.replace(/\/*$/, "") + "/" + segment.replace(/^\/*/, ""); } + +export function groupByLastNPathParts( + uris: string[], + n: number, +): Record { + return []; +} + +export function getUniqueUriPath( + item: string, + itemGroups: Record, +): string { + return "hi"; +} + +export function shortestRelativeUriPaths(uris: string[]): string[] { + if (uris.length === 0) { + return []; + } + + const partsLengths = uris.map((x) => x.split("/").length); + const currentRelativeUris = uris.map(getB); + const currentNumParts = uris.map(() => 1); + const isDuplicated = currentRelativeUris.map( + (x, i) => + currentRelativeUris.filter((y, j) => y === x && paths[i] !== paths[j]) + .length > 1, + ); + + while (isDuplicated.some(Boolean)) { + const firstDuplicatedPath = currentRelativeUris.find( + (x, i) => isDuplicated[i], + ); + if (!firstDuplicatedPath) { + break; + } + + currentRelativeUris.forEach((x, i) => { + if (x === firstDuplicatedPath) { + currentNumParts[i] += 1; + currentRelativeUris[i] = getLastNUriRelativePathParts( + paths[i], + currentNumParts[i], + ); + } + }); + + isDuplicated.forEach((x, i) => { + if (x) { + isDuplicated[i] = + // Once we've used up all the parts, we can't make it longer + currentNumParts[i] < partsLengths[i] && + currentRelativeUris.filter((y) => y === currentRelativeUris[i]) + .length > 1; + } + }); + } + + return currentRelativeUris; +} diff --git a/gui/src/components/mainInput/TipTapEditor.tsx b/gui/src/components/mainInput/TipTapEditor.tsx index e26510b739..5b68489c1d 100644 --- a/gui/src/components/mainInput/TipTapEditor.tsx +++ b/gui/src/components/mainInput/TipTapEditor.tsx @@ -71,7 +71,7 @@ import { selectIsInEditMode, } from "../../redux/slices/sessionSlice"; import { exitEditMode } from "../../redux/thunks"; -import { getFileName, getRelativePath } from "core/util/uri"; +import { getBasename, getRelativePath } from "core/util/uri"; const InputBoxDiv = styled.div<{ border?: string }>` resize: none; @@ -733,7 +733,7 @@ function TipTapEditor(props: TipTapEditorProps) { const rif: RangeInFile & { contents: string } = data.rangeInFileWithContents; - const basename = getFileName(rif.filepath); + const basename = getBasename(rif.filepath); const relativePath = getRelativePath( rif.filepath, await ideMessenger.ide.getWorkspaceDirs(), diff --git a/gui/src/components/markdown/FilenameLink.tsx b/gui/src/components/markdown/FilenameLink.tsx index 722499c033..aed1d1c9b6 100644 --- a/gui/src/components/markdown/FilenameLink.tsx +++ b/gui/src/components/markdown/FilenameLink.tsx @@ -2,7 +2,7 @@ import { RangeInFile } from "core"; import { useContext } from "react"; import { IdeMessengerContext } from "../../context/IdeMessenger"; import FileIcon from "../FileIcon"; -import { getFileName } from "core/util/uri"; +import { getBasename } from "core/util/uri"; interface FilenameLinkProps { rif: RangeInFile; @@ -26,7 +26,7 @@ function FilenameLink({ rif }: FilenameLinkProps) { > - {getFileName(rif.filepath)} + {getBasename(rif.filepath)} ); diff --git a/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx b/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx index 3d8a438787..5534b8e5ca 100644 --- a/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx +++ b/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx @@ -1,7 +1,7 @@ import FileIcon from "../../FileIcon"; import { useContext } from "react"; import { IdeMessengerContext } from "../../../context/IdeMessenger"; -import { getFileName } from "core/util/uri"; +import { getBasename } from "core/util/uri"; export interface FileInfoProps { filepath: string; @@ -25,7 +25,7 @@ const FileInfo = ({ filepath, range }: FileInfoProps) => { > - {getFileName(filepath)} + {getBasename(filepath)} {range && ` ${range}`} diff --git a/gui/src/redux/slices/sessionSlice.ts b/gui/src/redux/slices/sessionSlice.ts index 52e327de69..d64852f2f9 100644 --- a/gui/src/redux/slices/sessionSlice.ts +++ b/gui/src/redux/slices/sessionSlice.ts @@ -26,7 +26,7 @@ import { v4 as uuidv4 } from "uuid"; import { RootState } from "../store"; import { streamResponseThunk } from "../thunks/streamResponse"; import { findCurrentToolCall } from "../util"; -import { getFileName, getRelativePath } from "core/util/uri"; +import { getBasename, getRelativePath } from "core/util/uri"; // We need this to handle reorderings (e.g. a mid-array deletion) of the messages array. // The proper fix is adding a UUID to all chat messages, but this is the temp workaround. @@ -397,7 +397,7 @@ export const sessionSlice = createSlice({ payload.rangeInFileWithContents.filepath, window.workspacePaths ?? [], ); - const fileName = getFileName(payload.rangeInFileWithContents.filepath); + const fileName = getBasename(payload.rangeInFileWithContents.filepath); const lineNums = `(${ payload.rangeInFileWithContents.range.start.line + 1 diff --git a/gui/src/redux/thunks/gatherContext.ts b/gui/src/redux/thunks/gatherContext.ts index 82b9f0cb20..b232732e7a 100644 --- a/gui/src/redux/thunks/gatherContext.ts +++ b/gui/src/redux/thunks/gatherContext.ts @@ -13,7 +13,7 @@ import { updateFileSymbolsFromContextItems } from "../../util/symbols"; import { ThunkApiType } from "../store"; import { selectDefaultModel } from "../slices/configSlice"; import { setIsGatheringContext } from "../slices/sessionSlice"; -import { getFileName, getRelativePath } from "core/util/uri"; +import { getBasename, getRelativePath } from "core/util/uri"; export const gatherContext = createAsyncThunk< { @@ -85,7 +85,7 @@ export const gatherContext = createAsyncThunk< currentFile.path, await extra.ideMessenger.ide.getWorkspaceDirs(), )}\n${currentFileContents}\n\`\`\``, - name: `Active file: ${getFileName(currentFile.path)}`, + name: `Active file: ${getBasename(currentFile.path)}`, description: currentFile.path, id: { itemId: currentFile.path, From 0bb131680bf517eb5cae5d2adadd309fba3cde32 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 10 Dec 2024 13:19:15 -0800 Subject: [PATCH 05/84] path-uri-context-updates --- .../pipelines/BaseRetrievalPipeline.ts | 4 +-- .../pipelines/NoRerankerRetrievalPipeline.ts | 3 ++- .../pipelines/RerankerRetrievalPipeline.ts | 3 ++- core/context/retrieval/retrieval.ts | 6 ++--- core/indexing/chunk/chunk.ts | 12 +++------ core/util/generateRepoMap.ts | 4 +++ core/util/uri.ts | 26 +++++++++++++++++++ gui/src/context/SubmenuContextProviders.tsx | 16 ++++++------ 8 files changed, 49 insertions(+), 25 deletions(-) diff --git a/core/context/retrieval/pipelines/BaseRetrievalPipeline.ts b/core/context/retrieval/pipelines/BaseRetrievalPipeline.ts index 89323d9b25..3f23cebf70 100644 --- a/core/context/retrieval/pipelines/BaseRetrievalPipeline.ts +++ b/core/context/retrieval/pipelines/BaseRetrievalPipeline.ts @@ -15,7 +15,6 @@ export interface RetrievalPipelineOptions { nRetrieve: number; nFinal: number; tags: BranchAndDir[]; - pathSep: string; filterDirectory?: string; includeEmbeddings?: boolean; // Used to handle JB w/o an embeddings model } @@ -37,8 +36,7 @@ export default class BaseRetrievalPipeline implements IRetrievalPipeline { constructor(protected readonly options: RetrievalPipelineOptions) { this.lanceDbIndex = new LanceDbIndex( options.config.embeddingsProvider, - (path) => options.ide.readFile(path), - options.pathSep, + (uri) => options.ide.readFile(uri), ); } diff --git a/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts b/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts index 052a1fce6a..87fb84c0ec 100644 --- a/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts +++ b/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts @@ -1,4 +1,5 @@ import { Chunk } from "../../../"; +import { isUriWithinDirectory } from "../../../util/uri"; import { requestFilesFromRepoMap } from "../repoMapRequest"; import { deduplicateChunks } from "../util"; @@ -45,7 +46,7 @@ export default class NoRerankerRetrievalPipeline extends BaseRetrievalPipeline { if (filterDirectory) { // Backup if the individual retrieval methods don't listen retrievalResults = retrievalResults.filter((chunk) => - chunk.filepath.startsWith(filterDirectory), + isUriWithinDirectory(chunk.filepath, filterDirectory), ); } diff --git a/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts b/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts index 6f28a4a89c..33676936d8 100644 --- a/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts +++ b/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts @@ -1,5 +1,6 @@ import { Chunk } from "../../.."; import { RETRIEVAL_PARAMS } from "../../../util/parameters"; +import { isUriWithinDirectory } from "../../../util/uri"; import { requestFilesFromRepoMap } from "../repoMapRequest"; import { deduplicateChunks } from "../util"; @@ -41,7 +42,7 @@ export default class RerankerRetrievalPipeline extends BaseRetrievalPipeline { if (filterDirectory) { // Backup if the individual retrieval methods don't listen retrievalResults = retrievalResults.filter((chunk) => - chunk.filepath.startsWith(filterDirectory), + isUriWithinDirectory(chunk.filepath, filterDirectory), ); } diff --git a/core/context/retrieval/retrieval.ts b/core/context/retrieval/retrieval.ts index 8aae10a412..c97c65670b 100644 --- a/core/context/retrieval/retrieval.ts +++ b/core/context/retrieval/retrieval.ts @@ -1,8 +1,7 @@ -import path from "path"; - import { BranchAndDir, ContextItem, ContextProviderExtras } from "../../"; import TransformersJsEmbeddingsProvider from "../../indexing/embeddings/TransformersJsEmbeddingsProvider"; import { resolveRelativePathInWorkspace } from "../../util/ideUtils"; +import { getUriPathBasename } from "../../util/uri"; import { INSTRUCTIONS_BASE_ITEM } from "../providers/utils"; import { RetrievalPipelineOptions } from "./pipelines/BaseRetrievalPipeline"; @@ -83,7 +82,6 @@ export async function retrieveContextItemsFromEmbeddings( nFinal, nRetrieve, tags, - pathSep: await extras.ide.pathSep(), filterDirectory, ide: extras.ide, input: extras.fullInput, @@ -114,7 +112,7 @@ export async function retrieveContextItemsFromEmbeddings( ...results .sort((a, b) => a.filepath.localeCompare(b.filepath)) .map((r) => { - const name = `${path.basename(r.filepath)} (${r.startLine}-${ + const name = `${getUriPathBasename(r.filepath)} (${r.startLine}-${ r.endLine })`; const description = `${r.filepath}`; diff --git a/core/indexing/chunk/chunk.ts b/core/indexing/chunk/chunk.ts index 596f116dc3..142f75b28d 100644 --- a/core/indexing/chunk/chunk.ts +++ b/core/indexing/chunk/chunk.ts @@ -3,6 +3,7 @@ import { countTokensAsync } from "../../llm/countTokens.js"; import { extractMinimalStackTraceInfo } from "../../util/extractMinimalStackTraceInfo.js"; import { Telemetry } from "../../util/posthog.js"; import { supportedLanguages } from "../../util/treeSitter.js"; +import { getUriPathBasename } from "../../util/uri.js"; import { basicChunker } from "./basic.js"; import { codeChunker } from "./code.js"; @@ -84,11 +85,7 @@ export async function* chunkDocument({ } } -export function shouldChunk( - pathSep: string, - filepath: string, - contents: string, -): boolean { +export function shouldChunk(fileUri: string, contents: string): boolean { if (contents.length > 1000000) { // if a file has more than 1m characters then skip it return false; @@ -96,7 +93,6 @@ export function shouldChunk( if (contents.length === 0) { return false; } - const basename = filepath.split(pathSep).pop(); - // files without extensions are often binary files, skip it if so - return basename?.includes(".") ?? false; + const baseName = getUriPathBasename(fileUri); + return baseName.includes("."); } diff --git a/core/util/generateRepoMap.ts b/core/util/generateRepoMap.ts index 0aa4db341c..cfcfdaa8c3 100644 --- a/core/util/generateRepoMap.ts +++ b/core/util/generateRepoMap.ts @@ -13,6 +13,10 @@ export interface RepoMapOptions { dirs?: string[]; } +/* +Generates repo map from workspace files +Saves it to local file in Continue global directory + */ class RepoMapGenerator { private maxRepoMapTokens: number; diff --git a/core/util/uri.ts b/core/util/uri.ts index 2b8429acad..0e3c6225a4 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -19,6 +19,15 @@ export function getRelativePath( const fullPath = getFullPath(fileUri); } +export function splitUriPath(uri: string): string[] { + let parts = path.includes("/") ? path.split("/") : path.split("\\"); + if (withRoot !== undefined) { + const rootParts = splitPath(withRoot); + parts = parts.slice(rootParts.length - 1); + } + return parts; +} + export function getLastNUriRelativePathParts( workspaceDirs: string[], uri: string, @@ -95,3 +104,20 @@ export function shortestRelativeUriPaths(uris: string[]): string[] { return currentRelativeUris; } +export function isUriWithinDirectory( + uri: string, + directoryUri: string, +): boolean { + const uriPath = getFullPath(uri); + const directoryPath = getFullPath(directoryUri); + + if (uriPath === directoryPath) { + return false; + } + return uriPath.startsWith(directoryPath); +} + +export function getUriFileExtension(uri: string) { + const baseName = getUriPathBasename(uri); + return baseName.split(".")[-1] ?? ""; +} diff --git a/gui/src/context/SubmenuContextProviders.tsx b/gui/src/context/SubmenuContextProviders.tsx index b9cbe3bc79..b82f9dcaeb 100644 --- a/gui/src/context/SubmenuContextProviders.tsx +++ b/gui/src/context/SubmenuContextProviders.tsx @@ -1,11 +1,6 @@ import { ContextSubmenuItem } from "core"; import { createContext } from "react"; -import { - deduplicateArray, - getBasename, - getUniqueFilePath, - groupByLastNPathParts, -} from "core/util"; +import { deduplicateArray } from "core/util"; import MiniSearch, { SearchResult } from "minisearch"; import { useCallback, @@ -19,6 +14,11 @@ import { IdeMessengerContext } from "./IdeMessenger"; import { selectContextProviderDescriptions } from "../redux/selectors"; import { useWebviewListener } from "../hooks/useWebviewListener"; import { useAppSelector } from "../redux/hooks"; +import { + getUniqueUriPath, + getUriPathBasename, + groupByLastNPathParts, +} from "core/util/uri"; const MINISEARCH_OPTIONS = { prefix: true, @@ -93,8 +93,8 @@ export const SubmenuContextProvidersProvider = ({ return openFiles.map((file) => ({ id: file, - title: getBasename(file), - description: getUniqueFilePath(file, openFileGroups), + title: getUriPathBasename(file), + description: getUniqueUriPath(file, openFileGroups), providerTitle: "file", })); }, [ideMessenger]); From b6b3cceb7849db581e9615bc64f03d628d495603 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 10 Dec 2024 17:28:56 -0800 Subject: [PATCH 06/84] (BROKEN COMMI) path -> uri vscode utils and prompt files cont --- core/autocomplete/README.md | 9 +- .../constants/AutocompleteLanguageInfo.ts | 10 +- .../RootPathContextService.ts | 28 +- core/autocomplete/snippets/getAllSnippets.ts | 3 +- .../templating/AutocompleteTemplate.ts | 102 ++-- core/autocomplete/templating/formatting.ts | 10 +- .../util/AutocompleteLoggingService.ts | 3 +- core/config/getSystemPromptDotFile.ts | 5 +- core/config/promptFile.ts | 509 +++++++++--------- core/config/types.ts | 1 - .../providers/PromptFilesContextProvider.ts | 3 +- core/context/retrieval/repoMapRequest.ts | 2 - core/context/retrieval/retrieval.ts | 9 +- core/indexing/CodebaseIndexer.ts | 13 +- core/indexing/LanceDbIndex.test.ts | 2 - .../indexing/chunk/ChunkCodebaseIndex.test.ts | 3 - core/indexing/chunk/ChunkCodebaseIndex.ts | 11 +- core/indexing/test/indexing.ts | 5 +- core/indexing/walkDir.test.ts | 7 - core/promptFiles/v1/createNewPromptFile.ts | 5 +- core/promptFiles/v1/index.ts | 4 +- .../v1/slashCommandFromPromptFile.ts | 1 - core/promptFiles/v2/createNewPromptFile.ts | 11 +- core/promptFiles/v2/getPromptFiles.ts | 15 +- core/promptFiles/v2/parse.ts | 2 - core/promptFiles/v2/renderPromptFile.ts | 19 +- core/protocol/ide.ts | 1 - core/protocol/messenger/reverseMessageIde.ts | 3 - core/tools/implementations/createNewFile.ts | 18 +- .../implementations/readCurrentlyOpenFile.ts | 4 +- core/tools/implementations/readFile.ts | 5 +- .../tools/implementations/viewSubdirectory.ts | 6 +- core/util/filesystem.ts | 4 - core/util/ideUtils.test.ts | 3 - core/util/ideUtils.ts | 44 +- core/util/index.test.ts | 239 -------- core/util/pathModule.ts | 13 - core/util/treeSitter.ts | 9 +- core/util/uri.test.ts | 233 ++++++++ core/util/uri.ts | 23 +- .../constants/MessageTypes.kt | 1 - .../continue/IdeProtocolClient.kt | 5 - extensions/vscode/src/VsCodeIde.ts | 23 +- extensions/vscode/src/suggestions.ts | 23 +- extensions/vscode/src/util/ideUtils.ts | 64 +-- extensions/vscode/src/util/vscode.ts | 47 +- 46 files changed, 753 insertions(+), 807 deletions(-) delete mode 100644 core/util/pathModule.ts create mode 100644 core/util/uri.test.ts diff --git a/core/autocomplete/README.md b/core/autocomplete/README.md index 627ca12060..1136b54abc 100644 --- a/core/autocomplete/README.md +++ b/core/autocomplete/README.md @@ -28,11 +28,10 @@ Example: ```json title="config.json" { "tabAutocompleteModel": { - "title": "Qwen2.5-Coder 1.5b", - "model": "Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF", - "provider": "lmstudio", - }, - ... + "title": "Qwen2.5-Coder 1.5b", + "model": "Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF", + "provider": "lmstudio" + } } ``` diff --git a/core/autocomplete/constants/AutocompleteLanguageInfo.ts b/core/autocomplete/constants/AutocompleteLanguageInfo.ts index 2f21c5a3e9..d51fe1f1fa 100644 --- a/core/autocomplete/constants/AutocompleteLanguageInfo.ts +++ b/core/autocomplete/constants/AutocompleteLanguageInfo.ts @@ -1,3 +1,4 @@ +import { getUriFileExtension } from "../../util/uri"; import { BracketMatchingService } from "../filtering/BracketMatchingService"; import { CharacterFilter, @@ -27,7 +28,7 @@ export const Python = { name: "Python", // """"#" is for .ipynb files, where we add '"""' surrounding markdown blocks. // This stops the model from trying to complete the start of a new markdown block - topLevelKeywords: ["def", "class", "\"\"\"#"], + topLevelKeywords: ["def", "class", '"""#'], singleLineComment: "#", endOfLine: [], }; @@ -368,8 +369,7 @@ export const LANGUAGES: { [extension: string]: AutocompleteLanguageInfo } = { md: Markdown, }; -export function languageForFilepath( - filepath: string, -): AutocompleteLanguageInfo { - return LANGUAGES[filepath.split(".").slice(-1)[0]] || Typescript; +export function languageForFilepath(fileUri: string): AutocompleteLanguageInfo { + const extension = getUriFileExtension(fileUri); + return LANGUAGES[extension] || Typescript; } diff --git a/core/autocomplete/context/root-path-context/RootPathContextService.ts b/core/autocomplete/context/root-path-context/RootPathContextService.ts index 93648653fd..79b598f1ec 100644 --- a/core/autocomplete/context/root-path-context/RootPathContextService.ts +++ b/core/autocomplete/context/root-path-context/RootPathContextService.ts @@ -18,20 +18,20 @@ import { AutocompleteSnippetDeprecated } from "../../types"; import { AstPath } from "../../util/ast"; import { ImportDefinitionsService } from "../ImportDefinitionsService"; -function getSyntaxTreeString( - node: Parser.SyntaxNode, - indent: string = "", -): string { - let result = ""; - const nodeInfo = `${node.type} [${node.startPosition.row}:${node.startPosition.column} - ${node.endPosition.row}:${node.endPosition.column}]`; - result += `${indent}${nodeInfo}\n`; - - for (const child of node.children) { - result += getSyntaxTreeString(child, indent + " "); - } - - return result; -} +// function getSyntaxTreeString( +// node: Parser.SyntaxNode, +// indent: string = "", +// ): string { +// let result = ""; +// const nodeInfo = `${node.type} [${node.startPosition.row}:${node.startPosition.column} - ${node.endPosition.row}:${node.endPosition.column}]`; +// result += `${indent}${nodeInfo}\n`; + +// for (const child of node.children) { +// result += getSyntaxTreeString(child, indent + " "); +// } + +// return result; +// } export class RootPathContextService { private cache = new LRUCache({ diff --git a/core/autocomplete/snippets/getAllSnippets.ts b/core/autocomplete/snippets/getAllSnippets.ts index 30d89e7b94..f61425ccfd 100644 --- a/core/autocomplete/snippets/getAllSnippets.ts +++ b/core/autocomplete/snippets/getAllSnippets.ts @@ -1,4 +1,5 @@ import { IDE } from "../../index"; +import { isUriWithinDirectory } from "../../util/uri"; import { ContextRetrievalService } from "../context/ContextRetrievalService"; import { GetLspDefinitionsFunction } from "../types"; import { HelperVars } from "../util/HelperVars"; @@ -45,7 +46,7 @@ async function getIdeSnippets( const workspaceDirs = await ide.getWorkspaceDirs(); return ideSnippets.filter((snippet) => - workspaceDirs.some((dir) => snippet.filepath.startsWith(dir)), + workspaceDirs.some((dir) => isUriWithinDirectory(snippet.filepath, dir)), ); } diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index 9ade88e46a..8c9ae1d301 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -1,7 +1,10 @@ // Fill in the middle prompts import { CompletionOptions } from "../../index.js"; -import { getLastNPathParts, shortestRelativePaths } from "../../util/index.js"; +import { + getLastNUriRelativePathParts, + shortestRelativeUriPaths, +} from "../../util/uri.js"; import { AutocompleteCodeSnippet, AutocompleteSnippet, @@ -12,7 +15,7 @@ export interface AutocompleteTemplate { compilePrefixSuffix?: ( prefix: string, suffix: string, - filepath: string, + directoryUri: string, reponame: string, snippets: AutocompleteSnippet[], ) => [string, string]; @@ -21,7 +24,7 @@ export interface AutocompleteTemplate { | (( prefix: string, suffix: string, - filepath: string, + directoryUri: string, reponame: string, language: string, snippets: AutocompleteSnippet[], @@ -64,12 +67,12 @@ const qwenCoderFimTemplate: AutocompleteTemplate = { }, }; -const codestralFimTemplate: AutocompleteTemplate = { - template: "[SUFFIX]{{{suffix}}}[PREFIX]{{{prefix}}}", - completionOptions: { - stop: ["[PREFIX]", "[SUFFIX]"], - }, -}; +// const codestralFimTemplate: AutocompleteTemplate = { +// template: "[SUFFIX]{{{suffix}}}[PREFIX]{{{prefix}}}", +// completionOptions: { +// stop: ["[PREFIX]", "[SUFFIX]"], +// }, +// }; const codestralMultifileFimTemplate: AutocompleteTemplate = { compilePrefixSuffix: ( @@ -81,12 +84,15 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { ): [string, string] => { if (snippets.length === 0) { if (suffix.trim().length === 0 && prefix.trim().length === 0) { - return [`+++++ ${getLastNPathParts(filepath, 2)}\n${prefix}`, suffix]; + return [ + `+++++ ${getLastNUriRelativePathParts(filepath, 2)}\n${prefix}`, + suffix, + ]; } return [prefix, suffix]; } - const relativePaths = shortestRelativePaths([ + const relativePaths = shortestRelativeUriPaths([ ...snippets.map((snippet) => "filepath" in snippet ? snippet.filepath : "Untitled.txt", ), @@ -133,38 +139,38 @@ const codegemmaFimTemplate: AutocompleteTemplate = { }, }; -// https://arxiv.org/pdf/2402.19173.pdf section 5.1 -const starcoder2FimTemplate: AutocompleteTemplate = { - template: ( - prefix: string, - suffix: string, - filename: string, - reponame: string, - language: string, - snippets: AutocompleteSnippet[], - ): string => { - const otherFiles = - snippets.length === 0 - ? "" - : `${snippets - .map((snippet) => { - return snippet.content; - }) - .join("")}`; - - const prompt = `${otherFiles}${prefix}${suffix}`; - return prompt; - }, - completionOptions: { - stop: [ - "", - "", - "", - "", - "<|endoftext|>", - ], - }, -}; +// // https://arxiv.org/pdf/2402.19173.pdf section 5.1 +// const starcoder2FimTemplate: AutocompleteTemplate = { +// template: ( +// prefix: string, +// suffix: string, +// filename: string, +// reponame: string, +// language: string, +// snippets: AutocompleteSnippet[], +// ): string => { +// const otherFiles = +// snippets.length === 0 +// ? "" +// : `${snippets +// .map((snippet) => { +// return snippet.content; +// }) +// .join("")}`; + +// const prompt = `${otherFiles}${prefix}${suffix}`; +// return prompt; +// }, +// completionOptions: { +// stop: [ +// "", +// "", +// "", +// "", +// "<|endoftext|>", +// ], +// }, +// }; const codeLlamaFimTemplate: AutocompleteTemplate = { template: "
 {{{prefix}}} {{{suffix}}} ",
@@ -191,7 +197,7 @@ const codegeexFimTemplate: AutocompleteTemplate = {
   template: (
     prefix: string,
     suffix: string,
-    filepath: string,
+    directoryUri: string,
     reponame: string,
     language: string,
     allSnippets: AutocompleteSnippet[],
@@ -200,10 +206,10 @@ const codegeexFimTemplate: AutocompleteTemplate = {
       (snippet) => snippet.type === AutocompleteSnippetType.Code,
     ) as AutocompleteCodeSnippet[];
 
-    const relativePaths = shortestRelativePaths([
-      ...snippets.map((snippet) => snippet.filepath),
-      filepath,
-    ]);
+    const relativePaths = shortestRelativeUriPaths(
+      [...snippets.map((snippet) => snippet.filepath)],
+      directoryUri,
+    );
     const baseTemplate = `###PATH:${
       relativePaths[relativePaths.length - 1]
     }\n###LANGUAGE:${language}\n###MODE:BLOCK\n<|code_suffix|>${suffix}<|code_prefix|>${prefix}<|code_middle|>`;
diff --git a/core/autocomplete/templating/formatting.ts b/core/autocomplete/templating/formatting.ts
index bdbe56c50f..1794ad60f0 100644
--- a/core/autocomplete/templating/formatting.ts
+++ b/core/autocomplete/templating/formatting.ts
@@ -1,4 +1,4 @@
-import { getLastNPathParts } from "../../util";
+import { getLastNUriRelativePathParts } from "../../util/uri";
 import {
   AutocompleteClipboardSnippet,
   AutocompleteCodeSnippet,
@@ -39,7 +39,7 @@ const formatCodeSnippet = (
 ): AutocompleteCodeSnippet => {
   return {
     ...snippet,
-    content: `Path: ${getLastNPathParts(snippet.filepath, 2)}\n${snippet.content}`,
+    content: `Path: ${getLastNUriRelativePathParts(snippet.filepath, 2)}\n${snippet.content}`,
   };
 };
 
@@ -49,10 +49,6 @@ const formatDiffSnippet = (
   return snippet;
 };
 
-const getCurrentFilepath = (helper: HelperVars) => {
-  return getLastNPathParts(helper.filepath, 2);
-};
-
 const commentifySnippet = (
   helper: HelperVars,
   snippet: AutocompleteSnippet,
@@ -68,7 +64,7 @@ export const formatSnippets = (
   snippets: AutocompleteSnippet[],
 ): string => {
   const currentFilepathComment = addCommentMarks(
-    getCurrentFilepath(helper),
+    getLastNUriRelativePathParts(helper.filepath, 2),
     helper,
   );
 
diff --git a/core/autocomplete/util/AutocompleteLoggingService.ts b/core/autocomplete/util/AutocompleteLoggingService.ts
index 35e0ead644..7fd49cfaf4 100644
--- a/core/autocomplete/util/AutocompleteLoggingService.ts
+++ b/core/autocomplete/util/AutocompleteLoggingService.ts
@@ -1,6 +1,7 @@
 import { logDevData } from "../../util/devdata";
 import { COUNT_COMPLETION_REJECTED_AFTER } from "../../util/parameters";
 import { Telemetry } from "../../util/posthog";
+import { getUriFileExtension } from "../../util/uri";
 
 import { AutocompleteOutcome } from "./types";
 
@@ -105,7 +106,7 @@ export class AutocompleteLoggingService {
         completionId: restOfOutcome.completionId,
         completionOptions: restOfOutcome.completionOptions,
         debounceDelay: restOfOutcome.debounceDelay,
-        fileExtension: restOfOutcome.filepath.split(".")?.slice(-1)[0],
+        fileExtension: getUriFileExtension(restOfOutcome.filepath),
         maxPromptTokens: restOfOutcome.maxPromptTokens,
         modelName: restOfOutcome.modelName,
         modelProvider: restOfOutcome.modelProvider,
diff --git a/core/config/getSystemPromptDotFile.ts b/core/config/getSystemPromptDotFile.ts
index e5bd708e80..550144cd88 100644
--- a/core/config/getSystemPromptDotFile.ts
+++ b/core/config/getSystemPromptDotFile.ts
@@ -1,14 +1,13 @@
 import { IDE } from "..";
-import * as uri from "../util/uri";
+import { joinPathsToUri } from "../util/uri";
 export const SYSTEM_PROMPT_DOT_FILE = ".continuerules";
 
 export async function getSystemPromptDotFile(ide: IDE): Promise {
   const dirs = await ide.getWorkspaceDirs();
 
   let prompts: string[] = [];
-  // const pathSep = await ide.pathSep();
   for (const dir of dirs) {
-    const dotFile = uri.join(dir, SYSTEM_PROMPT_DOT_FILE);
+    const dotFile = joinPathsToUri(dir, SYSTEM_PROMPT_DOT_FILE);
     if (await ide.fileExists(dotFile)) {
       try {
         const content = await ide.readFile(dotFile);
diff --git a/core/config/promptFile.ts b/core/config/promptFile.ts
index de17a7120b..5d37e99f65 100644
--- a/core/config/promptFile.ts
+++ b/core/config/promptFile.ts
@@ -5,7 +5,6 @@ import * as YAML from "yaml";
 
 import { walkDir } from "../indexing/walkDir";
 import { renderTemplatedString } from "../promptFiles/v1/renderTemplatedString";
-import { getBasename } from "../util/index";
 import { renderChatMessage } from "../util/messageContent";
 
 import type {
@@ -19,7 +18,7 @@ import type {
 
 export const DEFAULT_PROMPTS_FOLDER = ".prompts";
 
-export async function getPromptFiles(
+async function getPromptFilesFromDir(
   ide: IDE,
   dir: string,
 ): Promise<{ path: string; content: string }[]> {
@@ -31,8 +30,9 @@ export async function getPromptFiles(
     }
 
     const paths = await walkDir(dir, ide, { ignoreFiles: [] });
-    const results = paths.map(async (path) => {
-      const content = await ide.readFile(path);
+    const promptFilePaths = paths.filter((p) => p.endsWith(".prompt"));
+    const results = promptFilePaths.map(async (path) => {
+      const content = await ide.readFile(path); // make a try catch
       return { path, content };
     });
     return Promise.all(results);
@@ -42,244 +42,273 @@ export async function getPromptFiles(
   }
 }
 
-const DEFAULT_PROMPT_FILE = `# This is an example ".prompt" file
-# It is used to define and reuse prompts within Continue
-# Continue will automatically create a slash command for each prompt in the .prompts folder
-# To learn more, see the full .prompt file reference: https://docs.continue.dev/features/prompt-files
-temperature: 0.0
----
-{{{ diff }}}
-
-Give me feedback on the above changes. For each file, you should output a markdown section including the following:
-- If you found any problems, an h3 like "❌ "
-- If you didn't find any problems, an h3 like "✅ "
-- If you found any problems, add below a bullet point description of what you found, including a minimal code snippet explaining how to fix it
-- If you didn't find any problems, you don't need to add anything else
-
-Here is an example. The example is surrounded in backticks, but your response should not be:
-
-\`\`\`
-### ✅ 
-
-### ❌ 
-
-
-\`\`\`
-
-You should look primarily for the following types of issues, and only mention other problems if they are highly pressing.
-
-- console.logs that have been left after debugging
-- repeated code
-- algorithmic errors that could fail under edge cases
-- something that could be refactored
-
-Make sure to review ALL files that were changed, do not skip any.
-`;
-
-export async function createNewPromptFile(
+export async function getAllPromptFilesV2(
   ide: IDE,
-  promptPath: string | undefined,
-): Promise {
+  overridePromptFolder?: string,
+): Promise<{ path: string; content: string }[]> {
   const workspaceDirs = await ide.getWorkspaceDirs();
-  if (workspaceDirs.length === 0) {
-    throw new Error(
-      "No workspace directories found. Make sure you've opened a folder in your IDE.",
-    );
-  }
-  const promptFilePath = path.join(
-    workspaceDirs[0],
-    promptPath ?? DEFAULT_PROMPTS_FOLDER,
-    "new-prompt-file.prompt",
-  );
-
-  await ide.writeFile(promptFilePath, DEFAULT_PROMPT_FILE);
-  await ide.openFile(promptFilePath);
-}
-
-export function slashCommandFromPromptFile(
-  path: string,
-  content: string,
-): SlashCommand | null {
-  const { name, description, systemMessage, prompt, version } = parsePromptFile(
-    path,
-    content,
+  let promptFiles: { path: string; content: string }[] = [];
+
+  promptFiles = (
+    await Promise.all(
+      workspaceDirs.map((dir) =>
+        getPromptFilesFromDir(
+          ide,
+          joinPathsToUri(dir, overridePromptFolder ?? ".continue/prompts"),
+        ),
+      ),
+    )
+  ).flat();
+
+  // Also read from ~/.continue/.prompts
+  promptFiles.push(...readAllGlobalPromptFiles());
+
+  return await Promise.all(
+    promptFiles.map(async (file) => {
+      const content = await ide.readFile(file.path);
+      return { path: file.path, content };
+    }),
   );
-
-  if (version !== 1) {
-    return null;
-  }
-
-  return {
-    name,
-    description,
-    run: async function* (context) {
-      const originalSystemMessage = context.llm.systemMessage;
-      context.llm.systemMessage = systemMessage;
-
-      const userInput = extractUserInput(context.input, name);
-      const renderedPrompt = await renderPrompt(prompt, context, userInput);
-      const messages = updateChatHistory(
-        context.history,
-        name,
-        renderedPrompt,
-        systemMessage,
-      );
-
-      for await (const chunk of context.llm.streamChat(
-        messages,
-        new AbortController().signal,
-      )) {
-        yield renderChatMessage(chunk);
-      }
-
-      context.llm.systemMessage = originalSystemMessage;
-    },
-  };
 }
 
-function parsePromptFile(path: string, content: string) {
-  let [preambleRaw, prompt] = content.split("\n---\n");
-  if (prompt === undefined) {
-    prompt = preambleRaw;
-    preambleRaw = "";
-  }
-
-  const preamble = YAML.parse(preambleRaw) ?? {};
-  const name = preamble.name ?? getBasename(path).split(".prompt")[0];
-  const description = preamble.description ?? name;
-  const version = preamble.version ?? 2;
-
-  let systemMessage: string | undefined = undefined;
-  if (prompt.includes("")) {
-    systemMessage = prompt.split("")[1].split("")[0].trim();
-    prompt = prompt.split("")[1].trim();
-  }
-
-  return { name, description, systemMessage, prompt, version };
-}
-
-function extractUserInput(input: string, commandName: string): string {
-  if (input.startsWith(`/${commandName}`)) {
-    return input.slice(commandName.length + 1).trimStart();
-  }
-  return input;
-}
-
-async function renderPrompt(
-  prompt: string,
-  context: ContinueSDK,
-  userInput: string,
-) {
-  const helpers = getContextProviderHelpers(context);
-
-  // A few context providers that don't need to be in config.json to work in .prompt files
-  const diff = await context.ide.getDiff(true);
-  const currentFile = await context.ide.getCurrentFile();
-  const inputData: Record = {
-    diff: diff.join("\n"),
-    input: userInput,
-  };
-  if (currentFile) {
-    inputData.currentFile = currentFile.path;
-  }
-
-  return renderTemplatedString(
-    prompt,
-    context.ide.readFile.bind(context.ide),
-    inputData,
-    helpers,
-  );
-}
-
-function getContextProviderHelpers(
-  context: ContinueSDK,
-): Array<[string, Handlebars.HelperDelegate]> | undefined {
-  return context.config.contextProviders?.map((provider: IContextProvider) => [
-    provider.description.title,
-    async (helperContext: any) => {
-      const items = await provider.getContextItems(helperContext, {
-        config: context.config,
-        embeddingsProvider: context.config.embeddingsProvider,
-        fetch: context.fetch,
-        fullInput: context.input,
-        ide: context.ide,
-        llm: context.llm,
-        reranker: context.config.reranker,
-        selectedCode: context.selectedCode,
-      });
-
-      items.forEach((item) =>
-        context.addContextItem(createContextItem(item, provider)),
-      );
-
-      return items.map((item) => item.content).join("\n\n");
-    },
-  ]);
-}
-
-function createContextItem(item: ContextItem, provider: IContextProvider) {
-  return {
-    ...item,
-    id: {
-      itemId: item.description,
-      providerTitle: provider.description.title,
-    },
-  };
-}
-
-function updateChatHistory(
-  history: ChatMessage[],
-  commandName: string,
-  renderedPrompt: string,
-  systemMessage?: string,
-) {
-  const messages = [...history];
-
-  for (let i = messages.length - 1; i >= 0; i--) {
-    const message = messages[i];
-    const { role, content } = message;
-    if (role !== "user") {
-      continue;
-    }
-
-    if (Array.isArray(content)) {
-      if (content.some((part) => part.text?.startsWith(`/${commandName}`))) {
-        messages[i] = updateArrayContent(
-          messages[i],
-          commandName,
-          renderedPrompt,
-        );
-        break;
-      }
-    } else if (
-      typeof content === "string" &&
-      content.startsWith(`/${commandName}`)
-    ) {
-      messages[i] = { ...message, content: renderedPrompt };
-      break;
-    }
-  }
-
-  if (systemMessage) {
-    messages[0]?.role === "system"
-      ? (messages[0].content = systemMessage)
-      : messages.unshift({ role: "system", content: systemMessage });
-  }
-
-  return messages;
-}
-
-function updateArrayContent(
-  message: any,
-  commandName: string,
-  renderedPrompt: string,
-) {
-  return {
-    ...message,
-    content: message.content.map((part: any) =>
-      part.text?.startsWith(`/${commandName}`)
-        ? { ...part, text: renderedPrompt }
-        : part,
-    ),
-  };
-}
+// const DEFAULT_PROMPT_FILE = `# This is an example ".prompt" file
+// # It is used to define and reuse prompts within Continue
+// # Continue will automatically create a slash command for each prompt in the .prompts folder
+// # To learn more, see the full .prompt file reference: https://docs.continue.dev/features/prompt-files
+// temperature: 0.0
+// ---
+// {{{ diff }}}
+
+// Give me feedback on the above changes. For each file, you should output a markdown section including the following:
+// - If you found any problems, an h3 like "❌ "
+// - If you didn't find any problems, an h3 like "✅ "
+// - If you found any problems, add below a bullet point description of what you found, including a minimal code snippet explaining how to fix it
+// - If you didn't find any problems, you don't need to add anything else
+
+// Here is an example. The example is surrounded in backticks, but your response should not be:
+
+// \`\`\`
+// ### ✅ 
+
+// ### ❌ 
+
+// 
+// \`\`\`
+
+// You should look primarily for the following types of issues, and only mention other problems if they are highly pressing.
+
+// - console.logs that have been left after debugging
+// - repeated code
+// - algorithmic errors that could fail under edge cases
+// - something that could be refactored
+
+// Make sure to review ALL files that were changed, do not skip any.
+// `;
+
+// export async function createNewPromptFile(
+//   ide: IDE,
+//   promptPath: string | undefined,
+// ): Promise {
+//   const workspaceDirs = await ide.getWorkspaceDirs();
+//   if (workspaceDirs.length === 0) {
+//     throw new Error(
+//       "No workspace directories found. Make sure you've opened a folder in your IDE.",
+//     );
+//   }
+//   const promptFilePath = path.join(
+//     workspaceDirs[0],
+//     promptPath ?? DEFAULT_PROMPTS_FOLDER,
+//     "new-prompt-file.prompt",
+//   );
+
+//   await ide.writeFile(promptFilePath, DEFAULT_PROMPT_FILE);
+//   await ide.openFile(promptFilePath);
+// }
+
+// export function slashCommandFromPromptFile(
+//   path: string,
+//   content: string,
+// ): SlashCommand | null {
+//   const { name, description, systemMessage, prompt, version } = parsePromptFile(
+//     path,
+//     content,
+//   );
+
+//   if (version !== 1) {
+//     return null;
+//   }
+
+//   return {
+//     name,
+//     description,
+//     run: async function* (context) {
+//       const originalSystemMessage = context.llm.systemMessage;
+//       context.llm.systemMessage = systemMessage;
+
+//       const userInput = extractUserInput(context.input, name);
+//       const renderedPrompt = await renderPrompt(prompt, context, userInput);
+//       const messages = updateChatHistory(
+//         context.history,
+//         name,
+//         renderedPrompt,
+//         systemMessage,
+//       );
+
+//       for await (const chunk of context.llm.streamChat(
+//         messages,
+//         new AbortController().signal,
+//       )) {
+//         yield renderChatMessage(chunk);
+//       }
+
+//       context.llm.systemMessage = originalSystemMessage;
+//     },
+//   };
+// }
+
+// function parsePromptFile(path: string, content: string) {
+//   let [preambleRaw, prompt] = content.split("\n---\n");
+//   if (prompt === undefined) {
+//     prompt = preambleRaw;
+//     preambleRaw = "";
+//   }
+
+//   const preamble = YAML.parse(preambleRaw) ?? {};
+//   const name = preamble.name ?? getBasename(path).split(".prompt")[0];
+//   const description = preamble.description ?? name;
+//   const version = preamble.version ?? 2;
+
+//   let systemMessage: string | undefined = undefined;
+//   if (prompt.includes("")) {
+//     systemMessage = prompt.split("")[1].split("")[0].trim();
+//     prompt = prompt.split("")[1].trim();
+//   }
+
+//   return { name, description, systemMessage, prompt, version };
+// }
+
+// function extractUserInput(input: string, commandName: string): string {
+//   if (input.startsWith(`/${commandName}`)) {
+//     return input.slice(commandName.length + 1).trimStart();
+//   }
+//   return input;
+// }
+
+// async function renderPrompt(
+//   prompt: string,
+//   context: ContinueSDK,
+//   userInput: string,
+// ) {
+//   const helpers = getContextProviderHelpers(context);
+
+//   // A few context providers that don't need to be in config.json to work in .prompt files
+//   const diff = await context.ide.getDiff(true);
+//   const currentFile = await context.ide.getCurrentFile();
+//   const inputData: Record = {
+//     diff: diff.join("\n"),
+//     input: userInput,
+//   };
+//   if (currentFile) {
+//     inputData.currentFile = currentFile.path;
+//   }
+
+//   return renderTemplatedString(
+//     prompt,
+//     context.ide.readFile.bind(context.ide),
+//     inputData,
+//     helpers,
+//   );
+// }
+
+// function getContextProviderHelpers(
+//   context: ContinueSDK,
+// ): Array<[string, Handlebars.HelperDelegate]> | undefined {
+//   return context.config.contextProviders?.map((provider: IContextProvider) => [
+//     provider.description.title,
+//     async (helperContext: any) => {
+//       const items = await provider.getContextItems(helperContext, {
+//         config: context.config,
+//         embeddingsProvider: context.config.embeddingsProvider,
+//         fetch: context.fetch,
+//         fullInput: context.input,
+//         ide: context.ide,
+//         llm: context.llm,
+//         reranker: context.config.reranker,
+//         selectedCode: context.selectedCode,
+//       });
+
+//       items.forEach((item) =>
+//         context.addContextItem(createContextItem(item, provider)),
+//       );
+
+//       return items.map((item) => item.content).join("\n\n");
+//     },
+//   ]);
+// }
+
+// function createContextItem(item: ContextItem, provider: IContextProvider) {
+//   return {
+//     ...item,
+//     id: {
+//       itemId: item.description,
+//       providerTitle: provider.description.title,
+//     },
+//   };
+// }
+
+// function updateChatHistory(
+//   history: ChatMessage[],
+//   commandName: string,
+//   renderedPrompt: string,
+//   systemMessage?: string,
+// ) {
+//   const messages = [...history];
+
+//   for (let i = messages.length - 1; i >= 0; i--) {
+//     const message = messages[i];
+//     const { role, content } = message;
+//     if (role !== "user") {
+//       continue;
+//     }
+
+//     if (Array.isArray(content)) {
+//       if (content.some((part) => part.text?.startsWith(`/${commandName}`))) {
+//         messages[i] = updateArrayContent(
+//           messages[i],
+//           commandName,
+//           renderedPrompt,
+//         );
+//         break;
+//       }
+//     } else if (
+//       typeof content === "string" &&
+//       content.startsWith(`/${commandName}`)
+//     ) {
+//       messages[i] = { ...message, content: renderedPrompt };
+//       break;
+//     }
+//   }
+
+//   if (systemMessage) {
+//     messages[0]?.role === "system"
+//       ? (messages[0].content = systemMessage)
+//       : messages.unshift({ role: "system", content: systemMessage });
+//   }
+
+//   return messages;
+// }
+
+// function updateArrayContent(
+//   message: any,
+//   commandName: string,
+//   renderedPrompt: string,
+// ) {
+//   return {
+//     ...message,
+//     content: message.content.map((part: any) =>
+//       part.text?.startsWith(`/${commandName}`)
+//         ? { ...part, text: renderedPrompt }
+//         : part,
+//     ),
+//   };
+// }
diff --git a/core/config/types.ts b/core/config/types.ts
index 625f4812f4..71c16c6057 100644
--- a/core/config/types.ts
+++ b/core/config/types.ts
@@ -488,7 +488,6 @@ declare global {
 
     // Callbacks
     onDidChangeActiveTextEditor(callback: (filepath: string) => void): void;
-    pathSep(): Promise;
   }
 
   // Slash Commands
diff --git a/core/context/providers/PromptFilesContextProvider.ts b/core/context/providers/PromptFilesContextProvider.ts
index bc9239f1df..9ea603f3f8 100644
--- a/core/context/providers/PromptFilesContextProvider.ts
+++ b/core/context/providers/PromptFilesContextProvider.ts
@@ -6,7 +6,6 @@ import {
   ContextSubmenuItem,
   LoadSubmenuItemsArgs,
 } from "../../";
-import { getAllPromptFilesV2 } from "../../promptFiles/v2/getPromptFiles";
 import { parsePreamble } from "../../promptFiles/v2/parse";
 import { renderPromptFileV2 } from "../../promptFiles/v2/renderPromptFile";
 
@@ -38,7 +37,7 @@ class PromptFilesContextProvider extends BaseContextProvider {
   async loadSubmenuItems(
     args: LoadSubmenuItemsArgs,
   ): Promise {
-    const promptFiles = await getAllPromptFilesV2(
+    const promptFiles = await getAllPromptFiles(
       args.ide,
       args.config.experimental?.promptPath,
     );
diff --git a/core/context/retrieval/repoMapRequest.ts b/core/context/retrieval/repoMapRequest.ts
index 5c82ace95e..17e76af43d 100644
--- a/core/context/retrieval/repoMapRequest.ts
+++ b/core/context/retrieval/repoMapRequest.ts
@@ -71,8 +71,6 @@ This is the question that you should select relevant files for: "${input}"`;
       return [];
     }
 
-    const pathSep = await ide.pathSep();
-    const subDirPrefix = filterDirectory ? filterDirectory + pathSep : "";
     const files =
       content
         .split("")[1]
diff --git a/core/context/retrieval/retrieval.ts b/core/context/retrieval/retrieval.ts
index a2b129dc81..1ee33af5ce 100644
--- a/core/context/retrieval/retrieval.ts
+++ b/core/context/retrieval/retrieval.ts
@@ -112,13 +112,12 @@ export async function retrieveContextItemsFromEmbeddings(
     ...results
       .sort((a, b) => a.filepath.localeCompare(b.filepath))
       .map((r) => {
-        const name = `${getUriPathBasename(r.filepath)} (${r.startLine}-${
-          r.endLine
-        })`;
+        const basename = getUriPathBasename(r.filepath);
+        const name = `${basename} (${r.startLine}-${r.endLine})`;
         const description = `${r.filepath}`;
 
-        if (r.filepath.includes("package.json")) {
-          console.log();
+        if (basename === "package.json") {
+          console.warn("Retrieval pipeline: package.json detected");
         }
 
         return {
diff --git a/core/indexing/CodebaseIndexer.ts b/core/indexing/CodebaseIndexer.ts
index 4b2138d5ac..cd074f7ad2 100644
--- a/core/indexing/CodebaseIndexer.ts
+++ b/core/indexing/CodebaseIndexer.ts
@@ -17,6 +17,7 @@ import {
   RefreshIndexResults,
 } from "./types.js";
 import { walkDirAsync } from "./walkDir.js";
+import { getUriPathBasename } from "../util/uri.js";
 
 export class PauseToken {
   constructor(private _paused: boolean) {}
@@ -75,19 +76,15 @@ export class CodebaseIndexer {
 
   protected async getIndexesToBuild(): Promise {
     const config = await this.configHandler.loadConfig();
-    const pathSep = await this.ide.pathSep();
-
     const indexes = [
       new ChunkCodebaseIndex(
         this.ide.readFile.bind(this.ide),
-        pathSep,
         this.continueServerClient,
         config.embeddingsProvider.maxEmbeddingChunkSize,
       ), // Chunking must come first
       new LanceDbIndex(
         config.embeddingsProvider,
         this.ide.readFile.bind(this.ide),
-        pathSep,
         this.continueServerClient,
       ),
       new FullTextSearchCodebaseIndex(),
@@ -224,7 +221,7 @@ export class CodebaseIndexer {
     const beginTime = Date.now();
 
     for (const directory of dirs) {
-      const dirBasename = await this.basename(directory);
+      const dirBasename = getUriPathBasename(directory);
       yield {
         progress,
         desc: `Discovering files in ${dirBasename}...`,
@@ -456,10 +453,4 @@ export class CodebaseIndexer {
     }
     return undefined;
   }
-
-  private async basename(filepath: string): Promise {
-    const pathSep = await this.ide.pathSep();
-    const path = filepath.split(pathSep);
-    return path[path.length - 1];
-  }
 }
diff --git a/core/indexing/LanceDbIndex.test.ts b/core/indexing/LanceDbIndex.test.ts
index 1d15117fdb..6cf36f78da 100644
--- a/core/indexing/LanceDbIndex.test.ts
+++ b/core/indexing/LanceDbIndex.test.ts
@@ -26,13 +26,11 @@ describe.skip("ChunkCodebaseIndex", () => {
   }
 
   beforeAll(async () => {
-    const pathSep = await testIde.pathSep();
     const mockConfig = await testConfigHandler.loadConfig();
 
     index = new LanceDbIndex(
       mockConfig.embeddingsProvider,
       testIde.readFile.bind(testIde),
-      pathSep,
       testContinueServerClient,
     );
 
diff --git a/core/indexing/chunk/ChunkCodebaseIndex.test.ts b/core/indexing/chunk/ChunkCodebaseIndex.test.ts
index 3f7904991c..ea636dc544 100644
--- a/core/indexing/chunk/ChunkCodebaseIndex.test.ts
+++ b/core/indexing/chunk/ChunkCodebaseIndex.test.ts
@@ -29,11 +29,8 @@ describe("ChunkCodebaseIndex", () => {
   }
 
   beforeAll(async () => {
-    const pathSep = await testIde.pathSep();
-
     index = new ChunkCodebaseIndex(
       testIde.readFile.bind(testIde),
-      pathSep,
       testContinueServerClient,
       1000,
     );
diff --git a/core/indexing/chunk/ChunkCodebaseIndex.ts b/core/indexing/chunk/ChunkCodebaseIndex.ts
index 7bd856ff1e..1f6a452589 100644
--- a/core/indexing/chunk/ChunkCodebaseIndex.ts
+++ b/core/indexing/chunk/ChunkCodebaseIndex.ts
@@ -4,7 +4,6 @@ import { RunResult } from "sqlite3";
 
 import { IContinueServerClient } from "../../continueServer/interface.js";
 import { Chunk, IndexTag, IndexingProgressUpdate } from "../../index.js";
-import { getBasename } from "../../util/index.js";
 import { DatabaseConnection, SqliteDb, tagToString } from "../refreshIndex.js";
 import {
   IndexResultType,
@@ -15,6 +14,7 @@ import {
 } from "../types.js";
 
 import { chunkDocument, shouldChunk } from "./chunk.js";
+import { getUriPathBasename } from "../../util/uri.js";
 
 export class ChunkCodebaseIndex implements CodebaseIndex {
   relativeExpectedTime: number = 1;
@@ -23,7 +23,6 @@ export class ChunkCodebaseIndex implements CodebaseIndex {
 
   constructor(
     private readonly readFile: (filepath: string) => Promise,
-    private readonly pathSep: string,
     private readonly continueServerClient: IContinueServerClient,
     private readonly maxChunkSize: number,
   ) {}
@@ -89,7 +88,7 @@ export class ChunkCodebaseIndex implements CodebaseIndex {
       accumulatedProgress += 1 / results.addTag.length / 4;
       yield {
         progress: accumulatedProgress,
-        desc: `Adding ${getBasename(item.path)}`,
+        desc: `Adding ${getUriPathBasename(item.path)}`,
         status: "indexing",
       };
     }
@@ -111,7 +110,7 @@ export class ChunkCodebaseIndex implements CodebaseIndex {
       accumulatedProgress += 1 / results.removeTag.length / 4;
       yield {
         progress: accumulatedProgress,
-        desc: `Removing ${getBasename(item.path)}`,
+        desc: `Removing ${getUriPathBasename(item.path)}`,
         status: "indexing",
       };
     }
@@ -138,7 +137,7 @@ export class ChunkCodebaseIndex implements CodebaseIndex {
       accumulatedProgress += 1 / results.del.length / 4;
       yield {
         progress: accumulatedProgress,
-        desc: `Removing ${getBasename(item.path)}`,
+        desc: `Removing ${getUriPathBasename(item.path)}`,
         status: "indexing",
       };
     }
@@ -165,7 +164,7 @@ export class ChunkCodebaseIndex implements CodebaseIndex {
 
   private async packToChunks(pack: PathAndCacheKey): Promise {
     const contents = await this.readFile(pack.path);
-    if (!shouldChunk(this.pathSep, pack.path, contents)) {
+    if (!shouldChunk(pack.path, contents)) {
       return [];
     }
     const chunks: Chunk[] = [];
diff --git a/core/indexing/test/indexing.ts b/core/indexing/test/indexing.ts
index 413c37e754..5f22d0aeb9 100644
--- a/core/indexing/test/indexing.ts
+++ b/core/indexing/test/indexing.ts
@@ -4,7 +4,7 @@ import { IndexTag } from "../..";
 import { IContinueServerClient } from "../../continueServer/interface";
 import { ChunkCodebaseIndex } from "../chunk/ChunkCodebaseIndex";
 import { tagToString } from "../refreshIndex";
-import { CodebaseIndex, PathAndCacheKey, RefreshIndexResults } from "../types";
+import { CodebaseIndex, RefreshIndexResults } from "../types";
 
 import { testIde } from "../../test/fixtures";
 import { addToTestDir, TEST_DIR } from "../../test/testDir";
@@ -54,11 +54,8 @@ const mockMarkComplete = jest
   .mockImplementation(() => Promise.resolve()) as any;
 
 export async function insertMockChunks() {
-  const pathSep = await testIde.pathSep();
-
   const index = new ChunkCodebaseIndex(
     testIde.readFile.bind(testIde),
-    pathSep,
     mockContinueServerClient,
     1000,
   );
diff --git a/core/indexing/walkDir.test.ts b/core/indexing/walkDir.test.ts
index 25ffdfc374..af22265ffc 100644
--- a/core/indexing/walkDir.test.ts
+++ b/core/indexing/walkDir.test.ts
@@ -25,13 +25,6 @@ async function expectPaths(
   toNotExist: string[],
   options?: WalkerOptions,
 ) {
-  // Convert to Windows paths
-  const pathSep = await ide.pathSep();
-  if (pathSep === "\\") {
-    toExist = toExist.map((p) => p.replace(/\//g, "\\"));
-    toNotExist = toNotExist.map((p) => p.replace(/\//g, "\\"));
-  }
-
   const result = await walkTestDir(options);
 
   for (const p of toExist) {
diff --git a/core/promptFiles/v1/createNewPromptFile.ts b/core/promptFiles/v1/createNewPromptFile.ts
index 19286fe6db..f6da908547 100644
--- a/core/promptFiles/v1/createNewPromptFile.ts
+++ b/core/promptFiles/v1/createNewPromptFile.ts
@@ -1,8 +1,7 @@
-import path from "path";
-
 import { IDE } from "../..";
 
 import { DEFAULT_PROMPTS_FOLDER } from ".";
+import { joinPathsToUri } from "../../util/uri";
 
 const DEFAULT_PROMPT_FILE = `# This is an example ".prompt" file
 # It is used to define and reuse prompts within Continue
@@ -48,7 +47,7 @@ export async function createNewPromptFile(
       "No workspace directories found. Make sure you've opened a folder in your IDE.",
     );
   }
-  const promptFilePath = path.join(
+  const promptFilePath = joinPathsToUri(
     workspaceDirs[0],
     promptPath ?? DEFAULT_PROMPTS_FOLDER,
     "new-prompt-file.prompt",
diff --git a/core/promptFiles/v1/index.ts b/core/promptFiles/v1/index.ts
index 2eed3a15d9..e14c2103c9 100644
--- a/core/promptFiles/v1/index.ts
+++ b/core/promptFiles/v1/index.ts
@@ -1,7 +1,7 @@
 import { createNewPromptFile } from "./createNewPromptFile";
-import { getPromptFiles } from "./getPromptFiles";
+import { getPromptFilesV1 } from "./getPromptFiles";
 import { slashCommandFromPromptFile } from "./slashCommandFromPromptFile";
 
 export const DEFAULT_PROMPTS_FOLDER = ".prompts";
 
-export { createNewPromptFile, getPromptFiles, slashCommandFromPromptFile };
+export { createNewPromptFile, getPromptFilesV1, slashCommandFromPromptFile };
diff --git a/core/promptFiles/v1/slashCommandFromPromptFile.ts b/core/promptFiles/v1/slashCommandFromPromptFile.ts
index 7d3750ad41..56b6fb3667 100644
--- a/core/promptFiles/v1/slashCommandFromPromptFile.ts
+++ b/core/promptFiles/v1/slashCommandFromPromptFile.ts
@@ -1,7 +1,6 @@
 import * as YAML from "yaml";
 
 import { ContinueSDK, SlashCommand } from "../..";
-import { getBasename } from "../../util/index";
 import { renderChatMessage } from "../../util/messageContent";
 
 import { getContextProviderHelpers } from "./getContextProviderHelpers";
diff --git a/core/promptFiles/v2/createNewPromptFile.ts b/core/promptFiles/v2/createNewPromptFile.ts
index ac71035103..56ac84614d 100644
--- a/core/promptFiles/v2/createNewPromptFile.ts
+++ b/core/promptFiles/v2/createNewPromptFile.ts
@@ -1,5 +1,6 @@
 import { IDE } from "../..";
 import { GlobalContext } from "../../util/GlobalContext";
+import { joinPathsToUri } from "../../util/uri";
 // import { getPathModuleForIde } from "../../util/pathModule";
 
 const FIRST_TIME_DEFAULT_PROMPT_FILE = `# This is an example ".prompt" file
@@ -45,11 +46,10 @@ export async function createNewPromptFileV2(
       "No workspace directories found. Make sure you've opened a folder in your IDE.",
     );
   }
-  const pathModule = await getPathModuleForIde(ide);
 
-  const baseDir = pathModule.join(
+  const baseDiruri = joinPathsToUri(
     workspaceDirs[0],
-    promptPath ?? pathModule.join(".continue", "prompts"),
+    promptPath ?? ".continue/prompts",
   );
 
   // Find the first available filename
@@ -57,7 +57,10 @@ export async function createNewPromptFileV2(
   let promptFileUri: string;
   do {
     const suffix = counter === 0 ? "" : `-${counter}`;
-    promptFileUri = pathModule.join(baseDir, `new-prompt-file${suffix}.prompt`);
+    promptFileUri = joinPathsToUri(
+      baseDiruri,
+      `new-prompt-file${suffix}.prompt`,
+    );
     counter++;
   } while (await ide.fileExists(promptFileUri));
 
diff --git a/core/promptFiles/v2/getPromptFiles.ts b/core/promptFiles/v2/getPromptFiles.ts
index 0353746be2..dd04c14bcb 100644
--- a/core/promptFiles/v2/getPromptFiles.ts
+++ b/core/promptFiles/v2/getPromptFiles.ts
@@ -1,7 +1,7 @@
 import { IDE } from "../..";
 import { walkDir } from "../../indexing/walkDir";
-import { getPathModuleForIde } from "../../util/pathModule";
 import { readAllGlobalPromptFiles } from "../../util/paths";
+import { joinPathsToUri } from "../../util/uri";
 
 async function getPromptFilesFromDir(
   ide: IDE,
@@ -15,7 +15,8 @@ async function getPromptFilesFromDir(
     }
 
     const paths = await walkDir(dir, ide, { ignoreFiles: [] });
-    const results = paths.map(async (path) => {
+    const promptFilePaths = paths.filter((p) => p.endsWith(".prompt"));
+    const results = promptFilePaths.map(async (path) => {
       const content = await ide.readFile(path); // make a try catch
       return { path, content };
     });
@@ -32,23 +33,17 @@ export async function getAllPromptFilesV2(
 ): Promise<{ path: string; content: string }[]> {
   const workspaceDirs = await ide.getWorkspaceDirs();
   let promptFiles: { path: string; content: string }[] = [];
-  const pathModule = await getPathModuleForIde(ide);
 
   promptFiles = (
     await Promise.all(
       workspaceDirs.map((dir) =>
         getPromptFilesFromDir(
           ide,
-          pathModule.join(
-            dir,
-            overridePromptFolder ?? pathModule.join(".continue", "prompts"),
-          ),
+          joinPathsToUri(dir, overridePromptFolder ?? ".continue/prompts"),
         ),
       ),
     )
-  )
-    .flat()
-    .filter(({ path }) => path.endsWith(".prompt"));
+  ).flat();
 
   // Also read from ~/.continue/.prompts
   promptFiles.push(...readAllGlobalPromptFiles());
diff --git a/core/promptFiles/v2/parse.ts b/core/promptFiles/v2/parse.ts
index d0c71c9627..3de31a6d64 100644
--- a/core/promptFiles/v2/parse.ts
+++ b/core/promptFiles/v2/parse.ts
@@ -1,7 +1,5 @@
 import * as YAML from "yaml";
 
-import { getBasename } from "../../util";
-
 export function extractName(preamble: { name?: string }, path: string): string {
   return preamble.name ?? getBasename(path).split(".prompt")[0];
 }
diff --git a/core/promptFiles/v2/renderPromptFile.ts b/core/promptFiles/v2/renderPromptFile.ts
index 89b36b0a85..22abf35768 100644
--- a/core/promptFiles/v2/renderPromptFile.ts
+++ b/core/promptFiles/v2/renderPromptFile.ts
@@ -1,7 +1,8 @@
 import { ContextItem, ContextProviderExtras } from "../..";
 import { contextProviderClassFromName } from "../../context/providers";
 import URLContextProvider from "../../context/providers/URLContextProvider";
-import { getBasename } from "../../util";
+import { resolveRelativePathInWorkspace } from "../../util/ideUtils";
+import { getUriPathBasename } from "../../util/uri";
 import { getPreambleAndBody } from "./parse";
 
 async function resolveAttachment(
@@ -25,9 +26,19 @@ async function resolveAttachment(
   }
 
   // Files
-  if (await extras.ide.fileExists(name)) {
-    const content = `\`\`\`${name}\n${await extras.ide.readFile(name)}\n\`\`\``;
-    return [{ name: getBasename(name), content, description: name }];
+  const resolvedFileUri = await resolveRelativePathInWorkspace(
+    name,
+    extras.ide,
+  );
+  if (resolvedFileUri) {
+    const content = `\`\`\`${name}\n${await extras.ide.readFile(resolvedFileUri)}\n\`\`\``;
+    return [
+      {
+        name: getUriPathBasename(resolvedFileUri),
+        content,
+        description: resolvedFileUri,
+      },
+    ];
   }
 
   // URLs
diff --git a/core/protocol/ide.ts b/core/protocol/ide.ts
index 6bb6cc5edb..efe5f68858 100644
--- a/core/protocol/ide.ts
+++ b/core/protocol/ide.ts
@@ -100,7 +100,6 @@ export type ToIdeFromWebviewOrCoreProtocol = {
     ControlPlaneSessionInfo | undefined,
   ];
   logoutOfControlPlane: [undefined, void];
-  pathSep: [undefined, string];
 };
 
 export type ToWebviewOrCoreFromIdeProtocol = {
diff --git a/core/protocol/messenger/reverseMessageIde.ts b/core/protocol/messenger/reverseMessageIde.ts
index 46e6418c0d..a180bb2274 100644
--- a/core/protocol/messenger/reverseMessageIde.ts
+++ b/core/protocol/messenger/reverseMessageIde.ts
@@ -189,8 +189,5 @@ export class ReverseMessageIde {
     this.on("getBranch", (data) => {
       return this.ide.getBranch(data.dir);
     });
-    this.on("pathSep", (data) => {
-      return this.ide.pathSep();
-    });
   }
 }
diff --git a/core/tools/implementations/createNewFile.ts b/core/tools/implementations/createNewFile.ts
index 0be187b193..1b733da010 100644
--- a/core/tools/implementations/createNewFile.ts
+++ b/core/tools/implementations/createNewFile.ts
@@ -1,17 +1,15 @@
-import { getPathModuleForIde } from "../../util/pathModule";
+import { inferResolvedUriFromRelativePath } from "../../util/ideUtils";
 
 import { ToolImpl } from ".";
 
 export const createNewFileImpl: ToolImpl = async (args, extras) => {
-  const pathSep = await extras.ide.pathSep();
-  let filepath = args.filepath;
-  if (!args.filepath.startsWith(pathSep)) {
-    const pathModule = await getPathModuleForIde(extras.ide);
-    const workspaceDirs = await extras.ide.getWorkspaceDirs();
-    const cwd = workspaceDirs[0];
-    filepath = pathModule.join(cwd, filepath);
+  const resolvedFilepath = await inferResolvedUriFromRelativePath(
+    args.filepath,
+    extras.ide,
+  );
+  if (resolvedFilepath) {
+    await extras.ide.writeFile(resolvedFilepath, args.contents);
+    await extras.ide.openFile(resolvedFilepath);
   }
-  await extras.ide.writeFile(filepath, args.contents);
-  await extras.ide.openFile(filepath);
   return [];
 };
diff --git a/core/tools/implementations/readCurrentlyOpenFile.ts b/core/tools/implementations/readCurrentlyOpenFile.ts
index 7fff3b621f..acfd9bec2b 100644
--- a/core/tools/implementations/readCurrentlyOpenFile.ts
+++ b/core/tools/implementations/readCurrentlyOpenFile.ts
@@ -1,5 +1,5 @@
 import { ToolImpl } from ".";
-import { getBasename } from "../../util";
+import { getUriPathBasename } from "../../util/uri";
 
 export const readCurrentlyOpenFileImpl: ToolImpl = async (args, extras) => {
   const result = await extras.ide.getCurrentFile();
@@ -8,7 +8,7 @@ export const readCurrentlyOpenFileImpl: ToolImpl = async (args, extras) => {
     return [];
   }
 
-  const basename = getBasename(result.path);
+  const basename = getUriPathBasename(result.path);
 
   return [
     {
diff --git a/core/tools/implementations/readFile.ts b/core/tools/implementations/readFile.ts
index 032db3dce5..674b41578f 100644
--- a/core/tools/implementations/readFile.ts
+++ b/core/tools/implementations/readFile.ts
@@ -1,12 +1,11 @@
-import { getBasename } from "../../util";
-
 import { ToolImpl } from ".";
+import { getUriPathBasename } from "../../util/uri";
 
 export const readFileImpl: ToolImpl = async (args, extras) => {
   const content = await extras.ide.readFile(args.filepath);
   return [
     {
-      name: getBasename(args.filepath),
+      name: getUriPathBasename(args.filepath),
       description: args.filepath,
       content,
     },
diff --git a/core/tools/implementations/viewSubdirectory.ts b/core/tools/implementations/viewSubdirectory.ts
index a8204ca6a2..e762858ff7 100644
--- a/core/tools/implementations/viewSubdirectory.ts
+++ b/core/tools/implementations/viewSubdirectory.ts
@@ -5,17 +5,17 @@ import { ToolImpl } from ".";
 
 export const viewSubdirectoryImpl: ToolImpl = async (args: any, extras) => {
   const { directory_path } = args;
-  const absolutePath = await resolveRelativePathInWorkspace(
+  const absoluteUri = await resolveRelativePathInWorkspace(
     directory_path,
     extras.ide,
   );
 
-  if (!absolutePath) {
+  if (!absoluteUri) {
     throw new Error(`Directory path "${directory_path}" does not exist.`);
   }
 
   const repoMap = await generateRepoMap(extras.llm, extras.ide, {
-    dirs: [absolutePath],
+    dirs: [absoluteUri],
   });
   return [
     {
diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts
index f85d58e57c..39f048119d 100644
--- a/core/util/filesystem.ts
+++ b/core/util/filesystem.ts
@@ -1,5 +1,4 @@
 import * as fs from "node:fs";
-import * as path from "node:path";
 
 import {
   ContinueRcJson,
@@ -28,9 +27,6 @@ class FileSystemIde implements IDE {
   ): Promise {
     return Promise.resolve();
   }
-  pathSep(): Promise {
-    return Promise.resolve(path.sep);
-  }
   fileExists(filepath: string): Promise {
     return Promise.resolve(fs.existsSync(filepath));
   }
diff --git a/core/util/ideUtils.test.ts b/core/util/ideUtils.test.ts
index 58cd7b7f40..a9069f4258 100644
--- a/core/util/ideUtils.test.ts
+++ b/core/util/ideUtils.test.ts
@@ -13,7 +13,6 @@ describe("resolveRelativePathInWorkspace", () => {
 
   it("should return the full path if the path is already absolute", async () => {
     const mockPath = "/absolute/path/to/file.txt";
-    jest.spyOn(mockIde, "pathSep").mockResolvedValue("/");
 
     const result = await resolveRelativePathInWorkspace(mockPath, mockIde);
     expect(result).toBe(mockPath);
@@ -24,7 +23,6 @@ describe("resolveRelativePathInWorkspace", () => {
     const workspaces = ["/workspace/one", "/workspace/two"];
     const expectedFullPath = `/workspace/one/${relativePath}`;
 
-    jest.spyOn(mockIde, "pathSep").mockResolvedValue("/");
     jest.spyOn(mockIde, "getWorkspaceDirs").mockResolvedValue(workspaces);
     jest
       .spyOn(mockIde, "fileExists")
@@ -38,7 +36,6 @@ describe("resolveRelativePathInWorkspace", () => {
     const relativePath = "non/existent/path.txt";
     const workspaces = ["/workspace/one", "/workspace/two"];
 
-    jest.spyOn(mockIde, "pathSep").mockResolvedValue("/");
     jest.spyOn(mockIde, "getWorkspaceDirs").mockResolvedValue(workspaces);
     jest.spyOn(mockIde, "fileExists").mockResolvedValue(false);
 
diff --git a/core/util/ideUtils.ts b/core/util/ideUtils.ts
index 0b7de8b029..ff7d064465 100644
--- a/core/util/ideUtils.ts
+++ b/core/util/ideUtils.ts
@@ -1,21 +1,49 @@
 import { IDE } from "..";
+import { pathToUriPathSegment } from "./uri";
 
+/*
+  This function takes a relative filepath
+  And checks each workspace for if it exists or not
+  Only returns fully resolved URI if it exists
+*/
 export async function resolveRelativePathInWorkspace(
   path: string,
   ide: IDE,
 ): Promise {
-  const pathSep = await ide.pathSep();
-  if (path.startsWith(pathSep)) {
-    return path;
-  }
-
+  const cleanPath = pathToUriPathSegment(path);
   const workspaces = await ide.getWorkspaceDirs();
   for (const workspace of workspaces) {
-    const fullPath = `${workspace}${pathSep}${path}`;
-    if (await ide.fileExists(fullPath)) {
-      return fullPath;
+    const fullUri = `${workspace}/${cleanPath}`;
+    if (await ide.fileExists(fullUri)) {
+      return fullUri;
     }
   }
 
   return undefined;
 }
+
+/*
+  This function takes a relative filepath (which may not exist)
+  And, based on which workspace has the closest matching path
+  Guesses which workspace and returns resolved URI
+
+  Original use case of for tools trying to create new files
+  If no meaninful path match just concatenates to first workspace's uri
+*/
+export async function inferResolvedUriFromRelativePath(
+  path: string,
+  ide: IDE,
+): Promise {
+  const cleanPath = pathToUriPathSegment(path);
+
+  const 
+  const workspaces = await ide.getWorkspaceDirs();
+  for (const workspace of workspaces) {
+    const fullUri = `${workspace}/${cleanPath}`;
+    if (await ide.fileExists(fullUri)) {
+      return fullUri;
+    }
+  }
+  // console.warn("No meaninful filepath inferred from relative path " + path)
+  return `${workspaces[0]}/${cleanPath}`
+}
\ No newline at end of file
diff --git a/core/util/index.test.ts b/core/util/index.test.ts
index 1c6b4a240b..74ee3ccc86 100644
--- a/core/util/index.test.ts
+++ b/core/util/index.test.ts
@@ -3,58 +3,12 @@ import {
   dedent,
   dedentAndGetCommonWhitespace,
   deduplicateArray,
-  getLastNPathParts,
   copyOf,
-  getBasename,
   getMarkdownLanguageTagForFile,
-  getRelativePath,
-  getUniqueFilePath,
-  groupByLastNPathParts,
   removeCodeBlocksAndTrim,
   removeQuotesAndEscapes,
-  shortestRelativePaths,
-  splitPath,
 } from "./";
 
-describe("getLastNPathParts", () => {
-  test("returns the last N parts of a filepath with forward slashes", () => {
-    const filepath = "home/user/documents/project/file.txt";
-    expect(getLastNPathParts(filepath, 2)).toBe("project/file.txt");
-  });
-
-  test("returns the last N parts of a filepath with backward slashes", () => {
-    const filepath = "C:\\home\\user\\documents\\project\\file.txt";
-    expect(getLastNPathParts(filepath, 3)).toBe("documents/project/file.txt");
-  });
-
-  test("returns the last part if N is 1", () => {
-    const filepath = "/home/user/documents/project/file.txt";
-    expect(getLastNPathParts(filepath, 1)).toBe("file.txt");
-  });
-
-  test("returns the entire path if N is greater than the number of parts", () => {
-    const filepath = "home/user/documents/project/file.txt";
-    expect(getLastNPathParts(filepath, 10)).toBe(
-      "home/user/documents/project/file.txt",
-    );
-  });
-
-  test("returns an empty string if N is 0", () => {
-    const filepath = "home/user/documents/project/file.txt";
-    expect(getLastNPathParts(filepath, 0)).toBe("");
-  });
-
-  test("handles paths with mixed forward and backward slashes", () => {
-    const filepath = "home\\user/documents\\project/file.txt";
-    expect(getLastNPathParts(filepath, 3)).toBe("documents/project/file.txt");
-  });
-
-  test("handles edge case with empty filepath", () => {
-    const filepath = "";
-    expect(getLastNPathParts(filepath, 2)).toBe("");
-  });
-});
-
 describe("deduplicateArray", () => {
   it("should return an empty array when given an empty array", () => {
     const result = deduplicateArray([], (a, b) => a === b);
@@ -414,199 +368,6 @@ describe("removeQuotesAndEscapes", () => {
   });
 });
 
-describe("getBasename", () => {
-  it("should return the base name of a Unix-style path", () => {
-    const filepath = "/home/user/documents/file.txt";
-    const output = getBasename(filepath);
-    expect(output).toBe("file.txt");
-  });
-
-  it("should return the base name of a Windows-style path", () => {
-    const filepath = "C:\\Users\\User\\Documents\\file.txt";
-    const output = getBasename(filepath);
-    expect(output).toBe("file.txt");
-  });
-
-  it("should handle paths with mixed separators", () => {
-    const filepath = "C:/Users\\User/Documents/file.txt";
-    const output = getBasename(filepath);
-    expect(output).toBe("file.txt");
-  });
-
-  it("should return an empty string for empty input", () => {
-    const filepath = "";
-    const output = getBasename(filepath);
-    expect(output).toBe("");
-  });
-});
-
-describe("groupByLastNPathParts", () => {
-  it("should group filepaths by their last N parts", () => {
-    const filepaths = [
-      "/a/b/c/d/file1.txt",
-      "/x/y/z/file1.txt",
-      "/a/b/c/d/file2.txt",
-    ];
-    const output = groupByLastNPathParts(filepaths, 2);
-    expect(output).toEqual({
-      "d/file1.txt": ["/a/b/c/d/file1.txt"],
-      "z/file1.txt": ["/x/y/z/file1.txt"],
-      "d/file2.txt": ["/a/b/c/d/file2.txt"],
-    });
-  });
-
-  it("should handle an empty array", () => {
-    const filepaths: string[] = [];
-    const output = groupByLastNPathParts(filepaths, 2);
-    expect(output).toEqual({});
-  });
-
-  it("should handle N greater than path parts", () => {
-    const filepaths = ["/file.txt"];
-    const output = groupByLastNPathParts(filepaths, 5);
-    expect(output).toEqual({ "/file.txt": ["/file.txt"] });
-  });
-});
-
-describe("getUniqueFilePath", () => {
-  it("should return a unique file path within the group", () => {
-    const item = "/a/b/c/file.txt";
-    const itemGroups = {
-      "c/file.txt": ["/a/b/c/file.txt", "/x/y/c/file.txt"],
-    };
-    const output = getUniqueFilePath(item, itemGroups);
-    expect(output).toBe("b/c/file.txt");
-  });
-
-  it("should return the last two parts if unique", () => {
-    const item = "/a/b/c/file.txt";
-    const itemGroups = {
-      "c/file.txt": ["/a/b/c/file.txt"],
-    };
-    const output = getUniqueFilePath(item, itemGroups);
-    expect(output).toBe("c/file.txt");
-  });
-
-  it("should handle when additional parts are needed to make it unique", () => {
-    const item = "/a/b/c/d/e/file.txt";
-    const itemGroups = {
-      "e/file.txt": ["/a/b/c/d/e/file.txt", "/x/y/z/e/file.txt"],
-    };
-    const output = getUniqueFilePath(item, itemGroups);
-    expect(output).toBe("d/e/file.txt");
-  });
-});
-
-describe("shortestRelativePaths", () => {
-  it("should return shortest unique paths", () => {
-    const paths = [
-      "/a/b/c/file.txt",
-      "/a/b/d/file.txt",
-      "/a/b/d/file2.txt",
-      "/x/y/z/file.txt",
-    ];
-    const output = shortestRelativePaths(paths);
-    expect(output).toEqual([
-      "c/file.txt",
-      "d/file.txt",
-      "file2.txt",
-      "z/file.txt",
-    ]);
-  });
-
-  it("should handle empty array", () => {
-    const paths: string[] = [];
-    const output = shortestRelativePaths(paths);
-    expect(output).toEqual([]);
-  });
-
-  it("should handle paths with same base names", () => {
-    const paths = [
-      "/a/b/c/d/file.txt",
-      "/a/b/c/e/file.txt",
-      "/a/b/f/g/h/file.txt",
-    ];
-    const output = shortestRelativePaths(paths);
-    expect(output).toEqual(["d/file.txt", "e/file.txt", "h/file.txt"]);
-  });
-
-  it("should handle paths where entire path is needed", () => {
-    const paths = ["/a/b/c/file.txt", "/a/b/c/file.txt", "/a/b/c/file.txt"];
-    const output = shortestRelativePaths(paths);
-    expect(output).toEqual(["file.txt", "file.txt", "file.txt"]);
-  });
-});
-
-describe("splitPath", () => {
-  it("should split Unix-style paths", () => {
-    const path = "/a/b/c/d/e.txt";
-    const output = splitPath(path);
-    expect(output).toEqual(["", "a", "b", "c", "d", "e.txt"]);
-  });
-
-  it("should split Windows-style paths", () => {
-    const path = "C:\\Users\\User\\Documents\\file.txt";
-    const output = splitPath(path);
-    expect(output).toEqual(["C:", "Users", "User", "Documents", "file.txt"]);
-  });
-
-  it("should handle withRoot parameter", () => {
-    const path = "/a/b/c/d/e.txt";
-    const withRoot = "/a/b";
-    const output = splitPath(path, withRoot);
-    expect(output).toEqual(["b", "c", "d", "e.txt"]);
-  });
-
-  it("should handle empty path", () => {
-    const path = "";
-    const output = splitPath(path);
-    expect(output).toEqual([""]);
-  });
-
-  it("should handle paths with multiple consecutive separators", () => {
-    const path = "/a//b/c/d/e.txt";
-    const output = splitPath(path);
-    expect(output).toEqual(["", "a", "", "b", "c", "d", "e.txt"]);
-  });
-});
-
-describe("getRelativePath", () => {
-  it("should return the relative path with respect to workspace directories", () => {
-    const filepath = "/workspace/project/src/file.ts";
-    const workspaceDirs = ["/workspace/project"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("src/file.ts");
-  });
-
-  it("should return the filename if not in any workspace", () => {
-    const filepath = "/other/place/file.ts";
-    const workspaceDirs = ["/workspace/project"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("file.ts");
-  });
-
-  it("should handle multiple workspace directories", () => {
-    const filepath = "/workspace2/project/src/file.ts";
-    const workspaceDirs = ["/workspace/project", "/workspace2/project"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("src/file.ts");
-  });
-
-  it("should handle Windows-style paths", () => {
-    const filepath = "C:\\workspace\\project\\src\\file.ts";
-    const workspaceDirs = ["C:\\workspace\\project"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("src/file.ts");
-  });
-
-  it("should handle paths with spaces or special characters", () => {
-    const filepath = "/workspace/project folder/src/file.ts";
-    const workspaceDirs = ["/workspace/project folder"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("src/file.ts");
-  });
-});
-
 describe("getMarkdownLanguageTagForFile", () => {
   it("should return correct language tag for known extensions", () => {
     expect(getMarkdownLanguageTagForFile("test.py")).toBe("python");
diff --git a/core/util/pathModule.ts b/core/util/pathModule.ts
deleted file mode 100644
index 8b06ab56c4..0000000000
--- a/core/util/pathModule.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { PlatformPath, posix, win32 } from "node:path";
-import { IDE } from "..";
-
-export type PathSep = "/" | "\\";
-
-function getPathModuleFromPathSep(pathSep: PathSep): PlatformPath {
-  return pathSep === "/" ? posix : win32;
-}
-
-export async function getPathModuleForIde(ide: IDE): Promise {
-  const pathSep = await ide.pathSep();
-  return getPathModuleFromPathSep(pathSep as PathSep);
-}
diff --git a/core/util/treeSitter.ts b/core/util/treeSitter.ts
index 2a67b91028..cfae9f7e1a 100644
--- a/core/util/treeSitter.ts
+++ b/core/util/treeSitter.ts
@@ -1,8 +1,8 @@
-import fs from "node:fs";
-import * as path from "node:path";
+// import fs from "node:fs";
 
 import Parser, { Language } from "web-tree-sitter";
 import { FileSymbolMap, IDE, SymbolWithRange } from "..";
+import { getUriFileExtension } from "./uri";
 
 export enum LanguageName {
   CPP = "cpp",
@@ -145,7 +145,7 @@ export async function getLanguageForFile(
 ): Promise {
   try {
     await Parser.init();
-    const extension = path.extname(filepath).slice(1);
+    const extension = getUriFileExtension(filepath);
 
     const languageName = supportedLanguages[extension];
     if (!languageName) {
@@ -165,7 +165,8 @@ export async function getLanguageForFile(
 }
 
 export const getFullLanguageName = (filepath: string) => {
-  return supportedLanguages[filepath.split(".").pop() ?? ""];
+  const extension = getUriFileExtension(filepath);
+  return supportedLanguages[extension];
 };
 
 export async function getQueryForFile(
diff --git a/core/util/uri.test.ts b/core/util/uri.test.ts
new file mode 100644
index 0000000000..f4f7409a43
--- /dev/null
+++ b/core/util/uri.test.ts
@@ -0,0 +1,233 @@
+import {
+  getLastNUriRelativePathParts,
+  getRelativePath,
+  getUniqueUriPath,
+  getUriPathBasename,
+  groupByLastNPathParts,
+  splitUriPath,
+} from "./uri";
+
+describe("getUriPathBasename", () => {
+  it("should return the base name of a Unix-style path", () => {
+    const filepath = "/home/user/documents/file.txt";
+    const output = getUriPathBasename(filepath);
+    expect(output).toBe("file.txt");
+  });
+
+  it("should return the base name of a Windows-style path", () => {
+    const filepath = "C:\\Users\\User\\Documents\\file.txt";
+    const output = getUriPathBasename(filepath);
+    expect(output).toBe("file.txt");
+  });
+
+  it("should handle paths with mixed separators", () => {
+    const filepath = "C:/Users\\User/Documents/file.txt";
+    const output = getUriPathBasename(filepath);
+    expect(output).toBe("file.txt");
+  });
+
+  it("should return an empty string for empty input", () => {
+    const filepath = "";
+    const output = getUriPathBasename(filepath);
+    expect(output).toBe("");
+  });
+});
+
+describe("groupByLastNPathParts", () => {
+  it("should group filepaths by their last N parts", () => {
+    const filepaths = [
+      "/a/b/c/d/file1.txt",
+      "/x/y/z/file1.txt",
+      "/a/b/c/d/file2.txt",
+    ];
+    const output = groupByLastNPathParts(filepaths, 2);
+    expect(output).toEqual({
+      "d/file1.txt": ["/a/b/c/d/file1.txt"],
+      "z/file1.txt": ["/x/y/z/file1.txt"],
+      "d/file2.txt": ["/a/b/c/d/file2.txt"],
+    });
+  });
+
+  it("should handle an empty array", () => {
+    const filepaths: string[] = [];
+    const output = groupByLastNPathParts(filepaths, 2);
+    expect(output).toEqual({});
+  });
+
+  it("should handle N greater than path parts", () => {
+    const filepaths = ["/file.txt"];
+    const output = groupByLastNPathParts(filepaths, 5);
+    expect(output).toEqual({ "/file.txt": ["/file.txt"] });
+  });
+});
+
+describe("getUniqueUriPath", () => {
+  it("should return a unique file path within the group", () => {
+    const item = "/a/b/c/file.txt";
+    const itemGroups = {
+      "c/file.txt": ["/a/b/c/file.txt", "/x/y/c/file.txt"],
+    };
+    const output = getUniqueUriPath(item, itemGroups);
+    expect(output).toBe("b/c/file.txt");
+  });
+
+  it("should return the last two parts if unique", () => {
+    const item = "/a/b/c/file.txt";
+    const itemGroups = {
+      "c/file.txt": ["/a/b/c/file.txt"],
+    };
+    const output = getUniqueUriPath(item, itemGroups);
+    expect(output).toBe("c/file.txt");
+  });
+
+  it("should handle when additional parts are needed to make it unique", () => {
+    const item = "/a/b/c/d/e/file.txt";
+    const itemGroups = {
+      "e/file.txt": ["/a/b/c/d/e/file.txt", "/x/y/z/e/file.txt"],
+    };
+    const output = getUniqueUriPath(item, itemGroups);
+    expect(output).toBe("d/e/file.txt");
+  });
+});
+
+describe("splitUriPath", () => {
+  it("should split Unix-style paths", () => {
+    const path = "/a/b/c/d/e.txt";
+    const output = splitUriPath(path);
+    expect(output).toEqual(["", "a", "b", "c", "d", "e.txt"]);
+  });
+
+  it("should split Windows-style paths", () => {
+    const path = "C:\\Users\\User\\Documents\\file.txt";
+    const output = splitUriPath(path);
+    expect(output).toEqual(["C:", "Users", "User", "Documents", "file.txt"]);
+  });
+
+  it("should handle empty path", () => {
+    const path = "";
+    const output = splitUriPath(path);
+    expect(output).toEqual([""]);
+  });
+
+  it("should handle paths with multiple consecutive separators", () => {
+    const path = "/a//b/c/d/e.txt";
+    const output = splitUriPath(path);
+    expect(output).toEqual(["", "a", "", "b", "c", "d", "e.txt"]);
+  });
+});
+
+describe("getRelativePath", () => {
+  it("should return the relative path with respect to workspace directories", () => {
+    const filepath = "/workspace/project/src/file.ts";
+    const workspaceDirs = ["/workspace/project"];
+    const output = getRelativePath(filepath, workspaceDirs);
+    expect(output).toBe("src/file.ts");
+  });
+
+  it("should return the filename if not in any workspace", () => {
+    const filepath = "/other/place/file.ts";
+    const workspaceDirs = ["/workspace/project"];
+    const output = getRelativePath(filepath, workspaceDirs);
+    expect(output).toBe("file.ts");
+  });
+
+  it("should handle multiple workspace directories", () => {
+    const filepath = "/workspace2/project/src/file.ts";
+    const workspaceDirs = ["/workspace/project", "/workspace2/project"];
+    const output = getRelativePath(filepath, workspaceDirs);
+    expect(output).toBe("src/file.ts");
+  });
+
+  it("should handle Windows-style paths", () => {
+    const filepath = "C:\\workspace\\project\\src\\file.ts";
+    const workspaceDirs = ["C:\\workspace\\project"];
+    const output = getRelativePath(filepath, workspaceDirs);
+    expect(output).toBe("src/file.ts");
+  });
+
+  it("should handle paths with spaces or special characters", () => {
+    const filepath = "/workspace/project folder/src/file.ts";
+    const workspaceDirs = ["/workspace/project folder"];
+    const output = getRelativePath(filepath, workspaceDirs);
+    expect(output).toBe("src/file.ts");
+  });
+});
+
+describe("getLastNPathParts", () => {
+  test("returns the last N parts of a filepath with forward slashes", () => {
+    const filepath = "home/user/documents/project/file.txt";
+    expect(getLastNUriRelativePathParts(filepath, 2)).toBe("project/file.txt");
+  });
+
+  test("returns the last N parts of a filepath with backward slashes", () => {
+    const filepath = "C:\\home\\user\\documents\\project\\file.txt";
+    expect(getLastNPathParts(filepath, 3)).toBe("documents/project/file.txt");
+  });
+
+  test("returns the last part if N is 1", () => {
+    const filepath = "/home/user/documents/project/file.txt";
+    expect(getLastNPathParts(filepath, 1)).toBe("file.txt");
+  });
+
+  test("returns the entire path if N is greater than the number of parts", () => {
+    const filepath = "home/user/documents/project/file.txt";
+    expect(getLastNPathParts(filepath, 10)).toBe(
+      "home/user/documents/project/file.txt",
+    );
+  });
+
+  test("returns an empty string if N is 0", () => {
+    const filepath = "home/user/documents/project/file.txt";
+    expect(getLastNPathParts(filepath, 0)).toBe("");
+  });
+
+  test("handles paths with mixed forward and backward slashes", () => {
+    const filepath = "home\\user/documents\\project/file.txt";
+    expect(getLastNPathParts(filepath, 3)).toBe("documents/project/file.txt");
+  });
+
+  test("handles edge case with empty filepath", () => {
+    const filepath = "";
+    expect(getLastNPathParts(filepath, 2)).toBe("");
+  });
+});
+
+describe("shortestRelativePaths", () => {
+  it("should return shortest unique paths", () => {
+    const paths = [
+      "/a/b/c/file.txt",
+      "/a/b/d/file.txt",
+      "/a/b/d/file2.txt",
+      "/x/y/z/file.txt",
+    ];
+    const output = shortestRelativePaths(paths);
+    expect(output).toEqual([
+      "c/file.txt",
+      "d/file.txt",
+      "file2.txt",
+      "z/file.txt",
+    ]);
+  });
+
+  it("should handle empty array", () => {
+    const paths: string[] = [];
+    const output = shortestRelativePaths(paths);
+    expect(output).toEqual([]);
+  });
+
+  it("should handle paths with same base names", () => {
+    const paths = [
+      "/a/b/c/d/file.txt",
+      "/a/b/c/e/file.txt",
+      "/a/b/f/g/h/file.txt",
+    ];
+    const output = shortestRelativePaths(paths);
+    expect(output).toEqual(["d/file.txt", "e/file.txt", "h/file.txt"]);
+  });
+
+  it("should handle paths where entire path is needed", () => {
+    const paths = ["/a/b/c/file.txt", "/a/b/c/file.txt", "/a/b/c/file.txt"];
+    const output = shortestRelativePaths(paths);
+    expect(output).toEqual(["file.txt", "file.txt", "file.txt"]);
+  });
+});
diff --git a/core/util/uri.ts b/core/util/uri.ts
index 0e3c6225a4..1a00115829 100644
--- a/core/util/uri.ts
+++ b/core/util/uri.ts
@@ -9,6 +9,17 @@ export function getFullPath(uri: string): string {
   }
 }
 
+export function pathToUriPathSegment(path: string) {
+  // Converts any OS path to cleaned up URI path segment format with no leading/trailing slashes
+  // e.g. \path\to\folder\ -> path/to/folder
+  //      \this\is\afile.ts -> this/is/afile.ts
+  //      is/already/clean -> is/already/clean
+  let clean = path.replace(/[\\]/g, "/"); // backslashes -> forward slashes
+  clean = clean.replace(/^\//, ""); // remove start slash
+  clean = clean.replace(/\/$/, ""); // remove end slash
+  return clean;
+}
+
 // Whenever working with partial URIs
 // Should always first get the path relative to the workspaces
 // If a matching workspace is not found, ONLY the file name should be used
@@ -41,8 +52,11 @@ export function getUriPathBasename(uri: string): string {
   return getPath();
 }
 
-export function join(uri: string, pathSegment: string) {
-  return uri.replace(/\/*$/, "") + "/" + segment.replace(/^\/*/, "");
+export function joinPathsToUri(uri: string, ...pathSegments: string[]) {
+  const components = URI.parse(uri);
+  const segments = pathSegments.map((segment) => pathToUriPathSegment(segment));
+  components.path = `${components.path}/${segments.join("/")}`;
+  return URI.serialize(components);
 }
 
 export function groupByLastNPathParts(
@@ -59,7 +73,10 @@ export function getUniqueUriPath(
   return "hi";
 }
 
-export function shortestRelativeUriPaths(uris: string[]): string[] {
+export function shortestRelativeUriPaths(
+  uris: string[],
+  directory: string,
+): string[] {
   if (uris.length === 0) {
     return [];
   }
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 264822b6c2..de0c061b2d 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
@@ -46,7 +46,6 @@ class MessageTypes {
             "applyToFile",
             "getGitHubAuthToken",
             "setGitHubAuthToken",
-            "pathSep",
             "getControlPlaneSessionInfo",
             "logoutOfControlPlane",
             "getTerminalContents",
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 d0f0da7618..90ef1a850a 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
@@ -391,7 +391,6 @@ class IdeProtocolClient(
                         respond(null)
                     }
 
-                    "setSuggestionsLocked" -> {}
                     "getSessionId" -> {}
 
                     "getLastModified" -> {
@@ -766,10 +765,6 @@ class IdeProtocolClient(
                         respond(null)
                     }
 
-                    "pathSep" -> {
-                        respond(File.separator)
-                    }
-
                     else -> {}
                 }
             } catch (error: Exception) {
diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts
index 5138ba490f..81eebda763 100644
--- a/extensions/vscode/src/VsCodeIde.ts
+++ b/extensions/vscode/src/VsCodeIde.ts
@@ -17,12 +17,9 @@ import { executeGotoProvider } from "./autocomplete/lsp";
 import { DiffManager } from "./diff/horizontal";
 import { Repository } from "./otherExtensions/git";
 import { VsCodeIdeUtils } from "./util/ideUtils";
-import {
-  getExtensionUri,
-  openEditorAndRevealRange,
-  uriFromFilePath,
-} from "./util/vscode";
+import { getExtensionUri, openEditorAndRevealRange } from "./util/vscode";
 import { VsCodeWebviewProtocol } from "./webviewProtocol";
+import URI from "uri-js";
 
 import type {
   ContinueRcJson,
@@ -48,12 +45,8 @@ class VsCodeIde implements IDE {
     this.ideUtils = new VsCodeIdeUtils();
   }
 
-  pathSep(): Promise {
-    return Promise.resolve(this.ideUtils.path.sep);
-  }
-  async fileExists(filepath: string): Promise {
-    const absPath = await this.ideUtils.resolveAbsFilepathInWorkspace(filepath);
-    return vscode.workspace.fs.stat(uriFromFilePath(absPath)).then(
+  async fileExists(uri: string): Promise {
+    return vscode.workspace.fs.stat(vscode.Uri.parse(uri)).then(
       () => true,
       () => false,
     );
@@ -437,7 +430,7 @@ class VsCodeIde implements IDE {
 
   private static MAX_BYTES = 100000;
 
-  async readFile(filepath: string): Promise {
+  async readFile(fileUri: string): Promise {
     try {
       filepath = this.ideUtils.getAbsolutePath(filepath);
       const uri = uriFromFilePath(filepath);
@@ -445,8 +438,8 @@ class VsCodeIde implements IDE {
       // First, check whether it's a notebook document
       // Need to iterate over the cells to get full contents
       const notebook =
-        vscode.workspace.notebookDocuments.find(
-          (doc) => doc.uri.toString() === uri.toString(),
+        vscode.workspace.notebookDocuments.find((doc) =>
+          URI.equal(doc.uri.toString(), uri.toString()),
         ) ??
         (uri.fsPath.endsWith("ipynb")
           ? await vscode.workspace.openNotebookDocument(uri)
@@ -507,7 +500,7 @@ class VsCodeIde implements IDE {
     }
     return {
       isUntitled: vscode.window.activeTextEditor.document.isUntitled,
-      path: vscode.window.activeTextEditor.document.uri.fsPath,
+      path: vscode.window.activeTextEditor.document.uri.toString(),
       contents: vscode.window.activeTextEditor.document.getText(),
     };
   }
diff --git a/extensions/vscode/src/suggestions.ts b/extensions/vscode/src/suggestions.ts
index f4acb9be75..c97906b75b 100644
--- a/extensions/vscode/src/suggestions.ts
+++ b/extensions/vscode/src/suggestions.ts
@@ -1,6 +1,7 @@
 import * as vscode from "vscode";
 
 import { openEditorAndRevealRange, translate } from "./util/vscode";
+import URI from "uri-js"
 
 export interface SuggestionRanges {
   oldRange: vscode.Range;
@@ -58,7 +59,7 @@ export function rerenderDecorations(editorUri: string) {
   const suggestions = editorToSuggestions.get(editorUri);
   const idx = currentSuggestion.get(editorUri);
   const editor = vscode.window.visibleTextEditors.find(
-    (editor) => editor.document.uri.toString() === editorUri,
+    (editor) => URI.equal(editor.document.uri.toString(), editorUri)
   );
   if (!suggestions || !editor) {
     return;
@@ -299,7 +300,7 @@ export async function rejectSuggestionCommand(
 }
 
 export async function showSuggestion(
-  editorFilename: string,
+  editorUri: string,
   range: vscode.Range,
   suggestion: string,
 ): Promise {
@@ -312,7 +313,7 @@ export async function showSuggestion(
     return Promise.resolve(false);
   }
 
-  const editor = await openEditorAndRevealRange(editorFilename, range);
+  const editor = await openEditorAndRevealRange(editorUri, range);
   if (!editor) {
     return Promise.resolve(false);
   }
@@ -339,19 +340,19 @@ export async function showSuggestion(
             );
             const content = editor!.document.getText(suggestionRange);
 
-            const filename = editor!.document.uri.toString();
-            if (editorToSuggestions.has(filename)) {
-              const suggestions = editorToSuggestions.get(filename)!;
+            const uriString = editor!.document.uri.toString();
+            if (editorToSuggestions.has(uriString)) {
+              const suggestions = editorToSuggestions.get(uriString)!;
               suggestions.push({
                 oldRange: range,
                 newRange: suggestionRange,
                 newSelected: true,
                 newContent: content,
               });
-              editorToSuggestions.set(filename, suggestions);
-              currentSuggestion.set(filename, suggestions.length - 1);
+              editorToSuggestions.set(uriString, suggestions);
+              currentSuggestion.set(uriString, suggestions.length - 1);
             } else {
-              editorToSuggestions.set(filename, [
+              editorToSuggestions.set(uriString, [
                 {
                   oldRange: range,
                   newRange: suggestionRange,
@@ -359,10 +360,10 @@ export async function showSuggestion(
                   newContent: content,
                 },
               ]);
-              currentSuggestion.set(filename, 0);
+              currentSuggestion.set(uriString, 0);
             }
 
-            rerenderDecorations(filename);
+            rerenderDecorations(uriString);
           }
           resolve(success);
         },
diff --git a/extensions/vscode/src/util/ideUtils.ts b/extensions/vscode/src/util/ideUtils.ts
index 1869dc64ff..502856a333 100644
--- a/extensions/vscode/src/util/ideUtils.ts
+++ b/extensions/vscode/src/util/ideUtils.ts
@@ -1,9 +1,7 @@
-import path from "node:path";
-
 import { EXTENSION_NAME } from "core/control-plane/env";
 import _ from "lodash";
 import * as vscode from "vscode";
-
+import URI from "uri-js";
 import { threadStopped } from "../debug/debug";
 import { VsCodeExtension } from "../extension/VsCodeExtension";
 import { GitExtension, Repository } from "../otherExtensions/git";
@@ -26,48 +24,47 @@ export class VsCodeIdeUtils {
   visibleMessages: Set = new Set();
 
   async gotoDefinition(
-    uri: string,
+    uri: vscode.Uri,
     position: vscode.Position,
   ): Promise {
     const locations: vscode.Location[] = await vscode.commands.executeCommand(
       "vscode.executeDefinitionProvider",
-      uriFromFilePath(filepath),
+      uri,
       position,
     );
     return locations;
   }
 
-  async documentSymbol(filepath: string): Promise {
+  async documentSymbol(uri: vscode.Uri): Promise {
     return await vscode.commands.executeCommand(
       "vscode.executeDocumentSymbolProvider",
-      uriFromFilePath(filepath),
+      uri,
     );
   }
 
   async references(
-    filepath: string,
+    uri: vscode.Uri,
     position: vscode.Position,
   ): Promise {
     return await vscode.commands.executeCommand(
       "vscode.executeReferenceProvider",
-      uriFromFilePath(filepath),
+      uri,
       position,
     );
   }
 
-  async foldingRanges(filepath: string): Promise {
+  async foldingRanges(uri: vscode.Uri): Promise {
     return await vscode.commands.executeCommand(
       "vscode.executeFoldingRangeProvider",
-      uriFromFilePath(filepath),
+      uri,
     );
   }
 
-  private _workspaceDirectories: string[] | undefined = undefined;
-  getWorkspaceDirectories(): string[] {
+  private _workspaceDirectories: vscode.Uri[] | undefined = undefined;
+  getWorkspaceDirectories(): vscode.Uri[] {
     if (this._workspaceDirectories === undefined) {
       this._workspaceDirectories =
-        vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath) ||
-        [];
+        vscode.workspace.workspaceFolders?.map((folder) => folder.uri) || [];
     }
 
     return this._workspaceDirectories;
@@ -135,37 +132,19 @@ export class VsCodeIdeUtils {
     );
   }
 
-  async resolveAbsFilepathInWorkspace(filepath: string): Promise {
-    // If the filepath is already absolute, return it as is
-    if (this.path.isAbsolute(filepath)) {
-      return filepath;
-    }
-
-    // Try to resolve for each workspace directory
-    const workspaceDirectories = this.getWorkspaceDirectories();
-    for (const dir of workspaceDirectories) {
-      const resolvedPath = this.path.resolve(dir, filepath);
-      if (await this.fileExists(resolvedPath)) {
-        return resolvedPath;
-      }
-    }
-
-    return filepath;
-  }
-
-  async openFile(filepath: string, range?: vscode.Range) {
+  async openFile(uri: vscode.Uri, range?: vscode.Range) {
     // vscode has a builtin open/get open files
     return await openEditorAndRevealRange(
-      await this.resolveAbsFilepathInWorkspace(filepath),
+      uri,
       range,
       vscode.ViewColumn.One,
       false,
     );
   }
 
-  async fileExists(filepath: string): Promise {
+  async fileExists(uri: vscode.Uri): Promise {
     try {
-      await vscode.workspace.fs.stat(uriFromFilePath(filepath));
+      await vscode.workspace.fs.stat(uri);
       return true;
     } catch {
       return false;
@@ -186,11 +165,6 @@ export class VsCodeIdeUtils {
       });
   }
 
-  setSuggestionsLocked(filepath: string, locked: boolean) {
-    editorSuggestionsLocked.set(filepath, locked);
-    // TODO: Rerender?
-  }
-
   async getUserSecret(key: string) {
     // Check if secret already exists in VS Code settings (global)
     let secret = vscode.workspace.getConfiguration(EXTENSION_NAME).get(key);
@@ -243,7 +217,7 @@ export class VsCodeIdeUtils {
       .flat()
       .filter(Boolean) // filter out undefined values
       .filter((uri) => this.documentIsCode(uri)) // Filter out undesired documents
-      .map((uri) => uri.fsPath);
+      .map((uri) => uri.toString());
   }
 
   getVisibleFiles(): string[] {
@@ -254,11 +228,11 @@ export class VsCodeIdeUtils {
       });
   }
 
-  saveFile(filepath: string) {
+  saveFile(uri: vscode.Uri) {
     vscode.window.visibleTextEditors
       .filter((editor) => this.documentIsCode(editor.document.uri))
       .forEach((editor) => {
-        if (editor.document.uri.fsPath === filepath) {
+        if (URI.equal(editor.document.uri, uri.toString())) {
           editor.document.save();
         }
       });
diff --git a/extensions/vscode/src/util/vscode.ts b/extensions/vscode/src/util/vscode.ts
index 18f084ae22..29aedb6358 100644
--- a/extensions/vscode/src/util/vscode.ts
+++ b/extensions/vscode/src/util/vscode.ts
@@ -1,7 +1,6 @@
-import * as path from "node:path";
-
 import { machineIdSync } from "node-machine-id";
 import * as vscode from "vscode";
+import URI from "uri-js";
 
 export function translate(range: vscode.Range, lines: number): vscode.Range {
   return new vscode.Range(
@@ -27,13 +26,13 @@ export function getExtensionUri(): vscode.Uri {
 }
 
 export function getViewColumnOfFile(
-  filepath: string,
+  uri: vscode.Uri,
 ): vscode.ViewColumn | undefined {
   for (const tabGroup of vscode.window.tabGroups.all) {
     for (const tab of tabGroup.tabs) {
       if (
         (tab?.input as any)?.uri &&
-        (tab.input as any).uri.fsPath === filepath
+        URI.equal((tab.input as any).uri, uri.toString())
       ) {
         return tabGroup.viewColumn;
       }
@@ -71,20 +70,13 @@ export function getRightViewColumn(): vscode.ViewColumn {
 let showTextDocumentInProcess = false;
 
 export function openEditorAndRevealRange(
-  editorFilename: string,
+  uri: vscode.Uri,
   range?: vscode.Range,
   viewColumn?: vscode.ViewColumn,
   preview?: boolean,
 ): Promise {
   return new Promise((resolve, _) => {
-    let filename = editorFilename;
-    if (editorFilename.startsWith("~")) {
-      filename = path.join(
-        process.env.HOME || process.env.USERPROFILE || "",
-        editorFilename.slice(1),
-      );
-    }
-    vscode.workspace.openTextDocument(filename).then(async (doc) => {
+    vscode.workspace.openTextDocument(uri).then(async (doc) => {
       try {
         // An error is thrown mysteriously if you open two documents in parallel, hence this
         while (showTextDocumentInProcess) {
@@ -97,7 +89,7 @@ export function openEditorAndRevealRange(
         showTextDocumentInProcess = true;
         vscode.window
           .showTextDocument(doc, {
-            viewColumn: getViewColumnOfFile(editorFilename) || viewColumn,
+            viewColumn: getViewColumnOfFile(uri) || viewColumn,
             preview,
           })
           .then((editor) => {
@@ -114,33 +106,6 @@ export function openEditorAndRevealRange(
   });
 }
 
-function windowsToPosix(windowsPath: string): string {
-  let posixPath = windowsPath.split("\\").join("/");
-  if (posixPath[1] === ":") {
-    posixPath = posixPath.slice(2);
-  }
-  // posixPath = posixPath.replace(" ", "\\ ");
-  return posixPath;
-}
-
-function isWindowsLocalButNotRemote(): boolean {
-  return (
-    vscode.env.remoteName !== undefined &&
-    [
-      "wsl",
-      "ssh-remote",
-      "dev-container",
-      "attached-container",
-      "tunnel",
-    ].includes(vscode.env.remoteName) &&
-    process.platform === "win32"
-  );
-}
-
-export function getPathSep(): string {
-  return isWindowsLocalButNotRemote() ? "/" : path.sep;
-}
-
 export function getUniqueId() {
   const id = vscode.env.machineId;
   if (id === "someValue.machineId") {

From 84fda7740c868d78eb84cb2a44d2d05e56d7de9a Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Tue, 10 Dec 2024 18:18:20 -0800
Subject: [PATCH 07/84] path->uri continued

---
 core/promptFiles/v1/getPromptFiles.ts | 25 -------------------------
 core/promptFiles/v1/index.ts          |  3 +--
 2 files changed, 1 insertion(+), 27 deletions(-)
 delete mode 100644 core/promptFiles/v1/getPromptFiles.ts

diff --git a/core/promptFiles/v1/getPromptFiles.ts b/core/promptFiles/v1/getPromptFiles.ts
deleted file mode 100644
index b9bb5d8234..0000000000
--- a/core/promptFiles/v1/getPromptFiles.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { IDE } from "../..";
-import { walkDir } from "../../indexing/walkDir";
-
-export async function getPromptFiles(
-  ide: IDE,
-  dir: string,
-): Promise<{ path: string; content: string }[]> {
-  try {
-    const exists = await ide.fileExists(dir);
-
-    if (!exists) {
-      return [];
-    }
-
-    const paths = await walkDir(dir, ide, { ignoreFiles: [] });
-    const results = paths.map(async (path) => {
-      const content = await ide.readFile(path); // make a try catch
-      return { path, content };
-    });
-    return Promise.all(results);
-  } catch (e) {
-    console.error(e);
-    return [];
-  }
-}
diff --git a/core/promptFiles/v1/index.ts b/core/promptFiles/v1/index.ts
index e14c2103c9..940403e38a 100644
--- a/core/promptFiles/v1/index.ts
+++ b/core/promptFiles/v1/index.ts
@@ -1,7 +1,6 @@
 import { createNewPromptFile } from "./createNewPromptFile";
-import { getPromptFilesV1 } from "./getPromptFiles";
 import { slashCommandFromPromptFile } from "./slashCommandFromPromptFile";
 
 export const DEFAULT_PROMPTS_FOLDER = ".prompts";
 
-export { createNewPromptFile, getPromptFilesV1, slashCommandFromPromptFile };
+export { createNewPromptFile, slashCommandFromPromptFile };

From 431345643e58f9c3a98787ee9fdc3abc19f28197 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Tue, 10 Dec 2024 19:02:49 -0800
Subject: [PATCH 08/84] vscode utils continued

---
 core/index.d.ts                           |   2 -
 extensions/vscode/src/VsCodeIde.ts        |  60 ++++++-------
 extensions/vscode/src/autocomplete/lsp.ts |  20 ++---
 extensions/vscode/src/decorations.ts      |   2 -
 extensions/vscode/src/diff/horizontal.ts  |   1 -
 extensions/vscode/src/suggestions.ts      |  10 +--
 extensions/vscode/src/util/ideUtils.ts    | 100 ++++++++--------------
 7 files changed, 78 insertions(+), 117 deletions(-)

diff --git a/core/index.d.ts b/core/index.d.ts
index 08e7a1fc5e..9cd6973211 100644
--- a/core/index.d.ts
+++ b/core/index.d.ts
@@ -687,8 +687,6 @@ export interface IDE {
 
   // Callbacks
   onDidChangeActiveTextEditor(callback: (filepath: string) => void): void;
-
-  pathSep(): Promise;
 }
 
 // Slash Commands
diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts
index 81eebda763..1d3e5e9718 100644
--- a/extensions/vscode/src/VsCodeIde.ts
+++ b/extensions/vscode/src/VsCodeIde.ts
@@ -54,7 +54,7 @@ class VsCodeIde implements IDE {
 
   async gotoDefinition(location: Location): Promise {
     const result = await executeGotoProvider({
-      uri: location.filepath,
+      uri: vscode.Uri.parse(location.filepath),
       line: location.position.line,
       character: location.position.character,
       name: "vscode.executeDefinitionProvider",
@@ -63,10 +63,10 @@ class VsCodeIde implements IDE {
     return result;
   }
 
-  onDidChangeActiveTextEditor(callback: (filepath: string) => void): void {
+  onDidChangeActiveTextEditor(callback: (uri: string) => void): void {
     vscode.window.onDidChangeActiveTextEditor((editor) => {
       if (editor) {
-        callback(editor.document.uri.fsPath);
+        callback(editor.document.uri.toString());
       }
     });
   }
@@ -220,7 +220,7 @@ class VsCodeIde implements IDE {
   };
 
   async getRepoName(dir: string): Promise {
-    const repo = await this.getRepo(vscode.Uri.file(dir));
+    const repo = await this.getRepo(dir);
     const remotes = repo?.state.remotes;
     if (!remotes) {
       return undefined;
@@ -267,7 +267,7 @@ class VsCodeIde implements IDE {
 
   readRangeInFile(filepath: string, range: Range): Promise {
     return this.ideUtils.readRangeInFile(
-      filepath,
+      vscode.Uri.parse(filepath),
       new vscode.Range(
         new vscode.Position(range.start.line, range.start.character),
         new vscode.Position(range.end.line, range.end.character),
@@ -279,7 +279,7 @@ class VsCodeIde implements IDE {
     const pathToLastModified: { [path: string]: number } = {};
     await Promise.all(
       files.map(async (file) => {
-        const stat = await vscode.workspace.fs.stat(uriFromFilePath(file));
+        const stat = await vscode.workspace.fs.stat(vscode.Uri.parse(file));
         pathToLastModified[file] = stat.mtime;
       }),
     );
@@ -287,8 +287,8 @@ class VsCodeIde implements IDE {
     return pathToLastModified;
   }
 
-  async getRepo(dir: vscode.Uri): Promise {
-    return this.ideUtils.getRepo(dir);
+  async getRepo(dir: string): Promise {
+    return this.ideUtils.getRepo(vscode.Uri.parse(dir));
   }
 
   async isTelemetryEnabled(): Promise {
@@ -371,7 +371,7 @@ class VsCodeIde implements IDE {
   }
 
   async getWorkspaceDirs(): Promise {
-    return this.ideUtils.getWorkspaceDirectories();
+    return this.ideUtils.getWorkspaceDirectories().map((uri) => uri.toString());
   }
 
   async getContinueDir(): Promise {
@@ -380,7 +380,7 @@ class VsCodeIde implements IDE {
 
   async writeFile(path: string, contents: string): Promise {
     await vscode.workspace.fs.writeFile(
-      vscode.Uri.file(path),
+      vscode.Uri.parse(path),
       Buffer.from(contents),
     );
   }
@@ -390,7 +390,7 @@ class VsCodeIde implements IDE {
   }
 
   async openFile(path: string): Promise {
-    await this.ideUtils.openFile(path);
+    await this.ideUtils.openFile(vscode.Uri.parse(path));
   }
 
   async showLines(
@@ -402,13 +402,15 @@ class VsCodeIde implements IDE {
       new vscode.Position(startLine, 0),
       new vscode.Position(endLine, 0),
     );
-    openEditorAndRevealRange(filepath, range).then((editor) => {
-      // Select the lines
-      editor.selection = new vscode.Selection(
-        new vscode.Position(startLine, 0),
-        new vscode.Position(endLine, 0),
-      );
-    });
+    openEditorAndRevealRange(vscode.Uri.parse(filepath), range).then(
+      (editor) => {
+        // Select the lines
+        editor.selection = new vscode.Selection(
+          new vscode.Position(startLine, 0),
+          new vscode.Position(endLine, 0),
+        );
+      },
+    );
   }
 
   async runCommand(command: string): Promise {
@@ -425,15 +427,14 @@ class VsCodeIde implements IDE {
   }
 
   async saveFile(filepath: string): Promise {
-    await this.ideUtils.saveFile(filepath);
+    await this.ideUtils.saveFile(vscode.Uri.parse(filepath));
   }
 
   private static MAX_BYTES = 100000;
 
   async readFile(fileUri: string): Promise {
     try {
-      filepath = this.ideUtils.getAbsolutePath(filepath);
-      const uri = uriFromFilePath(filepath);
+      const uri = vscode.Uri.parse(fileUri);
 
       // First, check whether it's a notebook document
       // Need to iterate over the cells to get full contents
@@ -452,16 +453,14 @@ class VsCodeIde implements IDE {
       }
 
       // Check whether it's an open document
-      const openTextDocument = vscode.workspace.textDocuments.find(
-        (doc) => doc.uri.fsPath === uri.fsPath,
+      const openTextDocument = vscode.workspace.textDocuments.find((doc) =>
+        URI.equal(doc.uri.toString(), uri.toString()),
       );
       if (openTextDocument !== undefined) {
         return openTextDocument.getText();
       }
 
-      const fileStats = await vscode.workspace.fs.stat(
-        uriFromFilePath(filepath),
-      );
+      const fileStats = await vscode.workspace.fs.stat(uri);
       if (fileStats.size > 10 * VsCodeIde.MAX_BYTES) {
         return "";
       }
@@ -491,7 +490,7 @@ class VsCodeIde implements IDE {
   }
 
   async getOpenFiles(): Promise {
-    return await this.ideUtils.getOpenFiles();
+    return this.ideUtils.getOpenFiles().map((uri) => uri.toString());
   }
 
   async getCurrentFile() {
@@ -599,11 +598,12 @@ class VsCodeIde implements IDE {
   }
 
   async getBranch(dir: string): Promise {
-    return this.ideUtils.getBranch(vscode.Uri.file(dir));
+    return this.ideUtils.getBranch(vscode.Uri.parse(dir));
   }
 
-  getGitRootPath(dir: string): Promise {
-    return this.ideUtils.getGitRoot(dir);
+  async getGitRootPath(dir: string): Promise {
+    const root = await this.ideUtils.getGitRoot(vscode.Uri.parse(dir));
+    return root?.toString();
   }
 
   async listDir(dir: string): Promise<[string, FileType][]> {
diff --git a/extensions/vscode/src/autocomplete/lsp.ts b/extensions/vscode/src/autocomplete/lsp.ts
index 08b16ace7c..a5b5267f50 100644
--- a/extensions/vscode/src/autocomplete/lsp.ts
+++ b/extensions/vscode/src/autocomplete/lsp.ts
@@ -9,14 +9,12 @@ import * as vscode from "vscode";
 
 import type { IDE, Range, RangeInFile, RangeInFileWithContents } from "core";
 import type Parser from "web-tree-sitter";
-import {
-  AutocompleteSnippetDeprecated,
-  GetLspDefinitionsFunction,
-} from "core/autocomplete/types";
+import { GetLspDefinitionsFunction } from "core/autocomplete/types";
 import {
   AutocompleteCodeSnippet,
   AutocompleteSnippetType,
 } from "core/autocomplete/snippets/types";
+import URI from "uri-js";
 
 type GotoProviderName =
   | "vscode.executeDefinitionProvider"
@@ -26,13 +24,13 @@ type GotoProviderName =
   | "vscode.executeReferenceProvider";
 
 interface GotoInput {
-  uri: string;
+  uri: vscode.Uri;
   line: number;
   character: number;
   name: GotoProviderName;
 }
 function gotoInputKey(input: GotoInput) {
-  return `${input.name}${input.uri.toString}${input.line}${input.character}`;
+  return `${input.name}${input.uri.toString()}${input.line}${input.character}`;
 }
 
 const MAX_CACHE_SIZE = 50;
@@ -50,14 +48,14 @@ export async function executeGotoProvider(
   try {
     const definitions = (await vscode.commands.executeCommand(
       input.name,
-      vscode.Uri.parse(input.uri),
+      input.uri,
       new vscode.Position(input.line, input.character),
     )) as any;
 
     const results = definitions
       .filter((d: any) => (d.targetUri || d.uri) && (d.targetRange || d.range))
       .map((d: any) => ({
-        filepath: (d.targetUri || d.uri).fsPath,
+        filepath: (d.targetUri || d.uri).toString(),
         range: d.targetRange || d.range,
       }));
 
@@ -156,7 +154,7 @@ async function crawlTypes(
   const definitions = await Promise.all(
     identifierNodes.map(async (node) => {
       const [typeDef] = await executeGotoProvider({
-        uri: rif.filepath,
+        uri: vscode.Uri.parse(rif.filepath),
         // TODO: tree-sitter is zero-indexed, but there seems to be an off-by-one
         // error at least with the .ts parser sometimes
         line:
@@ -184,7 +182,7 @@ async function crawlTypes(
       !definition ||
       results.some(
         (result) =>
-          result.filepath === definition.filepath &&
+          URI.equal(result.filepath, definition.filepath) &&
           intersection(result.range, definition.range) !== null,
       )
     ) {
@@ -204,7 +202,7 @@ async function crawlTypes(
 }
 
 export async function getDefinitionsForNode(
-  uri: string,
+  uri: vscode.Uri,
   node: Parser.SyntaxNode,
   ide: IDE,
   lang: AutocompleteLanguageInfo,
diff --git a/extensions/vscode/src/decorations.ts b/extensions/vscode/src/decorations.ts
index 6547c2986e..be2899730b 100644
--- a/extensions/vscode/src/decorations.ts
+++ b/extensions/vscode/src/decorations.ts
@@ -2,8 +2,6 @@ import * as path from "node:path";
 
 import * as vscode from "vscode";
 
-import { uriFromFilePath } from "./util/vscode";
-
 export function showAnswerInTextEditor(
   filename: string,
   range: vscode.Range,
diff --git a/extensions/vscode/src/diff/horizontal.ts b/extensions/vscode/src/diff/horizontal.ts
index 65c411265f..51fbeb2eba 100644
--- a/extensions/vscode/src/diff/horizontal.ts
+++ b/extensions/vscode/src/diff/horizontal.ts
@@ -6,7 +6,6 @@ import { devDataPath } from "core/util/paths";
 import * as vscode from "vscode";
 
 import { getMetaKeyLabel, getPlatform } from "../util/util";
-import { uriFromFilePath } from "../util/vscode";
 
 import type { VsCodeWebviewProtocol } from "../webviewProtocol";
 
diff --git a/extensions/vscode/src/suggestions.ts b/extensions/vscode/src/suggestions.ts
index c97906b75b..5a6a1fdd9c 100644
--- a/extensions/vscode/src/suggestions.ts
+++ b/extensions/vscode/src/suggestions.ts
@@ -1,7 +1,7 @@
 import * as vscode from "vscode";
 
 import { openEditorAndRevealRange, translate } from "./util/vscode";
-import URI from "uri-js"
+import URI from "uri-js";
 
 export interface SuggestionRanges {
   oldRange: vscode.Range;
@@ -15,7 +15,6 @@ export const editorToSuggestions: Map<
   string, // URI of file
   SuggestionRanges[]
 > = new Map();
-export const editorSuggestionsLocked: Map = new Map(); // Map from editor URI to whether the suggestions are locked
 export const currentSuggestion: Map = new Map(); // Map from editor URI to index of current SuggestionRanges in editorToSuggestions
 
 // When tab is reopened, rerender the decorations:
@@ -58,8 +57,8 @@ const oldSelDecorationType = vscode.window.createTextEditorDecorationType({
 export function rerenderDecorations(editorUri: string) {
   const suggestions = editorToSuggestions.get(editorUri);
   const idx = currentSuggestion.get(editorUri);
-  const editor = vscode.window.visibleTextEditors.find(
-    (editor) => URI.equal(editor.document.uri.toString(), editorUri)
+  const editor = vscode.window.visibleTextEditors.find((editor) =>
+    URI.equal(editor.document.uri.toString(), editorUri),
   );
   if (!suggestions || !editor) {
     return;
@@ -141,6 +140,7 @@ export function suggestionDownCommand() {
     return;
   }
   const editorUri = editor.document.uri.toString();
+  const uriString = editorUri.toString();
   const suggestions = editorToSuggestions.get(editorUri);
   const idx = currentSuggestion.get(editorUri);
   if (!suggestions || idx === undefined) {
@@ -300,7 +300,7 @@ export async function rejectSuggestionCommand(
 }
 
 export async function showSuggestion(
-  editorUri: string,
+  editorUri: vscode.Uri,
   range: vscode.Range,
   suggestion: string,
 ): Promise {
diff --git a/extensions/vscode/src/util/ideUtils.ts b/extensions/vscode/src/util/ideUtils.ts
index 502856a333..4cc694dc8a 100644
--- a/extensions/vscode/src/util/ideUtils.ts
+++ b/extensions/vscode/src/util/ideUtils.ts
@@ -8,14 +8,14 @@ import { GitExtension, Repository } from "../otherExtensions/git";
 import {
   SuggestionRanges,
   acceptSuggestionCommand,
-  editorSuggestionsLocked,
   rejectSuggestionCommand,
   showSuggestion as showSuggestionInEditor,
 } from "../suggestions";
 
 import { getUniqueId, openEditorAndRevealRange } from "./vscode";
 
-import type { FileEdit, RangeInFile, Thread } from "core";
+import type { FileEdit, Range, RangeInFile, Thread } from "core";
+import { isUriWithinDirectory } from "core/util/uri";
 
 const util = require("node:util");
 const asyncExec = util.promisify(require("node:child_process").exec);
@@ -74,20 +74,17 @@ export class VsCodeIdeUtils {
     return getUniqueId();
   }
 
-  // ------------------------------------ //
-  // On message handlers
-
   private _lastDecorationType: vscode.TextEditorDecorationType | null = null;
-  async highlightCode(rangeInFile: RangeInFile, color: string) {
-    const range = new vscode.Range(
-      rangeInFile.range.start.line,
-      rangeInFile.range.start.character,
-      rangeInFile.range.end.line,
-      rangeInFile.range.end.character,
+  async highlightCode(uri: vscode.Uri, range: Range, color: string) {
+    const vsCodeRange = new vscode.Range(
+      range.start.line,
+      range.start.character,
+      range.end.line,
+      range.end.character,
     );
     const editor = await openEditorAndRevealRange(
-      rangeInFile.filepath,
-      range,
+      uri,
+      vsCodeRange,
       vscode.ViewColumn.One,
     );
     if (editor) {
@@ -95,11 +92,13 @@ export class VsCodeIdeUtils {
         backgroundColor: color,
         isWholeLine: true,
       });
-      editor.setDecorations(decorationType, [range]);
+      editor.setDecorations(decorationType, [vsCodeRange]);
 
       const cursorDisposable = vscode.window.onDidChangeTextEditorSelection(
         (event) => {
-          if (event.textEditor.document.uri.fsPath === rangeInFile.filepath) {
+          if (
+            URI.equal(event.textEditor.document.uri.toString(), uri.toString())
+          ) {
             cursorDisposable.dispose();
             editor.setDecorations(decorationType, []);
           }
@@ -118,17 +117,16 @@ export class VsCodeIdeUtils {
     }
   }
 
-  showSuggestion(edit: FileEdit) {
-    // showSuggestion already exists
+  showSuggestion(uri: vscode.Uri, range: Range, suggestion: string) {
     showSuggestionInEditor(
-      edit.filepath,
+      uri,
       new vscode.Range(
-        edit.range.start.line,
-        edit.range.start.character,
-        edit.range.end.line,
-        edit.range.end.character,
+        range.start.line,
+        range.start.character,
+        range.end.line,
+        range.end.character,
       ),
-      edit.replacement,
+      suggestion,
     );
   }
 
@@ -207,7 +205,7 @@ export class VsCodeIdeUtils {
     return uri.scheme === "file" || uri.scheme === "vscode-remote";
   }
 
-  getOpenFiles(): string[] {
+  getOpenFiles(): vscode.Uri[] {
     return vscode.window.tabGroups.all
       .map((group) => {
         return group.tabs.map((tab) => {
@@ -216,8 +214,7 @@ export class VsCodeIdeUtils {
       })
       .flat()
       .filter(Boolean) // filter out undefined values
-      .filter((uri) => this.documentIsCode(uri)) // Filter out undesired documents
-      .map((uri) => uri.toString());
+      .filter((uri) => this.documentIsCode(uri)); // Filter out undesired documents
   }
 
   getVisibleFiles(): string[] {
@@ -232,44 +229,15 @@ export class VsCodeIdeUtils {
     vscode.window.visibleTextEditors
       .filter((editor) => this.documentIsCode(editor.document.uri))
       .forEach((editor) => {
-        if (URI.equal(editor.document.uri, uri.toString())) {
+        if (URI.equal(editor.document.uri.toString(), uri.toString())) {
           editor.document.save();
         }
       });
   }
 
-  private _cachedPath: path.PlatformPath | undefined;
-  get path(): path.PlatformPath {
-    if (this._cachedPath) {
-      return this._cachedPath;
-    }
-
-    // Return "path" module for either windows or posix depending on sample workspace folder path format
-    const sampleWorkspaceFolder =
-      vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
-    const isWindows = sampleWorkspaceFolder
-      ? !sampleWorkspaceFolder.startsWith("/")
-      : false;
-
-    this._cachedPath = isWindows ? path.win32 : path.posix;
-    return this._cachedPath;
-  }
-
-  getAbsolutePath(filepath: string): string {
-    const workspaceDirectories = this.getWorkspaceDirectories();
-    if (!this.path.isAbsolute(filepath) && workspaceDirectories.length === 1) {
-      return this.path.join(workspaceDirectories[0], filepath);
-    } else {
-      return filepath;
-    }
-  }
-
-  async readRangeInFile(
-    filepath: string,
-    range: vscode.Range,
-  ): Promise {
+  async readRangeInFile(uri: vscode.Uri, range: vscode.Range): Promise {
     const contents = new TextDecoder().decode(
-      await vscode.workspace.fs.readFile(vscode.Uri.file(filepath)),
+      await vscode.workspace.fs.readFile(uri),
     );
     const lines = contents.split("\n");
     return `${lines
@@ -484,11 +452,11 @@ export class VsCodeIdeUtils {
   async getRepo(forDirectory: vscode.Uri): Promise {
     const workspaceDirs = this.getWorkspaceDirectories();
     const parentDir = workspaceDirs.find((dir) =>
-      forDirectory.fsPath.startsWith(dir),
+      isUriWithinDirectory(forDirectory.toString(), dir.toString()),
     );
     if (parentDir) {
       // Check if the repository is already cached
-      const cachedRepo = this.repoCache.get(parentDir);
+      const cachedRepo = this.repoCache.get(parentDir.toString());
       if (cachedRepo) {
         return cachedRepo;
       }
@@ -513,15 +481,15 @@ export class VsCodeIdeUtils {
 
     if (parentDir) {
       // Cache the repository for the parent directory
-      this.repoCache.set(parentDir, repo);
+      this.repoCache.set(parentDir.toString(), repo);
     }
 
     return repo;
   }
 
-  async getGitRoot(forDirectory: string): Promise {
-    const repo = await this.getRepo(vscode.Uri.file(forDirectory));
-    return repo?.rootUri?.fsPath;
+  async getGitRoot(forDirectory: vscode.Uri): Promise {
+    const repo = await this.getRepo(forDirectory);
+    return repo?.rootUri;
   }
 
   async getBranch(forDirectory: vscode.Uri) {
@@ -556,7 +524,7 @@ export class VsCodeIdeUtils {
     const diffs: string[] = [];
 
     for (const dir of this.getWorkspaceDirectories()) {
-      const repo = await this.getRepo(vscode.Uri.file(dir));
+      const repo = await this.getRepo(dir);
       if (!repo) {
         continue;
       }
@@ -581,7 +549,7 @@ export class VsCodeIdeUtils {
         editor.selections.forEach((selection) => {
           // if (!selection.isEmpty) {
           rangeInFiles.push({
-            filepath: editor.document.uri.fsPath,
+            filepath: editor.document.uri.toString(),
             range: {
               start: {
                 line: selection.start.line,

From 2dfd8d204895dc24099951ad925a4d09fad02c3f Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Tue, 10 Dec 2024 19:06:08 -0800
Subject: [PATCH 09/84] remove-unused-decoration-manager

---
 extensions/vscode/src/decorations.ts | 206 ---------------------------
 1 file changed, 206 deletions(-)
 delete mode 100644 extensions/vscode/src/decorations.ts

diff --git a/extensions/vscode/src/decorations.ts b/extensions/vscode/src/decorations.ts
deleted file mode 100644
index be2899730b..0000000000
--- a/extensions/vscode/src/decorations.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-import * as path from "node:path";
-
-import * as vscode from "vscode";
-
-export function showAnswerInTextEditor(
-  filename: string,
-  range: vscode.Range,
-  answer: string,
-) {
-  vscode.workspace.openTextDocument(uriFromFilePath(filename)).then((doc) => {
-    const editor = vscode.window.activeTextEditor;
-    if (!editor) {
-      return;
-    }
-
-    // Open file, reveal range, show decoration
-    vscode.window.showTextDocument(doc).then((new_editor) => {
-      new_editor.revealRange(
-        new vscode.Range(range.end, range.end),
-        vscode.TextEditorRevealType.InCenter,
-      );
-
-      const decorationType = vscode.window.createTextEditorDecorationType({
-        after: {
-          contentText: `${answer}\n`,
-          color: "rgb(0, 255, 0, 0.8)",
-        },
-        backgroundColor: "rgb(0, 255, 0, 0.2)",
-      });
-      new_editor.setDecorations(decorationType, [range]);
-      vscode.window.showInformationMessage("Answer found!");
-
-      // Remove decoration when user moves cursor
-      vscode.window.onDidChangeTextEditorSelection((e) => {
-        if (
-          e.textEditor === new_editor &&
-          e.selections[0].active.line !== range.end.line
-        ) {
-          new_editor.setDecorations(decorationType, []);
-        }
-      });
-    });
-  });
-}
-
-type DecorationKey = {
-  editorUri: string;
-  options: vscode.DecorationOptions;
-  decorationType: vscode.TextEditorDecorationType;
-};
-
-class DecorationManager {
-  private editorToDecorations = new Map<
-    string,
-    Map
-  >();
-
-  constructor() {
-    vscode.window.onDidChangeVisibleTextEditors((editors) => {
-      for (const editor of editors) {
-        if (editor.document.isClosed) {
-          this.editorToDecorations.delete(editor.document.uri.toString());
-        }
-      }
-    });
-  }
-
-  private rerenderDecorations(
-    editorUri: string,
-    decorationType: vscode.TextEditorDecorationType,
-  ) {
-    const editor = vscode.window.activeTextEditor;
-    if (!editor) {
-      return;
-    }
-
-    const decorationTypes = this.editorToDecorations.get(editorUri);
-    if (!decorationTypes) {
-      return;
-    }
-
-    const decorations = decorationTypes.get(decorationType);
-    if (!decorations) {
-      return;
-    }
-
-    editor.setDecorations(decorationType, decorations);
-  }
-
-  addDecoration(key: DecorationKey) {
-    let decorationTypes = this.editorToDecorations.get(key.editorUri);
-    if (!decorationTypes) {
-      decorationTypes = new Map();
-      decorationTypes.set(key.decorationType, [key.options]);
-      this.editorToDecorations.set(key.editorUri, decorationTypes);
-    } else {
-      const decorations = decorationTypes.get(key.decorationType);
-      if (!decorations) {
-        decorationTypes.set(key.decorationType, [key.options]);
-      } else {
-        decorations.push(key.options);
-      }
-    }
-
-    this.rerenderDecorations(key.editorUri, key.decorationType);
-
-    vscode.window.onDidChangeTextEditorSelection((event) => {
-      if (event.textEditor.document.fileName === key.editorUri) {
-        this.deleteAllDecorations(key.editorUri);
-      }
-    });
-  }
-
-  deleteDecoration(key: DecorationKey) {
-    const decorationTypes = this.editorToDecorations.get(key.editorUri);
-    if (!decorationTypes) {
-      return;
-    }
-
-    let decorations = decorationTypes?.get(key.decorationType);
-    if (!decorations) {
-      return;
-    }
-
-    decorations = decorations.filter((decOpts) => decOpts !== key.options);
-    decorationTypes.set(key.decorationType, decorations);
-    this.rerenderDecorations(key.editorUri, key.decorationType);
-  }
-
-  deleteAllDecorations(editorUri: string) {
-    const decorationTypes = this.editorToDecorations.get(editorUri)?.keys();
-    if (!decorationTypes) {
-      return;
-    }
-    this.editorToDecorations.delete(editorUri);
-    for (const decorationType of decorationTypes) {
-      this.rerenderDecorations(editorUri, decorationType);
-    }
-  }
-}
-
-export const decorationManager = new DecorationManager();
-
-function constructBaseKey(
-  editor: vscode.TextEditor,
-  lineno: number,
-  decorationType?: vscode.TextEditorDecorationType,
-): DecorationKey {
-  return {
-    editorUri: editor.document.uri.toString(),
-    options: {
-      range: new vscode.Range(lineno, 0, lineno, 0),
-    },
-    decorationType:
-      decorationType || vscode.window.createTextEditorDecorationType({}),
-  };
-}
-
-export function showLintMessage(
-  editor: vscode.TextEditor,
-  lineno: number,
-  msg: string,
-): DecorationKey {
-  const key = constructBaseKey(editor, lineno);
-  key.decorationType = vscode.window.createTextEditorDecorationType({
-    after: {
-      contentText: "Linting error",
-      color: "rgb(255, 0, 0, 0.6)",
-    },
-    gutterIconPath: vscode.Uri.file(
-      path.join(__dirname, "..", "media", "error.png"),
-    ),
-    gutterIconSize: "contain",
-  });
-  key.options.hoverMessage = msg;
-  decorationManager.addDecoration(key);
-  return key;
-}
-
-export function highlightCode(
-  editor: vscode.TextEditor,
-  range: vscode.Range,
-  removeOnClick = true,
-): DecorationKey {
-  const decorationType = vscode.window.createTextEditorDecorationType({
-    backgroundColor: "rgb(255, 255, 0, 0.1)",
-  });
-  const key = {
-    editorUri: editor.document.uri.toString(),
-    options: {
-      range,
-    },
-    decorationType,
-  };
-  decorationManager.addDecoration(key);
-
-  if (removeOnClick) {
-    vscode.window.onDidChangeTextEditorSelection((e) => {
-      if (e.textEditor === editor) {
-        decorationManager.deleteDecoration(key);
-      }
-    });
-  }
-
-  return key;
-}

From 2225d0d702b76c366bf44062ab1cf6f7982d2e6e Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Tue, 10 Dec 2024 20:09:07 -0800
Subject: [PATCH 10/84] (BROKEN) path -> uri vscode utils continued

---
 core/util/index.ts                            | 12 +--
 extensions/vscode/src/VsCodeIde.ts            |  8 +-
 extensions/vscode/src/diff/horizontal.ts      |  8 +-
 .../vscode/src/diff/vertical/handler.ts       | 40 +++++-----
 .../vscode/src/diff/vertical/manager.ts       | 73 ++++++++++---------
 .../src/lang-server/promptFileCompletions.ts  | 27 +++----
 extensions/vscode/src/util/FileSearch.ts      | 22 +++---
 extensions/vscode/src/util/ideUtils.ts        |  6 +-
 8 files changed, 101 insertions(+), 95 deletions(-)

diff --git a/core/util/index.ts b/core/util/index.ts
index a2c8af5c59..b40d264e04 100644
--- a/core/util/index.ts
+++ b/core/util/index.ts
@@ -72,12 +72,12 @@ const SEP_REGEX = /[\\/]/;
 //   return filepath.split(SEP_REGEX).pop() ?? "";
 // }
 
-// export function getLastNPathParts(filepath: string, n: number): string {
-//   if (n <= 0) {
-//     return "";
-//   }
-//   return filepath.split(SEP_REGEX).slice(-n).join("/");
-// }
+export function getLastNPathParts(filepath: string, n: number): string {
+  if (n <= 0) {
+    return "";
+  }
+  return filepath.split(SEP_REGEX).slice(-n).join("/");
+}
 
 // export function groupByLastNPathParts(
 //   filepaths: string[],
diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts
index 1d3e5e9718..5a0c988e1d 100644
--- a/extensions/vscode/src/VsCodeIde.ts
+++ b/extensions/vscode/src/VsCodeIde.ts
@@ -349,7 +349,7 @@ class VsCodeIde implements IDE {
           filename === ".continuerc.json"
         ) {
           const contents = await this.readFile(
-            vscode.Uri.joinPath(workspaceDir, filename).fsPath,
+            vscode.Uri.joinPath(workspaceDir, filename).toString(),
           );
           configs.push(JSON.parse(contents));
         }
@@ -509,7 +509,7 @@ class VsCodeIde implements IDE {
 
     return tabArray
       .filter((t) => t.isPinned)
-      .map((t) => (t.input as vscode.TabInputText).uri.fsPath);
+      .map((t) => (t.input as vscode.TabInputText).uri.toString());
   }
 
   private async _searchDir(query: string, dir: string): Promise {
@@ -572,7 +572,7 @@ class VsCodeIde implements IDE {
     }
     return vscode.languages.getDiagnostics(uri).map((d) => {
       return {
-        filepath: uri.fsPath,
+        filepath: uri.toString(),
         range: {
           start: {
             line: d.range.start.line,
@@ -607,7 +607,7 @@ class VsCodeIde implements IDE {
   }
 
   async listDir(dir: string): Promise<[string, FileType][]> {
-    return vscode.workspace.fs.readDirectory(uriFromFilePath(dir)) as any;
+    return vscode.workspace.fs.readDirectory(vscode.Uri.parse(dir)) as any;
   }
 
   getIdeSettingsSync(): IdeSettings {
diff --git a/extensions/vscode/src/diff/horizontal.ts b/extensions/vscode/src/diff/horizontal.ts
index 51fbeb2eba..42be5903fb 100644
--- a/extensions/vscode/src/diff/horizontal.ts
+++ b/extensions/vscode/src/diff/horizontal.ts
@@ -10,16 +10,16 @@ import { getMetaKeyLabel, getPlatform } from "../util/util";
 import type { VsCodeWebviewProtocol } from "../webviewProtocol";
 
 interface DiffInfo {
-  originalFilepath: string;
-  newFilepath: string;
+  originalUri: string;
+  newUri: string;
   editor?: vscode.TextEditor;
   step_index: number;
   range: vscode.Range;
 }
 
-async function readFile(path: string): Promise {
+async function readFile(uri: vscode.Uri): Promise {
   return await vscode.workspace.fs
-    .readFile(uriFromFilePath(path))
+    .readFile(uri)
     .then((bytes) => new TextDecoder().decode(bytes));
 }
 
diff --git a/extensions/vscode/src/diff/vertical/handler.ts b/extensions/vscode/src/diff/vertical/handler.ts
index c686192772..4290589021 100644
--- a/extensions/vscode/src/diff/vertical/handler.ts
+++ b/extensions/vscode/src/diff/vertical/handler.ts
@@ -10,6 +10,7 @@ import {
 
 import type { VerticalDiffCodeLens } from "./manager";
 import type { ApplyState, DiffLine } from "core";
+import URI from "uri-js";
 
 export interface VerticalDiffHandlerOptions {
   input?: string;
@@ -27,8 +28,8 @@ export class VerticalDiffHandler implements vscode.Disposable {
   private newLinesAdded = 0;
   private deletionBuffer: string[] = [];
   private redDecorationManager: DecorationTypeRangeManager;
-  private get filepath() {
-    return this.editor.document.uri.fsPath;
+  private get fileUri() {
+    return this.editor.document.uri.toString();
   }
   public insertedInCurrentBlock = 0;
   public get range(): vscode.Range {
@@ -45,8 +46,8 @@ export class VerticalDiffHandler implements vscode.Disposable {
       string,
       VerticalDiffCodeLens[]
     >,
-    private readonly clearForFilepath: (
-      filepath: string | undefined,
+    private readonly clearForFileUri: (
+      fileUri: string | undefined,
       accept: boolean,
     ) => void,
     private readonly refreshCodeLens: () => void,
@@ -65,8 +66,11 @@ export class VerticalDiffHandler implements vscode.Disposable {
     );
 
     const disposable = vscode.window.onDidChangeActiveTextEditor((editor) => {
+      if (!editor) {
+        return;
+      }
       // When we switch away and back to this editor, need to re-draw decorations
-      if (editor?.document.uri.fsPath === this.filepath) {
+      if (URI.equal(editor.document.uri.toString(), this.fileUri)) {
         this.editor = editor;
         this.redDecorationManager.applyToNewEditor(editor);
         this.greenDecorationManager.applyToNewEditor(editor);
@@ -93,7 +97,7 @@ export class VerticalDiffHandler implements vscode.Disposable {
     }
 
     if (this.deletionBuffer.length || this.insertedInCurrentBlock > 0) {
-      const blocks = this.editorToVerticalDiffCodeLens.get(this.filepath) || [];
+      const blocks = this.editorToVerticalDiffCodeLens.get(this.fileUri) || [];
 
       blocks.push({
         start: this.currentLineIndex - this.insertedInCurrentBlock,
@@ -101,7 +105,7 @@ export class VerticalDiffHandler implements vscode.Disposable {
         numGreen: this.insertedInCurrentBlock,
       });
 
-      this.editorToVerticalDiffCodeLens.set(this.filepath, blocks);
+      this.editorToVerticalDiffCodeLens.set(this.fileUri, blocks);
     }
 
     if (this.deletionBuffer.length === 0) {
@@ -249,7 +253,7 @@ export class VerticalDiffHandler implements vscode.Disposable {
     this.greenDecorationManager.clear();
     this.clearIndexLineDecorations();
 
-    this.editorToVerticalDiffCodeLens.delete(this.filepath);
+    this.editorToVerticalDiffCodeLens.delete(this.fileUri);
 
     await this.editor.edit(
       (editBuilder) => {
@@ -270,7 +274,7 @@ export class VerticalDiffHandler implements vscode.Disposable {
 
     this.options.onStatusUpdate(
       "closed",
-      this.editorToVerticalDiffCodeLens.get(this.filepath)?.length ?? 0,
+      this.editorToVerticalDiffCodeLens.get(this.fileUri)?.length ?? 0,
       this.editor.document.getText(),
     );
 
@@ -362,19 +366,19 @@ export class VerticalDiffHandler implements vscode.Disposable {
 
       this.options.onStatusUpdate(
         "done",
-        this.editorToVerticalDiffCodeLens.get(this.filepath)?.length ?? 0,
+        this.editorToVerticalDiffCodeLens.get(this.fileUri)?.length ?? 0,
         this.editor.document.getText(),
       );
 
       // Reject on user typing
       // const listener = vscode.workspace.onDidChangeTextDocument((e) => {
-      //   if (e.document.uri.fsPath === this.filepath) {
+      //   if (URI.equal(e.document.uri.toString(), this.fileUri)) {
       //     this.clear(false);
       //     listener.dispose();
       //   }
       // });
     } catch (e) {
-      this.clearForFilepath(this.filepath, false);
+      this.clearForFileUri(this.fileUri, false);
       throw e;
     }
     return diffLines;
@@ -415,7 +419,7 @@ export class VerticalDiffHandler implements vscode.Disposable {
     this.shiftCodeLensObjects(startLine, offset);
 
     const numDiffs =
-      this.editorToVerticalDiffCodeLens.get(this.filepath)?.length ?? 0;
+      this.editorToVerticalDiffCodeLens.get(this.fileUri)?.length ?? 0;
 
     const status = numDiffs === 0 ? "closed" : undefined;
     this.options.onStatusUpdate(
@@ -429,7 +433,7 @@ export class VerticalDiffHandler implements vscode.Disposable {
     // Shift the codelens objects
     const blocks =
       this.editorToVerticalDiffCodeLens
-        .get(this.filepath)
+        .get(this.fileUri)
         ?.filter((x) => x.start !== startLine)
         .map((x) => {
           if (x.start > startLine) {
@@ -437,18 +441,18 @@ export class VerticalDiffHandler implements vscode.Disposable {
           }
           return x;
         }) || [];
-    this.editorToVerticalDiffCodeLens.set(this.filepath, blocks);
+    this.editorToVerticalDiffCodeLens.set(this.fileUri, blocks);
 
     this.refreshCodeLens();
   }
 
   public updateLineDelta(
-    filepath: string,
+    fileUri: string,
     startLine: number,
     lineDelta: number,
   ) {
     // Retrieve the diff blocks for the given file
-    const blocks = this.editorToVerticalDiffCodeLens.get(filepath);
+    const blocks = this.editorToVerticalDiffCodeLens.get(fileUri);
     if (!blocks) {
       return;
     }
@@ -462,7 +466,7 @@ export class VerticalDiffHandler implements vscode.Disposable {
   }
 
   public hasDiffForCurrentFile(): boolean {
-    const diffBlocks = this.editorToVerticalDiffCodeLens.get(this.filepath);
+    const diffBlocks = this.editorToVerticalDiffCodeLens.get(this.fileUri);
     return diffBlocks !== undefined && diffBlocks.length > 0;
   }
 }
diff --git a/extensions/vscode/src/diff/vertical/manager.ts b/extensions/vscode/src/diff/vertical/manager.ts
index 3342c53414..ee6b0d8f46 100644
--- a/extensions/vscode/src/diff/vertical/manager.ts
+++ b/extensions/vscode/src/diff/vertical/manager.ts
@@ -9,6 +9,7 @@ import EditDecorationManager from "../../quickEdit/EditDecorationManager";
 import { VsCodeWebviewProtocol } from "../../webviewProtocol";
 
 import { VerticalDiffHandler, VerticalDiffHandlerOptions } from "./handler";
+import URI from "uri-js";
 
 export interface VerticalDiffCodeLens {
   start: number;
@@ -19,9 +20,9 @@ export interface VerticalDiffCodeLens {
 export class VerticalDiffManager {
   public refreshCodeLens: () => void = () => {};
 
-  private filepathToHandler: Map = new Map();
+  private fileUriToHandler: Map = new Map();
 
-  filepathToCodeLens: Map = new Map();
+  fileUriToCodeLens: Map = new Map();
 
   private userChangeListener: vscode.Disposable | undefined;
 
@@ -36,35 +37,35 @@ export class VerticalDiffManager {
   }
 
   createVerticalDiffHandler(
-    filepath: string,
+    fileUri: string,
     startLine: number,
     endLine: number,
     options: VerticalDiffHandlerOptions,
   ) {
-    if (this.filepathToHandler.has(filepath)) {
-      this.filepathToHandler.get(filepath)?.clear(false);
-      this.filepathToHandler.delete(filepath);
+    if (this.fileUriToHandler.has(fileUri)) {
+      this.fileUriToHandler.get(fileUri)?.clear(false);
+      this.fileUriToHandler.delete(fileUri);
     }
     const editor = vscode.window.activeTextEditor; // TODO
-    if (editor && editor.document.uri.fsPath === filepath) {
+    if (editor && URI.equal(editor.document.uri.toString(), fileUri)) {
       const handler = new VerticalDiffHandler(
         startLine,
         endLine,
         editor,
-        this.filepathToCodeLens,
-        this.clearForFilepath.bind(this),
+        this.fileUriToCodeLens,
+        this.clearForfileUri.bind(this),
         this.refreshCodeLens,
         options,
       );
-      this.filepathToHandler.set(filepath, handler);
+      this.fileUriToHandler.set(fileUri, handler);
       return handler;
     } else {
       return undefined;
     }
   }
 
-  getHandlerForFile(filepath: string) {
-    return this.filepathToHandler.get(filepath);
+  getHandlerForFile(fileUri: string) {
+    return this.fileUriToHandler.get(fileUri);
   }
 
   // Creates a listener for document changes by user.
@@ -77,8 +78,8 @@ export class VerticalDiffManager {
     this.userChangeListener = vscode.workspace.onDidChangeTextDocument(
       (event) => {
         // Check if there is an active handler for the affected file
-        const filepath = event.document.uri.fsPath;
-        const handler = this.getHandlerForFile(filepath);
+        const fileUri = event.document.uri.toString();
+        const handler = this.getHandlerForFile(fileUri);
         if (handler) {
           // If there is an active diff for that file, handle the document change
           this.handleDocumentChange(event, handler);
@@ -108,26 +109,26 @@ export class VerticalDiffManager {
 
       // Update the diff handler with the new line delta
       handler.updateLineDelta(
-        event.document.uri.fsPath,
+        event.document.uri.toString(),
         change.range.start.line,
         lineDelta,
       );
     });
   }
 
-  clearForFilepath(filepath: string | undefined, accept: boolean) {
-    if (!filepath) {
+  clearForfileUri(fileUri: string | undefined, accept: boolean) {
+    if (!fileUri) {
       const activeEditor = vscode.window.activeTextEditor;
       if (!activeEditor) {
         return;
       }
-      filepath = activeEditor.document.uri.fsPath;
+      fileUri = activeEditor.document.uri.toString();
     }
 
-    const handler = this.filepathToHandler.get(filepath);
+    const handler = this.fileUriToHandler.get(fileUri);
     if (handler) {
       handler.clear(accept);
-      this.filepathToHandler.delete(filepath);
+      this.fileUriToHandler.delete(fileUri);
     }
 
     this.disableDocumentChangeListener();
@@ -137,28 +138,28 @@ export class VerticalDiffManager {
 
   async acceptRejectVerticalDiffBlock(
     accept: boolean,
-    filepath?: string,
+    fileUri?: string,
     index?: number,
   ) {
-    if (!filepath) {
+    if (!fileUri) {
       const activeEditor = vscode.window.activeTextEditor;
       if (!activeEditor) {
         return;
       }
-      filepath = activeEditor.document.uri.fsPath;
+      fileUri = activeEditor.document.uri.toString();
     }
 
     if (typeof index === "undefined") {
       index = 0;
     }
 
-    const blocks = this.filepathToCodeLens.get(filepath);
+    const blocks = this.fileUriToCodeLens.get(fileUri);
     const block = blocks?.[index];
     if (!blocks || !block) {
       return;
     }
 
-    const handler = this.getHandlerForFile(filepath);
+    const handler = this.getHandlerForFile(fileUri);
     if (!handler) {
       return;
     }
@@ -175,7 +176,7 @@ export class VerticalDiffManager {
     );
 
     if (blocks.length === 1) {
-      this.clearForFilepath(filepath, true);
+      this.clearForfileUri(fileUri, true);
     } else {
       // Re-enable listener for user changes to file
       this.enableDocumentChangeListener();
@@ -191,17 +192,17 @@ export class VerticalDiffManager {
   ) {
     vscode.commands.executeCommand("setContext", "continue.diffVisible", true);
 
-    // Get the current editor filepath/range
+    // Get the current editor fileUri/range
     let editor = vscode.window.activeTextEditor;
     if (!editor) {
       return;
     }
-    const filepath = editor.document.uri.fsPath;
+    const fileUri = editor.document.uri.toString();
     const startLine = 0;
     const endLine = editor.document.lineCount - 1;
 
     // Check for existing handlers in the same file the new one will be created in
-    const existingHandler = this.getHandlerForFile(filepath);
+    const existingHandler = this.getHandlerForFile(fileUri);
     if (existingHandler) {
       existingHandler.clear(false);
     }
@@ -212,7 +213,7 @@ export class VerticalDiffManager {
 
     // Create new handler with determined start/end
     const diffHandler = this.createVerticalDiffHandler(
-      filepath,
+      fileUri,
       startLine,
       endLine,
       {
@@ -223,7 +224,7 @@ export class VerticalDiffManager {
             status,
             numDiffs,
             fileContent,
-            filepath,
+            filepath: fileUri,
           }),
       },
     );
@@ -280,7 +281,7 @@ export class VerticalDiffManager {
       return undefined;
     }
 
-    const filepath = editor.document.uri.fsPath;
+    const fileUri = editor.document.uri.toString();
 
     let startLine, endLine: number;
 
@@ -293,7 +294,7 @@ export class VerticalDiffManager {
     }
 
     // Check for existing handlers in the same file the new one will be created in
-    const existingHandler = this.getHandlerForFile(filepath);
+    const existingHandler = this.getHandlerForFile(fileUri);
 
     if (existingHandler) {
       if (quickEdit) {
@@ -333,7 +334,7 @@ export class VerticalDiffManager {
 
     // Create new handler with determined start/end
     const diffHandler = this.createVerticalDiffHandler(
-      filepath,
+      fileUri,
       startLine,
       endLine,
       {
@@ -345,7 +346,7 @@ export class VerticalDiffManager {
             status,
             numDiffs,
             fileContent,
-            filepath,
+            filepath: fileUri,
           }),
       },
     );
@@ -411,7 +412,7 @@ export class VerticalDiffManager {
           suffix,
           llm,
           input,
-          getMarkdownLanguageTagForFile(filepath),
+          getMarkdownLanguageTagForFile(fileUri),
           onlyOneInsertion,
         );
 
diff --git a/extensions/vscode/src/lang-server/promptFileCompletions.ts b/extensions/vscode/src/lang-server/promptFileCompletions.ts
index ef873b44dc..8c5c050211 100644
--- a/extensions/vscode/src/lang-server/promptFileCompletions.ts
+++ b/extensions/vscode/src/lang-server/promptFileCompletions.ts
@@ -1,8 +1,9 @@
 import { IDE } from "core";
-import { getBasename, getLastNPathParts } from "core/util";
 import vscode from "vscode";
 
 import { FileSearch } from "../util/FileSearch";
+import { getUriPathBasename } from "core/util/uri";
+import { getLastNPathParts } from "core/util";
 
 class PromptFilesCompletionItemProvider
   implements vscode.CompletionItemProvider
@@ -30,13 +31,16 @@ class PromptFilesCompletionItemProvider
     }
 
     const searchText = linePrefix.split("@").pop() || "";
-    const files = this.fileSearch.search(searchText).map(({ filename }) => {
-      return filename;
-    });
+    const files = this.fileSearch.search(searchText);
 
     if (files.length === 0) {
       const openFiles = await this.ide.getOpenFiles();
-      files.push(...openFiles);
+      files.push(
+        ...openFiles.map((fileUri) => ({
+          id: fileUri,
+          relativePath: vscode.workspace.asRelativePath(fileUri),
+        })),
+      );
     }
 
     // Provide completion items
@@ -84,17 +88,10 @@ class PromptFilesCompletionItemProvider
         kind: vscode.CompletionItemKind.Field,
       },
       ...files.map((file) => {
-        const workspaceFolder = vscode.workspace.getWorkspaceFolder(
-          vscode.Uri.file(file),
-        );
-        const relativePath = workspaceFolder
-          ? vscode.workspace.asRelativePath(file)
-          : file;
-
         return {
-          label: getBasename(file),
-          detail: getLastNPathParts(file, 2),
-          insertText: relativePath,
+          label: getUriPathBasename(file.id),
+          detail: getLastNPathParts(file.relativePath, 2),
+          insertText: file.relativePath,
           kind: vscode.CompletionItemKind.File,
         };
       }),
diff --git a/extensions/vscode/src/util/FileSearch.ts b/extensions/vscode/src/util/FileSearch.ts
index cc02f3128c..efb065d7f2 100644
--- a/extensions/vscode/src/util/FileSearch.ts
+++ b/extensions/vscode/src/util/FileSearch.ts
@@ -4,19 +4,23 @@ import { walkDir } from "core/indexing/walkDir";
 import MiniSearch from "minisearch";
 import * as vscode from "vscode";
 
-type FileMiniSearchResult = { filename: string };
+type FileMiniSearchResult = { relativePath: string; id: string };
 
+/*
+  id = file URI
+*/
 export class FileSearch {
   constructor(private readonly ide: IDE) {
     this.initializeFileSearchState();
   }
 
   private miniSearch = new MiniSearch({
-    fields: ["filename"],
-    storeFields: ["filename"],
+    fields: ["relativePath", "id"],
+    storeFields: ["relativePath", "id"],
     searchOptions: {
       prefix: true,
       fuzzy: 2,
+      fields: ["relativePath"],
     },
   });
   private async initializeFileSearchState() {
@@ -28,12 +32,12 @@ export class FileSearch {
       }),
     );
 
-    const filenames = results.flat().map((file) => ({
-      id: file,
-      filename: vscode.workspace.asRelativePath(file),
-    }));
-
-    this.miniSearch.addAll(filenames);
+    this.miniSearch.addAll(
+      results.flat().map((file) => ({
+        id: file,
+        relativePath: vscode.workspace.asRelativePath(file),
+      })),
+    );
   }
 
   public search(query: string): FileMiniSearchResult[] {
diff --git a/extensions/vscode/src/util/ideUtils.ts b/extensions/vscode/src/util/ideUtils.ts
index 4cc694dc8a..4b6e723125 100644
--- a/extensions/vscode/src/util/ideUtils.ts
+++ b/extensions/vscode/src/util/ideUtils.ts
@@ -14,7 +14,7 @@ import {
 
 import { getUniqueId, openEditorAndRevealRange } from "./vscode";
 
-import type { FileEdit, Range, RangeInFile, Thread } from "core";
+import type { Range, RangeInFile, Thread } from "core";
 import { isUriWithinDirectory } from "core/util/uri";
 
 const util = require("node:util");
@@ -217,11 +217,11 @@ export class VsCodeIdeUtils {
       .filter((uri) => this.documentIsCode(uri)); // Filter out undesired documents
   }
 
-  getVisibleFiles(): string[] {
+  getVisibleFiles(): vscode.Uri[] {
     return vscode.window.visibleTextEditors
       .filter((editor) => this.documentIsCode(editor.document.uri))
       .map((editor) => {
-        return editor.document.uri.fsPath;
+        return editor.document.uri;
       });
   }
 

From ddfcefa036553cf8370847e3b3a1da3874fc95f9 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Tue, 10 Dec 2024 20:34:21 -0800
Subject: [PATCH 11/84] path -> ide part 243243

---
 core/indexing/chunk/chunk.test.ts             |  8 ++++----
 core/indexing/chunk/chunk.ts                  | 16 +++++++---------
 .../vscode/src/autocomplete/recentlyEdited.ts |  7 ++-----
 extensions/vscode/src/commands.ts             |  5 ++---
 .../vscode/src/extension/VsCodeMessenger.ts   |  5 ++---
 .../src/quickEdit/QuickEditQuickPick.ts       |  7 +++----
 .../src/test/test-suites/ideUtils.test.ts     | 19 ++++++++-----------
 extensions/vscode/src/util/util.ts            | 10 ----------
 8 files changed, 28 insertions(+), 49 deletions(-)

diff --git a/core/indexing/chunk/chunk.test.ts b/core/indexing/chunk/chunk.test.ts
index 85683a51e8..7a8ce6f76d 100644
--- a/core/indexing/chunk/chunk.test.ts
+++ b/core/indexing/chunk/chunk.test.ts
@@ -6,25 +6,25 @@ describe("shouldChunk", () => {
   test("should chunk a typescript file", () => {
     const filePath = path.join("directory", "file.ts");
     const fileContent = generateString(10000);
-    expect(shouldChunk(path.sep, filePath, fileContent)).toBe(true);
+    expect(shouldChunk(filePath, fileContent)).toBe(true);
   });
 
   test("should not chunk a large typescript file", () => {
     const filePath = path.join("directory", "file.ts");
     const fileContent = generateString(1500000);
-    expect(shouldChunk(path.sep, filePath, fileContent)).toBe(false);
+    expect(shouldChunk(filePath, fileContent)).toBe(false);
   });
 
   test("should not chunk an empty file", () => {
     const filePath = path.join("directory", "file.ts");
     const fileContent = generateString(0);
-    expect(shouldChunk(path.sep, filePath, fileContent)).toBe(false);
+    expect(shouldChunk(filePath, fileContent)).toBe(false);
   });
 
   test("should not chunk a file without extension", () => {
     const filePath = path.join("directory", "with.dot", "filename");
     const fileContent = generateString(10000);
-    expect(shouldChunk(path.sep, filePath, fileContent)).toBe(false);
+    expect(shouldChunk(filePath, fileContent)).toBe(false);
   });
 });
 
diff --git a/core/indexing/chunk/chunk.ts b/core/indexing/chunk/chunk.ts
index 142f75b28d..20a2a19b00 100644
--- a/core/indexing/chunk/chunk.ts
+++ b/core/indexing/chunk/chunk.ts
@@ -3,7 +3,7 @@ import { countTokensAsync } from "../../llm/countTokens.js";
 import { extractMinimalStackTraceInfo } from "../../util/extractMinimalStackTraceInfo.js";
 import { Telemetry } from "../../util/posthog.js";
 import { supportedLanguages } from "../../util/treeSitter.js";
-import { getUriPathBasename } from "../../util/uri.js";
+import { getUriFileExtension, getUriPathBasename } from "../../util/uri.js";
 
 import { basicChunker } from "./basic.js";
 import { codeChunker } from "./code.js";
@@ -16,25 +16,23 @@ export type ChunkDocumentParam = {
 };
 
 async function* chunkDocumentWithoutId(
-  filepath: string,
+  fileUri: string,
   contents: string,
   maxChunkSize: number,
 ): AsyncGenerator {
   if (contents.trim() === "") {
     return;
   }
-
-  const segs = filepath.split(".");
-  const ext = segs[segs.length - 1];
-  if (ext in supportedLanguages) {
+  const extension = getUriFileExtension(fileUri);
+  if (extension in supportedLanguages) {
     try {
-      for await (const chunk of codeChunker(filepath, contents, maxChunkSize)) {
+      for await (const chunk of codeChunker(fileUri, contents, maxChunkSize)) {
         yield chunk;
       }
       return;
     } catch (e: any) {
-      Telemetry.capture("code_chunker_error", {
-        fileExtension: ext,
+      void Telemetry.capture("code_chunker_error", {
+        fileExtension: extension,
         stack: extractMinimalStackTraceInfo(e.stack),
       });
       // falls back to basicChunker
diff --git a/extensions/vscode/src/autocomplete/recentlyEdited.ts b/extensions/vscode/src/autocomplete/recentlyEdited.ts
index 221d7df663..6ab614c90a 100644
--- a/extensions/vscode/src/autocomplete/recentlyEdited.ts
+++ b/extensions/vscode/src/autocomplete/recentlyEdited.ts
@@ -23,9 +23,6 @@ export class RecentlyEditedTracker {
 
   constructor() {
     vscode.workspace.onDidChangeTextDocument((event) => {
-      if (event.document.uri.scheme !== "file") {
-        return;
-      }
       event.contentChanges.forEach((change) => {
         const editedRange = {
           uri: event.document.uri,
@@ -123,7 +120,7 @@ export class RecentlyEditedTracker {
     return this.recentlyEditedRanges.map((entry) => {
       return {
         ...entry,
-        filepath: entry.uri.fsPath,
+        filepath: entry.uri.toString(),
       };
     });
   }
@@ -140,7 +137,7 @@ export class RecentlyEditedTracker {
           const lines = contents.split("\n");
 
           return {
-            filepath: entry.uri.fsPath,
+            filepath: entry.uri.toString(),
             contents,
             range: {
               start: { line: 0, character: 0 },
diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts
index 3706cf79a4..bdc63ddecc 100644
--- a/extensions/vscode/src/commands.ts
+++ b/extensions/vscode/src/commands.ts
@@ -32,7 +32,6 @@ import { VerticalDiffManager } from "./diff/vertical/manager";
 import EditDecorationManager from "./quickEdit/EditDecorationManager";
 import { QuickEdit, QuickEditShowParams } from "./quickEdit/QuickEditQuickPick";
 import { Battery } from "./util/battery";
-import { getFullyQualifiedPath } from "./util/util";
 import { VsCodeIde } from "./VsCodeIde";
 
 import type { VsCodeWebviewProtocol } from "./webviewProtocol";
@@ -231,7 +230,7 @@ async function processDiff(
   diffManager: DiffManager,
   ide: VsCodeIde,
   verticalDiffManager: VerticalDiffManager,
-  newFilepath?: string | vscode.Uri,
+  newFilepath?: vscode.Uri,
   streamId?: string,
 ) {
   captureCommandTelemetry(`${action}Diff`);
@@ -241,7 +240,7 @@ async function processDiff(
   if (fullPath instanceof vscode.Uri) {
     fullPath = fullPath.fsPath;
   } else if (fullPath) {
-    fullPath = getFullyQualifiedPath(ide, fullPath);
+    fullPath = resolveF(ide, fullPath);
   } else {
     const curFile = await ide.getCurrentFile();
     fullPath = curFile?.path;
diff --git a/extensions/vscode/src/extension/VsCodeMessenger.ts b/extensions/vscode/src/extension/VsCodeMessenger.ts
index 3f201f0165..244777fa81 100644
--- a/extensions/vscode/src/extension/VsCodeMessenger.ts
+++ b/extensions/vscode/src/extension/VsCodeMessenger.ts
@@ -16,7 +16,6 @@ import {
   CORE_TO_WEBVIEW_PASS_THROUGH,
   WEBVIEW_TO_CORE_PASS_THROUGH,
 } from "core/protocol/passThrough";
-import { getBasename } from "core/util";
 import { InProcessMessenger, Message } from "core/protocol/messenger";
 import * as vscode from "vscode";
 
@@ -27,10 +26,10 @@ import {
   getControlPlaneSessionInfo,
   WorkOsAuthProvider,
 } from "../stubs/WorkOsAuthProvider";
-import { getFullyQualifiedPath } from "../util/util";
 import { getExtensionUri } from "../util/vscode";
 import { VsCodeIde } from "../VsCodeIde";
 import { VsCodeWebviewProtocol } from "../webviewProtocol";
+import { getUriPathBasename } from "core/util/uri";
 
 /**
  * A shared messenger class between Core and Webview
@@ -246,7 +245,7 @@ export class VsCodeMessenger {
       const [instant, diffLines] = await applyCodeBlock(
         editor.document.getText(),
         data.text,
-        getBasename(editor.document.fileName),
+        getUriPathBasename(editor.document.uri.toString()),
         llm,
         fastLlm,
       );
diff --git a/extensions/vscode/src/quickEdit/QuickEditQuickPick.ts b/extensions/vscode/src/quickEdit/QuickEditQuickPick.ts
index 4b59dcbda2..ce4a1b6458 100644
--- a/extensions/vscode/src/quickEdit/QuickEditQuickPick.ts
+++ b/extensions/vscode/src/quickEdit/QuickEditQuickPick.ts
@@ -17,7 +17,6 @@ import { getModelQuickPickVal } from "./ModelSelectionQuickPick";
 // @ts-ignore - error finding typings
 // @ts-ignore
 
-
 /**
  * Used to track what action to take after a user interacts
  * with the initial Quick Pick
@@ -133,11 +132,11 @@ export class QuickEdit {
     }
 
     const hasChanges = !!this.verticalDiffManager.getHandlerForFile(
-      editor.document.uri.fsPath,
+      editor.document.uri.toString(),
     );
 
     if (hasChanges) {
-      this.openAcceptRejectMenu("", editor.document.uri.fsPath);
+      this.openAcceptRejectMenu("", editor.document.uri.toString());
     } else {
       await this.initiateNewQuickPick(editor, params);
     }
@@ -185,7 +184,7 @@ export class QuickEdit {
     });
 
     if (prompt) {
-      await this.handleUserPrompt(prompt, editor.document.uri.fsPath);
+      await this.handleUserPrompt(prompt, editor.document.uri.toString());
     }
   }
 
diff --git a/extensions/vscode/src/test/test-suites/ideUtils.test.ts b/extensions/vscode/src/test/test-suites/ideUtils.test.ts
index 3a289a0794..37ba5cc335 100644
--- a/extensions/vscode/src/test/test-suites/ideUtils.test.ts
+++ b/extensions/vscode/src/test/test-suites/ideUtils.test.ts
@@ -10,6 +10,9 @@ import { testWorkspacePath } from "../runner/runTestOnVSCodeHost";
 const util = require("node:util");
 const asyncExec = util.promisify(require("node:child_process").exec);
 
+/*
+  TODO check uri => path assumptions, will only work in some environments.
+*/
 describe("IDE Utils", () => {
   const utils = new VsCodeIdeUtils();
   const testPyPath = path.join(testWorkspacePath, "test.py");
@@ -17,22 +20,16 @@ describe("IDE Utils", () => {
 
   test("getWorkspaceDirectories", async () => {
     const [dir] = utils.getWorkspaceDirectories();
-    assert(dir.endsWith("test-workspace"));
+    assert(dir.toString().endsWith("test-workspace"));
   });
 
   test("fileExists", async () => {
     const exists2 = await utils.fileExists(
-      path.join(testWorkspacePath, "test.py"),
+      vscode.Uri.file(path.join(testWorkspacePath, "test.py")),
     );
     assert(exists2);
   });
 
-  test("getAbsolutePath", async () => {
-    const groundTruth = path.join(testWorkspacePath, "test.py");
-    assert(utils.getAbsolutePath("test.py") === groundTruth);
-    assert(utils.getAbsolutePath(groundTruth) === groundTruth);
-  });
-
   test("getOpenFiles", async () => {
     let openFiles = utils.getOpenFiles();
     assert(openFiles.length === 0);
@@ -43,7 +40,7 @@ describe("IDE Utils", () => {
     });
     openFiles = utils.getOpenFiles();
     assert(openFiles.length === 1);
-    assert(openFiles[0] === testPyPath);
+    assert(openFiles[0].fsPath === testPyPath);
 
     document = await vscode.workspace.openTextDocument(testJsPath);
     await vscode.window.showTextDocument(document, {
@@ -51,8 +48,8 @@ describe("IDE Utils", () => {
     });
     openFiles = utils.getOpenFiles();
     assert(openFiles.length === 2);
-    assert(openFiles.includes(testPyPath));
-    assert(openFiles.includes(testJsPath));
+    assert(openFiles.find((f) => f.fsPath === testPyPath));
+    assert(openFiles.find((f) => f.fsPath === testJsPath));
   });
 
   test("getUniqueId", async () => {
diff --git a/extensions/vscode/src/util/util.ts b/extensions/vscode/src/util/util.ts
index 48777fa072..e8a6cd0b46 100644
--- a/extensions/vscode/src/util/util.ts
+++ b/extensions/vscode/src/util/util.ts
@@ -124,13 +124,3 @@ export function getExtensionVersion(): string {
   const extension = vscode.extensions.getExtension("continue.continue");
   return extension?.packageJSON.version || "0.1.0";
 }
-
-export function getFullyQualifiedPath(ide: VsCodeIde, filepath: string) {
-  if (ide.ideUtils.path.isAbsolute(filepath)) {return filepath;}
-
-  const workspaceFolders = vscode.workspace.workspaceFolders;
-
-  if (workspaceFolders && workspaceFolders.length > 0) {
-    return ide.ideUtils.path.join(workspaceFolders[0].uri.fsPath, filepath);
-  }
-}

From 3a99509915d43b410ed0d10a3b983980479d6ae5 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Tue, 10 Dec 2024 20:50:18 -0800
Subject: [PATCH 12/84] path to uri vscode continued

---
 .../src/autocomplete/completionProvider.ts    | 17 +++---
 extensions/vscode/src/autocomplete/lsp.ts     |  2 +-
 extensions/vscode/src/commands.ts             | 55 ++++++++-----------
 3 files changed, 33 insertions(+), 41 deletions(-)

diff --git a/extensions/vscode/src/autocomplete/completionProvider.ts b/extensions/vscode/src/autocomplete/completionProvider.ts
index f9d397388c..c5366572ea 100644
--- a/extensions/vscode/src/autocomplete/completionProvider.ts
+++ b/extensions/vscode/src/autocomplete/completionProvider.ts
@@ -18,6 +18,7 @@ import {
   setupStatusBar,
   stopStatusBarLoading,
 } from "./statusBar";
+import URI from "uri-js";
 
 import type { IDE } from "core";
 import type { TabAutocompleteModel } from "../util/loadAutocompleteModel";
@@ -76,12 +77,6 @@ export class ContinueCompletionProvider
       this.onError.bind(this),
       getDefinitionsFromLsp,
     );
-
-    vscode.workspace.onDidChangeTextDocument((event) => {
-      if (event.document.uri.fsPath === this._lastShownCompletion?.filepath) {
-        // console.log("updating completion");
-      }
-    });
   }
 
   _lastShownCompletion: AutocompleteOutcome | undefined;
@@ -167,7 +162,9 @@ export class ContinueCompletionProvider
         const notebook = vscode.workspace.notebookDocuments.find((notebook) =>
           notebook
             .getCells()
-            .some((cell) => cell.document.uri === document.uri),
+            .some((cell) =>
+              URI.equal(cell.document.uri.toString(), document.uri.toString()),
+            ),
         );
         if (notebook) {
           const cells = notebook.getCells();
@@ -182,7 +179,9 @@ export class ContinueCompletionProvider
             })
             .join("\n\n");
           for (const cell of cells) {
-            if (cell.document.uri === document.uri) {
+            if (
+              URI.equal(cell.document.uri.toString(), document.uri.toString())
+            ) {
               break;
             } else {
               pos.line += cell.document.getText().split("\n").length + 1;
@@ -195,7 +194,7 @@ export class ContinueCompletionProvider
 
       const input: AutocompleteInput = {
         completionId: uuidv4(),
-        filepath: document.uri.fsPath,
+        filepath: document.uri.toString(),
         pos,
         recentlyEditedFiles: [],
         recentlyEditedRanges:
diff --git a/extensions/vscode/src/autocomplete/lsp.ts b/extensions/vscode/src/autocomplete/lsp.ts
index a5b5267f50..18ec44ce17 100644
--- a/extensions/vscode/src/autocomplete/lsp.ts
+++ b/extensions/vscode/src/autocomplete/lsp.ts
@@ -360,7 +360,7 @@ export const getDefinitionsFromLsp: GetLspDefinitionsFunction = async (
     const results: RangeInFileWithContents[] = [];
     for (const node of treePath.reverse()) {
       const definitions = await getDefinitionsForNode(
-        filepath,
+        vscode.Uri.parse(filepath),
         node,
         ide,
         lang,
diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts
index bdc63ddecc..9d443f3c36 100644
--- a/extensions/vscode/src/commands.ts
+++ b/extensions/vscode/src/commands.ts
@@ -69,7 +69,7 @@ function addCodeToContextFromRange(
   }
 
   const rangeInFileWithContents = {
-    filepath: document.uri.fsPath,
+    filepath: document.uri.toString(),
     contents: document.getText(range),
     range: {
       start: {
@@ -99,7 +99,7 @@ function getRangeInFileWithContents(
 
   if (editor) {
     const selection = editor.selection;
-    const filepath = editor.document.uri.fsPath;
+    const filepath = editor.document.uri.toString();
 
     if (range) {
       const contents = editor.document.getText(range);
@@ -130,7 +130,7 @@ function getRangeInFileWithContents(
     const contents = editor.document.getText(selectionRange);
 
     return {
-      filepath: editor.document.uri.fsPath,
+      filepath,
       contents,
       range: {
         start: {
@@ -181,7 +181,7 @@ async function addEntireFileToContext(
   // Get the contents of the file
   const contents = (await vscode.workspace.fs.readFile(filepath)).toString();
   const rangeInFileWithContents = {
-    filepath: filepath.fsPath,
+    filepath: filepath.toString(),
     contents: contents,
     range: {
       start: {
@@ -230,39 +230,33 @@ async function processDiff(
   diffManager: DiffManager,
   ide: VsCodeIde,
   verticalDiffManager: VerticalDiffManager,
-  newFilepath?: vscode.Uri,
+  newFileUri?: vscode.Uri,
   streamId?: string,
 ) {
   captureCommandTelemetry(`${action}Diff`);
 
-  let fullPath = newFilepath;
-
-  if (fullPath instanceof vscode.Uri) {
-    fullPath = fullPath.fsPath;
-  } else if (fullPath) {
-    fullPath = resolveF(ide, fullPath);
-  } else {
-    const curFile = await ide.getCurrentFile();
-    fullPath = curFile?.path;
+  let newOrCurrentUri = newFileUri?.toString();
+  if (!newOrCurrentUri) {
+    const currentFile = await ide.getCurrentFile();
+    newOrCurrentUri = currentFile?.path;
   }
-
-  if (!fullPath) {
+  if (!newOrCurrentUri) {
     console.warn(
-      `Unable to resolve filepath while attempting to resolve diff: ${newFilepath}`,
+      `No file provided or current file open while attempting to resolve diff`,
     );
     return;
   }
 
-  await ide.openFile(fullPath);
+  await ide.openFile(newOrCurrentUri);
 
   // Clear vertical diffs depending on action
-  verticalDiffManager.clearForFilepath(fullPath, action === "accept");
+  verticalDiffManager.clearForfileUri(newOrCurrentUri, action === "accept");
 
   // Accept or reject the diff
   if (action === "accept") {
-    await diffManager.acceptDiff(fullPath);
+    await diffManager.acceptDiff(newOrCurrentUri);
   } else {
-    await diffManager.rejectDiff(fullPath);
+    await diffManager.rejectDiff(newOrCurrentUri);
   }
 
   void sidebar.webviewProtocol.request("setEditStatus", {
@@ -270,11 +264,11 @@ async function processDiff(
   });
 
   if (streamId) {
-    const fileContent = await ide.readFile(fullPath);
+    const fileContent = await ide.readFile(newOrCurrentUri);
 
     await sidebar.webviewProtocol.request("updateApplyState", {
       fileContent,
-      filepath: fullPath,
+      filepath: newOrCurrentUri,
       streamId,
       status: "closed",
       numDiffs: 0,
@@ -376,13 +370,13 @@ const getCommandsMap: (
         newFilepath,
         streamId,
       ),
-    "continue.acceptVerticalDiffBlock": (filepath?: string, index?: number) => {
+    "continue.acceptVerticalDiffBlock": (fileUri?: string, index?: number) => {
       captureCommandTelemetry("acceptVerticalDiffBlock");
-      verticalDiffManager.acceptRejectVerticalDiffBlock(true, filepath, index);
+      verticalDiffManager.acceptRejectVerticalDiffBlock(true, fileUri, index);
     },
-    "continue.rejectVerticalDiffBlock": (filepath?: string, index?: number) => {
+    "continue.rejectVerticalDiffBlock": (fileUri?: string, index?: number) => {
       captureCommandTelemetry("rejectVerticalDiffBlock");
-      verticalDiffManager.acceptRejectVerticalDiffBlock(false, filepath, index);
+      verticalDiffManager.acceptRejectVerticalDiffBlock(false, fileUri, index);
     },
     "continue.quickFix": async (
       range: vscode.Range,
@@ -571,11 +565,10 @@ const getCommandsMap: (
           rangeInFileWithContents,
         );
       } else {
-        const filepath = document.uri.fsPath;
         const contents = document.getText();
 
         sidebar.webviewProtocol?.request("addCodeToEdit", {
-          filepath,
+          filepath: document.uri.toString(),
           contents,
         });
       }
@@ -790,9 +783,9 @@ const getCommandsMap: (
           .stat(uri)
           ?.then((stat) => stat.type === vscode.FileType.Directory);
         if (isDirectory) {
-          for await (const filepath of walkDirAsync(uri.fsPath, ide)) {
+          for await (const filepath of walkDirAsync(uri.toString(), ide)) {
             addEntireFileToContext(
-              uriFromFilePath(filepath),
+              vscode.Uri.parse(filepath),
               sidebar.webviewProtocol,
             );
           }

From fe47f45ddf3cd55c2435535281bf2e41fa7a1ead Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Tue, 10 Dec 2024 21:27:45 -0800
Subject: [PATCH 13/84] path -> uri extensions/vscode

---
 core/core.ts                                              | 1 +
 extensions/vscode/src/ContinueGUIWebviewViewProvider.ts   | 4 ++--
 extensions/vscode/src/activation/languageClient.ts        | 3 +--
 extensions/vscode/src/extension/autocomplete.ts           | 0
 extensions/vscode/src/extension/indexing.ts               | 0
 .../codeLens/providers/ConfigPyCodeLensProvider.ts        | 5 +----
 .../codeLens/providers/DiffViewerCodeLensProvider.ts      | 8 +++++---
 .../codeLens/providers/TutorialCodeLensProvider.ts        | 2 --
 .../codeLens/providers/VerticalPerLineCodeLensProvider.ts | 8 ++++----
 extensions/vscode/src/quickEdit/index.ts                  | 0
 extensions/vscode/src/util/util.ts                        | 2 --
 11 files changed, 14 insertions(+), 19 deletions(-)
 delete mode 100644 extensions/vscode/src/extension/autocomplete.ts
 delete mode 100644 extensions/vscode/src/extension/indexing.ts
 delete mode 100644 extensions/vscode/src/lang-server/codeLens/providers/TutorialCodeLensProvider.ts
 delete mode 100644 extensions/vscode/src/quickEdit/index.ts

diff --git a/core/core.ts b/core/core.ts
index eadb027456..3b11364512 100644
--- a/core/core.ts
+++ b/core/core.ts
@@ -729,6 +729,7 @@ export class Core {
       if (data?.uris?.length) {
         for (const uri of data.uris) {
           // Listen for file changes in the workspace
+          // URI TODO is this equality statement valid?
           const filePath = getFullPath(uri);
           if (filePath === getConfigJsonPath()) {
             // Trigger a toast notification to provide UI feedback that config
diff --git a/extensions/vscode/src/ContinueGUIWebviewViewProvider.ts b/extensions/vscode/src/ContinueGUIWebviewViewProvider.ts
index f98b09f93e..66ff97f1c2 100644
--- a/extensions/vscode/src/ContinueGUIWebviewViewProvider.ts
+++ b/extensions/vscode/src/ContinueGUIWebviewViewProvider.ts
@@ -216,8 +216,8 @@ export class ContinueGUIWebviewViewProvider
         
         
         
         
diff --git a/extensions/vscode/src/activation/languageClient.ts b/extensions/vscode/src/activation/languageClient.ts
index c3b4b96c59..61babbc088 100644
--- a/extensions/vscode/src/activation/languageClient.ts
+++ b/extensions/vscode/src/activation/languageClient.ts
@@ -55,7 +55,7 @@ function startPythonLanguageServer(context: ExtensionContext): LanguageClient {
   const command = `cd ${path.join(
     extensionPath,
     "scripts",
-  )} && source env/bin/activate.fish && python -m pyls`;
+  )} && source ${path.join("env", "bin", "activate.fish")} && python -m pyls`;
   const serverOptions: ServerOptions = {
     command: command,
     args: ["-vv"],
@@ -113,4 +113,3 @@ async function startPylance(context: ExtensionContext) {
   );
   return client;
 }
-
diff --git a/extensions/vscode/src/extension/autocomplete.ts b/extensions/vscode/src/extension/autocomplete.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/extensions/vscode/src/extension/indexing.ts b/extensions/vscode/src/extension/indexing.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/extensions/vscode/src/lang-server/codeLens/providers/ConfigPyCodeLensProvider.ts b/extensions/vscode/src/lang-server/codeLens/providers/ConfigPyCodeLensProvider.ts
index 469426cfb2..c0cb8f6cc2 100644
--- a/extensions/vscode/src/lang-server/codeLens/providers/ConfigPyCodeLensProvider.ts
+++ b/extensions/vscode/src/lang-server/codeLens/providers/ConfigPyCodeLensProvider.ts
@@ -9,10 +9,7 @@ export class ConfigPyCodeLensProvider implements vscode.CodeLensProvider {
   ): vscode.CodeLens[] | Thenable {
     const codeLenses: vscode.CodeLens[] = [];
 
-    if (
-      !document.uri.fsPath.endsWith(".continue/config.json") &&
-      !document.uri.fsPath.endsWith(".continue\\config.json")
-    ) {
+    if (!document.uri.path.endsWith(".continue/config.json")) {
       return codeLenses;
     }
 
diff --git a/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts b/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts
index f3ef5c67a1..dba240912c 100644
--- a/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts
+++ b/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts
@@ -15,7 +15,9 @@ export class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
     if (path.dirname(document.uri.fsPath) === DIFF_DIRECTORY) {
       const codeLenses: vscode.CodeLens[] = [];
       let range = new vscode.Range(0, 0, 1, 0);
-      const diffInfo = this.diffManager.diffAtNewFilepath(document.uri.fsPath);
+      const diffInfo = this.diffManager.diffAtNewFilepath(
+        document.uri.toString(),
+      );
       if (diffInfo) {
         range = diffInfo.range;
       }
@@ -23,12 +25,12 @@ export class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
         new vscode.CodeLens(range, {
           title: `Accept All ✅ (${getMetaKeyLabel()}⇧⏎)`,
           command: "continue.acceptDiff",
-          arguments: [document.uri.fsPath],
+          arguments: [document.uri.toString()],
         }),
         new vscode.CodeLens(range, {
           title: `Reject All ❌ (${getMetaKeyLabel()}⇧⌫)`,
           command: "continue.rejectDiff",
-          arguments: [document.uri.fsPath],
+          arguments: [document.uri.toString()],
         }),
       );
       return codeLenses;
diff --git a/extensions/vscode/src/lang-server/codeLens/providers/TutorialCodeLensProvider.ts b/extensions/vscode/src/lang-server/codeLens/providers/TutorialCodeLensProvider.ts
deleted file mode 100644
index 139597f9cb..0000000000
--- a/extensions/vscode/src/lang-server/codeLens/providers/TutorialCodeLensProvider.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/extensions/vscode/src/lang-server/codeLens/providers/VerticalPerLineCodeLensProvider.ts b/extensions/vscode/src/lang-server/codeLens/providers/VerticalPerLineCodeLensProvider.ts
index 5d07e6ce23..52fd07f793 100644
--- a/extensions/vscode/src/lang-server/codeLens/providers/VerticalPerLineCodeLensProvider.ts
+++ b/extensions/vscode/src/lang-server/codeLens/providers/VerticalPerLineCodeLensProvider.ts
@@ -24,8 +24,8 @@ export class VerticalDiffCodeLensProvider implements vscode.CodeLensProvider {
     document: vscode.TextDocument,
     _: vscode.CancellationToken,
   ): vscode.CodeLens[] | Thenable {
-    const filepath = document.uri.fsPath;
-    const blocks = this.editorToVerticalDiffCodeLens.get(filepath);
+    const uri = document.uri.toString();
+    const blocks = this.editorToVerticalDiffCodeLens.get(uri);
 
     if (!blocks) {
       return [];
@@ -45,12 +45,12 @@ export class VerticalDiffCodeLensProvider implements vscode.CodeLensProvider {
         new vscode.CodeLens(range, {
           title: `Accept`,
           command: "continue.acceptVerticalDiffBlock",
-          arguments: [filepath, i],
+          arguments: [uri, i],
         }),
         new vscode.CodeLens(range, {
           title: `Reject`,
           command: "continue.rejectVerticalDiffBlock",
-          arguments: [filepath, i],
+          arguments: [uri, i],
         }),
       );
 
diff --git a/extensions/vscode/src/quickEdit/index.ts b/extensions/vscode/src/quickEdit/index.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/extensions/vscode/src/util/util.ts b/extensions/vscode/src/util/util.ts
index e8a6cd0b46..6691a66d20 100644
--- a/extensions/vscode/src/util/util.ts
+++ b/extensions/vscode/src/util/util.ts
@@ -1,7 +1,5 @@
 import * as vscode from "vscode";
 
-import { VsCodeIde } from "../VsCodeIde";
-
 const os = require("node:os");
 
 function charIsEscapedAtIndex(index: number, str: string): boolean {

From 3d37c1e1bf0cd20edcdb106c884e5adc7807d2ca Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Wed, 11 Dec 2024 12:29:50 -0800
Subject: [PATCH 14/84] path -> uri: all files have been triaged

---
 core/autocomplete/prefiltering/index.ts | 21 ++----------
 core/index.d.ts                         | 20 +++++------
 core/protocol/messenger/messageIde.ts   | 45 ++++++++++++++-----------
 core/test/testDir.ts                    | 32 +++++++++++++-----
 core/util/devdata.ts                    |  4 +--
 core/util/filesystem.ts                 | 40 +++++++++++++---------
 core/util/uri.ts                        | 37 ++++++++++++++++++++
 extensions/vscode/src/VsCodeIde.ts      | 30 ++++++++---------
 8 files changed, 138 insertions(+), 91 deletions(-)

diff --git a/core/autocomplete/prefiltering/index.ts b/core/autocomplete/prefiltering/index.ts
index 40ffa5384b..59905870ee 100644
--- a/core/autocomplete/prefiltering/index.ts
+++ b/core/autocomplete/prefiltering/index.ts
@@ -3,9 +3,9 @@ import path from "node:path";
 import ignore from "ignore";
 
 import { IDE } from "../..";
-import { getBasename } from "../../util";
 import { getConfigJsonPath } from "../../util/paths";
 import { HelperVars } from "../util/HelperVars";
+import { getRelativePath } from "../../util/uri";
 
 async function isDisabledForFile(
   currentFilepath: string,
@@ -15,26 +15,11 @@ async function isDisabledForFile(
   if (disableInFiles) {
     // Relative path needed for `ignore`
     const workspaceDirs = await ide.getWorkspaceDirs();
-    let filepath = currentFilepath;
-    for (const workspaceDir of workspaceDirs) {
-      const relativePath = path.relative(workspaceDir, filepath);
-      const relativePathBase = relativePath.split(path.sep).at(0);
-      const isInWorkspace =
-        !path.isAbsolute(relativePath) && relativePathBase !== "..";
-      if (isInWorkspace) {
-        filepath = path.relative(workspaceDir, filepath);
-        break;
-      }
-    }
-
-    // Worst case we can check filetype glob patterns
-    if (filepath === currentFilepath) {
-      filepath = getBasename(filepath);
-    }
+    const relativePath = getRelativePath(currentFilepath, workspaceDirs);
 
     // @ts-ignore
     const pattern = ignore.default().add(disableInFiles);
-    if (pattern.ignores(filepath)) {
+    if (pattern.ignores(relativePath)) {
       return true;
     }
   }
diff --git a/core/index.d.ts b/core/index.d.ts
index 9cd6973211..d1f2b28fd4 100644
--- a/core/index.d.ts
+++ b/core/index.d.ts
@@ -611,7 +611,7 @@ export interface IDE {
 
   getWorkspaceConfigs(): Promise;
 
-  fileExists(filepath: string): Promise;
+  fileExists(fileUri: string): Promise;
 
   writeFile(path: string, contents: string): Promise;
 
@@ -625,20 +625,16 @@ export interface IDE {
 
   runCommand(command: string): Promise;
 
-  saveFile(filepath: string): Promise;
+  saveFile(fileUri: string): Promise;
 
-  readFile(filepath: string): Promise;
+  readFile(fileUri: string): Promise;
 
-  readRangeInFile(filepath: string, range: Range): Promise;
+  readRangeInFile(fileUri: string, range: Range): Promise;
 
-  showLines(
-    filepath: string,
-    startLine: number,
-    endLine: number,
-  ): Promise;
+  showLines(fileUri: string, startLine: number, endLine: number): Promise;
 
   showDiff(
-    filepath: string,
+    fileUri: string,
     newContents: string,
     stepIndex: number,
   ): Promise;
@@ -660,7 +656,7 @@ export interface IDE {
 
   subprocess(command: string, cwd?: string): Promise<[string, string]>;
 
-  getProblems(filepath?: string | undefined): Promise;
+  getProblems(fileUri?: string | undefined): Promise;
 
   getBranch(dir: string): Promise;
 
@@ -686,7 +682,7 @@ export interface IDE {
   gotoDefinition(location: Location): Promise;
 
   // Callbacks
-  onDidChangeActiveTextEditor(callback: (filepath: string) => void): void;
+  onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void;
 }
 
 // Slash Commands
diff --git a/core/protocol/messenger/messageIde.ts b/core/protocol/messenger/messageIde.ts
index febdf559cd..d19012d6ee 100644
--- a/core/protocol/messenger/messageIde.ts
+++ b/core/protocol/messenger/messageIde.ts
@@ -27,16 +27,13 @@ export class MessageIde implements IDE {
     ) => void,
   ) {}
 
-  pathSep(): Promise {
-    return this.request("pathSep", undefined);
-  }
-  fileExists(filepath: string): Promise {
-    return this.request("fileExists", { filepath });
+  fileExists(fileUri: string): Promise {
+    return this.request("fileExists", { filepath: fileUri });
   }
   async gotoDefinition(location: Location): Promise {
     return this.request("gotoDefinition", { location });
   }
-  onDidChangeActiveTextEditor(callback: (filepath: string) => void): void {
+  onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void {
     this.on("didChangeActiveTextEditor", (data) => callback(data.filepath));
   }
 
@@ -126,11 +123,15 @@ export class MessageIde implements IDE {
   }
 
   async showLines(
-    filepath: string,
+    fileUri: string,
     startLine: number,
     endLine: number,
   ): Promise {
-    return await this.request("showLines", { filepath, startLine, endLine });
+    return await this.request("showLines", {
+      filepath: fileUri,
+      startLine,
+      endLine,
+    });
   }
 
   async listFolders(): Promise {
@@ -148,16 +149,16 @@ export class MessageIde implements IDE {
     return dir;
   }
 
-  async writeFile(path: string, contents: string): Promise {
-    await this.request("writeFile", { path, contents });
+  async writeFile(fileUri: string, contents: string): Promise {
+    await this.request("writeFile", { path: fileUri, contents });
   }
 
   async showVirtualFile(title: string, contents: string): Promise {
     await this.request("showVirtualFile", { name: title, content: contents });
   }
 
-  async openFile(path: string): Promise {
-    await this.request("openFile", { path });
+  async openFile(fileUri: string): Promise {
+    await this.request("openFile", { path: fileUri });
   }
 
   async openUrl(url: string): Promise {
@@ -168,18 +169,22 @@ export class MessageIde implements IDE {
     await this.request("runCommand", { command });
   }
 
-  async saveFile(filepath: string): Promise {
-    await this.request("saveFile", { filepath });
+  async saveFile(fileUri: string): Promise {
+    await this.request("saveFile", { filepath: fileUri });
   }
-  async readFile(filepath: string): Promise {
-    return await this.request("readFile", { filepath });
+  async readFile(fileUri: string): Promise {
+    return await this.request("readFile", { filepath: fileUri });
   }
   async showDiff(
-    filepath: string,
+    fileUri: string,
     newContents: string,
     stepIndex: number,
   ): Promise {
-    await this.request("showDiff", { filepath, newContents, stepIndex });
+    await this.request("showDiff", {
+      filepath: fileUri,
+      newContents,
+      stepIndex,
+    });
   }
 
   getOpenFiles(): Promise {
@@ -198,8 +203,8 @@ export class MessageIde implements IDE {
     return this.request("getSearchResults", { query });
   }
 
-  getProblems(filepath: string): Promise {
-    return this.request("getProblems", { filepath });
+  getProblems(fileUri: string): Promise {
+    return this.request("getProblems", { filepath: fileUri });
   }
 
   subprocess(command: string, cwd?: string): Promise<[string, string]> {
diff --git a/core/test/testDir.ts b/core/test/testDir.ts
index 7bc63105b4..dcd410b759 100644
--- a/core/test/testDir.ts
+++ b/core/test/testDir.ts
@@ -1,20 +1,27 @@
 import fs from "fs";
 import os from "os";
 import path from "path";
+import {
+  localPathOrUriToPath,
+  localPathToUri,
+  pathOrUriToUri,
+} from "../util/uri";
+import { fileURLToPath } from "url";
 
 // Want this outside of the git repository so we can change branches in tests
-export const TEST_DIR = path.join(os.tmpdir(), "testWorkspaceDir");
+const TEST_DIR_PATH = path.join(os.tmpdir(), "testWorkspaceDir");
+export const TEST_DIR = localPathToUri(TEST_DIR_PATH); // URI
 
 export function setUpTestDir() {
-  if (fs.existsSync(TEST_DIR)) {
-    fs.rmSync(TEST_DIR, { recursive: true });
+  if (fs.existsSync(TEST_DIR_PATH)) {
+    fs.rmSync(TEST_DIR_PATH, { recursive: true });
   }
-  fs.mkdirSync(TEST_DIR);
+  fs.mkdirSync(TEST_DIR_PATH);
 }
 
 export function tearDownTestDir() {
-  if (fs.existsSync(TEST_DIR)) {
-    fs.rmSync(TEST_DIR, { recursive: true });
+  if (fs.existsSync(TEST_DIR_PATH)) {
+    fs.rmSync(TEST_DIR_PATH, { recursive: true });
   }
 }
 
@@ -24,9 +31,18 @@ export function tearDownTestDir() {
   "index/index.ts" creates an empty index/index.ts
   ["index/index.ts", "hello"] creates index/index.ts with contents "hello"
 */
-export function addToTestDir(paths: (string | string[])[]) {
+export function addToTestDir(pathsOrUris: (string | string[])[]) {
+  // Allow tests to use URIs or local paths
+  const paths = pathsOrUris.map((val) => {
+    if (Array.isArray(val)) {
+      return [localPathOrUriToPath(val[0]), val[1]];
+    } else {
+      return localPathOrUriToPath(val);
+    }
+  });
+
   for (const p of paths) {
-    const filepath = path.join(TEST_DIR, Array.isArray(p) ? p[0] : p);
+    const filepath = path.join(TEST_DIR_PATH, Array.isArray(p) ? p[0] : p);
     fs.mkdirSync(path.dirname(filepath), { recursive: true });
 
     if (Array.isArray(p)) {
diff --git a/core/util/devdata.ts b/core/util/devdata.ts
index 12723e9159..3f786fa846 100644
--- a/core/util/devdata.ts
+++ b/core/util/devdata.ts
@@ -1,9 +1,9 @@
-import { writeFileSync } from "fs";
+import fs from "fs";
 
 import { getDevDataFilePath } from "./paths.js";
 
 export function logDevData(tableName: string, data: any) {
   const filepath: string = getDevDataFilePath(tableName);
   const jsonLine = JSON.stringify(data);
-  writeFileSync(filepath, `${jsonLine}\n`, { flag: "a" });
+  fs.writeFileSync(filepath, `${jsonLine}\n`, { flag: "a" });
 }
diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts
index 39f048119d..e11cf37ec6 100644
--- a/core/util/filesystem.ts
+++ b/core/util/filesystem.ts
@@ -17,6 +17,7 @@ import {
 import { GetGhTokenArgs } from "../protocol/ide.js";
 
 import { getContinueGlobalPath } from "./paths.js";
+import { fileURLToPath } from "node:url";
 
 class FileSystemIde implements IDE {
   constructor(private readonly workspaceDir: string) {}
@@ -27,14 +28,15 @@ class FileSystemIde implements IDE {
   ): Promise {
     return Promise.resolve();
   }
-  fileExists(filepath: string): Promise {
+  fileExists(fileUri: string): Promise {
+    const filepath = fileURLToPath(fileUri);
     return Promise.resolve(fs.existsSync(filepath));
   }
 
   gotoDefinition(location: Location): Promise {
     return Promise.resolve([]);
   }
-  onDidChangeActiveTextEditor(callback: (filepath: string) => void): void {
+  onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void {
     return;
   }
 
@@ -51,14 +53,17 @@ class FileSystemIde implements IDE {
   async getGitHubAuthToken(args: GetGhTokenArgs): Promise {
     return undefined;
   }
-  async getLastModified(files: string[]): Promise<{ [path: string]: number }> {
+  async getLastModified(
+    fileUris: string[],
+  ): Promise<{ [path: string]: number }> {
     const result: { [path: string]: number } = {};
-    for (const file of files) {
+    for (const uri of fileUris) {
       try {
-        const stats = fs.statSync(file);
-        result[file] = stats.mtimeMs;
+        const filepath = fileURLToPath(uri);
+        const stats = fs.statSync(filepath);
+        result[uri] = stats.mtimeMs;
       } catch (error) {
-        console.error(`Error getting last modified time for ${file}:`, error);
+        console.error(`Error getting last modified time for ${uri}:`, error);
       }
     }
     return result;
@@ -67,8 +72,9 @@ class FileSystemIde implements IDE {
     return Promise.resolve(dir);
   }
   async listDir(dir: string): Promise<[string, FileType][]> {
+    const filepath = fileURLToPath(dir);
     const all: [string, FileType][] = fs
-      .readdirSync(dir, { withFileTypes: true })
+      .readdirSync(filepath, { withFileTypes: true })
       .map((dirent: any) => [
         dirent.name,
         dirent.isDirectory()
@@ -105,7 +111,7 @@ class FileSystemIde implements IDE {
     });
   }
 
-  readRangeInFile(filepath: string, range: Range): Promise {
+  readRangeInFile(fileUri: string, range: Range): Promise {
     return Promise.resolve("");
   }
 
@@ -149,7 +155,7 @@ class FileSystemIde implements IDE {
   }
 
   showLines(
-    filepath: string,
+    fileUri: string,
     startLine: number,
     endLine: number,
   ): Promise {
@@ -164,9 +170,10 @@ class FileSystemIde implements IDE {
     return Promise.resolve([]);
   }
 
-  writeFile(path: string, contents: string): Promise {
+  writeFile(fileUri: string, contents: string): Promise {
+    const filepath = fileURLToPath(fileUri);
     return new Promise((resolve, reject) => {
-      fs.writeFile(path, contents, (err) => {
+      fs.writeFile(filepath, contents, (err) => {
         if (err) {
           reject(err);
         }
@@ -195,11 +202,12 @@ class FileSystemIde implements IDE {
     return Promise.resolve();
   }
 
-  saveFile(filepath: string): Promise {
+  saveFile(fileUri: string): Promise {
     return Promise.resolve();
   }
 
-  readFile(filepath: string): Promise {
+  readFile(fileUri: string): Promise {
+    const filepath = fileURLToPath(fileUri);
     return new Promise((resolve, reject) => {
       fs.readFile(filepath, "utf8", (err, contents) => {
         if (err) {
@@ -215,7 +223,7 @@ class FileSystemIde implements IDE {
   }
 
   showDiff(
-    filepath: string,
+    fileUri: string,
     newContents: string,
     stepIndex: number,
   ): Promise {
@@ -238,7 +246,7 @@ class FileSystemIde implements IDE {
     return "";
   }
 
-  async getProblems(filepath?: string | undefined): Promise {
+  async getProblems(fileUri?: string | undefined): Promise {
     return Promise.resolve([]);
   }
 
diff --git a/core/util/uri.ts b/core/util/uri.ts
index 1a00115829..f7e4b9fb4d 100644
--- a/core/util/uri.ts
+++ b/core/util/uri.ts
@@ -1,4 +1,5 @@
 import URI from "uri-js";
+import { fileURLToPath, pathToFileURL } from "url";
 
 export function getFullPath(uri: string): string {
   try {
@@ -9,6 +10,11 @@ export function getFullPath(uri: string): string {
   }
 }
 
+export function localPathToUri(path: string) {
+  const url = pathToFileURL(path);
+  return URI.normalize(url.toString());
+}
+
 export function pathToUriPathSegment(path: string) {
   // Converts any OS path to cleaned up URI path segment format with no leading/trailing slashes
   // e.g. \path\to\folder\ -> path/to/folder
@@ -30,6 +36,37 @@ export function getRelativePath(
   const fullPath = getFullPath(fileUri);
 }
 
+/*
+  
+*/
+export function localPathOrUriToPath(localPathOrUri: string): string {
+  try {
+    return fileURLToPath(localPathOrUri);
+  } catch (e) {
+    console.log("Converted url to path");
+    return localPathOrUri;
+  }
+}
+
+/*
+  To smooth out the transition from path to URI will use this function to warn when path is used
+*/
+export function pathOrUriToUri(
+  pathOrUri: string,
+  workspaceDirUris: string[],
+  showTraceOnPath = true,
+): string {
+  try {
+    // URI.parse(pathOrUri);
+
+    return pathOrUri;
+  } catch (e) {
+    if (showTraceOnPath) {
+      console.trace("Received relative path", e);
+    }
+  }
+}
+
 export function splitUriPath(uri: string): string[] {
   let parts = path.includes("/") ? path.split("/") : path.split("\\");
   if (withRoot !== undefined) {
diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts
index 5a0c988e1d..2f3a576724 100644
--- a/extensions/vscode/src/VsCodeIde.ts
+++ b/extensions/vscode/src/VsCodeIde.ts
@@ -265,9 +265,9 @@ class VsCodeIde implements IDE {
     });
   }
 
-  readRangeInFile(filepath: string, range: Range): Promise {
+  readRangeInFile(fileUri: string, range: Range): Promise {
     return this.ideUtils.readRangeInFile(
-      vscode.Uri.parse(filepath),
+      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),
@@ -378,9 +378,9 @@ class VsCodeIde implements IDE {
     return getContinueGlobalPath();
   }
 
-  async writeFile(path: string, contents: string): Promise {
+  async writeFile(fileUri: string, contents: string): Promise {
     await vscode.workspace.fs.writeFile(
-      vscode.Uri.parse(path),
+      vscode.Uri.parse(fileUri),
       Buffer.from(contents),
     );
   }
@@ -389,12 +389,12 @@ class VsCodeIde implements IDE {
     this.ideUtils.showVirtualFile(title, contents);
   }
 
-  async openFile(path: string): Promise {
-    await this.ideUtils.openFile(vscode.Uri.parse(path));
+  async openFile(fileUri: string): Promise {
+    await this.ideUtils.openFile(vscode.Uri.parse(fileUri));
   }
 
   async showLines(
-    filepath: string,
+    fileUri: string,
     startLine: number,
     endLine: number,
   ): Promise {
@@ -402,7 +402,7 @@ class VsCodeIde implements IDE {
       new vscode.Position(startLine, 0),
       new vscode.Position(endLine, 0),
     );
-    openEditorAndRevealRange(vscode.Uri.parse(filepath), range).then(
+    openEditorAndRevealRange(vscode.Uri.parse(fileUri), range).then(
       (editor) => {
         // Select the lines
         editor.selection = new vscode.Selection(
@@ -426,8 +426,8 @@ class VsCodeIde implements IDE {
     }
   }
 
-  async saveFile(filepath: string): Promise {
-    await this.ideUtils.saveFile(vscode.Uri.parse(filepath));
+  async saveFile(fileUri: string): Promise {
+    await this.ideUtils.saveFile(vscode.Uri.parse(fileUri));
   }
 
   private static MAX_BYTES = 100000;
@@ -482,11 +482,11 @@ class VsCodeIde implements IDE {
   }
 
   async showDiff(
-    filepath: string,
+    fileUri: string,
     newContents: string,
     stepIndex: number,
   ): Promise {
-    await this.diffManager.writeDiff(filepath, newContents, stepIndex);
+    await this.diffManager.writeDiff(fileUri, newContents, stepIndex);
   }
 
   async getOpenFiles(): Promise {
@@ -563,9 +563,9 @@ class VsCodeIde implements IDE {
     return results.join("\n\n");
   }
 
-  async getProblems(filepath?: string | undefined): Promise {
-    const uri = filepath
-      ? vscode.Uri.file(filepath)
+  async getProblems(fileUri?: string | undefined): Promise {
+    const uri = fileUri
+      ? vscode.Uri.parse(fileUri)
       : vscode.window.activeTextEditor?.document.uri;
     if (!uri) {
       return [];

From ad52a7eab3b8853451b9470e9484016ffeba8be5 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Wed, 11 Dec 2024 14:43:37 -0800
Subject: [PATCH 15/84] path->uri progress

---
 core/autocomplete/templating/formatting.ts    | 17 ++---
 core/autocomplete/templating/index.ts         |  2 +-
 core/commands/util.ts                         | 28 +++++++
 core/util/ideUtils.ts                         | 20 ++---
 extensions/vscode/src/VsCodeIde.ts            | 18 ++---
 gui/src/components/mainInput/TipTapEditor.tsx | 47 ++----------
 gui/src/components/mainInput/resolveInput.ts  | 66 ++++++++---------
 .../StepContainerPreToolbar.tsx               | 12 +--
 .../markdown/StyledMarkdownPreview.tsx        | 73 ++++++++++---------
 9 files changed, 138 insertions(+), 145 deletions(-)

diff --git a/core/autocomplete/templating/formatting.ts b/core/autocomplete/templating/formatting.ts
index 1794ad60f0..aa67a11dc5 100644
--- a/core/autocomplete/templating/formatting.ts
+++ b/core/autocomplete/templating/formatting.ts
@@ -1,4 +1,4 @@
-import { getLastNUriRelativePathParts } from "../../util/uri";
+import { getLastNUriRelativePathParts, getRelativePath } from "../../util/uri";
 import {
   AutocompleteClipboardSnippet,
   AutocompleteCodeSnippet,
@@ -14,14 +14,11 @@ const getCommentMark = (helper: HelperVars) => {
 
 const addCommentMarks = (text: string, helper: HelperVars) => {
   const commentMark = getCommentMark(helper);
-  const lines = [
-    ...text
-      .trim()
-      .split("\n")
-      .map((line) => `${commentMark} ${line}`),
-  ];
-
-  return lines.join("\n");
+  return text
+    .trim()
+    .split("\n")
+    .map((line) => `${commentMark} ${line}`)
+    .join("\n");
 };
 
 const formatClipboardSnippet = (
@@ -62,7 +59,9 @@ const commentifySnippet = (
 export const formatSnippets = (
   helper: HelperVars,
   snippets: AutocompleteSnippet[],
+  workspaceDirs: string[],
 ): string => {
+  const relativeFilePath = getRelativePath(helper.filepath, workspaceDirs);
   const currentFilepathComment = addCommentMarks(
     getLastNUriRelativePathParts(helper.filepath, 2),
     helper,
diff --git a/core/autocomplete/templating/index.ts b/core/autocomplete/templating/index.ts
index 8aaa982487..7e920f5249 100644
--- a/core/autocomplete/templating/index.ts
+++ b/core/autocomplete/templating/index.ts
@@ -81,7 +81,7 @@ export function renderPrompt({
       snippets,
     );
   } else {
-    const formattedSnippets = formatSnippets(helper, snippets);
+    const formattedSnippets = formatSnippets(helper, snippets, workspaceDirs);
     prefix = [formattedSnippets, prefix].join("\n");
   }
 
diff --git a/core/commands/util.ts b/core/commands/util.ts
index 74266e70f3..95fedadf7e 100644
--- a/core/commands/util.ts
+++ b/core/commands/util.ts
@@ -1,4 +1,32 @@
 import { ContextItemWithId, RangeInFileWithContents } from "../";
+import { getRelativePath, getUriPathBasename } from "../util/uri";
+import { v4 as uuidv4 } from "uuid";
+
+export function rifWithContentsToContextItem(
+  rif: RangeInFileWithContents,
+): ContextItemWithId {
+  const basename = getUriPathBasename(rif.filepath);
+  const relativePath = getRelativePath(
+    rif.filepath,
+    window.workspacePaths ?? [],
+  );
+  const rangeStr = `(${rif.range.start.line + 1}-${rif.range.end.line + 1})`;
+
+  const itemName = `${basename} ${rangeStr}`;
+  return {
+    content: rif.contents,
+    name: itemName,
+    description: `${relativePath} ${rangeStr}`, // This is passed to the LLM - do not pass full URI
+    id: {
+      providerTitle: "code",
+      itemId: uuidv4(),
+    },
+    uri: {
+      type: "file",
+      value: rif.filepath,
+    },
+  };
+}
 
 export function ctxItemToRifWithContents(
   item: ContextItemWithId,
diff --git a/core/util/ideUtils.ts b/core/util/ideUtils.ts
index ff7d064465..d720afb3f2 100644
--- a/core/util/ideUtils.ts
+++ b/core/util/ideUtils.ts
@@ -1,5 +1,5 @@
 import { IDE } from "..";
-import { pathToUriPathSegment } from "./uri";
+import { isUriWithinDirectory, joinPathsToUri } from "./uri";
 
 /*
   This function takes a relative filepath
@@ -10,10 +10,9 @@ export async function resolveRelativePathInWorkspace(
   path: string,
   ide: IDE,
 ): Promise {
-  const cleanPath = pathToUriPathSegment(path);
   const workspaces = await ide.getWorkspaceDirs();
-  for (const workspace of workspaces) {
-    const fullUri = `${workspace}/${cleanPath}`;
+  for (const workspaceUri of workspaces) {
+    const fullUri = joinPathsToUri(workspaceUri, path);
     if (await ide.fileExists(fullUri)) {
       return fullUri;
     }
@@ -34,16 +33,13 @@ export async function inferResolvedUriFromRelativePath(
   path: string,
   ide: IDE,
 ): Promise {
-  const cleanPath = pathToUriPathSegment(path);
-
-  const 
-  const workspaces = await ide.getWorkspaceDirs();
-  for (const workspace of workspaces) {
-    const fullUri = `${workspace}/${cleanPath}`;
+  for (const workspaceUri of workspaceDirs) {
+    if (isUriWithinDirectory(path))
+      const fullUri = joinPathsToUri(workspaceUri, path);
     if (await ide.fileExists(fullUri)) {
       return fullUri;
     }
   }
   // console.warn("No meaninful filepath inferred from relative path " + path)
-  return `${workspaces[0]}/${cleanPath}`
-}
\ No newline at end of file
+  return `${workspaces[0]}/${cleanPath}`;
+}
diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts
index 2f3a576724..4111ecf51a 100644
--- a/extensions/vscode/src/VsCodeIde.ts
+++ b/extensions/vscode/src/VsCodeIde.ts
@@ -1,6 +1,5 @@
 import * as child_process from "node:child_process";
 import { exec } from "node:child_process";
-import * as path from "node:path";
 
 import { Range } from "core";
 import { EXTENSION_NAME } from "core/control-plane/env";
@@ -513,16 +512,13 @@ class VsCodeIde implements IDE {
   }
 
   private async _searchDir(query: string, dir: string): Promise {
+    const relativeDir = vscode.workspace.asRelativePath(dir);
+    const ripGrepUri = vscode.Uri.joinPath(
+      getExtensionUri(),
+      "out/node_modules/@vscode/ripgrep/bin/rg",
+    );
     const p = child_process.spawn(
-      path.join(
-        getExtensionUri().fsPath,
-        "out",
-        "node_modules",
-        "@vscode",
-        "ripgrep",
-        "bin",
-        "rg",
-      ),
+      ripGrepUri.fsPath,
       [
         "-i", // Case-insensitive search
         "-C",
@@ -531,7 +527,7 @@ class VsCodeIde implements IDE {
         query, // Pattern to search for
         ".", // Directory to search in
       ],
-      { cwd: dir },
+      { cwd: relativeDir },
     );
     let output = "";
 
diff --git a/gui/src/components/mainInput/TipTapEditor.tsx b/gui/src/components/mainInput/TipTapEditor.tsx
index b39cf4bb86..759373cc77 100644
--- a/gui/src/components/mainInput/TipTapEditor.tsx
+++ b/gui/src/components/mainInput/TipTapEditor.tsx
@@ -6,12 +6,7 @@ import Placeholder from "@tiptap/extension-placeholder";
 import Text from "@tiptap/extension-text";
 import { Plugin } from "@tiptap/pm/state";
 import { Editor, EditorContent, JSONContent, useEditor } from "@tiptap/react";
-import {
-  ContextItemWithId,
-  ContextProviderDescription,
-  InputModifiers,
-  RangeInFile,
-} from "core";
+import { ContextProviderDescription, InputModifiers } from "core";
 import { modelSupportsImages } from "core/llm/autodetect";
 import { debounce } from "lodash";
 import { usePostHog } from "posthog-js/react";
@@ -24,7 +19,6 @@ import {
   useState,
 } from "react";
 import styled from "styled-components";
-import { v4 } from "uuid";
 import {
   defaultBorderRadius,
   lightGray,
@@ -71,12 +65,12 @@ import {
   setMainEditorContentTrigger,
 } from "../../redux/slices/sessionSlice";
 import { exitEditMode } from "../../redux/thunks";
-import { getUriPathBasename, getRelativePath } from "core/util/uri";
 import {
   loadLastSession,
   loadSession,
   saveCurrentSession,
 } from "../../redux/thunks/session";
+import { rifWithContentsToContextItem } from "core/commands/util";
 
 const InputBoxDiv = styled.div<{ border?: string }>`
   resize: none;
@@ -750,38 +744,13 @@ function TipTapEditor(props: TipTapEditorProps) {
       if (!props.isMainInput || !editor) {
         return;
       }
-
-      const rif: RangeInFile & { contents: string } =
-        data.rangeInFileWithContents;
-      const basename = getUriPathBasename(rif.filepath);
-      const relativePath = getRelativePath(
-        rif.filepath,
-        window.workspacePaths ?? [],
+      const contextItem = rifWithContentsToContextItem(
+        data.rangeInFileWithContents,
       );
-      const rangeStr = `(${rif.range.start.line + 1}-${
-        rif.range.end.line + 1
-      })`;
-
-      const itemName = `${basename} ${rangeStr}`;
-      const item: ContextItemWithId = {
-        content: rif.contents,
-        name: itemName,
-        // Description is passed on to the LLM to give more context on file path
-        description: `${relativePath} ${rangeStr}`,
-        id: {
-          providerTitle: "code",
-          itemId: v4(),
-        },
-        uri: {
-          type: "file",
-          value: rif.filepath,
-        },
-      };
-
       let index = 0;
-      for (const el of editor.getJSON().content) {
-        if (el.attrs?.item?.name === itemName) {
-          return; // Prevent duplicate code blocks
+      for (const el of editor.getJSON()?.content ?? []) {
+        if (el.attrs?.item?.name === contextItem.name) {
+          return; // Prevent exact duplicate code blocks
         }
         if (el.type === "codeBlock") {
           index += 2;
@@ -794,7 +763,7 @@ function TipTapEditor(props: TipTapEditorProps) {
         .insertContentAt(index, {
           type: "codeBlock",
           attrs: {
-            item,
+            item: contextItem,
           },
         })
         .run();
diff --git a/gui/src/components/mainInput/resolveInput.ts b/gui/src/components/mainInput/resolveInput.ts
index 6caac6c576..6f52327173 100644
--- a/gui/src/components/mainInput/resolveInput.ts
+++ b/gui/src/components/mainInput/resolveInput.ts
@@ -11,6 +11,9 @@ import { stripImages } from "core/util/messageContent";
 import { IIdeMessenger } from "../../context/IdeMessenger";
 import { Dispatch } from "@reduxjs/toolkit";
 import { setIsGatheringContext } from "../../redux/slices/sessionSlice";
+import { inferResolvedUriFromRelativePath } from "core/util/ideUtils";
+import { ctxItemToRifWithContents } from "core/commands/util";
+import { getUriFileExtension } from "core/util/uri";
 
 interface MentionAttrs {
   label: string;
@@ -68,46 +71,41 @@ async function resolveEditorContent({
           parts.push({ type: "text", text });
         }
       } else if (p.type === "codeBlock") {
-        if (!p.attrs.item.editing) {
-          let meta = p.attrs.item.description.split(" ");
-          let relativeFilepath = meta[0] || "";
-          let extName = relativeFilepath.split(".").slice(-1)[0];
-          const text =
-            "\n\n" +
-            "```" +
-            extName +
-            " " +
-            p.attrs.item.description +
-            "\n" +
-            p.attrs.item.content +
-            "\n```";
-          if (parts[parts.length - 1]?.type === "text") {
-            parts[parts.length - 1].text += "\n" + text;
-          } else {
-            parts.push({
-              type: "text",
-              text,
-            });
+        if (p.attrs?.item) {
+          const contextItem = p.atts.item as ContextItemWithId;
+          const rif = ctxItemToRifWithContents(contextItem);
+          // If not editing, include codeblocks in the prompt
+          // If editing is handled by selectedCode below
+          if (!contextItem.editing) {
+            const fileExtension = getUriFileExtension(rif.filepath);
+            // let extName = relativeFilepath.split(".").slice(-1)[0];
+            const text =
+              "\n\n" +
+              "```" +
+              fileExtension +
+              " " +
+              contextItem.description +
+              "\n" +
+              contextItem.content +
+              "\n```";
+            if (parts[parts.length - 1]?.type === "text") {
+              parts[parts.length - 1].text += "\n" + text;
+            } else {
+              parts.push({
+                type: "text",
+                text,
+              });
+            }
           }
+          selectedCode.push(rif);
+        } else {
+          console.warn("codeBlock has no item attribute");
         }
-
-        const name: string = p.attrs.item.name;
-        let lines = name.substring(name.lastIndexOf("(") + 1);
-        lines = lines.substring(0, lines.lastIndexOf(")"));
-        const [start, end] = lines.split("-");
-
-        selectedCode.push({
-          filepath: p.attrs.item.description,
-          range: {
-            start: { line: parseInt(start) - 1, character: 0 },
-            end: { line: parseInt(end) - 1, character: 0 },
-          },
-        });
       } else if (p.type === "image") {
         parts.push({
           type: "imageUrl",
           imageUrl: {
-            url: p.attrs.src,
+            url: p.attrs?.src,
           },
         });
       } else {
diff --git a/gui/src/components/markdown/StepContainerPreToolbar/StepContainerPreToolbar.tsx b/gui/src/components/markdown/StepContainerPreToolbar/StepContainerPreToolbar.tsx
index 61dcfbb3a5..45c5a335c2 100644
--- a/gui/src/components/markdown/StepContainerPreToolbar/StepContainerPreToolbar.tsx
+++ b/gui/src/components/markdown/StepContainerPreToolbar/StepContainerPreToolbar.tsx
@@ -44,7 +44,7 @@ const ToolbarDiv = styled.div<{ isExpanded: boolean }>`
 export interface StepContainerPreToolbarProps {
   codeBlockContent: string;
   language: string;
-  filepath: string;
+  fileUri: string;
   isGeneratingCodeBlock: boolean;
   codeBlockIndex: number; // To track which codeblock we are applying
   range?: string;
@@ -84,7 +84,7 @@ export default function StepContainerPreToolbar(
     : props.isGeneratingCodeBlock;
 
   const isNextCodeBlock = nextCodeBlockIndex === props.codeBlockIndex;
-  const hasFileExtension = /\.[0-9a-z]+$/i.test(props.filepath);
+  const hasFileExtension = /\.[0-9a-z]+$/i.test(props.fileUri);
 
   const defaultModel = useAppSelector(selectDefaultModel);
 
@@ -94,7 +94,7 @@ export default function StepContainerPreToolbar(
     }
     ideMessenger.post("applyToFile", {
       streamId: streamIdRef.current,
-      filepath: props.filepath,
+      filepath: props.fileUri,
       text: codeBlockContent,
       curSelectedModelTitle: defaultModel.title,
     });
@@ -139,14 +139,14 @@ export default function StepContainerPreToolbar(
 
   function onClickAcceptApply() {
     ideMessenger.post("acceptDiff", {
-      filepath: props.filepath,
+      filepath: props.fileUri,
       streamId: streamIdRef.current,
     });
   }
 
   function onClickRejectApply() {
     ideMessenger.post("rejectDiff", {
-      filepath: props.filepath,
+      filepath: props.fileUri,
       streamId: streamIdRef.current,
     });
   }
@@ -172,7 +172,7 @@ export default function StepContainerPreToolbar(
             }`}
           />
           
- +
diff --git a/gui/src/components/markdown/StyledMarkdownPreview.tsx b/gui/src/components/markdown/StyledMarkdownPreview.tsx index 3dcfcc869e..9c06164a13 100644 --- a/gui/src/components/markdown/StyledMarkdownPreview.tsx +++ b/gui/src/components/markdown/StyledMarkdownPreview.tsx @@ -1,6 +1,6 @@ import { SymbolWithRange } from "core"; import { ctxItemToRifWithContents } from "core/commands/util"; -import { memo, useEffect, useMemo, useRef } from "react"; +import { memo, useContext, useEffect, useMemo, useRef } from "react"; import { useRemark } from "react-remark"; import rehypeHighlight, { Options } from "rehype-highlight"; import rehypeKatex from "rehype-katex"; @@ -27,7 +27,8 @@ import { patchNestedMarkdown } from "./utils/patchNestedMarkdown"; import { useAppSelector } from "../../redux/hooks"; import { fixDoubleDollarNewLineLatex } from "./utils/fixDoubleDollarLatex"; import { selectUIConfig } from "../../redux/slices/configSlice"; - +import { inferResolvedUriFromRelativePath } from "core/util/ideUtils"; +import { IdeMessengerContext } from "../../context/IdeMessenger"; const StyledMarkdown = styled.div<{ fontSize?: number; @@ -133,31 +134,6 @@ function getCodeChildrenContent(children: any) { return undefined; } -function processCodeBlocks(tree: any) { - const lastNode = tree.children[tree.children.length - 1]; - const lastCodeNode = lastNode.type === "code" ? lastNode : null; - - visit(tree, "code", (node: any) => { - if (!node.lang) { - node.lang = "javascript"; - } else if (node.lang.includes(".")) { - node.lang = node.lang.split(".").slice(-1)[0]; - } - - node.data = node.data || {}; - node.data.hProperties = node.data.hProperties || {}; - - node.data.hProperties["data-isgeneratingcodeblock"] = lastCodeNode === node; - node.data.hProperties["data-codeblockcontent"] = node.value; - - if (node.meta) { - let meta = node.meta.split(" "); - node.data.hProperties.filepath = meta[0]; - node.data.hProperties.range = meta[1]; - } - }); -} - const StyledMarkdownPreview = memo(function StyledMarkdownPreview( props: StyledMarkdownPreviewProps, ) { @@ -194,6 +170,8 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( }, [allSymbols, previousFileContextItems]); const symbolsRef = useUpdatingRef(previousFileContextItemSymbols); + const ideMessenger = useContext(IdeMessengerContext); + const [reactContent, setMarkdownSource] = useRemark({ remarkPlugins: [ remarkTables, @@ -203,7 +181,34 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( singleDollarTextMath: false, }, ], - () => processCodeBlocks, + () => (tree: any) => { + const lastNode = tree.children[tree.children.length - 1]; + const lastCodeNode = lastNode.type === "code" ? lastNode : null; + + visit(tree, "code", (node: any) => { + if (!node.lang) { + node.lang = "javascript"; + } else if (node.lang.includes(".")) { + node.lang = node.lang.split(".").slice(-1)[0]; + } + + node.data = node.data || {}; + node.data.hProperties = node.data.hProperties || {}; + + node.data.hProperties["data-isgeneratingcodeblock"] = + lastCodeNode === node; + node.data.hProperties["data-codeblockcontent"] = node.value; + + if (node.meta) { + let meta = node.meta.split(" "); + node.data.hProperties.fileUri = inferResolvedUriFromRelativePath( + meta[0], + ideMessenger.ide, + ); + node.data.hProperties.range = meta[1]; // E.g + } + }); + }, ], rehypePlugins: [ rehypeKatex as any, @@ -239,7 +244,7 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( }, pre: ({ node, ...preProps }) => { const preChildProps = preProps?.children?.[0]?.props; - const { className, filepath, range } = preProps?.children?.[0]?.props; + const { className, fileUri, range } = preProps?.children?.[0]?.props; const codeBlockContent = preChildProps["data-codeblockcontent"]; const isGeneratingCodeBlock = @@ -254,8 +259,8 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( // If we don't have a filepath show the more basic toolbar // that is just action buttons on hover. // We also use this in JB since we haven't yet implemented - // the logic for lazy apply. - if (!filepath || isJetBrains()) { + // the logic forfileUri lazy apply. + if (!fileUri || isJetBrains()) { return ( @@ -326,7 +331,9 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( const uiConfig = useAppSelector(selectUIConfig); const codeWrapState = uiConfig?.codeWrap ? "pre-wrap" : "pre"; return ( - {reactContent} + + {reactContent} + ); }); From 25329bbd77d553e6e71c02b76f057971afb32103 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 11 Dec 2024 19:15:32 -0800 Subject: [PATCH 16/84] path -> uri continued --- core/autocomplete/filtering/test/util.ts | 10 ++- .../templating/AutocompleteTemplate.ts | 6 +- core/autocomplete/templating/index.ts | 6 +- core/commands/slash/onboard.ts | 66 ++++++++++--------- core/commands/slash/share.ts | 8 +-- core/commands/util.ts | 4 ++ core/config/types.ts | 1 - .../providers/PromptFilesContextProvider.ts | 3 +- core/context/retrieval/repoMapRequest.ts | 1 + core/edit/lazy/deterministic.ts | 10 +-- core/index.d.ts | 2 - core/promptFiles/v2/parse.ts | 3 +- core/protocol/ide.ts | 1 - core/protocol/messenger/messageIde.ts | 11 ---- core/protocol/messenger/reverseMessageIde.ts | 4 -- .../implementations/runTerminalCommand.ts | 3 +- core/util/filesystem.ts | 4 -- core/util/index.ts | 46 ------------- core/util/paths.ts | 19 ++++++ core/util/treeSitter.ts | 3 +- core/util/uri.ts | 4 ++ .../constants/MessageTypes.kt | 1 - .../continue/IdeProtocolClient.kt | 4 -- extensions/vscode/src/VsCodeIde.ts | 6 +- extensions/vscode/src/commands.ts | 55 ++++++---------- extensions/vscode/src/diff/horizontal.ts | 45 ++++--------- .../vscode/src/extension/VsCodeExtension.ts | 10 +-- .../vscode/src/extension/VsCodeMessenger.ts | 62 ++++------------- .../providers/DiffViewerCodeLensProvider.ts | 5 +- .../providers/QuickActionsCodeLensProvider.ts | 6 +- .../src/quickEdit/QuickEditQuickPick.ts | 6 +- extensions/vscode/src/util/tutorial.ts | 27 ++++++++ extensions/vscode/src/webviewProtocol.ts | 22 ------- .../CodeToEditCard/CodeToEditListItem.tsx | 15 ++++- .../components/mainInput/ContextItemsPeek.tsx | 10 ++- .../markdown/CodeSnippetPreview.tsx | 34 +++++----- 36 files changed, 211 insertions(+), 312 deletions(-) create mode 100644 extensions/vscode/src/util/tutorial.ts diff --git a/core/autocomplete/filtering/test/util.ts b/core/autocomplete/filtering/test/util.ts index db8d991317..7aa7f7c662 100644 --- a/core/autocomplete/filtering/test/util.ts +++ b/core/autocomplete/filtering/test/util.ts @@ -1,8 +1,6 @@ -import fs from "node:fs"; -import path from "node:path"; - import MockLLM from "../../../llm/llms/Mock"; import { testConfigHandler, testIde } from "../../../test/fixtures"; +import { joinPathsToUri } from "../../../util/uri"; import { CompletionProvider } from "../../CompletionProvider"; import { AutocompleteInput } from "../../util/types"; @@ -39,8 +37,8 @@ export async function testAutocompleteFiltering( // Create a real file const [workspaceDir] = await ide.getWorkspaceDirs(); - const filepath = path.join(workspaceDir, test.filename); - fs.writeFileSync(filepath, test.input.replace(FIM_DELIMITER, "")); + const fileUri = joinPathsToUri(workspaceDir, test.filename); + await ide.writeFile(fileUri, test.input.replace(FIM_DELIMITER, "")); // Prepare completion input and provider const completionProvider = new CompletionProvider( @@ -55,7 +53,7 @@ export async function testAutocompleteFiltering( const character = prefix.split("\n")[line].length; const autocompleteInput: AutocompleteInput = { completionId: "test-completion-id", - filepath, + filepath: fileUri, pos: { line, character, diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index 8c9ae1d301..1ada0d55c5 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -15,7 +15,7 @@ export interface AutocompleteTemplate { compilePrefixSuffix?: ( prefix: string, suffix: string, - directoryUri: string, + filepath: string, reponame: string, snippets: AutocompleteSnippet[], ) => [string, string]; @@ -24,7 +24,7 @@ export interface AutocompleteTemplate { | (( prefix: string, suffix: string, - directoryUri: string, + filepath: string, reponame: string, language: string, snippets: AutocompleteSnippet[], @@ -85,7 +85,7 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { if (snippets.length === 0) { if (suffix.trim().length === 0 && prefix.trim().length === 0) { return [ - `+++++ ${getLastNUriRelativePathParts(filepath, 2)}\n${prefix}`, + `+++++ ${getLastNUriRelativePathParts(workspaceDirs, filepath, 2)}\n${prefix}`, suffix, ]; } diff --git a/core/autocomplete/templating/index.ts b/core/autocomplete/templating/index.ts index 7e920f5249..2e0569cb28 100644 --- a/core/autocomplete/templating/index.ts +++ b/core/autocomplete/templating/index.ts @@ -1,7 +1,6 @@ import Handlebars from "handlebars"; import { CompletionOptions } from "../.."; -import { getBasename } from "../../util"; import { AutocompleteLanguageInfo } from "../constants/AutocompleteLanguageInfo"; import { HelperVars } from "../util/HelperVars"; @@ -13,6 +12,7 @@ import { getStopTokens } from "./getStopTokens"; import { SnippetPayload } from "../snippets"; import { formatSnippets } from "./formatting"; import { getSnippets } from "./filtering"; +import { getUriPathBasename } from "../../util/uri"; function getTemplate(helper: HelperVars): AutocompleteTemplate { if (helper.options.template) { @@ -33,7 +33,7 @@ function renderStringTemplate( filepath: string, reponame: string, ) { - const filename = getBasename(filepath); + const filename = getUriPathBasename(filepath); const compiledTemplate = Handlebars.compile(template); return compiledTemplate({ @@ -63,7 +63,7 @@ export function renderPrompt({ let prefix = helper.input.manuallyPassPrefix || helper.prunedPrefix; let suffix = helper.input.manuallyPassPrefix ? "" : helper.prunedSuffix; - const reponame = getBasename(workspaceDirs[0] ?? "myproject"); + const reponame = getUriPathBasename(workspaceDirs[0] ?? "myproject"); const { template, compilePrefixSuffix, completionOptions } = getTemplate(helper); diff --git a/core/commands/slash/onboard.ts b/core/commands/slash/onboard.ts index b9a39aa0b3..f481058aaa 100644 --- a/core/commands/slash/onboard.ts +++ b/core/commands/slash/onboard.ts @@ -1,15 +1,18 @@ -import * as fs from "fs/promises"; -import * as path from "path"; - import ignore from "ignore"; -import { IDE, SlashCommand } from "../.."; +import { FileType, IDE, SlashCommand } from "../.."; import { defaultIgnoreDir, defaultIgnoreFile, + getGlobalContinueIgArray, gitIgArrayFromFile, } from "../../indexing/ignore"; import { renderChatMessage } from "../../util/messageContent"; +import { + getRelativePath, + getUriPathBasename, + joinPathsToUri, +} from "../../util/uri"; const LANGUAGE_DEP_MGMT_FILENAMES = [ "package.json", // JavaScript (Node.js) @@ -55,27 +58,32 @@ const OnboardSlashCommand: SlashCommand = { }; async function getEntriesFilteredByIgnore(dir: string, ide: IDE) { - const entries = await fs.readdir(dir, { withFileTypes: true }); - - let ig = ignore().add(defaultIgnoreDir).add(defaultIgnoreFile); - - const gitIgnorePath = path.join(dir, ".gitignore"); + const ig = ignore() + .add(defaultIgnoreDir) + .add(defaultIgnoreFile) + .add(getGlobalContinueIgArray()); + const entries = await ide.listDir(dir); - const hasIgnoreFile = await fs - .access(gitIgnorePath) - .then(() => true) - .catch(() => false); + const ignoreUri = joinPathsToUri(dir, ".gitignore"); + const fileExists = await ide.fileExists(ignoreUri); - if (hasIgnoreFile) { - const gitIgnore = await ide.readFile(gitIgnorePath); + if (fileExists) { + const gitIgnore = await ide.readFile(ignoreUri); const igPatterns = gitIgArrayFromFile(gitIgnore); - ig = ig.add(igPatterns); + ig.add(igPatterns); } - const filteredEntries = entries.filter((entry) => !ig.ignores(entry.name)); + const workspaceDirs = await ide.getWorkspaceDirs(); - return filteredEntries; + const withRelativePaths = entries.map((entry) => ({ + uri: entry[0], + type: entry[1], + basename: getUriPathBasename(entry[0]), + relativePath: getRelativePath(entry[0], workspaceDirs), + })); + + return withRelativePaths.filter((entry) => !ig.ignores(entry.relativePath)); } async function gatherProjectContext( @@ -83,7 +91,6 @@ async function gatherProjectContext( ide: IDE, ): Promise { let context = ""; - async function exploreDirectory(dir: string, currentDepth: number = 0) { if (currentDepth > MAX_EXPLORE_DEPTH) { return; @@ -92,19 +99,16 @@ async function gatherProjectContext( const entries = await getEntriesFilteredByIgnore(dir, ide); for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - const relativePath = path.relative(workspaceDir, fullPath); - - if (entry.isDirectory()) { - context += `\nFolder: ${relativePath}\n`; - await exploreDirectory(fullPath, currentDepth + 1); + if (entry.type === FileType.Directory) { + context += `\nFolder: ${entry.relativePath}\n`; + await exploreDirectory(entry.uri, currentDepth + 1); } else { - if (entry.name.toLowerCase() === "readme.md") { - const content = await fs.readFile(fullPath, "utf-8"); - context += `README for ${relativePath}:\n${content}\n\n`; - } else if (LANGUAGE_DEP_MGMT_FILENAMES.includes(entry.name)) { - const content = await fs.readFile(fullPath, "utf-8"); - context += `${entry.name} for ${relativePath}:\n${content}\n\n`; + if (entry.basename.toLowerCase() === "readme.md") { + const content = await ide.readFile(entry.uri); + context += `README for ${entry.relativePath}:\n${content}\n\n`; + } else if (LANGUAGE_DEP_MGMT_FILENAMES.includes(entry.basename)) { + const content = await ide.readFile(entry.uri); + context += `${entry.basename} for ${entry.relativePath}:\n${content}\n\n`; } } } diff --git a/core/commands/slash/share.ts b/core/commands/slash/share.ts index 4b69f4c623..f606385828 100644 --- a/core/commands/slash/share.ts +++ b/core/commands/slash/share.ts @@ -1,10 +1,9 @@ -import * as fs from "node:fs"; import { homedir } from "node:os"; -import path from "path"; import { languageForFilepath } from "../../autocomplete/constants/AutocompleteLanguageInfo.js"; import { SlashCommand } from "../../index.js"; import { renderChatMessage } from "../../util/messageContent.js"; +import { getContinueGlobalPath } from "../../util/paths.js"; // If useful elsewhere, helper funcs should move to core/util/index.ts or similar function getOffsetDatetime(date: Date): Date { @@ -62,10 +61,7 @@ const ShareSlashCommand: SlashCommand = { }\n\n${msgText}`; } - let outputDir: string = params?.outputDir; - if (!outputDir) { - outputDir = await ide.getContinueDir(); - } + let outputDir: string = params?.outputDir ?? getContinueGlobalPath(); if (outputDir.startsWith("~")) { outputDir = outputDir.replace(/^~/, homedir); diff --git a/core/commands/util.ts b/core/commands/util.ts index 95fedadf7e..e1e9cd60eb 100644 --- a/core/commands/util.ts +++ b/core/commands/util.ts @@ -59,3 +59,7 @@ export function ctxItemToRifWithContents( return rif; } + +// export function codeContextItemToMarkdownSource( +// item: ContextItemWithId, +// ): string {} diff --git a/core/config/types.ts b/core/config/types.ts index 71c16c6057..93ca6acca5 100644 --- a/core/config/types.ts +++ b/core/config/types.ts @@ -446,7 +446,6 @@ declare global { fileExists(filepath: string): Promise; writeFile(path: string, contents: string): Promise; showVirtualFile(title: string, contents: string): Promise; - getContinueDir(): Promise; openFile(path: string): Promise; runCommand(command: string): Promise; saveFile(filepath: string): Promise; diff --git a/core/context/providers/PromptFilesContextProvider.ts b/core/context/providers/PromptFilesContextProvider.ts index 9ea603f3f8..bc9239f1df 100644 --- a/core/context/providers/PromptFilesContextProvider.ts +++ b/core/context/providers/PromptFilesContextProvider.ts @@ -6,6 +6,7 @@ import { ContextSubmenuItem, LoadSubmenuItemsArgs, } from "../../"; +import { getAllPromptFilesV2 } from "../../promptFiles/v2/getPromptFiles"; import { parsePreamble } from "../../promptFiles/v2/parse"; import { renderPromptFileV2 } from "../../promptFiles/v2/renderPromptFile"; @@ -37,7 +38,7 @@ class PromptFilesContextProvider extends BaseContextProvider { async loadSubmenuItems( args: LoadSubmenuItemsArgs, ): Promise { - const promptFiles = await getAllPromptFiles( + const promptFiles = await getAllPromptFilesV2( args.ide, args.config.experimental?.promptPath, ); diff --git a/core/context/retrieval/repoMapRequest.ts b/core/context/retrieval/repoMapRequest.ts index 17e76af43d..5aaabead77 100644 --- a/core/context/retrieval/repoMapRequest.ts +++ b/core/context/retrieval/repoMapRequest.ts @@ -71,6 +71,7 @@ This is the question that you should select relevant files for: "${input}"`; return []; } + const subDirPrefix = filterDirectory ? filterDirectory + pathSep : ""; const files = content .split("")[1] diff --git a/core/edit/lazy/deterministic.ts b/core/edit/lazy/deterministic.ts index 564656d23e..ff550c0fe0 100644 --- a/core/edit/lazy/deterministic.ts +++ b/core/edit/lazy/deterministic.ts @@ -1,5 +1,3 @@ -import path from "path"; - import { distance } from "fastest-levenshtein"; import Parser from "web-tree-sitter"; @@ -9,6 +7,10 @@ import { myersDiff } from "../../diff/myers"; import { getParserForFile } from "../../util/treeSitter"; import { findInAst } from "./findInAst"; +import { + getFileExtensionFromBasename, + getUriFileExtension, +} from "../../util/uri"; type AstReplacements = Array<{ nodeToReplace: Parser.SyntaxNode; @@ -125,8 +127,8 @@ export async function deterministicApplyLazyEdit( ); if (firstSimilarNode?.parent?.equals(oldTree.rootNode)) { // If so, we tack lazy blocks to start and end, and run the usual algorithm - const ext = path.extname(filename).slice(1); - const language = LANGUAGES[ext]; + const extension = getFileExtensionFromBasename(filename); + const language = LANGUAGES[extension]; if (language) { newLazyFile = `${language.singleLineComment} ... existing code ...\n\n${newLazyFile}\n\n${language.singleLineComment} ... existing code...`; newTree = parser.parse(newLazyFile); diff --git a/core/index.d.ts b/core/index.d.ts index d1f2b28fd4..db8e626488 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -617,8 +617,6 @@ export interface IDE { showVirtualFile(title: string, contents: string): Promise; - getContinueDir(): Promise; - openFile(path: string): Promise; openUrl(url: string): Promise; diff --git a/core/promptFiles/v2/parse.ts b/core/promptFiles/v2/parse.ts index 3de31a6d64..334733d79c 100644 --- a/core/promptFiles/v2/parse.ts +++ b/core/promptFiles/v2/parse.ts @@ -1,7 +1,8 @@ import * as YAML from "yaml"; +import { getLastNPathParts } from "../../util"; export function extractName(preamble: { name?: string }, path: string): string { - return preamble.name ?? getBasename(path).split(".prompt")[0]; + return preamble.name ?? getLastNPathParts(path, 1).split(".prompt")[0]; } export function getPreambleAndBody(content: string): [string, string] { diff --git a/core/protocol/ide.ts b/core/protocol/ide.ts index efe5f68858..dd0edeb54e 100644 --- a/core/protocol/ide.ts +++ b/core/protocol/ide.ts @@ -26,7 +26,6 @@ export type ToIdeFromWebviewOrCoreProtocol = { listFolders: [undefined, string[]]; writeFile: [{ path: string; contents: string }, void]; showVirtualFile: [{ name: string; content: string }, void]; - getContinueDir: [undefined, string]; openFile: [{ path: string }, void]; openUrl: [string, void]; runCommand: [{ command: string }, void]; diff --git a/core/protocol/messenger/messageIde.ts b/core/protocol/messenger/messageIde.ts index d19012d6ee..df25c83794 100644 --- a/core/protocol/messenger/messageIde.ts +++ b/core/protocol/messenger/messageIde.ts @@ -138,17 +138,6 @@ export class MessageIde implements IDE { return await this.request("listFolders", undefined); } - _continueDir: string | null = null; - - async getContinueDir(): Promise { - if (this._continueDir) { - return this._continueDir; - } - const dir = await this.request("getContinueDir", undefined); - this._continueDir = dir; - return dir; - } - async writeFile(fileUri: string, contents: string): Promise { await this.request("writeFile", { path: fileUri, contents }); } diff --git a/core/protocol/messenger/reverseMessageIde.ts b/core/protocol/messenger/reverseMessageIde.ts index a180bb2274..3894a36979 100644 --- a/core/protocol/messenger/reverseMessageIde.ts +++ b/core/protocol/messenger/reverseMessageIde.ts @@ -126,10 +126,6 @@ export class ReverseMessageIde { return undefined; }); - this.on("getContinueDir", () => { - return this.ide.getContinueDir(); - }); - this.on("writeFile", (data) => { return this.ide.writeFile(data.path, data.contents); }); diff --git a/core/tools/implementations/runTerminalCommand.ts b/core/tools/implementations/runTerminalCommand.ts index 5a159b04a5..ccb79f2908 100644 --- a/core/tools/implementations/runTerminalCommand.ts +++ b/core/tools/implementations/runTerminalCommand.ts @@ -2,6 +2,7 @@ import childProcess from "node:child_process"; import util from "node:util"; import { ToolImpl } from "."; +import { fileURLToPath } from "node:url"; const asyncExec = util.promisify(childProcess.exec); @@ -11,7 +12,7 @@ export const runTerminalCommandImpl: ToolImpl = async (args, extras) => { if (ideInfo.remoteName === "local" || ideInfo.remoteName === "") { try { const output = await asyncExec(args.command, { - cwd: (await extras.ide.getWorkspaceDirs())[0], + cwd: fileURLToPath((await extras.ide.getWorkspaceDirs())[0]), }); return [ { diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts index e11cf37ec6..a44aa1add5 100644 --- a/core/util/filesystem.ts +++ b/core/util/filesystem.ts @@ -186,10 +186,6 @@ class FileSystemIde implements IDE { return Promise.resolve(); } - getContinueDir(): Promise { - return Promise.resolve(getContinueGlobalPath()); - } - openFile(path: string): Promise { return Promise.resolve(); } diff --git a/core/util/index.ts b/core/util/index.ts index b40d264e04..d310b3fde1 100644 --- a/core/util/index.ts +++ b/core/util/index.ts @@ -119,52 +119,6 @@ export function getLastNPathParts(filepath: string, n: number): string { // return getLastNPathParts(item, n); // } -// export function shortestRelativePaths(paths: string[]): string[] { -// if (paths.length === 0) { -// return []; -// } - -// const partsLengths = paths.map((x) => x.split(SEP_REGEX).length); -// const currentRelativePaths = paths.map(getB); -// const currentNumParts = paths.map(() => 1); -// const isDuplicated = currentRelativePaths.map( -// (x, i) => -// currentRelativePaths.filter((y, j) => y === x && paths[i] !== paths[j]) -// .length > 1, -// ); - -// while (isDuplicated.some(Boolean)) { -// const firstDuplicatedPath = currentRelativePaths.find( -// (x, i) => isDuplicated[i], -// ); -// if (!firstDuplicatedPath) { -// break; -// } - -// currentRelativePaths.forEach((x, i) => { -// if (x === firstDuplicatedPath) { -// currentNumParts[i] += 1; -// currentRelativePaths[i] = getLastNUriRelativePathParts( -// paths[i], -// currentNumParts[i], -// ); -// } -// }); - -// isDuplicated.forEach((x, i) => { -// if (x) { -// isDuplicated[i] = -// // Once we've used up all the parts, we can't make it longer -// currentNumParts[i] < partsLengths[i] && -// currentRelativePaths.filter((y) => y === currentRelativePaths[i]) -// .length > 1; -// } -// }); -// } - -// return currentRelativePaths; -// } - // export function splitPath(path: string, withRoot?: string): string[] { // let parts = path.includes("/") ? path.split("/") : path.split("\\"); // if (withRoot !== undefined) { diff --git a/core/util/paths.ts b/core/util/paths.ts index d6d2d33655..8f451c40a4 100644 --- a/core/util/paths.ts +++ b/core/util/paths.ts @@ -321,6 +321,15 @@ export function getLogsDirPath(): string { return logsPath; } +export function getLogFilePath(): string { + const logFilePath = path.join(getContinueGlobalPath(), "continue.log"); + // Make sure the file/directory exist + if (!fs.existsSync(logFilePath)) { + fs.writeFileSync(logFilePath, ""); + } + return logFilePath; +} + export function getCoreLogsPath(): string { return path.join(getLogsDirPath(), "core.log"); } @@ -379,3 +388,13 @@ export function setupInitialDotContinueDirectory() { } }); } + +export function getDiffsDirectoryPath(): string { + const diffsPath = path.join(getContinueGlobalPath(), ".diffs"); // .replace(/^C:/, "c:"); ?? + if (!fs.existsSync(diffsPath)) { + fs.mkdirSync(diffsPath, { + recursive: true, + }); + } + return diffsPath; +} diff --git a/core/util/treeSitter.ts b/core/util/treeSitter.ts index cfae9f7e1a..2efb03114a 100644 --- a/core/util/treeSitter.ts +++ b/core/util/treeSitter.ts @@ -1,4 +1,5 @@ -// import fs from "node:fs"; +import fs from "node:fs"; +import path from "path"; import Parser, { Language } from "web-tree-sitter"; import { FileSymbolMap, IDE, SymbolWithRange } from ".."; diff --git a/core/util/uri.ts b/core/util/uri.ts index f7e4b9fb4d..57af61235a 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -89,6 +89,10 @@ export function getUriPathBasename(uri: string): string { return getPath(); } +export function getFileExtensionFromBasename(filename: string) { + return filename.split(".").pop() ?? ""; +} + export function joinPathsToUri(uri: string, ...pathSegments: string[]) { const components = URI.parse(uri); const segments = pathSegments.map((segment) => pathToUriPathSegment(segment)); 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 de0c061b2d..426de1d50f 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 @@ -19,7 +19,6 @@ class MessageTypes { "getWorkspaceDirs", "showLines", "listFolders", - "getContinueDir", "writeFile", "fileExists", "showVirtualFile", 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 90ef1a850a..e8e90519a0 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 @@ -560,10 +560,6 @@ class IdeProtocolClient( respond(file.exists()) } - "getContinueDir" -> { - respond(getContinueGlobalPath()) - } - "openFile" -> { setFileOpen((data as Map)["path"] as String) respond(null) diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts index 4111ecf51a..c519f6bf89 100644 --- a/extensions/vscode/src/VsCodeIde.ts +++ b/extensions/vscode/src/VsCodeIde.ts @@ -373,10 +373,6 @@ class VsCodeIde implements IDE { return this.ideUtils.getWorkspaceDirectories().map((uri) => uri.toString()); } - async getContinueDir(): Promise { - return getContinueGlobalPath(); - } - async writeFile(fileUri: string, contents: string): Promise { await vscode.workspace.fs.writeFile( vscode.Uri.parse(fileUri), @@ -441,7 +437,7 @@ class VsCodeIde implements IDE { vscode.workspace.notebookDocuments.find((doc) => URI.equal(doc.uri.toString(), uri.toString()), ) ?? - (uri.fsPath.endsWith("ipynb") + (uri.path.endsWith("ipynb") ? await vscode.workspace.openNotebookDocument(uri) : undefined); if (notebook) { diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts index 9d443f3c36..c5fee0ec95 100644 --- a/extensions/vscode/src/commands.ts +++ b/extensions/vscode/src/commands.ts @@ -1,7 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import * as fs from "node:fs"; import * as os from "node:os"; -import * as path from "node:path"; import { ContextMenuConfig, RangeInFileWithContents } from "core"; import { CompletionProvider } from "core/autocomplete/CompletionProvider"; @@ -12,7 +10,11 @@ import { EXTENSION_NAME } from "core/control-plane/env"; import { Core } from "core/core"; import { walkDirAsync } from "core/indexing/walkDir"; import { GlobalContext } from "core/util/GlobalContext"; -import { getConfigJsonPath, getDevDataFilePath } from "core/util/paths"; +import { + getConfigJsonPath, + getDevDataFilePath, + getLogFilePath, +} from "core/util/paths"; import { Telemetry } from "core/util/posthog"; import readLastLines from "read-last-lines"; import * as vscode from "vscode"; @@ -160,17 +162,17 @@ async function addHighlightedCodeToContext( } async function addEntireFileToContext( - filepath: vscode.Uri, + uri: vscode.Uri, webviewProtocol: VsCodeWebviewProtocol | undefined, ) { // If a directory, add all files in the directory - const stat = await vscode.workspace.fs.stat(filepath); + const stat = await vscode.workspace.fs.stat(uri); if (stat.type === vscode.FileType.Directory) { - const files = await vscode.workspace.fs.readDirectory(filepath); + const files = await vscode.workspace.fs.readDirectory(uri); for (const [filename, type] of files) { if (type === vscode.FileType.File) { addEntireFileToContext( - vscode.Uri.joinPath(filepath, filename), + vscode.Uri.joinPath(uri, filename), webviewProtocol, ); } @@ -179,9 +181,9 @@ async function addEntireFileToContext( } // Get the contents of the file - const contents = (await vscode.workspace.fs.readFile(filepath)).toString(); + const contents = (await vscode.workspace.fs.readFile(uri)).toString(); const rangeInFileWithContents = { - filepath: filepath.toString(), + filepath: uri.toString(), contents: contents, range: { start: { @@ -230,12 +232,12 @@ async function processDiff( diffManager: DiffManager, ide: VsCodeIde, verticalDiffManager: VerticalDiffManager, - newFileUri?: vscode.Uri, + newFileUri?: string, streamId?: string, ) { captureCommandTelemetry(`${action}Diff`); - let newOrCurrentUri = newFileUri?.toString(); + let newOrCurrentUri = newFileUri; if (!newOrCurrentUri) { const currentFile = await ide.getCurrentFile(); newOrCurrentUri = currentFile?.path; @@ -343,24 +345,18 @@ const getCommandsMap: ( ); } return { - "continue.acceptDiff": async ( - newFilepath?: string | vscode.Uri, - streamId?: string, - ) => + "continue.acceptDiff": async (newFileUri?: string, streamId?: string) => processDiff( "accept", sidebar, diffManager, ide, verticalDiffManager, - newFilepath, + newFileUri, streamId, ), - "continue.rejectDiff": async ( - newFilepath?: string | vscode.Uri, - streamId?: string, - ) => + "continue.rejectDiff": async (newFilepath?: string, streamId?: string) => processDiff( "reject", sidebar, @@ -626,17 +622,8 @@ const getCommandsMap: ( }, "continue.viewLogs": async () => { captureCommandTelemetry("viewLogs"); - - // Open ~/.continue/continue.log - const logFile = path.join(os.homedir(), ".continue", "continue.log"); - // Make sure the file/directory exist - if (!fs.existsSync(logFile)) { - fs.mkdirSync(path.dirname(logFile), { recursive: true }); - fs.writeFileSync(logFile, ""); - } - - const uri = vscode.Uri.file(logFile); - await vscode.window.showTextDocument(uri); + const logFilePath = getLogFilePath(); + await vscode.window.showTextDocument(vscode.Uri.file(logFilePath)); }, "continue.debugTerminal": async () => { captureCommandTelemetry("debugTerminal"); @@ -783,9 +770,9 @@ const getCommandsMap: ( .stat(uri) ?.then((stat) => stat.type === vscode.FileType.Directory); if (isDirectory) { - for await (const filepath of walkDirAsync(uri.toString(), ide)) { + for await (const fileUri of walkDirAsync(uri.toString(), ide)) { addEntireFileToContext( - vscode.Uri.parse(filepath), + vscode.Uri.parse(fileUri), sidebar.webviewProtocol, ); } @@ -917,7 +904,7 @@ const getCommandsMap: ( } else if ( selectedOption === "$(gear) Configure autocomplete options" ) { - ide.openFile(getConfigJsonPath()); + ide.openFile(vscode.Uri.file(getConfigJsonPath()).toString()); } else if ( autocompleteModels.some((model) => model.title === selectedOption) ) { diff --git a/extensions/vscode/src/diff/horizontal.ts b/extensions/vscode/src/diff/horizontal.ts index 42be5903fb..dc85a56833 100644 --- a/extensions/vscode/src/diff/horizontal.ts +++ b/extensions/vscode/src/diff/horizontal.ts @@ -1,7 +1,3 @@ -import * as fs from "node:fs"; -import * as os from "node:os"; -import * as path from "node:path"; - import { devDataPath } from "core/util/paths"; import * as vscode from "vscode"; @@ -17,47 +13,34 @@ interface DiffInfo { range: vscode.Range; } -async function readFile(uri: vscode.Uri): Promise { +async function readFile(fileUri: string): Promise { return await vscode.workspace.fs - .readFile(uri) + .readFile(vscode.Uri.parse(fileUri)) .then((bytes) => new TextDecoder().decode(bytes)); } -async function writeFile(uri: vscode.Uri, contents: string) { - await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(contents)); +async function writeFile(fileUri: string, contents: string) { + await vscode.workspace.fs.writeFile( + vscode.Uri.parse(fileUri), + new TextEncoder().encode(contents), + ); } -// THIS IS LOCAL -export const DIFF_DIRECTORY = path - .join(os.homedir(), ".continue", ".diffs") - .replace(/^C:/, "c:"); - export class DiffManager { // Create a temporary file in the global .continue directory which displays the updated version // Doing this because virtual files are read-only private diffs: Map = new Map(); - diffAtNewFilepath(newFilepath: string): DiffInfo | undefined { - return this.diffs.get(newFilepath); - } - - private async setupDirectory() { - // Make sure the diff directory exists - if (!fs.existsSync(DIFF_DIRECTORY)) { - fs.mkdirSync(DIFF_DIRECTORY, { - recursive: true, - }); - } + diffAtNewFilepath(newFileUri: string): DiffInfo | undefined { + return this.diffs.get(newFileUri); } webviewProtocol: VsCodeWebviewProtocol | undefined; constructor(private readonly extensionContext: vscode.ExtensionContext) { - this.setupDirectory(); - // Listen for file closes, and if it's a diff file, clean up vscode.workspace.onDidCloseTextDocument((document) => { - const newFilepath = document.uri.fsPath; + const newFilepath = document.uri.toString(); const diffInfo = this.diffs.get(newFilepath); if (diffInfo) { this.cleanUpDiff(diffInfo, false); @@ -73,15 +56,15 @@ export class DiffManager { } private remoteTmpDir = "/tmp/continue"; - private getNewFilepath(originalFilepath: string): string { + private getNewFileUri(originalFileUri: string): vscode.Uri { if (vscode.env.remoteName) { // If we're in a remote, use the remote's temp directory // Doing this because there's no easy way to find the home directory, // and there aren't write permissions to the root directory // and writing these to local causes separate issues // because the vscode.diff command will always try to read from remote - vscode.workspace.fs.createDirectory(uriFromFilePath(this.remoteTmpDir)); - return path.join( + vscode.workspace.fs.createDirectory(vscode.Uri.file(this.remoteTmpDir)); + return vscode.Uri.j.join( this.remoteTmpDir, this.escapeFilepath(originalFilepath), ); @@ -160,8 +143,6 @@ export class DiffManager { newContent: string, step_index: number, ): Promise { - await this.setupDirectory(); - // Create or update existing diff const newFilepath = this.getNewFilepath(originalFilepath); await writeFile(uriFromFilePath(newFilepath), newContent); diff --git a/extensions/vscode/src/extension/VsCodeExtension.ts b/extensions/vscode/src/extension/VsCodeExtension.ts index de4a36d02c..806e602a78 100644 --- a/extensions/vscode/src/extension/VsCodeExtension.ts +++ b/extensions/vscode/src/extension/VsCodeExtension.ts @@ -159,7 +159,7 @@ export class VsCodeExtension { const { verticalDiffCodeLens } = registerAllCodeLensProviders( context, this.diffManager, - this.verticalDiffManager.filepathToCodeLens, + this.verticalDiffManager.fileUriToCodeLens, config, ); @@ -182,7 +182,7 @@ export class VsCodeExtension { registerAllCodeLensProviders( context, this.diffManager, - this.verticalDiffManager.filepathToCodeLens, + this.verticalDiffManager.fileUriToCodeLens, newConfig, ); } @@ -264,8 +264,8 @@ export class VsCodeExtension { vscode.workspace.onDidSaveTextDocument(async (event) => { this.core.invoke("files/changed", { - uris: [event.uri.toString()] - }) + uris: [event.uri.toString()], + }); }); vscode.workspace.onDidDeleteFiles(async (event) => { @@ -317,7 +317,7 @@ export class VsCodeExtension { // Refresh index when branch is changed this.ide.getWorkspaceDirs().then((dirs) => dirs.forEach(async (dir) => { - const repo = await this.ide.getRepo(vscode.Uri.file(dir)); + const repo = await this.ide.getRepo(dir); if (repo) { repo.state.onDidChange(() => { // args passed to this callback are always undefined, so keep track of previous branch diff --git a/extensions/vscode/src/extension/VsCodeMessenger.ts b/extensions/vscode/src/extension/VsCodeMessenger.ts index 244777fa81..a7494e4df4 100644 --- a/extensions/vscode/src/extension/VsCodeMessenger.ts +++ b/extensions/vscode/src/extension/VsCodeMessenger.ts @@ -1,5 +1,4 @@ import * as fs from "node:fs"; -import * as path from "node:path"; import { ConfigHandler } from "core/config/ConfigHandler"; import { getModelByRole } from "core/config/util"; @@ -30,6 +29,7 @@ import { getExtensionUri } from "../util/vscode"; import { VsCodeIde } from "../VsCodeIde"; import { VsCodeWebviewProtocol } from "../webviewProtocol"; import { getUriPathBasename } from "core/util/uri"; +import { showTutorial } from "../util/tutorial"; /** * A shared messenger class between Core and Webview @@ -86,22 +86,16 @@ export class VsCodeMessenger { ) { /** WEBVIEW ONLY LISTENERS **/ this.onWebview("showFile", (msg) => { - const fullPath = getFullyQualifiedPath(this.ide, msg.data.filepath); - - if (fullPath) { - this.ide.openFile(fullPath); - } + this.ide.openFile(msg.data.filepath); }); this.onWebview("vscode/openMoveRightMarkdown", (msg) => { vscode.commands.executeCommand( "markdown.showPreview", - vscode.Uri.file( - path.join( - getExtensionUri().fsPath, - "media", - "move-chat-panel-right.md", - ), + vscode.Uri.joinPath( + getExtensionUri(), + "media", + "move-chat-panel-right.md", ), ); }); @@ -175,25 +169,14 @@ export class VsCodeMessenger { fileContent: data.text, }); - let filepath = data.filepath; - - // If there is a filepath, verify it exists and then open the file - if (filepath) { - const fullPath = getFullyQualifiedPath(ide, filepath); - - if (!fullPath) { - return; - } - - const fileExists = await this.ide.fileExists(fullPath); - - // If there is no existing file at the path, create it + if (data.filepath) { + const fileExists = await this.ide.fileExists(data.filepath); if (!fileExists) { - await this.ide.writeFile(fullPath, ""); - await this.ide.openFile(fullPath); + await this.ide.writeFile(data.filepath, ""); + await this.ide.openFile(data.filepath); } - await this.ide.openFile(fullPath); + await this.ide.openFile(data.filepath); } // Get active text editor @@ -282,25 +265,7 @@ export class VsCodeMessenger { }); this.onWebview("showTutorial", async (msg) => { - const tutorialPath = path.join( - getExtensionUri().fsPath, - "continue_tutorial.py", - ); - // Ensure keyboard shortcuts match OS - if (process.platform !== "darwin") { - let tutorialContent = fs.readFileSync(tutorialPath, "utf8"); - tutorialContent = tutorialContent - .replace("⌘", "^") - .replace("Cmd", "Ctrl"); - fs.writeFileSync(tutorialPath, tutorialContent); - } - - const doc = await vscode.workspace.openTextDocument( - vscode.Uri.file(tutorialPath), - ); - await vscode.window.showTextDocument(doc, { - preview: false, - }); + await showTutorial(this.ide); }); this.onWebview("openUrl", (msg) => { @@ -466,9 +431,6 @@ export class VsCodeMessenger { this.onWebviewOrCore("showVirtualFile", async (msg) => { return ide.showVirtualFile(msg.data.name, msg.data.content); }); - this.onWebviewOrCore("getContinueDir", async (msg) => { - return ide.getContinueDir(); - }); this.onWebviewOrCore("openFile", async (msg) => { return ide.openFile(msg.data.path); }); diff --git a/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts b/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts index dba240912c..32fbe97f16 100644 --- a/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts +++ b/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts @@ -2,8 +2,9 @@ import path from "path"; import * as vscode from "vscode"; -import { DiffManager, DIFF_DIRECTORY } from "../../../diff/horizontal"; +import { DiffManager } from "../../../diff/horizontal"; import { getMetaKeyLabel } from "../../../util/util"; +import { getDiffsDirectoryPath } from "core/util/paths"; export class DiffViewerCodeLensProvider implements vscode.CodeLensProvider { constructor(private diffManager: DiffManager) {} @@ -12,7 +13,7 @@ export class DiffViewerCodeLensProvider implements vscode.CodeLensProvider { document: vscode.TextDocument, _: vscode.CancellationToken, ): vscode.CodeLens[] | Thenable { - if (path.dirname(document.uri.fsPath) === DIFF_DIRECTORY) { + if (path.dirname(document.uri.fsPath) === getDiffsDirectoryPath()) { const codeLenses: vscode.CodeLens[] = []; let range = new vscode.Range(0, 0, 1, 0); const diffInfo = this.diffManager.diffAtNewFilepath( diff --git a/extensions/vscode/src/lang-server/codeLens/providers/QuickActionsCodeLensProvider.ts b/extensions/vscode/src/lang-server/codeLens/providers/QuickActionsCodeLensProvider.ts index b4c30350b4..18ed64dda2 100644 --- a/extensions/vscode/src/lang-server/codeLens/providers/QuickActionsCodeLensProvider.ts +++ b/extensions/vscode/src/lang-server/codeLens/providers/QuickActionsCodeLensProvider.ts @@ -7,11 +7,7 @@ import { CONTINUE_WORKSPACE_KEY, getContinueWorkspaceConfig, } from "../../../util/workspaceConfig"; - -const TUTORIAL_FILE_NAME = "continue_tutorial.py"; -function isTutorialFile(uri: vscode.Uri) { - return uri.fsPath.endsWith(TUTORIAL_FILE_NAME); -} +import { isTutorialFile } from "../../../util/tutorial"; export const ENABLE_QUICK_ACTIONS_KEY = "enableQuickActions"; diff --git a/extensions/vscode/src/quickEdit/QuickEditQuickPick.ts b/extensions/vscode/src/quickEdit/QuickEditQuickPick.ts index ce4a1b6458..aefc7408f7 100644 --- a/extensions/vscode/src/quickEdit/QuickEditQuickPick.ts +++ b/extensions/vscode/src/quickEdit/QuickEditQuickPick.ts @@ -248,7 +248,7 @@ export class QuickEdit { private setActiveEditorAndPrevInput(editor: vscode.TextEditor) { const existingHandler = this.verticalDiffManager.getHandlerForFile( - editor.document.uri.fsPath ?? "", + editor.document.uri.toString(), ); this.editorWhenOpened = editor; @@ -447,8 +447,8 @@ export class QuickEdit { if (searchResults.length > 0) { quickPick.items = searchResults - .map(({ filename }) => ({ - label: filename, + .map(({ relativePath }) => ({ + label: relativePath, alwaysShow: true, })) .slice(0, QuickEdit.maxFileSearchResults); diff --git a/extensions/vscode/src/util/tutorial.ts b/extensions/vscode/src/util/tutorial.ts new file mode 100644 index 0000000000..45a86a6757 --- /dev/null +++ b/extensions/vscode/src/util/tutorial.ts @@ -0,0 +1,27 @@ +import { IDE } from "core"; +import { getExtensionUri } from "./vscode"; +import * as vscode from "vscode"; + +const TUTORIAL_FILE_NAME = "continue_tutorial.py"; +export function getTutorialUri(): vscode.Uri { + return vscode.Uri.joinPath(getExtensionUri(), TUTORIAL_FILE_NAME); +} + +export function isTutorialFile(uri: vscode.Uri) { + return uri.path.endsWith(TUTORIAL_FILE_NAME); +} + +export async function showTutorial(ide: IDE) { + const tutorialUri = getTutorialUri(); + // Ensure keyboard shortcuts match OS + if (process.platform !== "darwin") { + let tutorialContent = await ide.readFile(tutorialUri.toString()); + tutorialContent = tutorialContent.replace("⌘", "^").replace("Cmd", "Ctrl"); + await ide.writeFile(tutorialUri.toString(), tutorialContent); + } + + const doc = await vscode.workspace.openTextDocument(tutorialUri); + await vscode.window.showTextDocument(doc, { + preview: false, + }); +} diff --git a/extensions/vscode/src/webviewProtocol.ts b/extensions/vscode/src/webviewProtocol.ts index 1fc383fae9..dcb07fb5a0 100644 --- a/extensions/vscode/src/webviewProtocol.ts +++ b/extensions/vscode/src/webviewProtocol.ts @@ -1,6 +1,3 @@ -import fs from "node:fs"; -import path from "path"; - import { FromWebviewProtocol, ToWebviewProtocol } from "core/protocol"; import { WebviewMessengerResult } from "core/protocol/util"; import { extractMinimalStackTraceInfo } from "core/util/extractMinimalStackTraceInfo"; @@ -12,25 +9,6 @@ import * as vscode from "vscode"; import { IMessenger } from "../../../core/protocol/messenger"; import { showFreeTrialLoginMessage } from "./util/messages"; -import { getExtensionUri } from "./util/vscode"; - -export async function showTutorial() { - const tutorialPath = path.join( - getExtensionUri().fsPath, - "continue_tutorial.py", - ); - // Ensure keyboard shortcuts match OS - if (process.platform !== "darwin") { - let tutorialContent = fs.readFileSync(tutorialPath, "utf8"); - tutorialContent = tutorialContent.replace("⌘", "^").replace("Cmd", "Ctrl"); - fs.writeFileSync(tutorialPath, tutorialContent); - } - - const doc = await vscode.workspace.openTextDocument( - vscode.Uri.file(tutorialPath), - ); - await vscode.window.showTextDocument(doc, { preview: false }); -} export class VsCodeWebviewProtocol implements IMessenger diff --git a/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx b/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx index f6e4420f41..f91300e742 100644 --- a/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx +++ b/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx @@ -4,12 +4,13 @@ import { ChevronRightIcon, XMarkIcon, } from "@heroicons/react/24/outline"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import StyledMarkdownPreview from "../markdown/StyledMarkdownPreview"; import { getMarkdownLanguageTagForFile } from "core/util"; import styled from "styled-components"; import { CodeToEdit } from "core"; -import { getLastNPathParts } from "core/util/uri"; +import { rifWithContentsToContextItem } from "core/commands/util"; +import { getUriPathBasename } from "core/util/uri"; export interface CodeToEditListItemProps { code: CodeToEdit; @@ -36,7 +37,15 @@ export default function CodeToEditListItem({ }: CodeToEditListItemProps) { const [showCodeSnippet, setShowCodeSnippet] = useState(false); - const filepath = code.filepath.split("/").pop() || code.filepath; + const context = useMemo(() => { + return rifWithContentsToContextItem({ + contents: code.contents, + range: { + start: {}, + }, + }); + }, []); + const basename = getUriPathBasename(code.filepath); const fileSubpath = getLastNPathParts(code.filepath, 2); let isInsertion = false; diff --git a/gui/src/components/mainInput/ContextItemsPeek.tsx b/gui/src/components/mainInput/ContextItemsPeek.tsx index 1e3997930d..226d87a6db 100644 --- a/gui/src/components/mainInput/ContextItemsPeek.tsx +++ b/gui/src/components/mainInput/ContextItemsPeek.tsx @@ -29,10 +29,14 @@ function ContextItemsPeekItem({ contextItem }: ContextItemsPeekItemProps) { const isUrl = contextItem.uri?.type === "url"; function openContextItem() { - const { uri, name, description, content } = contextItem; + const { uri, name, content } = contextItem; if (isUrl) { - ideMessenger.post("openUrl", uri.value); + if (uri?.value) { + ideMessenger.post("openUrl", uri.value); + } else { + console.error("Couldn't open url", uri); + } } else if (uri) { const isRangeInFile = name.includes(" (") && name.endsWith(")"); @@ -44,7 +48,7 @@ function ContextItemsPeekItem({ contextItem }: ContextItemsPeekItemProps) { rif.range.end.line, ); } else { - ideMessenger.ide.openFile(description); + ideMessenger.ide.openFile(uri.value); } } else { ideMessenger.ide.showVirtualFile(name, content); diff --git a/gui/src/components/markdown/CodeSnippetPreview.tsx b/gui/src/components/markdown/CodeSnippetPreview.tsx index 6bdfdf3536..2a1fe556a3 100644 --- a/gui/src/components/markdown/CodeSnippetPreview.tsx +++ b/gui/src/components/markdown/CodeSnippetPreview.tsx @@ -5,7 +5,7 @@ import { } from "@heroicons/react/24/outline"; import { ContextItemWithId } from "core"; import { dedent, getMarkdownLanguageTagForFile } from "core/util"; -import React, { useContext } from "react"; +import React, { useContext, useMemo } from "react"; import styled from "styled-components"; import { defaultBorderRadius, lightGray, vscEditorBackground } from ".."; import { IdeMessengerContext } from "../../context/IdeMessenger"; @@ -13,6 +13,7 @@ import { getFontSize } from "../../util"; import FileIcon from "../FileIcon"; import HeaderButtonWithToolTip from "../gui/HeaderButtonWithToolTip"; import StyledMarkdownPreview from "./StyledMarkdownPreview"; +import { ctxItemToRifWithContents } from "core/commands/util"; const PreviewMarkdownDiv = styled.div<{ borderColor?: string; @@ -49,7 +50,6 @@ interface CodeSnippetPreviewProps { const MAX_PREVIEW_HEIGHT = 300; -// Pre-compile the regular expression outside of the function const backticksRegex = /`{3,}/gm; function CodeSnippetPreview(props: CodeSnippetPreviewProps) { @@ -58,12 +58,14 @@ function CodeSnippetPreview(props: CodeSnippetPreviewProps) { const [collapsed, setCollapsed] = React.useState(true); const [hovered, setHovered] = React.useState(false); - const content = dedent`${props.item.content}`; + const content = useMemo(() => { + return dedent`${props.item.content}`; + }, [props.item.content]); - const fence = React.useMemo(() => { + const fence = useMemo(() => { const backticks = content.match(backticksRegex); return backticks ? backticks.sort().at(-1) + "`" : "```"; - }, [props.item.content]); + }, [content]); const codeBlockRef = React.useRef(null); @@ -79,19 +81,19 @@ function CodeSnippetPreview(props: CodeSnippetPreviewProps) { { - if (props.item.id.providerTitle === "file") { + if ( + props.item.id.providerTitle === "file" && + props.item.uri?.value + ) { ideMessenger.post("showFile", { - filepath: props.item.description, + filepath: props.item.uri.value, }); } else if (props.item.id.providerTitle === "code") { - const lines = props.item.name - .split("(")[1] - .split(")")[0] - .split("-"); + const rif = ctxItemToRifWithContents(props.item); ideMessenger.ide.showLines( - props.item.description.split(" ")[0], - parseInt(lines[0]) - 1, - parseInt(lines[1]) - 1, + rif.filepath, + rif.range.start.line, + rif.range.end.line, ); } else { ideMessenger.post("showVirtualFile", { @@ -124,9 +126,7 @@ function CodeSnippetPreview(props: CodeSnippetPreviewProps) { ref={codeBlockRef} > From c566eae06c3a0932aad59589a07318a6d7b0f1c7 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 11 Dec 2024 23:23:02 -0800 Subject: [PATCH 17/84] remove horizontal diff manager - diffmanager --- core/config/types.ts | 5 - core/index.d.ts | 6 - core/protocol/ide.ts | 4 - core/protocol/messenger/messageIde.ts | 11 - core/protocol/messenger/reverseMessageIde.ts | 4 - core/util/filesystem.ts | 8 - extensions/vscode/src/VsCodeIde.ts | 16 +- extensions/vscode/src/commands.ts | 16 +- extensions/vscode/src/diff/horizontal.ts | 319 ------------------ .../vscode/src/extension/VsCodeExtension.ts | 15 +- .../vscode/src/extension/VsCodeMessenger.ts | 7 - .../providers/DiffViewerCodeLensProvider.ts | 42 --- .../codeLens/registerAllCodeLensProviders.ts | 9 - 13 files changed, 3 insertions(+), 459 deletions(-) delete mode 100644 extensions/vscode/src/diff/horizontal.ts delete mode 100644 extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts diff --git a/core/config/types.ts b/core/config/types.ts index 93ca6acca5..ff5834919c 100644 --- a/core/config/types.ts +++ b/core/config/types.ts @@ -456,11 +456,6 @@ declare global { startLine: number, endLine: number, ): Promise; - showDiff( - filepath: string, - newContents: string, - stepIndex: number, - ): Promise; getOpenFiles(): Promise; getCurrentFile(): Promise; - showDiff( - fileUri: string, - newContents: string, - stepIndex: number, - ): Promise; - getOpenFiles(): Promise; getCurrentFile(): Promise< diff --git a/core/protocol/ide.ts b/core/protocol/ide.ts index dd0edeb54e..37d63d127a 100644 --- a/core/protocol/ide.ts +++ b/core/protocol/ide.ts @@ -34,10 +34,6 @@ export type ToIdeFromWebviewOrCoreProtocol = { saveFile: [{ filepath: string }, void]; fileExists: [{ filepath: string }, boolean]; readFile: [{ filepath: string }, string]; - showDiff: [ - { filepath: string; newContents: string; stepIndex: number }, - void, - ]; diffLine: [ { diffLine: DiffLine; diff --git a/core/protocol/messenger/messageIde.ts b/core/protocol/messenger/messageIde.ts index df25c83794..22d0dc7d60 100644 --- a/core/protocol/messenger/messageIde.ts +++ b/core/protocol/messenger/messageIde.ts @@ -164,17 +164,6 @@ export class MessageIde implements IDE { async readFile(fileUri: string): Promise { return await this.request("readFile", { filepath: fileUri }); } - async showDiff( - fileUri: string, - newContents: string, - stepIndex: number, - ): Promise { - await this.request("showDiff", { - filepath: fileUri, - newContents, - stepIndex, - }); - } getOpenFiles(): Promise { return this.request("getOpenFiles", undefined); diff --git a/core/protocol/messenger/reverseMessageIde.ts b/core/protocol/messenger/reverseMessageIde.ts index 3894a36979..cfd788a060 100644 --- a/core/protocol/messenger/reverseMessageIde.ts +++ b/core/protocol/messenger/reverseMessageIde.ts @@ -154,10 +154,6 @@ export class ReverseMessageIde { return this.ide.readFile(data.filepath); }); - this.on("showDiff", (data) => { - return this.ide.showDiff(data.filepath, data.newContents, data.stepIndex); - }); - this.on("getOpenFiles", () => { return this.ide.getOpenFiles(); }); diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts index a44aa1add5..3c69e33851 100644 --- a/core/util/filesystem.ts +++ b/core/util/filesystem.ts @@ -218,14 +218,6 @@ class FileSystemIde implements IDE { return Promise.resolve(undefined); } - showDiff( - fileUri: string, - newContents: string, - stepIndex: number, - ): Promise { - return Promise.resolve(); - } - getBranch(dir: string): Promise { return Promise.resolve(""); } diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts index c519f6bf89..0aacd398ac 100644 --- a/extensions/vscode/src/VsCodeIde.ts +++ b/extensions/vscode/src/VsCodeIde.ts @@ -5,15 +5,10 @@ import { Range } from "core"; import { EXTENSION_NAME } from "core/control-plane/env"; import { walkDir } from "core/indexing/walkDir"; import { GetGhTokenArgs } from "core/protocol/ide"; -import { - editConfigJson, - getConfigJsonPath, - getContinueGlobalPath, -} from "core/util/paths"; +import { editConfigJson, getConfigJsonPath } from "core/util/paths"; import * as vscode from "vscode"; import { executeGotoProvider } from "./autocomplete/lsp"; -import { DiffManager } from "./diff/horizontal"; import { Repository } from "./otherExtensions/git"; import { VsCodeIdeUtils } from "./util/ideUtils"; import { getExtensionUri, openEditorAndRevealRange } from "./util/vscode"; @@ -37,7 +32,6 @@ class VsCodeIde implements IDE { ideUtils: VsCodeIdeUtils; constructor( - private readonly diffManager: DiffManager, private readonly vscodeWebviewProtocolPromise: Promise, private readonly context: vscode.ExtensionContext, ) { @@ -476,14 +470,6 @@ class VsCodeIde implements IDE { await vscode.env.openExternal(vscode.Uri.parse(url)); } - async showDiff( - fileUri: string, - newContents: string, - stepIndex: number, - ): Promise { - await this.diffManager.writeDiff(fileUri, newContents, stepIndex); - } - async getOpenFiles(): Promise { return this.ideUtils.getOpenFiles().map((uri) => uri.toString()); } diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts index c5fee0ec95..9dd4bcb3de 100644 --- a/extensions/vscode/src/commands.ts +++ b/extensions/vscode/src/commands.ts @@ -29,7 +29,7 @@ import { setupStatusBar, } from "./autocomplete/statusBar"; import { ContinueGUIWebviewViewProvider } from "./ContinueGUIWebviewViewProvider"; -import { DiffManager } from "./diff/horizontal"; + import { VerticalDiffManager } from "./diff/vertical/manager"; import EditDecorationManager from "./quickEdit/EditDecorationManager"; import { QuickEdit, QuickEditShowParams } from "./quickEdit/QuickEditQuickPick"; @@ -229,7 +229,6 @@ function hideGUI() { async function processDiff( action: "accept" | "reject", sidebar: ContinueGUIWebviewViewProvider, - diffManager: DiffManager, ide: VsCodeIde, verticalDiffManager: VerticalDiffManager, newFileUri?: string, @@ -254,13 +253,6 @@ async function processDiff( // Clear vertical diffs depending on action verticalDiffManager.clearForfileUri(newOrCurrentUri, action === "accept"); - // Accept or reject the diff - if (action === "accept") { - await diffManager.acceptDiff(newOrCurrentUri); - } else { - await diffManager.rejectDiff(newOrCurrentUri); - } - void sidebar.webviewProtocol.request("setEditStatus", { status: "done", }); @@ -284,7 +276,6 @@ const getCommandsMap: ( extensionContext: vscode.ExtensionContext, sidebar: ContinueGUIWebviewViewProvider, configHandler: ConfigHandler, - diffManager: DiffManager, verticalDiffManager: VerticalDiffManager, continueServerClientPromise: Promise, battery: Battery, @@ -296,7 +287,6 @@ const getCommandsMap: ( extensionContext, sidebar, configHandler, - diffManager, verticalDiffManager, continueServerClientPromise, battery, @@ -349,7 +339,6 @@ const getCommandsMap: ( processDiff( "accept", sidebar, - diffManager, ide, verticalDiffManager, newFileUri, @@ -360,7 +349,6 @@ const getCommandsMap: ( processDiff( "reject", sidebar, - diffManager, ide, verticalDiffManager, newFilepath, @@ -995,7 +983,6 @@ export function registerAllCommands( extensionContext: vscode.ExtensionContext, sidebar: ContinueGUIWebviewViewProvider, configHandler: ConfigHandler, - diffManager: DiffManager, verticalDiffManager: VerticalDiffManager, continueServerClientPromise: Promise, battery: Battery, @@ -1011,7 +998,6 @@ export function registerAllCommands( extensionContext, sidebar, configHandler, - diffManager, verticalDiffManager, continueServerClientPromise, battery, diff --git a/extensions/vscode/src/diff/horizontal.ts b/extensions/vscode/src/diff/horizontal.ts deleted file mode 100644 index dc85a56833..0000000000 --- a/extensions/vscode/src/diff/horizontal.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { devDataPath } from "core/util/paths"; -import * as vscode from "vscode"; - -import { getMetaKeyLabel, getPlatform } from "../util/util"; - -import type { VsCodeWebviewProtocol } from "../webviewProtocol"; - -interface DiffInfo { - originalUri: string; - newUri: string; - editor?: vscode.TextEditor; - step_index: number; - range: vscode.Range; -} - -async function readFile(fileUri: string): Promise { - return await vscode.workspace.fs - .readFile(vscode.Uri.parse(fileUri)) - .then((bytes) => new TextDecoder().decode(bytes)); -} - -async function writeFile(fileUri: string, contents: string) { - await vscode.workspace.fs.writeFile( - vscode.Uri.parse(fileUri), - new TextEncoder().encode(contents), - ); -} - -export class DiffManager { - // Create a temporary file in the global .continue directory which displays the updated version - // Doing this because virtual files are read-only - private diffs: Map = new Map(); - - diffAtNewFilepath(newFileUri: string): DiffInfo | undefined { - return this.diffs.get(newFileUri); - } - - webviewProtocol: VsCodeWebviewProtocol | undefined; - - constructor(private readonly extensionContext: vscode.ExtensionContext) { - // Listen for file closes, and if it's a diff file, clean up - vscode.workspace.onDidCloseTextDocument((document) => { - const newFilepath = document.uri.toString(); - const diffInfo = this.diffs.get(newFilepath); - if (diffInfo) { - this.cleanUpDiff(diffInfo, false); - } - }); - } - - private escapeFilepath(filepath: string): string { - return filepath - .replace(/\//g, "_f_") - .replace(/\\/g, "_b_") - .replace(/:/g, "_c_"); - } - - private remoteTmpDir = "/tmp/continue"; - private getNewFileUri(originalFileUri: string): vscode.Uri { - if (vscode.env.remoteName) { - // If we're in a remote, use the remote's temp directory - // Doing this because there's no easy way to find the home directory, - // and there aren't write permissions to the root directory - // and writing these to local causes separate issues - // because the vscode.diff command will always try to read from remote - vscode.workspace.fs.createDirectory(vscode.Uri.file(this.remoteTmpDir)); - return vscode.Uri.j.join( - this.remoteTmpDir, - this.escapeFilepath(originalFilepath), - ); - } - return path.join(DIFF_DIRECTORY, this.escapeFilepath(originalFilepath)); - } - - private async openDiffEditor( - originalFilepath: string, - newFilepath: string, - ): Promise { - // If the file doesn't yet exist or the basename is a single digit number (vscode terminal), don't open the diff editor - try { - await vscode.workspace.fs.stat(uriFromFilePath(newFilepath)); - } catch (e) { - console.log("File doesn't exist, not opening diff editor", e); - return undefined; - } - if (path.basename(originalFilepath).match(/^\d$/)) { - return undefined; - } - - const rightUri = uriFromFilePath(newFilepath); - const leftUri = uriFromFilePath(originalFilepath); - const title = "Continue Diff"; - vscode.commands.executeCommand("vscode.diff", leftUri, rightUri, title); - - const editor = vscode.window.activeTextEditor; - if (!editor) { - return; - } - - // Change the vscode setting to allow codeLens in diff editor - vscode.workspace - .getConfiguration("diffEditor", editor.document.uri) - .update("codeLens", true, vscode.ConfigurationTarget.Global); - - if ( - this.extensionContext.globalState.get( - "continue.showDiffInfoMessage", - ) !== false - ) { - vscode.window - .showInformationMessage( - `Accept (${getMetaKeyLabel()}⇧⏎) or reject (${getMetaKeyLabel()}⇧⌫) at the top of the file.`, - "Got it", - "Don't show again", - ) - .then((selection) => { - if (selection === "Don't show again") { - // Get the global state - this.extensionContext.globalState.update( - "continue.showDiffInfoMessage", - false, - ); - } - }); - } - - return editor; - } - - private _findFirstDifferentLine(contentA: string, contentB: string): number { - const linesA = contentA.split("\n"); - const linesB = contentB.split("\n"); - for (let i = 0; i < linesA.length && i < linesB.length; i++) { - if (linesA[i] !== linesB[i]) { - return i; - } - } - return 0; - } - - async writeDiff( - originalFilepath: string, - newContent: string, - step_index: number, - ): Promise { - // Create or update existing diff - const newFilepath = this.getNewFilepath(originalFilepath); - await writeFile(uriFromFilePath(newFilepath), newContent); - - // Open the diff editor if this is a new diff - if (!this.diffs.has(newFilepath)) { - // Figure out the first line that is different - const oldContent = await readFile(originalFilepath); - const line = this._findFirstDifferentLine(oldContent, newContent); - - const diffInfo: DiffInfo = { - originalFilepath, - newFilepath, - step_index, - range: new vscode.Range(line, 0, line + 1, 0), - }; - this.diffs.set(newFilepath, diffInfo); - } - - // Open the editor if it hasn't been opened yet - const diffInfo = this.diffs.get(newFilepath); - if (diffInfo && !diffInfo?.editor) { - diffInfo.editor = await this.openDiffEditor( - originalFilepath, - newFilepath, - ); - this.diffs.set(newFilepath, diffInfo); - } - - if (getPlatform() === "windows") { - // Just a matter of how it renders - // Lags on windows without this - // Flashes too much on mac with it - vscode.commands.executeCommand( - "workbench.action.files.revert", - uriFromFilePath(newFilepath), - ); - } - - return newFilepath; - } - - cleanUpDiff(diffInfo: DiffInfo, hideEditor = true) { - // Close the editor, remove the record, delete the file - if (hideEditor && diffInfo.editor) { - try { - vscode.window.showTextDocument(diffInfo.editor.document); - vscode.commands.executeCommand("workbench.action.closeActiveEditor"); - } catch {} - } - this.diffs.delete(diffInfo.newFilepath); - vscode.workspace.fs.delete(uriFromFilePath(diffInfo.newFilepath)); - } - - private inferNewFilepath() { - const activeEditorPath = - vscode.window.activeTextEditor?.document.uri.fsPath; - if (activeEditorPath && path.dirname(activeEditorPath) === DIFF_DIRECTORY) { - return activeEditorPath; - } - const visibleEditors = vscode.window.visibleTextEditors.map( - (editor) => editor.document.uri.fsPath, - ); - for (const editorPath of visibleEditors) { - if (path.dirname(editorPath) === DIFF_DIRECTORY) { - for (const otherEditorPath of visibleEditors) { - if ( - path.dirname(otherEditorPath) !== DIFF_DIRECTORY && - this.getNewFilepath(otherEditorPath) === editorPath - ) { - return editorPath; - } - } - } - } - - if (this.diffs.size === 1) { - return Array.from(this.diffs.keys())[0]; - } - return undefined; - } - - async acceptDiff(newFilepath?: string) { - // When coming from a keyboard shortcut, we have to infer the newFilepath from visible text editors - if (!newFilepath) { - newFilepath = this.inferNewFilepath(); - } - if (!newFilepath) { - console.log("No newFilepath provided to accept the diff"); - return; - } - // Get the diff info, copy new file to original, then delete from record and close the corresponding editor - const diffInfo = this.diffs.get(newFilepath); - if (!diffInfo) { - console.log("No corresponding diffInfo found for newFilepath"); - return; - } - - // Save the right-side file, then copy over to original - vscode.workspace.textDocuments - .find((doc) => doc.uri.fsPath === newFilepath) - ?.save() - .then(async () => { - await writeFile( - uriFromFilePath(diffInfo.originalFilepath), - await readFile(diffInfo.newFilepath), - ); - this.cleanUpDiff(diffInfo); - }); - - await recordAcceptReject(true, diffInfo); - } - - async rejectDiff(newFilepath?: string) { - // If no newFilepath is provided and there is only one in the dictionary, use that - if (!newFilepath) { - newFilepath = this.inferNewFilepath(); - } - if (!newFilepath) { - console.log( - "No newFilepath provided to reject the diff, diffs.size was", - this.diffs.size, - ); - return; - } - const diffInfo = this.diffs.get(newFilepath); - if (!diffInfo) { - console.log("No corresponding diffInfo found for newFilepath"); - return; - } - - // Stop the step at step_index in case it is still streaming - this.webviewProtocol?.request("setInactive", undefined); - - vscode.workspace.textDocuments - .find((doc) => doc.uri.fsPath === newFilepath) - ?.save() - .then(() => { - this.cleanUpDiff(diffInfo); - }); - - await recordAcceptReject(false, diffInfo); - } -} - -async function recordAcceptReject(accepted: boolean, diffInfo: DiffInfo) { - const devDataDir = devDataPath(); - const suggestionsPath = path.join(devDataDir, "suggestions.json"); - - // Initialize suggestions list - let suggestions = []; - - // Check if suggestions.json exists - try { - const rawData = await readFile(suggestionsPath); - suggestions = JSON.parse(rawData); - } catch {} - - // Add the new suggestion to the list - suggestions.push({ - accepted, - timestamp: Date.now(), - suggestion: diffInfo.originalFilepath, - }); - - // Send the suggestion to the server - // ideProtocolClient.sendAcceptRejectSuggestion(accepted); - - // Write the updated suggestions back to the file - await writeFile( - vscode.Uri.file(suggestionsPath), - JSON.stringify(suggestions, null, 4), - ); -} diff --git a/extensions/vscode/src/extension/VsCodeExtension.ts b/extensions/vscode/src/extension/VsCodeExtension.ts index 806e602a78..aa69de0504 100644 --- a/extensions/vscode/src/extension/VsCodeExtension.ts +++ b/extensions/vscode/src/extension/VsCodeExtension.ts @@ -18,7 +18,6 @@ import { } from "../autocomplete/statusBar"; import { registerAllCommands } from "../commands"; import { ContinueGUIWebviewViewProvider } from "../ContinueGUIWebviewViewProvider"; -import { DiffManager } from "../diff/horizontal"; import { VerticalDiffManager } from "../diff/vertical/manager"; import { registerAllCodeLensProviders } from "../lang-server/codeLens"; import { registerAllPromptFilesCompletionProviders } from "../lang-server/promptFileCompletions"; @@ -47,7 +46,6 @@ export class VsCodeExtension { private tabAutocompleteModel: TabAutocompleteModel; private sidebar: ContinueGUIWebviewViewProvider; private windowId: string; - private diffManager: DiffManager; private editDecorationManager: EditDecorationManager; private verticalDiffManager: VerticalDiffManager; webviewProtocolPromise: Promise; @@ -70,12 +68,7 @@ export class VsCodeExtension { resolveWebviewProtocol = resolve; }, ); - this.diffManager = new DiffManager(context); - this.ide = new VsCodeIde( - this.diffManager, - this.webviewProtocolPromise, - context, - ); + this.ide = new VsCodeIde(this.webviewProtocolPromise, context); this.extensionContext = context; this.windowId = uuidv4(); @@ -152,13 +145,9 @@ export class VsCodeExtension { this.configHandler.reloadConfig.bind(this.configHandler), ); - // Indexing + pause token - this.diffManager.webviewProtocol = this.sidebar.webviewProtocol; - this.configHandler.loadConfig().then((config) => { const { verticalDiffCodeLens } = registerAllCodeLensProviders( context, - this.diffManager, this.verticalDiffManager.fileUriToCodeLens, config, ); @@ -181,7 +170,6 @@ export class VsCodeExtension { registerAllCodeLensProviders( context, - this.diffManager, this.verticalDiffManager.fileUriToCodeLens, newConfig, ); @@ -240,7 +228,6 @@ export class VsCodeExtension { context, this.sidebar, this.configHandler, - this.diffManager, this.verticalDiffManager, this.core.continueServerClientPromise, this.battery, diff --git a/extensions/vscode/src/extension/VsCodeMessenger.ts b/extensions/vscode/src/extension/VsCodeMessenger.ts index a7494e4df4..ebe11b890c 100644 --- a/extensions/vscode/src/extension/VsCodeMessenger.ts +++ b/extensions/vscode/src/extension/VsCodeMessenger.ts @@ -132,13 +132,6 @@ export class VsCodeMessenger { this.onWebview("readFile", async (msg) => { return await ide.readFile(msg.data.filepath); }); - this.onWebview("showDiff", async (msg) => { - return await ide.showDiff( - msg.data.filepath, - msg.data.newContents, - msg.data.stepIndex, - ); - }); webviewProtocol.on( "acceptDiff", diff --git a/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts b/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts deleted file mode 100644 index 32fbe97f16..0000000000 --- a/extensions/vscode/src/lang-server/codeLens/providers/DiffViewerCodeLensProvider.ts +++ /dev/null @@ -1,42 +0,0 @@ -import path from "path"; - -import * as vscode from "vscode"; - -import { DiffManager } from "../../../diff/horizontal"; -import { getMetaKeyLabel } from "../../../util/util"; -import { getDiffsDirectoryPath } from "core/util/paths"; - -export class DiffViewerCodeLensProvider implements vscode.CodeLensProvider { - constructor(private diffManager: DiffManager) {} - - public provideCodeLenses( - document: vscode.TextDocument, - _: vscode.CancellationToken, - ): vscode.CodeLens[] | Thenable { - if (path.dirname(document.uri.fsPath) === getDiffsDirectoryPath()) { - const codeLenses: vscode.CodeLens[] = []; - let range = new vscode.Range(0, 0, 1, 0); - const diffInfo = this.diffManager.diffAtNewFilepath( - document.uri.toString(), - ); - if (diffInfo) { - range = diffInfo.range; - } - codeLenses.push( - new vscode.CodeLens(range, { - title: `Accept All ✅ (${getMetaKeyLabel()}⇧⏎)`, - command: "continue.acceptDiff", - arguments: [document.uri.toString()], - }), - new vscode.CodeLens(range, { - title: `Reject All ❌ (${getMetaKeyLabel()}⇧⌫)`, - command: "continue.rejectDiff", - arguments: [document.uri.toString()], - }), - ); - return codeLenses; - } else { - return []; - } - } -} diff --git a/extensions/vscode/src/lang-server/codeLens/registerAllCodeLensProviders.ts b/extensions/vscode/src/lang-server/codeLens/registerAllCodeLensProviders.ts index 2defb97a0d..82503f922a 100644 --- a/extensions/vscode/src/lang-server/codeLens/registerAllCodeLensProviders.ts +++ b/extensions/vscode/src/lang-server/codeLens/registerAllCodeLensProviders.ts @@ -1,7 +1,6 @@ import { ContinueConfig } from "core"; import * as vscode from "vscode"; -import { DiffManager } from "../../diff/horizontal"; import { VerticalDiffCodeLens } from "../../diff/vertical/manager"; import * as providers from "./providers"; @@ -67,7 +66,6 @@ function registerQuickActionsProvider( * It also sets up a subscription to VS Code Quick Actions settings changes. * * @param context - The VS Code extension context - * @param diffManager - The DiffManager instance for managing diffs * @param editorToVerticalDiffCodeLens - A Map of editor IDs to VerticalDiffCodeLens arrays * @param config - The Continue configuration object * @@ -75,7 +73,6 @@ function registerQuickActionsProvider( */ export function registerAllCodeLensProviders( context: vscode.ExtensionContext, - diffManager: DiffManager, editorToVerticalDiffCodeLens: Map, config: ContinueConfig, ) { @@ -113,11 +110,6 @@ export function registerAllCodeLensProviders( new providers.SuggestionsCodeLensProvider(), ); - diffsCodeLensDisposable = registerCodeLensProvider( - "*", - new providers.DiffViewerCodeLensProvider(diffManager), - ); - configPyCodeLensDisposable = registerCodeLensProvider( "*", new providers.ConfigPyCodeLensProvider(), @@ -131,7 +123,6 @@ export function registerAllCodeLensProviders( context.subscriptions.push(verticalPerLineCodeLensProvider); context.subscriptions.push(suggestionsCodeLensDisposable); - context.subscriptions.push(diffsCodeLensDisposable); // was opening config UI from config.json. Not currently applicable. // context.subscriptions.push(configPyCodeLensDisposable); From f66409292574695cd99c7b7b2fc5a986a03bbb1d Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 11 Dec 2024 23:36:00 -0800 Subject: [PATCH 18/84] path -> uri: code to edit, some commands --- core/commands/util.ts | 4 ++-- core/util/uri.test.ts | 3 ++- gui/src/components/CodeToEditCard/CodeToEditListItem.tsx | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/commands/util.ts b/core/commands/util.ts index e1e9cd60eb..a136d4df4f 100644 --- a/core/commands/util.ts +++ b/core/commands/util.ts @@ -38,8 +38,8 @@ export function ctxItemToRifWithContents( if (nameSplit.length > 1) { const lines = nameSplit[1].split(")")[0].split("-"); - startLine = Number.parseInt(lines[0], 10); - endLine = Number.parseInt(lines[1], 10); + startLine = Number.parseInt(lines[0], 10) - 1; + endLine = Number.parseInt(lines[1], 10) - 1; } const rif: RangeInFileWithContents = { diff --git a/core/util/uri.test.ts b/core/util/uri.test.ts index f4f7409a43..1b5a7e2e07 100644 --- a/core/util/uri.test.ts +++ b/core/util/uri.test.ts @@ -1,3 +1,4 @@ +import { getLastNPathParts } from "."; import { getLastNUriRelativePathParts, getRelativePath, @@ -156,7 +157,7 @@ describe("getRelativePath", () => { describe("getLastNPathParts", () => { test("returns the last N parts of a filepath with forward slashes", () => { const filepath = "home/user/documents/project/file.txt"; - expect(getLastNUriRelativePathParts(filepath, 2)).toBe("project/file.txt"); + expect(getLastNPathParts(filepath, 2)).toBe("project/file.txt"); }); test("returns the last N parts of a filepath with backward slashes", () => { diff --git a/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx b/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx index f91300e742..295084ebb8 100644 --- a/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx +++ b/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx @@ -37,6 +37,7 @@ export default function CodeToEditListItem({ }: CodeToEditListItemProps) { const [showCodeSnippet, setShowCodeSnippet] = useState(false); + const x = useMemo(() => {}, [code.contents, code.filepath]); const context = useMemo(() => { return rifWithContentsToContextItem({ contents: code.contents, From 236c0b4442567e5d57ee2b1e5c13b6213eb32b27 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 11 Dec 2024 23:38:39 -0800 Subject: [PATCH 19/84] code to edit --- .../CodeToEditCard/CodeToEditListItem.tsx | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx b/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx index 295084ebb8..4935d129d2 100644 --- a/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx +++ b/gui/src/components/CodeToEditCard/CodeToEditListItem.tsx @@ -10,7 +10,10 @@ import { getMarkdownLanguageTagForFile } from "core/util"; import styled from "styled-components"; import { CodeToEdit } from "core"; import { rifWithContentsToContextItem } from "core/commands/util"; -import { getUriPathBasename } from "core/util/uri"; +import { + getLastNUriRelativePathParts, + getUriPathBasename, +} from "core/util/uri"; export interface CodeToEditListItemProps { code: CodeToEdit; @@ -37,20 +40,15 @@ export default function CodeToEditListItem({ }: CodeToEditListItemProps) { const [showCodeSnippet, setShowCodeSnippet] = useState(false); - const x = useMemo(() => {}, [code.contents, code.filepath]); - const context = useMemo(() => { - return rifWithContentsToContextItem({ - contents: code.contents, - range: { - start: {}, - }, - }); - }, []); - const basename = getUriPathBasename(code.filepath); - const fileSubpath = getLastNPathParts(code.filepath, 2); + const fileName = getUriPathBasename(code.filepath); + const last2Parts = getLastNUriRelativePathParts( + window.workspacePaths ?? [], + code.filepath, + 2, + ); let isInsertion = false; - let title = filepath; + let title = fileName; if ("range" in code) { const start = code.range.start.line + 1; @@ -96,7 +94,7 @@ export default function CodeToEditListItem({ {title} - {fileSubpath} + {last2Parts} From 4bddb193660edb09fc95e54c397578c34c6e0dd3 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 12 Dec 2024 00:47:54 -0800 Subject: [PATCH 20/84] prompt files path -> uri and refactor --- core/config/load.ts | 48 +-- core/config/promptFile.ts | 314 ------------------ .../providers/PromptFilesContextProvider.ts | 5 +- core/promptFiles/v1/createNewPromptFile.ts | 58 ---- .../v1/getContextProviderHelpers.ts | 29 +- core/promptFiles/v1/handlebarUtils.ts | 9 +- core/promptFiles/v1/index.ts | 7 +- .../v1/slashCommandFromPromptFile.ts | 72 ++-- core/promptFiles/v2/createNewPromptFile.ts | 5 +- core/promptFiles/v2/getPromptFiles.ts | 28 +- core/promptFiles/v2/parsePromptFileV1V2.ts | 23 ++ 11 files changed, 110 insertions(+), 488 deletions(-) delete mode 100644 core/config/promptFile.ts delete mode 100644 core/promptFiles/v1/createNewPromptFile.ts create mode 100644 core/promptFiles/v2/parsePromptFileV1V2.ts diff --git a/core/config/load.ts b/core/config/load.ts index b28734c46a..5608acade0 100644 --- a/core/config/load.ts +++ b/core/config/load.ts @@ -58,7 +58,6 @@ import { getConfigTsPath, getContinueDotEnv, getEsbuildBinaryPath, - readAllGlobalPromptFiles, } from "../util/paths"; import { @@ -68,12 +67,9 @@ import { defaultSlashCommandsVscode, } from "./default"; import { getSystemPromptDotFile } from "./getSystemPromptDotFile"; -import { - DEFAULT_PROMPTS_FOLDER, - getPromptFiles, - slashCommandFromPromptFile, -} from "./promptFile.js"; import { ConfigValidationError, validateConfig } from "./validation.js"; +import { slashCommandFromPromptFileV1 } from "../promptFiles/v1/slashCommandFromPromptFile"; +import { getAllPromptFiles } from "../promptFiles/v2/getPromptFiles"; export interface ConfigResult { config: T | undefined; @@ -183,8 +179,8 @@ function loadSerializedConfig( async function serializedToIntermediateConfig( initial: SerializedContinueConfig, ide: IDE, - loadPromptFiles: boolean = true, ): Promise { + // DEPRECATED - load custom slash commands const slashCommands: SlashCommand[] = []; for (const command of initial.slashCommands || []) { const newCommand = slashCommandFromDescription(command); @@ -196,33 +192,18 @@ async function serializedToIntermediateConfig( slashCommands.push(slashFromCustomCommand(command)); } - const workspaceDirs = await ide.getWorkspaceDirs(); - const promptFolder = initial.experimental?.promptPath; - - if (loadPromptFiles) { - // v1 prompt files - let promptFiles: { path: string; content: string }[] = []; - promptFiles = ( - await Promise.all( - workspaceDirs.map((dir) => - getPromptFiles( - ide, - path.join(dir, promptFolder ?? DEFAULT_PROMPTS_FOLDER), - ), - ), - ) - ) - .flat() - .filter(({ path }) => path.endsWith(".prompt")); - - // Also read from ~/.continue/.prompts - promptFiles.push(...readAllGlobalPromptFiles()); + // DEPRECATED - load slash commands from v1 prompt files + // NOTE: still checking the v1 default .prompts folder for slash commands + const promptFiles = await getAllPromptFiles( + ide, + initial.experimental?.promptPath, + true, + ); - for (const file of promptFiles) { - const slashCommand = slashCommandFromPromptFile(file.path, file.content); - if (slashCommand) { - slashCommands.push(slashCommand); - } + for (const file of promptFiles) { + const slashCommand = slashCommandFromPromptFileV1(file.path, file.content); + if (slashCommand) { + slashCommands.push(slashCommand); } } @@ -842,6 +823,5 @@ export { finalToBrowserConfig, intermediateToFinalConfig, loadFullConfigNode, - serializedToIntermediateConfig, type BrowserSerializedContinueConfig, }; diff --git a/core/config/promptFile.ts b/core/config/promptFile.ts deleted file mode 100644 index 5d37e99f65..0000000000 --- a/core/config/promptFile.ts +++ /dev/null @@ -1,314 +0,0 @@ -import path from "path"; - -import Handlebars from "handlebars"; -import * as YAML from "yaml"; - -import { walkDir } from "../indexing/walkDir"; -import { renderTemplatedString } from "../promptFiles/v1/renderTemplatedString"; -import { renderChatMessage } from "../util/messageContent"; - -import type { - ChatMessage, - ContextItem, - ContinueSDK, - IContextProvider, - IDE, - SlashCommand, -} from ".."; - -export const DEFAULT_PROMPTS_FOLDER = ".prompts"; - -async function getPromptFilesFromDir( - ide: IDE, - dir: string, -): Promise<{ path: string; content: string }[]> { - try { - const exists = await ide.fileExists(dir); - - if (!exists) { - return []; - } - - const paths = await walkDir(dir, ide, { ignoreFiles: [] }); - const promptFilePaths = paths.filter((p) => p.endsWith(".prompt")); - const results = promptFilePaths.map(async (path) => { - const content = await ide.readFile(path); // make a try catch - return { path, content }; - }); - return Promise.all(results); - } catch (e) { - console.error(e); - return []; - } -} - -export async function getAllPromptFilesV2( - ide: IDE, - overridePromptFolder?: string, -): Promise<{ path: string; content: string }[]> { - const workspaceDirs = await ide.getWorkspaceDirs(); - let promptFiles: { path: string; content: string }[] = []; - - promptFiles = ( - await Promise.all( - workspaceDirs.map((dir) => - getPromptFilesFromDir( - ide, - joinPathsToUri(dir, overridePromptFolder ?? ".continue/prompts"), - ), - ), - ) - ).flat(); - - // Also read from ~/.continue/.prompts - promptFiles.push(...readAllGlobalPromptFiles()); - - return await Promise.all( - promptFiles.map(async (file) => { - const content = await ide.readFile(file.path); - return { path: file.path, content }; - }), - ); -} - -// const DEFAULT_PROMPT_FILE = `# This is an example ".prompt" file -// # It is used to define and reuse prompts within Continue -// # Continue will automatically create a slash command for each prompt in the .prompts folder -// # To learn more, see the full .prompt file reference: https://docs.continue.dev/features/prompt-files -// temperature: 0.0 -// --- -// {{{ diff }}} - -// Give me feedback on the above changes. For each file, you should output a markdown section including the following: -// - If you found any problems, an h3 like "❌ " -// - If you didn't find any problems, an h3 like "✅ " -// - If you found any problems, add below a bullet point description of what you found, including a minimal code snippet explaining how to fix it -// - If you didn't find any problems, you don't need to add anything else - -// Here is an example. The example is surrounded in backticks, but your response should not be: - -// \`\`\` -// ### ✅ - -// ### ❌ - -// -// \`\`\` - -// You should look primarily for the following types of issues, and only mention other problems if they are highly pressing. - -// - console.logs that have been left after debugging -// - repeated code -// - algorithmic errors that could fail under edge cases -// - something that could be refactored - -// Make sure to review ALL files that were changed, do not skip any. -// `; - -// export async function createNewPromptFile( -// ide: IDE, -// promptPath: string | undefined, -// ): Promise { -// const workspaceDirs = await ide.getWorkspaceDirs(); -// if (workspaceDirs.length === 0) { -// throw new Error( -// "No workspace directories found. Make sure you've opened a folder in your IDE.", -// ); -// } -// const promptFilePath = path.join( -// workspaceDirs[0], -// promptPath ?? DEFAULT_PROMPTS_FOLDER, -// "new-prompt-file.prompt", -// ); - -// await ide.writeFile(promptFilePath, DEFAULT_PROMPT_FILE); -// await ide.openFile(promptFilePath); -// } - -// export function slashCommandFromPromptFile( -// path: string, -// content: string, -// ): SlashCommand | null { -// const { name, description, systemMessage, prompt, version } = parsePromptFile( -// path, -// content, -// ); - -// if (version !== 1) { -// return null; -// } - -// return { -// name, -// description, -// run: async function* (context) { -// const originalSystemMessage = context.llm.systemMessage; -// context.llm.systemMessage = systemMessage; - -// const userInput = extractUserInput(context.input, name); -// const renderedPrompt = await renderPrompt(prompt, context, userInput); -// const messages = updateChatHistory( -// context.history, -// name, -// renderedPrompt, -// systemMessage, -// ); - -// for await (const chunk of context.llm.streamChat( -// messages, -// new AbortController().signal, -// )) { -// yield renderChatMessage(chunk); -// } - -// context.llm.systemMessage = originalSystemMessage; -// }, -// }; -// } - -// function parsePromptFile(path: string, content: string) { -// let [preambleRaw, prompt] = content.split("\n---\n"); -// if (prompt === undefined) { -// prompt = preambleRaw; -// preambleRaw = ""; -// } - -// const preamble = YAML.parse(preambleRaw) ?? {}; -// const name = preamble.name ?? getBasename(path).split(".prompt")[0]; -// const description = preamble.description ?? name; -// const version = preamble.version ?? 2; - -// let systemMessage: string | undefined = undefined; -// if (prompt.includes("")) { -// systemMessage = prompt.split("")[1].split("")[0].trim(); -// prompt = prompt.split("")[1].trim(); -// } - -// return { name, description, systemMessage, prompt, version }; -// } - -// function extractUserInput(input: string, commandName: string): string { -// if (input.startsWith(`/${commandName}`)) { -// return input.slice(commandName.length + 1).trimStart(); -// } -// return input; -// } - -// async function renderPrompt( -// prompt: string, -// context: ContinueSDK, -// userInput: string, -// ) { -// const helpers = getContextProviderHelpers(context); - -// // A few context providers that don't need to be in config.json to work in .prompt files -// const diff = await context.ide.getDiff(true); -// const currentFile = await context.ide.getCurrentFile(); -// const inputData: Record = { -// diff: diff.join("\n"), -// input: userInput, -// }; -// if (currentFile) { -// inputData.currentFile = currentFile.path; -// } - -// return renderTemplatedString( -// prompt, -// context.ide.readFile.bind(context.ide), -// inputData, -// helpers, -// ); -// } - -// function getContextProviderHelpers( -// context: ContinueSDK, -// ): Array<[string, Handlebars.HelperDelegate]> | undefined { -// return context.config.contextProviders?.map((provider: IContextProvider) => [ -// provider.description.title, -// async (helperContext: any) => { -// const items = await provider.getContextItems(helperContext, { -// config: context.config, -// embeddingsProvider: context.config.embeddingsProvider, -// fetch: context.fetch, -// fullInput: context.input, -// ide: context.ide, -// llm: context.llm, -// reranker: context.config.reranker, -// selectedCode: context.selectedCode, -// }); - -// items.forEach((item) => -// context.addContextItem(createContextItem(item, provider)), -// ); - -// return items.map((item) => item.content).join("\n\n"); -// }, -// ]); -// } - -// function createContextItem(item: ContextItem, provider: IContextProvider) { -// return { -// ...item, -// id: { -// itemId: item.description, -// providerTitle: provider.description.title, -// }, -// }; -// } - -// function updateChatHistory( -// history: ChatMessage[], -// commandName: string, -// renderedPrompt: string, -// systemMessage?: string, -// ) { -// const messages = [...history]; - -// for (let i = messages.length - 1; i >= 0; i--) { -// const message = messages[i]; -// const { role, content } = message; -// if (role !== "user") { -// continue; -// } - -// if (Array.isArray(content)) { -// if (content.some((part) => part.text?.startsWith(`/${commandName}`))) { -// messages[i] = updateArrayContent( -// messages[i], -// commandName, -// renderedPrompt, -// ); -// break; -// } -// } else if ( -// typeof content === "string" && -// content.startsWith(`/${commandName}`) -// ) { -// messages[i] = { ...message, content: renderedPrompt }; -// break; -// } -// } - -// if (systemMessage) { -// messages[0]?.role === "system" -// ? (messages[0].content = systemMessage) -// : messages.unshift({ role: "system", content: systemMessage }); -// } - -// return messages; -// } - -// function updateArrayContent( -// message: any, -// commandName: string, -// renderedPrompt: string, -// ) { -// return { -// ...message, -// content: message.content.map((part: any) => -// part.text?.startsWith(`/${commandName}`) -// ? { ...part, text: renderedPrompt } -// : part, -// ), -// }; -// } diff --git a/core/context/providers/PromptFilesContextProvider.ts b/core/context/providers/PromptFilesContextProvider.ts index bc9239f1df..c3b4381f40 100644 --- a/core/context/providers/PromptFilesContextProvider.ts +++ b/core/context/providers/PromptFilesContextProvider.ts @@ -6,7 +6,7 @@ import { ContextSubmenuItem, LoadSubmenuItemsArgs, } from "../../"; -import { getAllPromptFilesV2 } from "../../promptFiles/v2/getPromptFiles"; +import { getAllPromptFiles } from "../../promptFiles/v2/getPromptFiles"; import { parsePreamble } from "../../promptFiles/v2/parse"; import { renderPromptFileV2 } from "../../promptFiles/v2/renderPromptFile"; @@ -38,9 +38,10 @@ class PromptFilesContextProvider extends BaseContextProvider { async loadSubmenuItems( args: LoadSubmenuItemsArgs, ): Promise { - const promptFiles = await getAllPromptFilesV2( + const promptFiles = await getAllPromptFiles( args.ide, args.config.experimental?.promptPath, + // Note, NOT checking v1 default folder here, deprecated for context provider ); return promptFiles.map((file) => { const preamble = parsePreamble(file.path, file.content); diff --git a/core/promptFiles/v1/createNewPromptFile.ts b/core/promptFiles/v1/createNewPromptFile.ts deleted file mode 100644 index f6da908547..0000000000 --- a/core/promptFiles/v1/createNewPromptFile.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { IDE } from "../.."; - -import { DEFAULT_PROMPTS_FOLDER } from "."; -import { joinPathsToUri } from "../../util/uri"; - -const DEFAULT_PROMPT_FILE = `# This is an example ".prompt" file -# It is used to define and reuse prompts within Continue -# Continue will automatically create a slash command for each prompt in the .prompts folder -# To learn more, see the full .prompt file reference: https://docs.continue.dev/features/prompt-files -temperature: 0.0 ---- -{{{ diff }}} - -Give me feedback on the above changes. For each file, you should output a markdown section including the following: -- If you found any problems, an h3 like "❌ " -- If you didn't find any problems, an h3 like "✅ " -- If you found any problems, add below a bullet point description of what you found, including a minimal code snippet explaining how to fix it -- If you didn't find any problems, you don't need to add anything else - -Here is an example. The example is surrounded in backticks, but your response should not be: - -\`\`\` -### ✅ - -### ❌ - - -\`\`\` - -You should look primarily for the following types of issues, and only mention other problems if they are highly pressing. - -- console.logs that have been left after debugging -- repeated code -- algorithmic errors that could fail under edge cases -- something that could be refactored - -Make sure to review ALL files that were changed, do not skip any. -`; - -export async function createNewPromptFile( - ide: IDE, - promptPath: string | undefined, -): Promise { - const workspaceDirs = await ide.getWorkspaceDirs(); - if (workspaceDirs.length === 0) { - throw new Error( - "No workspace directories found. Make sure you've opened a folder in your IDE.", - ); - } - const promptFilePath = joinPathsToUri( - workspaceDirs[0], - promptPath ?? DEFAULT_PROMPTS_FOLDER, - "new-prompt-file.prompt", - ); - - await ide.writeFile(promptFilePath, DEFAULT_PROMPT_FILE); - await ide.openFile(promptFilePath); -} diff --git a/core/promptFiles/v1/getContextProviderHelpers.ts b/core/promptFiles/v1/getContextProviderHelpers.ts index 263724efa4..e027ffbc56 100644 --- a/core/promptFiles/v1/getContextProviderHelpers.ts +++ b/core/promptFiles/v1/getContextProviderHelpers.ts @@ -1,9 +1,20 @@ import Handlebars from "handlebars"; +import { ContextItem, ContinueSDK, IContextProvider } from "../.."; + +function createContextItem(item: ContextItem, provider: IContextProvider) { + return { + ...item, + id: { + itemId: item.description, + providerTitle: provider.description.title, + }, + }; +} export function getContextProviderHelpers( - context: any, + context: ContinueSDK, ): Array<[string, Handlebars.HelperDelegate]> | undefined { - return context.config.contextProviders?.map((provider: any) => [ + return context.config.contextProviders?.map((provider: IContextProvider) => [ provider.description.title, async (helperContext: any) => { const items = await provider.getContextItems(helperContext, { @@ -17,21 +28,11 @@ export function getContextProviderHelpers( selectedCode: context.selectedCode, }); - items.forEach((item: any) => + items.forEach((item) => context.addContextItem(createContextItem(item, provider)), ); - return items.map((item: any) => item.content).join("\n\n"); + return items.map((item) => item.content).join("\n\n"); }, ]); } - -function createContextItem(item: any, provider: any) { - return { - ...item, - id: { - itemId: item.description, - providerTitle: provider.description.title, - }, - }; -} diff --git a/core/promptFiles/v1/handlebarUtils.ts b/core/promptFiles/v1/handlebarUtils.ts index 947b76fbf1..ffb7195902 100644 --- a/core/promptFiles/v1/handlebarUtils.ts +++ b/core/promptFiles/v1/handlebarUtils.ts @@ -1,7 +1,7 @@ import Handlebars from "handlebars"; import { v4 as uuidv4 } from "uuid"; -export function convertToLetter(num: number): string { +function convertToLetter(num: number): string { let result = ""; while (num > 0) { const remainder = (num - 1) % 26; @@ -15,7 +15,7 @@ export function convertToLetter(num: number): string { * We replace filepaths with alphabetic characters to handle * escaping issues. */ -export const replaceFilepaths = ( +const replaceFilepaths = ( value: string, ctxProviderNames: string[], ): [string, { [key: string]: string }] => { @@ -95,7 +95,10 @@ export async function prepareTemplateAndData( return [newTemplate, data]; } -export function compileAndRenderTemplate(template: string, data: Record): string { +export function compileAndRenderTemplate( + template: string, + data: Record, +): string { const templateFn = Handlebars.compile(template); return templateFn(data); } diff --git a/core/promptFiles/v1/index.ts b/core/promptFiles/v1/index.ts index 940403e38a..1d6726b3e3 100644 --- a/core/promptFiles/v1/index.ts +++ b/core/promptFiles/v1/index.ts @@ -1,6 +1 @@ -import { createNewPromptFile } from "./createNewPromptFile"; -import { slashCommandFromPromptFile } from "./slashCommandFromPromptFile"; - -export const DEFAULT_PROMPTS_FOLDER = ".prompts"; - -export { createNewPromptFile, slashCommandFromPromptFile }; +export const DEFAULT_PROMPTS_FOLDER_V1 = ".prompts"; diff --git a/core/promptFiles/v1/slashCommandFromPromptFile.ts b/core/promptFiles/v1/slashCommandFromPromptFile.ts index 56b6fb3667..b4f6705132 100644 --- a/core/promptFiles/v1/slashCommandFromPromptFile.ts +++ b/core/promptFiles/v1/slashCommandFromPromptFile.ts @@ -1,34 +1,14 @@ -import * as YAML from "yaml"; - import { ContinueSDK, SlashCommand } from "../.."; +import { getLastNPathParts } from "../../util"; import { renderChatMessage } from "../../util/messageContent"; +import { parsePromptFileV1V2 } from "../v2/parsePromptFileV1V2"; import { getContextProviderHelpers } from "./getContextProviderHelpers"; import { renderTemplatedString } from "./renderTemplatedString"; import { updateChatHistory } from "./updateChatHistory"; export function extractName(preamble: { name?: string }, path: string): string { - return preamble.name ?? getBasename(path).split(".prompt")[0]; -} - -export function parsePromptFile(path: string, content: string) { - let [preambleRaw, prompt] = content.split("\n---\n"); - if (prompt === undefined) { - prompt = preambleRaw; - preambleRaw = ""; - } - - const preamble = YAML.parse(preambleRaw) ?? {}; - const name = extractName(preamble, path); - const description = preamble.description ?? name; - - let systemMessage: string | undefined = undefined; - if (prompt.includes("")) { - systemMessage = prompt.split("")[1].split("")[0].trim(); - prompt = prompt.split("")[1].trim(); - } - - return { name, description, systemMessage, prompt }; + return preamble.name ?? getLastNPathParts(path, 1).split(".prompt")[0]; } export function extractUserInput(input: string, commandName: string): string { @@ -38,26 +18,23 @@ export function extractUserInput(input: string, commandName: string): string { return input; } -export async function getDefaultVariables( - context: ContinueSDK, - userInput: string, -): Promise> { - const currentFile = await context.ide.getCurrentFile(); - const vars: Record = { input: userInput }; - if (currentFile) { - vars.currentFile = currentFile.path; - } - return vars; -} - -export async function renderPrompt( +async function renderPromptV1( prompt: string, context: ContinueSDK, userInput: string, ) { const helpers = getContextProviderHelpers(context); - const inputData = await getDefaultVariables(context, userInput); + // A few context providers that don't need to be in config.json to work in .prompt files + const diff = await context.ide.getDiff(true); + const currentFile = await context.ide.getCurrentFile(); + const inputData: Record = { + diff: diff.join("\n"), + input: userInput, + }; + if (currentFile) { + inputData.currentFile = currentFile.path; + } return renderTemplatedString( prompt, @@ -67,21 +44,26 @@ export async function renderPrompt( ); } -export function slashCommandFromPromptFile( +export function slashCommandFromPromptFileV1( path: string, content: string, -): SlashCommand { - const { name, description, systemMessage, prompt } = parsePromptFile( - path, - content, - ); +): SlashCommand | null { + const { name, description, systemMessage, prompt, version } = + parsePromptFileV1V2(path, content); + + if (version !== 1) { + return null; + } return { name, description, run: async function* (context) { + const originalSystemMessage = context.llm.systemMessage; + context.llm.systemMessage = systemMessage; + const userInput = extractUserInput(context.input, name); - const renderedPrompt = await renderPrompt(prompt, context, userInput); + const renderedPrompt = await renderPromptV1(prompt, context, userInput); const messages = updateChatHistory( context.history, name, @@ -95,6 +77,8 @@ export function slashCommandFromPromptFile( )) { yield renderChatMessage(chunk); } + + context.llm.systemMessage = originalSystemMessage; }, }; } diff --git a/core/promptFiles/v2/createNewPromptFile.ts b/core/promptFiles/v2/createNewPromptFile.ts index 56ac84614d..02ff786d1f 100644 --- a/core/promptFiles/v2/createNewPromptFile.ts +++ b/core/promptFiles/v2/createNewPromptFile.ts @@ -1,7 +1,6 @@ import { IDE } from "../.."; import { GlobalContext } from "../../util/GlobalContext"; import { joinPathsToUri } from "../../util/uri"; -// import { getPathModuleForIde } from "../../util/pathModule"; const FIRST_TIME_DEFAULT_PROMPT_FILE = `# This is an example ".prompt" file # It is used to define and reuse prompts within Continue @@ -47,7 +46,7 @@ export async function createNewPromptFileV2( ); } - const baseDiruri = joinPathsToUri( + const baseDirUri = joinPathsToUri( workspaceDirs[0], promptPath ?? ".continue/prompts", ); @@ -58,7 +57,7 @@ export async function createNewPromptFileV2( do { const suffix = counter === 0 ? "" : `-${counter}`; promptFileUri = joinPathsToUri( - baseDiruri, + baseDirUri, `new-prompt-file${suffix}.prompt`, ); counter++; diff --git a/core/promptFiles/v2/getPromptFiles.ts b/core/promptFiles/v2/getPromptFiles.ts index dd04c14bcb..2998b5f8f8 100644 --- a/core/promptFiles/v2/getPromptFiles.ts +++ b/core/promptFiles/v2/getPromptFiles.ts @@ -2,8 +2,10 @@ import { IDE } from "../.."; import { walkDir } from "../../indexing/walkDir"; import { readAllGlobalPromptFiles } from "../../util/paths"; import { joinPathsToUri } from "../../util/uri"; +import { DEFAULT_PROMPTS_FOLDER_V1 } from "../v1"; -async function getPromptFilesFromDir( +export const DEFAULT_PROMPTS_FOLDER_V2 = ".continue/prompts"; +export async function getPromptFilesFromDir( ide: IDE, dir: string, ): Promise<{ path: string; content: string }[]> { @@ -27,22 +29,28 @@ async function getPromptFilesFromDir( } } -export async function getAllPromptFilesV2( +export async function getAllPromptFiles( ide: IDE, overridePromptFolder?: string, + checkV1DefaultFolder: boolean = false, ): Promise<{ path: string; content: string }[]> { const workspaceDirs = await ide.getWorkspaceDirs(); let promptFiles: { path: string; content: string }[] = []; + let dirsToCheck = [DEFAULT_PROMPTS_FOLDER_V2]; + if (checkV1DefaultFolder) { + dirsToCheck.push(DEFAULT_PROMPTS_FOLDER_V1); + } + if (overridePromptFolder) { + dirsToCheck = [overridePromptFolder]; + } + + const fullDirs = workspaceDirs + .map((dir) => dirsToCheck.map((d) => joinPathsToUri(dir, d))) + .flat(); + promptFiles = ( - await Promise.all( - workspaceDirs.map((dir) => - getPromptFilesFromDir( - ide, - joinPathsToUri(dir, overridePromptFolder ?? ".continue/prompts"), - ), - ), - ) + await Promise.all(fullDirs.map((dir) => getPromptFilesFromDir(ide, dir))) ).flat(); // Also read from ~/.continue/.prompts diff --git a/core/promptFiles/v2/parsePromptFileV1V2.ts b/core/promptFiles/v2/parsePromptFileV1V2.ts new file mode 100644 index 0000000000..ad607f3cc9 --- /dev/null +++ b/core/promptFiles/v2/parsePromptFileV1V2.ts @@ -0,0 +1,23 @@ +import * as YAML from "yaml"; +import { getLastNPathParts } from "../../util"; + +export function parsePromptFileV1V2(path: string, content: string) { + let [preambleRaw, prompt] = content.split("\n---\n"); + if (prompt === undefined) { + prompt = preambleRaw; + preambleRaw = ""; + } + + const preamble = YAML.parse(preambleRaw) ?? {}; + const name = preamble.name ?? getLastNPathParts(path, 1).split(".prompt")[0]; + const description = preamble.description ?? name; + const version = preamble.version ?? 2; + + let systemMessage: string | undefined = undefined; + if (prompt.includes("")) { + systemMessage = prompt.split("")[1].split("")[0].trim(); + prompt = prompt.split("")[1].trim(); + } + + return { name, description, systemMessage, prompt, version }; +} From 500b7219f42d69009f8152cf5ec709369d3491db Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 12 Dec 2024 00:53:51 -0800 Subject: [PATCH 21/84] move around getlastnpathparts --- core/indexing/CodeSnippetsIndex.ts | 6 +- .../v1/slashCommandFromPromptFile.ts | 2 +- core/promptFiles/v2/parse.ts | 2 +- core/promptFiles/v2/parsePromptFileV1V2.ts | 2 +- core/util/index.ts | 79 ------------------- core/util/uri.test.ts | 3 +- core/util/uri.ts | 79 +++++++++++++++++++ .../src/lang-server/promptFileCompletions.ts | 3 +- 8 files changed, 87 insertions(+), 89 deletions(-) diff --git a/core/indexing/CodeSnippetsIndex.ts b/core/indexing/CodeSnippetsIndex.ts index 0138817f60..48d60083b3 100644 --- a/core/indexing/CodeSnippetsIndex.ts +++ b/core/indexing/CodeSnippetsIndex.ts @@ -1,6 +1,5 @@ import Parser from "web-tree-sitter"; -import { getBasename, getLastNPathParts } from "../util/"; import { migrate } from "../util/paths"; import { getFullLanguageName, @@ -29,6 +28,7 @@ import type { IndexTag, IndexingProgressUpdate, } from "../"; +import { getLastNPathParts, getUriPathBasename } from "../util/uri"; type SnippetChunk = ChunkWithoutID & { title: string; signature: string }; @@ -250,7 +250,7 @@ export class CodeSnippetsCodebaseIndex implements CodebaseIndex { } yield { - desc: `Indexing ${getBasename(compute.path)}`, + desc: `Indexing ${getUriPathBasename(compute.path)}`, progress: i / results.compute.length, status: "indexing", }; @@ -353,7 +353,7 @@ export class CodeSnippetsCodebaseIndex implements CodebaseIndex { return { name: row.title, description: getLastNPathParts(row.path, 2), - content: `\`\`\`${getBasename(row.path)}\n${row.content}\n\`\`\``, + content: `\`\`\`${getUriPathBasename(row.path)}\n${row.content}\n\`\`\``, uri: { type: "file", value: row.path, diff --git a/core/promptFiles/v1/slashCommandFromPromptFile.ts b/core/promptFiles/v1/slashCommandFromPromptFile.ts index b4f6705132..d89ee10825 100644 --- a/core/promptFiles/v1/slashCommandFromPromptFile.ts +++ b/core/promptFiles/v1/slashCommandFromPromptFile.ts @@ -1,6 +1,6 @@ import { ContinueSDK, SlashCommand } from "../.."; -import { getLastNPathParts } from "../../util"; import { renderChatMessage } from "../../util/messageContent"; +import { getLastNPathParts } from "../../util/uri"; import { parsePromptFileV1V2 } from "../v2/parsePromptFileV1V2"; import { getContextProviderHelpers } from "./getContextProviderHelpers"; diff --git a/core/promptFiles/v2/parse.ts b/core/promptFiles/v2/parse.ts index 334733d79c..95e60230e3 100644 --- a/core/promptFiles/v2/parse.ts +++ b/core/promptFiles/v2/parse.ts @@ -1,5 +1,5 @@ import * as YAML from "yaml"; -import { getLastNPathParts } from "../../util"; +import { getLastNPathParts } from "../../util/uri"; export function extractName(preamble: { name?: string }, path: string): string { return preamble.name ?? getLastNPathParts(path, 1).split(".prompt")[0]; diff --git a/core/promptFiles/v2/parsePromptFileV1V2.ts b/core/promptFiles/v2/parsePromptFileV1V2.ts index ad607f3cc9..11e2b10be1 100644 --- a/core/promptFiles/v2/parsePromptFileV1V2.ts +++ b/core/promptFiles/v2/parsePromptFileV1V2.ts @@ -1,5 +1,5 @@ import * as YAML from "yaml"; -import { getLastNPathParts } from "../../util"; +import { getLastNPathParts } from "../../util/uri"; export function parsePromptFileV1V2(path: string, content: string) { let [preambleRaw, prompt] = content.split("\n---\n"); diff --git a/core/util/index.ts b/core/util/index.ts index d310b3fde1..04dc6d5675 100644 --- a/core/util/index.ts +++ b/core/util/index.ts @@ -66,85 +66,6 @@ export function dedentAndGetCommonWhitespace(s: string): [string, string] { return [lines.map((x) => x.replace(lcp, "")).join("\n"), lcp]; } -const SEP_REGEX = /[\\/]/; - -// export function getBasename(filepath: string): string { -// return filepath.split(SEP_REGEX).pop() ?? ""; -// } - -export function getLastNPathParts(filepath: string, n: number): string { - if (n <= 0) { - return ""; - } - return filepath.split(SEP_REGEX).slice(-n).join("/"); -} - -// export function groupByLastNPathParts( -// filepaths: string[], -// n: number, -// ): Record { -// return filepaths.reduce( -// (groups, item) => { -// const lastNParts = getLastNPathParts(item, n); -// if (!groups[lastNParts]) { -// groups[lastNParts] = []; -// } -// groups[lastNParts].push(item); -// return groups; -// }, -// {} as Record, -// ); -// } - -// export function getUniqueFilePath( -// item: string, -// itemGroups: Record, -// ): string { -// const lastTwoParts = getLastNPathParts(item, 2); -// const group = itemGroups[lastTwoParts]; - -// let n = 2; -// if (group.length > 1) { -// while ( -// group.some( -// (otherItem) => -// otherItem !== item && -// getLastNPathParts(otherItem, n) === getLastNPathParts(item, n), -// ) -// ) { -// n++; -// } -// } - -// return getLastNPathParts(item, n); -// } - -// export function splitPath(path: string, withRoot?: string): string[] { -// let parts = path.includes("/") ? path.split("/") : path.split("\\"); -// if (withRoot !== undefined) { -// const rootParts = splitPath(withRoot); -// parts = parts.slice(rootParts.length - 1); -// } -// return parts; -// } - -// export function getRelativePath( -// filepath: string, -// workspaceDirs: string[], -// ): string { -// for (const workspaceDir of workspaceDirs) { -// const filepathParts = splitPath(filepath); -// const workspaceDirParts = splitPath(workspaceDir); -// if ( -// filepathParts.slice(0, workspaceDirParts.length).join("/") === -// workspaceDirParts.join("/") -// ) { -// return filepathParts.slice(workspaceDirParts.length).join("/"); -// } -// } -// return splitPath(filepath).pop() ?? ""; // If the file is not in any of the workspaces, return the plain filename -// } - export function getMarkdownLanguageTagForFile(filepath: string): string { const extToLangMap: { [key: string]: string } = { py: "python", diff --git a/core/util/uri.test.ts b/core/util/uri.test.ts index 1b5a7e2e07..09cb27c6c0 100644 --- a/core/util/uri.test.ts +++ b/core/util/uri.test.ts @@ -1,6 +1,5 @@ -import { getLastNPathParts } from "."; import { - getLastNUriRelativePathParts, + getLastNPathParts, getRelativePath, getUniqueUriPath, getUriPathBasename, diff --git a/core/util/uri.ts b/core/util/uri.ts index 57af61235a..ef32230135 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -179,3 +179,82 @@ export function getUriFileExtension(uri: string) { const baseName = getUriPathBasename(uri); return baseName.split(".")[-1] ?? ""; } + +const SEP_REGEX = /[\\/]/; + +// export function getBasename(filepath: string): string { +// return filepath.split(SEP_REGEX).pop() ?? ""; +// } + +export function getLastNPathParts(filepath: string, n: number): string { + if (n <= 0) { + return ""; + } + return filepath.split(SEP_REGEX).slice(-n).join("/"); +} + +// export function groupByLastNPathParts( +// filepaths: string[], +// n: number, +// ): Record { +// return filepaths.reduce( +// (groups, item) => { +// const lastNParts = getLastNPathParts(item, n); +// if (!groups[lastNParts]) { +// groups[lastNParts] = []; +// } +// groups[lastNParts].push(item); +// return groups; +// }, +// {} as Record, +// ); +// } + +// export function getUniqueFilePath( +// item: string, +// itemGroups: Record, +// ): string { +// const lastTwoParts = getLastNPathParts(item, 2); +// const group = itemGroups[lastTwoParts]; + +// let n = 2; +// if (group.length > 1) { +// while ( +// group.some( +// (otherItem) => +// otherItem !== item && +// getLastNPathParts(otherItem, n) === getLastNPathParts(item, n), +// ) +// ) { +// n++; +// } +// } + +// return getLastNPathParts(item, n); +// } + +// export function splitPath(path: string, withRoot?: string): string[] { +// let parts = path.includes("/") ? path.split("/") : path.split("\\"); +// if (withRoot !== undefined) { +// const rootParts = splitPath(withRoot); +// parts = parts.slice(rootParts.length - 1); +// } +// return parts; +// } + +// export function getRelativePath( +// filepath: string, +// workspaceDirs: string[], +// ): string { +// for (const workspaceDir of workspaceDirs) { +// const filepathParts = splitPath(filepath); +// const workspaceDirParts = splitPath(workspaceDir); +// if ( +// filepathParts.slice(0, workspaceDirParts.length).join("/") === +// workspaceDirParts.join("/") +// ) { +// return filepathParts.slice(workspaceDirParts.length).join("/"); +// } +// } +// return splitPath(filepath).pop() ?? ""; // If the file is not in any of the workspaces, return the plain filename +// } diff --git a/extensions/vscode/src/lang-server/promptFileCompletions.ts b/extensions/vscode/src/lang-server/promptFileCompletions.ts index 8c5c050211..ba15906854 100644 --- a/extensions/vscode/src/lang-server/promptFileCompletions.ts +++ b/extensions/vscode/src/lang-server/promptFileCompletions.ts @@ -2,8 +2,7 @@ import { IDE } from "core"; import vscode from "vscode"; import { FileSearch } from "../util/FileSearch"; -import { getUriPathBasename } from "core/util/uri"; -import { getLastNPathParts } from "core/util"; +import { getUriPathBasename, getLastNPathParts } from "core/util/uri"; class PromptFilesCompletionItemProvider implements vscode.CompletionItemProvider From 244c93c379c999d7e4f8b42ca7ab4bf1b7e15ccd Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 12 Dec 2024 11:22:06 -0800 Subject: [PATCH 22/84] file tree context provider --- core/context/providers/FileTreeContextProvider.ts | 13 ++++++++----- core/util/generateRepoMap.ts | 4 ---- manual-testing-sandbox/helloNested.py | 0 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 manual-testing-sandbox/helloNested.py diff --git a/core/context/providers/FileTreeContextProvider.ts b/core/context/providers/FileTreeContextProvider.ts index fbb4b90a8d..0a8bbd5526 100644 --- a/core/context/providers/FileTreeContextProvider.ts +++ b/core/context/providers/FileTreeContextProvider.ts @@ -4,7 +4,7 @@ import { ContextProviderExtras, } from "../../index.js"; import { walkDir } from "../../indexing/walkDir.js"; -import { getUriPathBasename } from "../../util/uri.js"; +import { getRelativePath, getUriPathBasename } from "../../util/uri.js"; import { BaseContextProvider } from "../index.js"; interface Directory { @@ -44,16 +44,19 @@ class FileTreeContextProvider extends BaseContextProvider { const trees = []; for (const workspaceDir of workspaceDirs) { - const contents = await walkDir(workspaceDir, extras.ide); - const subDirTree: Directory = { name: getUriPathBasename(workspaceDir), files: [], directories: [], }; - for (const file of contents) { - const parts = splitPath(file, workspaceDir); + const uris = await walkDir(workspaceDir, extras.ide); + const relativePaths = uris.map((uri) => + getRelativePath(uri, [workspaceDir]), + ); + + for (const path of relativePaths) { + const parts = path.split("/"); let currentTree = subDirTree; for (const part of parts.slice(0, -1)) { diff --git a/core/util/generateRepoMap.ts b/core/util/generateRepoMap.ts index db1c1a2139..6aad1f95ab 100644 --- a/core/util/generateRepoMap.ts +++ b/core/util/generateRepoMap.ts @@ -13,10 +13,6 @@ export interface RepoMapOptions { dirs?: string[]; } -/* -Generates repo map from workspace files -Saves it to local file in Continue global directory - */ class RepoMapGenerator { private maxRepoMapTokens: number; diff --git a/manual-testing-sandbox/helloNested.py b/manual-testing-sandbox/helloNested.py deleted file mode 100644 index e69de29bb2..0000000000 From 5846b32d61cb6d8544aed413c4a2f7d7cafba7d5 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 12 Dec 2024 11:53:14 -0800 Subject: [PATCH 23/84] path -> uri: core, share slash command --- core/commands/slash/share.ts | 5 ++++- core/commands/util.ts | 4 ---- core/core.ts | 21 +++++++++------------ 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/core/commands/slash/share.ts b/core/commands/slash/share.ts index f606385828..d840f7db1e 100644 --- a/core/commands/slash/share.ts +++ b/core/commands/slash/share.ts @@ -1,4 +1,7 @@ +import fs from "fs"; import { homedir } from "node:os"; +import { fileURLToPath } from "node:url"; +import path from "path"; import { languageForFilepath } from "../../autocomplete/constants/AutocompleteLanguageInfo.js"; import { SlashCommand } from "../../index.js"; @@ -77,7 +80,7 @@ const ShareSlashCommand: SlashCommand = { // folders are included. We default to using the first item in the list, if // it exists. const workspaceDirectory = workspaceDirs?.[0] || ""; - outputDir = outputDir.replace(/^./, workspaceDirectory); + outputDir = outputDir.replace(/^./, fileURLToPath(workspaceDirectory)); } if (!fs.existsSync(outputDir)) { diff --git a/core/commands/util.ts b/core/commands/util.ts index a136d4df4f..de7b6b97c3 100644 --- a/core/commands/util.ts +++ b/core/commands/util.ts @@ -59,7 +59,3 @@ export function ctxItemToRifWithContents( return rif; } - -// export function codeContextItemToMarkdownSource( -// item: ContextItemWithId, -// ): string {} diff --git a/core/core.ts b/core/core.ts index 3b11364512..faf58fc82f 100644 --- a/core/core.ts +++ b/core/core.ts @@ -732,21 +732,18 @@ export class Core { // URI TODO is this equality statement valid? const filePath = getFullPath(uri); if (filePath === getConfigJsonPath()) { - // Trigger a toast notification to provide UI feedback that config - // has been updated + // Trigger a toast notification to provide UI feedback that config has been updated const showToast = this.globalContext.get("showConfigUpdateToast") ?? true; if (showToast) { - void this.ide.showToast("info", "Config updated"); - - // URI TODO - this is a small regression - add core -> toast functionality that handles responses to update global context here - // vscode.window - // .showInformationMessage("Config updated", "Don't show again") - // .then((selection) => { - // if (selection === "Don't show again") { - // this.globalContext.update("showConfigUpdateToast", false); - // } - // }); + const selection = await this.ide.showToast( + "info", + "Config updated", + "Don't show again", + ); + if (selection === "Don't show again") { + this.globalContext.update("showConfigUpdateToast", false); + } } } From 01efdfeb6e2046f09192138cb3117e3614273cf9 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 12 Dec 2024 14:16:26 -0800 Subject: [PATCH 24/84] walkdir and repomap p1 --- .../templating/AutocompleteTemplate.ts | 122 +++++++++--------- core/context/retrieval/repoMapRequest.ts | 9 +- core/indexing/walkDir.test.ts | 4 +- core/indexing/walkDir.ts | 89 +++++-------- .../vscode/src/extension/VsCodeMessenger.ts | 2 - 5 files changed, 102 insertions(+), 124 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index 1ada0d55c5..f8c8215fa2 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -1,10 +1,6 @@ // Fill in the middle prompts import { CompletionOptions } from "../../index.js"; -import { - getLastNUriRelativePathParts, - shortestRelativeUriPaths, -} from "../../util/uri.js"; import { AutocompleteCodeSnippet, AutocompleteSnippet, @@ -18,6 +14,7 @@ export interface AutocompleteTemplate { filepath: string, reponame: string, snippets: AutocompleteSnippet[], + workspaceDirs: string[], ) => [string, string]; template: | string @@ -28,6 +25,7 @@ export interface AutocompleteTemplate { reponame: string, language: string, snippets: AutocompleteSnippet[], + workspaceDirs: [], ) => string); completionOptions?: Partial; } @@ -67,32 +65,30 @@ const qwenCoderFimTemplate: AutocompleteTemplate = { }, }; -// const codestralFimTemplate: AutocompleteTemplate = { -// template: "[SUFFIX]{{{suffix}}}[PREFIX]{{{prefix}}}", -// completionOptions: { -// stop: ["[PREFIX]", "[SUFFIX]"], -// }, -// }; +const codestralFimTemplate: AutocompleteTemplate = { + template: "[SUFFIX]{{{suffix}}}[PREFIX]{{{prefix}}}", + completionOptions: { + stop: ["[PREFIX]", "[SUFFIX]"], + }, +}; const codestralMultifileFimTemplate: AutocompleteTemplate = { compilePrefixSuffix: ( - prefix: string, - suffix: string, - filepath: string, - reponame: string, - snippets: AutocompleteSnippet[], + prefix, + suffix, + filepath, + reponame, + snippets, + workspaceDirs, ): [string, string] => { if (snippets.length === 0) { if (suffix.trim().length === 0 && prefix.trim().length === 0) { - return [ - `+++++ ${getLastNUriRelativePathParts(workspaceDirs, filepath, 2)}\n${prefix}`, - suffix, - ]; + return [`+++++ ${getLastNPathParts(filepath, 2)}\n${prefix}`, suffix]; } return [prefix, suffix]; } - const relativePaths = shortestRelativeUriPaths([ + const relativePaths = shortestRelativePaths([ ...snippets.map((snippet) => "filepath" in snippet ? snippet.filepath : "Untitled.txt", ), @@ -139,38 +135,39 @@ const codegemmaFimTemplate: AutocompleteTemplate = { }, }; -// // https://arxiv.org/pdf/2402.19173.pdf section 5.1 -// const starcoder2FimTemplate: AutocompleteTemplate = { -// template: ( -// prefix: string, -// suffix: string, -// filename: string, -// reponame: string, -// language: string, -// snippets: AutocompleteSnippet[], -// ): string => { -// const otherFiles = -// snippets.length === 0 -// ? "" -// : `${snippets -// .map((snippet) => { -// return snippet.content; -// }) -// .join("")}`; - -// const prompt = `${otherFiles}${prefix}${suffix}`; -// return prompt; -// }, -// completionOptions: { -// stop: [ -// "", -// "", -// "", -// "", -// "<|endoftext|>", -// ], -// }, -// }; +// https://arxiv.org/pdf/2402.19173.pdf section 5.1 +const starcoder2FimTemplate: AutocompleteTemplate = { + template: ( + prefix, + suffix, + filename, + reponame, + language, + snippets, + workspaceDirs, + ): string => { + const otherFiles = + snippets.length === 0 + ? "" + : `${snippets + .map((snippet) => { + return snippet.content; + }) + .join("")}`; + + const prompt = `${otherFiles}${prefix}${suffix}`; + return prompt; + }, + completionOptions: { + stop: [ + "", + "", + "", + "", + "<|endoftext|>", + ], + }, +}; const codeLlamaFimTemplate: AutocompleteTemplate = { template: "
 {{{prefix}}} {{{suffix}}} ",
@@ -195,21 +192,22 @@ const deepseekFimTemplate: AutocompleteTemplate = {
 // https://github.com/THUDM/CodeGeeX4/blob/main/guides/Infilling_guideline.md
 const codegeexFimTemplate: AutocompleteTemplate = {
   template: (
-    prefix: string,
-    suffix: string,
-    directoryUri: string,
-    reponame: string,
-    language: string,
-    allSnippets: AutocompleteSnippet[],
+    prefix,
+    suffix,
+    filepath,
+    reponame,
+    language,
+    allSnippets,
+    workspaceDirs,
   ): string => {
     const snippets = allSnippets.filter(
       (snippet) => snippet.type === AutocompleteSnippetType.Code,
     ) as AutocompleteCodeSnippet[];
 
-    const relativePaths = shortestRelativeUriPaths(
-      [...snippets.map((snippet) => snippet.filepath)],
-      directoryUri,
-    );
+    const relativePaths = shortestRelativePaths([
+      ...snippets.map((snippet) => snippet.filepath),
+      filepath,
+    ]);
     const baseTemplate = `###PATH:${
       relativePaths[relativePaths.length - 1]
     }\n###LANGUAGE:${language}\n###MODE:BLOCK\n<|code_suffix|>${suffix}<|code_prefix|>${prefix}<|code_middle|>`;
diff --git a/core/context/retrieval/repoMapRequest.ts b/core/context/retrieval/repoMapRequest.ts
index 5aaabead77..a6a77127a8 100644
--- a/core/context/retrieval/repoMapRequest.ts
+++ b/core/context/retrieval/repoMapRequest.ts
@@ -2,6 +2,7 @@ import { Chunk, ContinueConfig, IDE, ILLM } from "../..";
 import { getModelByRole } from "../../config/util";
 import generateRepoMap from "../../util/generateRepoMap";
 import { renderChatMessage } from "../../util/messageContent";
+import { getUriPathBasename } from "../../util/uri";
 
 const SUPPORTED_MODEL_TITLE_FAMILIES = [
   "claude-3",
@@ -35,7 +36,7 @@ export async function requestFilesFromRepoMap(
   config: ContinueConfig,
   ide: IDE,
   input: string,
-  filterDirectory?: string,
+  filterDirUri?: string,
 ): Promise {
   const llm = getModelByRole(config, "repoMapFileSelection") ?? defaultLlm;
 
@@ -47,7 +48,7 @@ export async function requestFilesFromRepoMap(
   try {
     const repoMap = await generateRepoMap(llm, ide, {
       includeSignatures: false,
-      dirs: filterDirectory ? [filterDirectory] : undefined,
+      dirs: filterDirUri ? [filterDirUri] : undefined,
     });
 
     const prompt = `${repoMap}
@@ -71,7 +72,9 @@ This is the question that you should select relevant files for: "${input}"`;
       return [];
     }
 
-    const subDirPrefix = filterDirectory ? filterDirectory + pathSep : "";
+    const subDirPrefix = filterDirUri
+      ? getUriPathBasename(filterDirUri) + "/"
+      : "";
     const files =
       content
         .split("")[1]
diff --git a/core/indexing/walkDir.test.ts b/core/indexing/walkDir.test.ts
index af22265ffc..390a81b8a8 100644
--- a/core/indexing/walkDir.test.ts
+++ b/core/indexing/walkDir.test.ts
@@ -15,7 +15,7 @@ async function walkTestDir(
   options?: WalkerOptions,
 ): Promise {
   return walkDir(TEST_DIR, ide, {
-    returnRelativePaths: true,
+    returnRelativeUris: true,
     ...options,
   });
 }
@@ -259,7 +259,7 @@ describe("walkDir", () => {
       [path.join(TEST_DIR, "a.txt"), path.join(TEST_DIR, "b", "c.txt")],
       [],
       {
-        returnRelativePaths: false,
+        returnRelativeUris: false,
       },
     );
   });
diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts
index 47f5a2cac4..75fe734e9c 100644
--- a/core/indexing/walkDir.ts
+++ b/core/indexing/walkDir.ts
@@ -1,5 +1,3 @@
-import path from "node:path";
-
 import ignore, { Ignore } from "ignore";
 
 import { FileType, IDE } from "../index.d.js";
@@ -12,11 +10,12 @@ import {
   getGlobalContinueIgArray,
   gitIgArrayFromFile,
 } from "./ignore.js";
+import { joinPathsToUri } from "../util/uri.js";
 
 export interface WalkerOptions {
   ignoreFiles?: string[];
   onlyDirs?: boolean;
-  returnRelativePaths?: boolean;
+  returnRelativeUris?: boolean;
   additionalIgnoreRules?: string[];
 }
 
@@ -25,7 +24,7 @@ type Entry = [string, FileType];
 // helper struct used for the DFS walk
 type WalkableEntry = {
   relPath: string;
-  absPath: string;
+  uri: string;
   type: FileType;
   entry: Entry;
 };
@@ -45,7 +44,7 @@ class DFSWalker {
   private readonly ignoreFileNames: Set;
 
   constructor(
-    private readonly path: string,
+    private readonly uri: string,
     private readonly ide: IDE,
     private readonly options: WalkerOptions,
   ) {
@@ -54,10 +53,6 @@ class DFSWalker {
 
   // walk is a depth-first search implementation
   public async *walk(): AsyncGenerator {
-    const fixupFunc = await this.newPathFixupFunc(
-      this.options.returnRelativePaths ? "" : this.path,
-      this.ide,
-    );
     const root = this.newRootWalkContext();
     const stack = [root];
     for (let cur = stack.pop(); cur; cur = stack.pop()) {
@@ -77,10 +72,11 @@ class DFSWalker {
           });
           if (this.options.onlyDirs) {
             // when onlyDirs is enabled the walker will only return directory names
-            yield fixupFunc(w.relPath);
+            yield w.relPath + "/";
           }
         } else {
-          yield fixupFunc(w.relPath);
+          // Note that shouldInclude handles skipping files if options.onlyDirs is true
+          yield w.relPath;
         }
       }
     }
@@ -91,7 +87,7 @@ class DFSWalker {
     return {
       walkableEntry: {
         relPath: "",
-        absPath: this.path,
+        uri: this.uri,
         type: 2 as FileType.Directory,
         entry: ["", 2 as FileType.Directory],
       },
@@ -110,15 +106,13 @@ class DFSWalker {
   private async listDirForWalking(
     walkableEntry: WalkableEntry,
   ): Promise {
-    const entries = await this.ide.listDir(walkableEntry.absPath);
-    return entries.map((e) => {
-      return {
-        relPath: path.join(walkableEntry.relPath, e[0]),
-        absPath: path.join(walkableEntry.absPath, e[0]),
-        type: e[1],
-        entry: e,
-      };
-    });
+    const entries = await this.ide.listDir(walkableEntry.uri);
+    return entries.map((e) => ({
+      relPath: `${walkableEntry.relPath}/${e[0]}`,
+      uri: joinPathsToUri(walkableEntry.uri, e[0]),
+      type: e[1],
+      entry: e,
+    }));
   }
 
   private async getIgnoreToApplyInDir(
@@ -138,25 +132,22 @@ class DFSWalker {
   }
 
   private async loadIgnoreFiles(entries: WalkableEntry[]): Promise {
-    const ignoreEntries = entries.filter((w) => this.isIgnoreFile(w.entry));
+    const ignoreEntries = entries.filter((w) =>
+      this.entryIsIgnoreFile(w.entry),
+    );
     const promises = ignoreEntries.map(async (w) => {
-      return await this.ide.readFile(w.absPath);
+      return await this.ide.readFile(w.uri);
     });
     return Promise.all(promises);
   }
 
-  private isIgnoreFile(e: Entry): boolean {
-    const p = e[0];
-    return this.ignoreFileNames.has(p);
-  }
-
   private shouldInclude(
     walkableEntry: WalkableEntry,
     ignoreContexts: IgnoreContext[],
   ) {
     if (this.entryIsSymlink(walkableEntry.entry)) {
       // If called from the root, a symlink either links to a real file in this repository,
-      // and therefore will be walked OR it linksto something outside of the repository and
+      // and therefore will be walked OR it links to something outside of the repository and
       // we do not want to index it
       return false;
     }
@@ -189,50 +180,38 @@ class DFSWalker {
     return entry[1] === (64 as FileType.SymbolicLink);
   }
 
-  // returns a function which will optionally prefix a root path and fixup the paths for the appropriate OS filesystem (i.e. windows)
-  // the reason to construct this function once is to avoid the need to call ide.pathSep() multiple times
-  private async newPathFixupFunc(
-    rootPath: string,
-    ide: IDE,
-  ): Promise<(relPath: string) => string> {
-    const pathSep = await ide.pathSep();
-    const prefix = rootPath === "" ? "" : rootPath + pathSep;
-    if (pathSep === "/") {
-      if (rootPath === "") {
-        // return a no-op function in this case to avoid unnecessary string concatentation
-        return (relPath: string) => relPath;
-      }
-      return (relPath: string) => prefix + relPath;
+  private entryIsIgnoreFile(e: Entry): boolean {
+    if (e[1] === FileType.Directory || e[1] === FileType.SymbolicLink) {
+      return false;
     }
-    // this serves to 'fix-up' the path on Windows
-    return (relPath: string) => {
-      return prefix + relPath.split("/").join(pathSep);
-    };
+    return this.ignoreFileNames.has(e[0]);
   }
 }
 
 const defaultOptions: WalkerOptions = {
   ignoreFiles: [".gitignore", ".continueignore"],
   additionalIgnoreRules: [...DEFAULT_IGNORE_DIRS, ...DEFAULT_IGNORE_FILETYPES],
+  onlyDirs: false,
+  returnRelativeUris: false,
 };
 
 export async function walkDir(
-  path: string,
+  uri: string,
   ide: IDE,
-  _options?: WalkerOptions,
+  _optionOverrides?: WalkerOptions,
 ): Promise {
-  let paths: string[] = [];
-  for await (const p of walkDirAsync(path, ide, _options)) {
-    paths.push(p);
+  let urisOrRelativePaths: string[] = [];
+  for await (const p of walkDirAsync(uri, ide, _optionOverrides)) {
+    urisOrRelativePaths.push(p);
   }
-  return paths;
+  return urisOrRelativePaths;
 }
 
 export async function* walkDirAsync(
   path: string,
   ide: IDE,
-  _options?: WalkerOptions,
+  _optionOverrides?: WalkerOptions,
 ): AsyncGenerator {
-  const options = { ...defaultOptions, ..._options };
+  const options = { ...defaultOptions, ..._optionOverrides };
   yield* new DFSWalker(path, ide, options).walk();
 }
diff --git a/extensions/vscode/src/extension/VsCodeMessenger.ts b/extensions/vscode/src/extension/VsCodeMessenger.ts
index ebe11b890c..ec5bde252a 100644
--- a/extensions/vscode/src/extension/VsCodeMessenger.ts
+++ b/extensions/vscode/src/extension/VsCodeMessenger.ts
@@ -1,5 +1,3 @@
-import * as fs from "node:fs";
-
 import { ConfigHandler } from "core/config/ConfigHandler";
 import { getModelByRole } from "core/config/util";
 import { applyCodeBlock } from "core/edit/lazy/applyCodeBlock";

From 5fbace715d0e02ed7786477f6fee542ffb7f9f09 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Thu, 12 Dec 2024 15:12:09 -0800
Subject: [PATCH 25/84] remove listfolders and walkdirinworkspaces

---
 .../templating/AutocompleteTemplate.ts        | 42 ++++++++++++-------
 core/autocomplete/templating/index.ts         |  2 +
 core/autocomplete/util/HelperVars.ts          |  3 ++
 core/config/types.ts                          |  2 -
 core/context/providers/FileContextProvider.ts | 17 +++-----
 .../providers/FolderContextProvider.ts        |  9 ++--
 .../providers/RepoMapContextProvider.ts       |  5 ++-
 core/index.d.ts                               |  2 -
 core/indexing/docs/suggestions/index.ts       | 17 +++-----
 core/indexing/walkDir.test.ts                 |  4 +-
 core/indexing/walkDir.ts                      | 37 ++++++++++++----
 core/protocol/ide.ts                          |  1 -
 core/protocol/messenger/messageIde.ts         |  4 --
 core/protocol/messenger/reverseMessageIde.ts  |  4 --
 core/util/filesystem.ts                       |  4 --
 core/util/uri.ts                              |  4 +-
 .../constants/MessageTypes.kt                 |  1 -
 .../continue/IdeProtocolClient.kt             | 15 -------
 extensions/vscode/src/VsCodeIde.ts            | 12 ------
 .../vscode/src/extension/VsCodeMessenger.ts   |  3 --
 extensions/vscode/src/util/FileSearch.ts      | 17 +++-----
 21 files changed, 89 insertions(+), 116 deletions(-)

diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts
index f8c8215fa2..25b4620fab 100644
--- a/core/autocomplete/templating/AutocompleteTemplate.ts
+++ b/core/autocomplete/templating/AutocompleteTemplate.ts
@@ -1,6 +1,10 @@
 // Fill in the middle prompts
 
 import { CompletionOptions } from "../../index.js";
+import {
+  getLastNUriRelativePathParts,
+  shortestRelativeUriPaths,
+} from "../../util/uri.js";
 import {
   AutocompleteCodeSnippet,
   AutocompleteSnippet,
@@ -14,7 +18,7 @@ export interface AutocompleteTemplate {
     filepath: string,
     reponame: string,
     snippets: AutocompleteSnippet[],
-    workspaceDirs: string[],
+    workspaceUris: string[],
   ) => [string, string];
   template:
     | string
@@ -25,7 +29,7 @@ export interface AutocompleteTemplate {
         reponame: string,
         language: string,
         snippets: AutocompleteSnippet[],
-        workspaceDirs: [],
+        workspaceUris: string[],
       ) => string);
   completionOptions?: Partial;
 }
@@ -79,21 +83,27 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = {
     filepath,
     reponame,
     snippets,
-    workspaceDirs,
+    workspaceUris,
   ): [string, string] => {
     if (snippets.length === 0) {
       if (suffix.trim().length === 0 && prefix.trim().length === 0) {
-        return [`+++++ ${getLastNPathParts(filepath, 2)}\n${prefix}`, suffix];
+        return [
+          `+++++ ${getLastNUriRelativePathParts(workspaceUris, filepath, 2)}\n${prefix}`,
+          suffix,
+        ];
       }
       return [prefix, suffix];
     }
 
-    const relativePaths = shortestRelativePaths([
-      ...snippets.map((snippet) =>
-        "filepath" in snippet ? snippet.filepath : "Untitled.txt",
-      ),
-      filepath,
-    ]);
+    const relativePaths = shortestRelativeUriPaths(
+      [
+        ...snippets.map((snippet) =>
+          "filepath" in snippet ? snippet.filepath : "Untitled.txt",
+        ),
+        filepath,
+      ],
+      workspaceUris,
+    );
 
     const otherFiles = snippets
       .map((snippet, i) => {
@@ -144,7 +154,7 @@ const starcoder2FimTemplate: AutocompleteTemplate = {
     reponame,
     language,
     snippets,
-    workspaceDirs,
+    workspaceUris,
   ): string => {
     const otherFiles =
       snippets.length === 0
@@ -198,16 +208,16 @@ const codegeexFimTemplate: AutocompleteTemplate = {
     reponame,
     language,
     allSnippets,
-    workspaceDirs,
+    workspaceUris,
   ): string => {
     const snippets = allSnippets.filter(
       (snippet) => snippet.type === AutocompleteSnippetType.Code,
     ) as AutocompleteCodeSnippet[];
 
-    const relativePaths = shortestRelativePaths([
-      ...snippets.map((snippet) => snippet.filepath),
-      filepath,
-    ]);
+    const relativePaths = shortestRelativeUriPaths(
+      [...snippets.map((snippet) => snippet.filepath), filepath],
+      workspaceUris,
+    );
     const baseTemplate = `###PATH:${
       relativePaths[relativePaths.length - 1]
     }\n###LANGUAGE:${language}\n###MODE:BLOCK\n<|code_suffix|>${suffix}<|code_prefix|>${prefix}<|code_middle|>`;
diff --git a/core/autocomplete/templating/index.ts b/core/autocomplete/templating/index.ts
index 2e0569cb28..d130a3c180 100644
--- a/core/autocomplete/templating/index.ts
+++ b/core/autocomplete/templating/index.ts
@@ -79,6 +79,7 @@ export function renderPrompt({
       helper.filepath,
       reponame,
       snippets,
+      helper.workspaceUris,
     );
   } else {
     const formattedSnippets = formatSnippets(helper, snippets, workspaceDirs);
@@ -103,6 +104,7 @@ export function renderPrompt({
           reponame,
           helper.lang.name,
           snippets,
+          helper.workspaceUris,
         );
 
   const stopTokens = getStopTokens(
diff --git a/core/autocomplete/util/HelperVars.ts b/core/autocomplete/util/HelperVars.ts
index 0470384936..f92ef5a1df 100644
--- a/core/autocomplete/util/HelperVars.ts
+++ b/core/autocomplete/util/HelperVars.ts
@@ -20,6 +20,7 @@ import { AutocompleteInput } from "./types";
 export class HelperVars {
   lang: AutocompleteLanguageInfo;
   treePath: AstPath | undefined;
+  workspaceUris: string[] = [];
 
   private _fileContents: string | undefined;
   private _fileLines: string[] | undefined;
@@ -43,6 +44,8 @@ export class HelperVars {
       return;
     }
 
+    this.workspaceUris = await this.ide.getWorkspaceDirs();
+
     this._fileContents =
       this.input.manuallyPassFileContents ??
       (await this.ide.readFile(this.filepath));
diff --git a/core/config/types.ts b/core/config/types.ts
index 9224a50e3e..6c7e580dde 100644
--- a/core/config/types.ts
+++ b/core/config/types.ts
@@ -607,8 +607,6 @@ declare global {
   
     getAvailableThreads(): Promise;
   
-    listFolders(): Promise;
-  
     getWorkspaceDirs(): Promise;
   
     getWorkspaceConfigs(): Promise;
diff --git a/core/context/providers/FileContextProvider.ts b/core/context/providers/FileContextProvider.ts
index 81f8a573cc..ad277d3201 100644
--- a/core/context/providers/FileContextProvider.ts
+++ b/core/context/providers/FileContextProvider.ts
@@ -6,7 +6,7 @@ import {
   ContextSubmenuItem,
   LoadSubmenuItemsArgs,
 } from "../../";
-import { walkDir } from "../../indexing/walkDir";
+import { walkDirInWorkspaces } from "../../indexing/walkDir";
 import {
   getUniqueUriPath,
   getUriPathBasename,
@@ -46,20 +46,15 @@ class FileContextProvider extends BaseContextProvider {
   async loadSubmenuItems(
     args: LoadSubmenuItemsArgs,
   ): Promise {
-    const workspaceDirs = await args.ide.getWorkspaceDirs();
-    const results = await Promise.all(
-      workspaceDirs.map((dir) => {
-        return walkDir(dir, args.ide);
-      }),
-    );
+    const results = await walkDirInWorkspaces(args.ide);
     const files = results.flat().slice(-MAX_SUBMENU_ITEMS);
     const fileGroups = groupByLastNPathParts(files, 2);
 
-    return files.map((file) => {
+    return files.map((uri) => {
       return {
-        id: file,
-        title: getUriPathBasename(file),
-        description: getUniqueUriPath(file, fileGroups),
+        id: uri,
+        title: getUriPathBasename(uri),
+        description: getUniqueUriPath(uri, fileGroups),
       };
     });
   }
diff --git a/core/context/providers/FolderContextProvider.ts b/core/context/providers/FolderContextProvider.ts
index 93d728c9a4..59f537b44c 100644
--- a/core/context/providers/FolderContextProvider.ts
+++ b/core/context/providers/FolderContextProvider.ts
@@ -5,12 +5,14 @@ import {
   ContextSubmenuItem,
   LoadSubmenuItemsArgs,
 } from "../../index.js";
+import { walkDirInWorkspaces } from "../../indexing/walkDir.js";
 import {
   getUniqueUriPath,
   getUriPathBasename,
   groupByLastNPathParts,
 } from "../../util/uri.js";
 import { BaseContextProvider } from "../index.js";
+import { retrieveContextItemsFromEmbeddings } from "../retrieval/retrieval.js";
 
 class FolderContextProvider extends BaseContextProvider {
   static description: ContextProviderDescription = {
@@ -25,15 +27,14 @@ class FolderContextProvider extends BaseContextProvider {
     query: string,
     extras: ContextProviderExtras,
   ): Promise {
-    const { retrieveContextItemsFromEmbeddings } = await import(
-      "../retrieval/retrieval.js"
-    );
     return retrieveContextItemsFromEmbeddings(extras, this.options, query);
   }
   async loadSubmenuItems(
     args: LoadSubmenuItemsArgs,
   ): Promise {
-    const folders = await args.ide.listFolders();
+    const folders = await walkDirInWorkspaces(args.ide, {
+      onlyDirs: true,
+    });
     const folderGroups = groupByLastNPathParts(folders, 2);
 
     return folders.map((folder) => {
diff --git a/core/context/providers/RepoMapContextProvider.ts b/core/context/providers/RepoMapContextProvider.ts
index 8f01eb8ffe..73d0bf012d 100644
--- a/core/context/providers/RepoMapContextProvider.ts
+++ b/core/context/providers/RepoMapContextProvider.ts
@@ -6,6 +6,7 @@ import {
   ContextSubmenuItem,
   LoadSubmenuItemsArgs,
 } from "../../";
+import { walkDirInWorkspaces } from "../../indexing/walkDir";
 import generateRepoMap from "../../util/generateRepoMap";
 import {
   getUniqueUriPath,
@@ -45,7 +46,9 @@ class RepoMapContextProvider extends BaseContextProvider {
   async loadSubmenuItems(
     args: LoadSubmenuItemsArgs,
   ): Promise {
-    const folders = await args.ide.listFolders();
+    const folders = await walkDirInWorkspaces(args.ide, {
+      onlyDirs: true,
+    });
     const folderGroups = groupByLastNPathParts(folders, 2);
 
     return [
diff --git a/core/index.d.ts b/core/index.d.ts
index 988a99e11a..3612f1354f 100644
--- a/core/index.d.ts
+++ b/core/index.d.ts
@@ -605,8 +605,6 @@ export interface IDE {
 
   getAvailableThreads(): Promise;
 
-  listFolders(): Promise;
-
   getWorkspaceDirs(): Promise;
 
   getWorkspaceConfigs(): Promise;
diff --git a/core/indexing/docs/suggestions/index.ts b/core/indexing/docs/suggestions/index.ts
index db192489ad..d32d7c0d09 100644
--- a/core/indexing/docs/suggestions/index.ts
+++ b/core/indexing/docs/suggestions/index.ts
@@ -6,7 +6,8 @@ import {
   PackageDetails,
   ParsedPackageInfo,
 } from "../../..";
-import { walkDir } from "../../walkDir";
+import { getUriPathBasename } from "../../../util/uri";
+import { walkDir, walkDirInWorkspaces } from "../../walkDir";
 
 import { PythonPackageCrawler } from "./packageCrawlers/Python";
 import { NodePackageCrawler } from "./packageCrawlers/TsJs";
@@ -24,16 +25,10 @@ export interface PackageCrawler {
 }
 
 export async function getAllSuggestedDocs(ide: IDE) {
-  const workspaceDirs = await ide.getWorkspaceDirs();
-  const results = await Promise.all(
-    workspaceDirs.map((dir) => {
-      return walkDir(dir, ide);
-    }),
-  );
-  const allPaths = results.flat(); // TODO only get files, not dirs. Not critical for now
-  const allFiles = allPaths.map((path) => ({
-    path,
-    name: path.split(/[\\/]/).pop()!,
+  const allFileUris = await walkDirInWorkspaces(ide);
+  const allFiles = allFileUris.map((uri) => ({
+    path: uri,
+    name: getUriPathBasename(uri),
   }));
 
   // Build map of language -> package files
diff --git a/core/indexing/walkDir.test.ts b/core/indexing/walkDir.test.ts
index 390a81b8a8..92df792fab 100644
--- a/core/indexing/walkDir.test.ts
+++ b/core/indexing/walkDir.test.ts
@@ -15,7 +15,7 @@ async function walkTestDir(
   options?: WalkerOptions,
 ): Promise {
   return walkDir(TEST_DIR, ide, {
-    returnRelativeUris: true,
+    returnRelativeUrisPaths: true,
     ...options,
   });
 }
@@ -259,7 +259,7 @@ describe("walkDir", () => {
       [path.join(TEST_DIR, "a.txt"), path.join(TEST_DIR, "b", "c.txt")],
       [],
       {
-        returnRelativeUris: false,
+        returnRelativeUrisPaths: false,
       },
     );
   });
diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts
index 75fe734e9c..d41ee41f8a 100644
--- a/core/indexing/walkDir.ts
+++ b/core/indexing/walkDir.ts
@@ -15,7 +15,7 @@ import { joinPathsToUri } from "../util/uri.js";
 export interface WalkerOptions {
   ignoreFiles?: string[];
   onlyDirs?: boolean;
-  returnRelativeUris?: boolean;
+  returnRelativeUrisPaths?: boolean;
   additionalIgnoreRules?: string[];
 }
 
@@ -23,7 +23,7 @@ type Entry = [string, FileType];
 
 // helper struct used for the DFS walk
 type WalkableEntry = {
-  relPath: string;
+  relativeUriPath: string;
   uri: string;
   type: FileType;
   entry: Entry;
@@ -72,11 +72,19 @@ class DFSWalker {
           });
           if (this.options.onlyDirs) {
             // when onlyDirs is enabled the walker will only return directory names
-            yield w.relPath + "/";
+            if (this.options.returnRelativeUrisPaths) {
+              yield w.relativeUriPath;
+            } else {
+              yield w.uri;
+            }
           }
         } else {
           // Note that shouldInclude handles skipping files if options.onlyDirs is true
-          yield w.relPath;
+          if (this.options.returnRelativeUrisPaths) {
+            yield w.relativeUriPath;
+          } else {
+            yield w.uri;
+          }
         }
       }
     }
@@ -86,7 +94,7 @@ class DFSWalker {
     const globalIgnoreFile = getGlobalContinueIgArray();
     return {
       walkableEntry: {
-        relPath: "",
+        relativeUriPath: "",
         uri: this.uri,
         type: 2 as FileType.Directory,
         entry: ["", 2 as FileType.Directory],
@@ -108,7 +116,7 @@ class DFSWalker {
   ): Promise {
     const entries = await this.ide.listDir(walkableEntry.uri);
     return entries.map((e) => ({
-      relPath: `${walkableEntry.relPath}/${e[0]}`,
+      relativeUriPath: `${walkableEntry.relativeUriPath}/${e[0]}`,
       uri: joinPathsToUri(walkableEntry.uri, e[0]),
       type: e[1],
       entry: e,
@@ -126,7 +134,7 @@ class DFSWalker {
     const patterns = ignoreFilesInDir.map((c) => gitIgArrayFromFile(c)).flat();
     const newIgnoreContext = {
       ignore: ignore().add(patterns),
-      dirname: curDir.walkableEntry.relPath,
+      dirname: curDir.walkableEntry.relativeUriPath,
     };
     return [...curDir.ignoreContexts, newIgnoreContext];
   }
@@ -151,7 +159,7 @@ class DFSWalker {
       // we do not want to index it
       return false;
     }
-    let relPath = walkableEntry.relPath;
+    let relPath = walkableEntry.relativeUriPath;
     if (this.entryIsDirectory(walkableEntry.entry)) {
       relPath = `${relPath}/`;
     } else {
@@ -192,7 +200,7 @@ const defaultOptions: WalkerOptions = {
   ignoreFiles: [".gitignore", ".continueignore"],
   additionalIgnoreRules: [...DEFAULT_IGNORE_DIRS, ...DEFAULT_IGNORE_FILETYPES],
   onlyDirs: false,
-  returnRelativeUris: false,
+  returnRelativeUrisPaths: false,
 };
 
 export async function walkDir(
@@ -215,3 +223,14 @@ export async function* walkDirAsync(
   const options = { ...defaultOptions, ..._optionOverrides };
   yield* new DFSWalker(path, ide, options).walk();
 }
+
+export async function walkDirInWorkspaces(
+  ide: IDE,
+  _optionOverrides?: WalkerOptions,
+): Promise {
+  const workspaceDirs = await ide.getWorkspaceDirs();
+  const results = await Promise.all(
+    workspaceDirs.map((dir) => walkDir(dir, ide, _optionOverrides)),
+  );
+  return results.flat();
+}
diff --git a/core/protocol/ide.ts b/core/protocol/ide.ts
index 37d63d127a..667ebd7b21 100644
--- a/core/protocol/ide.ts
+++ b/core/protocol/ide.ts
@@ -23,7 +23,6 @@ export type ToIdeFromWebviewOrCoreProtocol = {
   // Methods from IDE type
   getIdeInfo: [undefined, IdeInfo];
   getWorkspaceDirs: [undefined, string[]];
-  listFolders: [undefined, string[]];
   writeFile: [{ path: string; contents: string }, void];
   showVirtualFile: [{ name: string; content: string }, void];
   openFile: [{ path: string }, void];
diff --git a/core/protocol/messenger/messageIde.ts b/core/protocol/messenger/messageIde.ts
index 22d0dc7d60..e2cd50c781 100644
--- a/core/protocol/messenger/messageIde.ts
+++ b/core/protocol/messenger/messageIde.ts
@@ -134,10 +134,6 @@ export class MessageIde implements IDE {
     });
   }
 
-  async listFolders(): Promise {
-    return await this.request("listFolders", undefined);
-  }
-
   async writeFile(fileUri: string, contents: string): Promise {
     await this.request("writeFile", { path: fileUri, contents });
   }
diff --git a/core/protocol/messenger/reverseMessageIde.ts b/core/protocol/messenger/reverseMessageIde.ts
index cfd788a060..e3f97ea4fb 100644
--- a/core/protocol/messenger/reverseMessageIde.ts
+++ b/core/protocol/messenger/reverseMessageIde.ts
@@ -117,10 +117,6 @@ export class ReverseMessageIde {
       return this.ide.showLines(data.filepath, data.startLine, data.endLine);
     });
 
-    this.on("listFolders", () => {
-      return this.ide.listFolders();
-    });
-
     this.on("getControlPlaneSessionInfo", async (msg) => {
       // Not supported in testing
       return undefined;
diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts
index 3c69e33851..886e58e104 100644
--- a/core/util/filesystem.ts
+++ b/core/util/filesystem.ts
@@ -166,10 +166,6 @@ class FileSystemIde implements IDE {
     return Promise.resolve([this.workspaceDir]);
   }
 
-  listFolders(): Promise {
-    return Promise.resolve([]);
-  }
-
   writeFile(fileUri: string, contents: string): Promise {
     const filepath = fileURLToPath(fileUri);
     return new Promise((resolve, reject) => {
diff --git a/core/util/uri.ts b/core/util/uri.ts
index ef32230135..e638762cef 100644
--- a/core/util/uri.ts
+++ b/core/util/uri.ts
@@ -82,7 +82,7 @@ export function getLastNUriRelativePathParts(
   n: number,
 ): string {
   const path = getRelativePath(uri, workspaceDirs);
-  return getLastN(path, n);
+  return getLastNPathParts(path, n);
 }
 
 export function getUriPathBasename(uri: string): string {
@@ -116,7 +116,7 @@ export function getUniqueUriPath(
 
 export function shortestRelativeUriPaths(
   uris: string[],
-  directory: string,
+  workspaceUris: string[],
 ): string[] {
   if (uris.length === 0) {
     return [];
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 426de1d50f..0374e9346b 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
@@ -18,7 +18,6 @@ class MessageTypes {
             "getTerminalContents",
             "getWorkspaceDirs",
             "showLines",
-            "listFolders",
             "writeFile",
             "fileExists",
             "showVirtualFile",
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 bc2867a7e0..0598ca8c2a 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
@@ -582,21 +582,6 @@ class IdeProtocolClient(
                         respond(result)
                     }
 
-                    "listFolders" -> {
-                        val workspacePath = workspacePath ?: return@launch
-                        val folders = mutableListOf()
-                        fun findNestedFolders(dirPath: String) {
-                            val dir = File(dirPath)
-                            val nestedFolders =
-                                dir.listFiles { file -> file.isDirectory }?.map { file -> file.absolutePath }
-                                    ?: emptyList()
-                            folders.addAll(nestedFolders);
-                            nestedFolders.forEach { folder -> findNestedFolders(folder) }
-                        }
-                        findNestedFolders(workspacePath)
-                        respond(folders)
-                    }
-
                     "getSearchResults" -> {
                         val query = (data as Map)["query"] as String
                         respond(search(query))
diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts
index 0aacd398ac..b8968f7b02 100644
--- a/extensions/vscode/src/VsCodeIde.ts
+++ b/extensions/vscode/src/VsCodeIde.ts
@@ -351,18 +351,6 @@ class VsCodeIde implements IDE {
     return configs;
   }
 
-  async listFolders(): Promise {
-    const allDirs: string[] = [];
-
-    const workspaceDirs = await this.getWorkspaceDirs();
-    for (const directory of workspaceDirs) {
-      const dirs = await walkDir(directory, this, { onlyDirs: true });
-      allDirs.push(...dirs);
-    }
-
-    return allDirs;
-  }
-
   async getWorkspaceDirs(): Promise {
     return this.ideUtils.getWorkspaceDirectories().map((uri) => uri.toString());
   }
diff --git a/extensions/vscode/src/extension/VsCodeMessenger.ts b/extensions/vscode/src/extension/VsCodeMessenger.ts
index ec5bde252a..9fe86d5b43 100644
--- a/extensions/vscode/src/extension/VsCodeMessenger.ts
+++ b/extensions/vscode/src/extension/VsCodeMessenger.ts
@@ -413,9 +413,6 @@ export class VsCodeMessenger {
     this.onWebviewOrCore("getWorkspaceDirs", async (msg) => {
       return ide.getWorkspaceDirs();
     });
-    this.onWebviewOrCore("listFolders", async (msg) => {
-      return ide.listFolders();
-    });
     this.onWebviewOrCore("writeFile", async (msg) => {
       return ide.writeFile(msg.data.path, msg.data.contents);
     });
diff --git a/extensions/vscode/src/util/FileSearch.ts b/extensions/vscode/src/util/FileSearch.ts
index efb065d7f2..fe977e4e53 100644
--- a/extensions/vscode/src/util/FileSearch.ts
+++ b/extensions/vscode/src/util/FileSearch.ts
@@ -1,5 +1,5 @@
 import { IDE } from "core";
-import { walkDir } from "core/indexing/walkDir";
+import { walkDirInWorkspaces } from "core/indexing/walkDir";
 // @ts-ignore
 import MiniSearch from "minisearch";
 import * as vscode from "vscode";
@@ -24,18 +24,11 @@ export class FileSearch {
     },
   });
   private async initializeFileSearchState() {
-    const workspaceDirs = await this.ide.getWorkspaceDirs();
-
-    const results = await Promise.all(
-      workspaceDirs.map((dir) => {
-        return walkDir(dir, this.ide);
-      }),
-    );
-
+    const results = await walkDirInWorkspaces(this.ide);
     this.miniSearch.addAll(
-      results.flat().map((file) => ({
-        id: file,
-        relativePath: vscode.workspace.asRelativePath(file),
+      results.flat().map((uri) => ({
+        id: uri,
+        relativePath: vscode.workspace.asRelativePath(uri),
       })),
     );
   }

From 62bb4135e9c3d975098a8dc1798182f0a64f61f4 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Thu, 12 Dec 2024 16:33:22 -0800
Subject: [PATCH 26/84] findUriWithinDirs

---
 core/autocomplete/prefiltering/index.ts       |   9 +-
 core/autocomplete/snippets/getAllSnippets.ts  |   6 +-
 core/autocomplete/templating/formatting.ts    |  24 ++-
 core/commands/slash/onboard.ts                |   6 +-
 core/commands/util.ts                         |   6 +-
 .../providers/FileTreeContextProvider.ts      |  10 +-
 .../providers/OpenFilesContextProvider.ts     |  11 +-
 .../pipelines/NoRerankerRetrievalPipeline.ts  |   7 +-
 .../pipelines/RerankerRetrievalPipeline.ts    |   6 +-
 core/core.ts                                  |   6 +-
 core/test/testDir.ts                          |   7 +-
 core/util/ideUtils.ts                         |  20 +-
 core/util/uri.test.ts                         |  64 -------
 core/util/uri.ts                              | 177 ++++++++----------
 extensions/vscode/src/util/ideUtils.ts        |  19 +-
 gui/src/redux/slices/sessionSlice.ts          |  10 +-
 gui/src/redux/thunks/gatherContext.ts         |  12 +-
 17 files changed, 165 insertions(+), 235 deletions(-)

diff --git a/core/autocomplete/prefiltering/index.ts b/core/autocomplete/prefiltering/index.ts
index 59905870ee..7512b0ab10 100644
--- a/core/autocomplete/prefiltering/index.ts
+++ b/core/autocomplete/prefiltering/index.ts
@@ -5,7 +5,7 @@ import ignore from "ignore";
 import { IDE } from "../..";
 import { getConfigJsonPath } from "../../util/paths";
 import { HelperVars } from "../util/HelperVars";
-import { getRelativePath } from "../../util/uri";
+import { findUriInDirs } from "../../util/uri";
 
 async function isDisabledForFile(
   currentFilepath: string,
@@ -15,11 +15,14 @@ async function isDisabledForFile(
   if (disableInFiles) {
     // Relative path needed for `ignore`
     const workspaceDirs = await ide.getWorkspaceDirs();
-    const relativePath = getRelativePath(currentFilepath, workspaceDirs);
+    const { relativePathOrBasename } = findUriInDirs(
+      currentFilepath,
+      workspaceDirs,
+    );
 
     // @ts-ignore
     const pattern = ignore.default().add(disableInFiles);
-    if (pattern.ignores(relativePath)) {
+    if (pattern.ignores(relativePathOrBasename)) {
       return true;
     }
   }
diff --git a/core/autocomplete/snippets/getAllSnippets.ts b/core/autocomplete/snippets/getAllSnippets.ts
index f61425ccfd..5ff4a7d601 100644
--- a/core/autocomplete/snippets/getAllSnippets.ts
+++ b/core/autocomplete/snippets/getAllSnippets.ts
@@ -1,5 +1,5 @@
 import { IDE } from "../../index";
-import { isUriWithinDirectory } from "../../util/uri";
+import { findUriInDirs } from "../../util/uri";
 import { ContextRetrievalService } from "../context/ContextRetrievalService";
 import { GetLspDefinitionsFunction } from "../types";
 import { HelperVars } from "../util/HelperVars";
@@ -46,7 +46,9 @@ async function getIdeSnippets(
     const workspaceDirs = await ide.getWorkspaceDirs();
 
     return ideSnippets.filter((snippet) =>
-      workspaceDirs.some((dir) => isUriWithinDirectory(snippet.filepath, dir)),
+      workspaceDirs.some(
+        (dir) => !!findUriInDirs(snippet.filepath, [dir]).foundInDir,
+      ),
     );
   }
 
diff --git a/core/autocomplete/templating/formatting.ts b/core/autocomplete/templating/formatting.ts
index aa67a11dc5..7724522ebc 100644
--- a/core/autocomplete/templating/formatting.ts
+++ b/core/autocomplete/templating/formatting.ts
@@ -23,20 +23,25 @@ const addCommentMarks = (text: string, helper: HelperVars) => {
 
 const formatClipboardSnippet = (
   snippet: AutocompleteClipboardSnippet,
+  workspaceDirs: string[],
 ): AutocompleteCodeSnippet => {
-  return formatCodeSnippet({
-    filepath: "Untitled.txt",
-    content: snippet.content,
-    type: AutocompleteSnippetType.Code,
-  });
+  return formatCodeSnippet(
+    {
+      filepath: "Untitled.txt",
+      content: snippet.content,
+      type: AutocompleteSnippetType.Code,
+    },
+    workspaceDirs,
+  );
 };
 
 const formatCodeSnippet = (
   snippet: AutocompleteCodeSnippet,
+  workspaceDirs: string[],
 ): AutocompleteCodeSnippet => {
   return {
     ...snippet,
-    content: `Path: ${getLastNUriRelativePathParts(snippet.filepath, 2)}\n${snippet.content}`,
+    content: `Path: ${getLastNUriRelativePathParts(workspaceDirs, snippet.filepath, 2)}\n${snippet.content}`,
   };
 };
 
@@ -61,9 +66,8 @@ export const formatSnippets = (
   snippets: AutocompleteSnippet[],
   workspaceDirs: string[],
 ): string => {
-  const relativeFilePath = getRelativePath(helper.filepath, workspaceDirs);
   const currentFilepathComment = addCommentMarks(
-    getLastNUriRelativePathParts(helper.filepath, 2),
+    getLastNUriRelativePathParts(workspaceDirs, helper.filepath, 2),
     helper,
   );
 
@@ -72,11 +76,11 @@ export const formatSnippets = (
       .map((snippet) => {
         switch (snippet.type) {
           case AutocompleteSnippetType.Code:
-            return formatCodeSnippet(snippet);
+            return formatCodeSnippet(snippet, workspaceDirs);
           case AutocompleteSnippetType.Diff:
             return formatDiffSnippet(snippet);
           case AutocompleteSnippetType.Clipboard:
-            return formatClipboardSnippet(snippet);
+            return formatClipboardSnippet(snippet, workspaceDirs);
         }
       })
       .map((item) => {
diff --git a/core/commands/slash/onboard.ts b/core/commands/slash/onboard.ts
index 99544cd48c..3252a5bbb6 100644
--- a/core/commands/slash/onboard.ts
+++ b/core/commands/slash/onboard.ts
@@ -9,7 +9,7 @@ import {
 } from "../../indexing/ignore";
 import { renderChatMessage } from "../../util/messageContent";
 import {
-  getRelativePath,
+  findUriInDirs,
   getUriPathBasename,
   joinPathsToUri,
 } from "../../util/uri";
@@ -81,13 +81,13 @@ async function getEntriesFilteredByIgnore(dir: string, ide: IDE) {
       (entry) => entry[1] === FileType.File || entry[1] === FileType.Directory,
     )
     .map((entry) => {
-      const relativePath = getRelativePath(entry[0], workspaceDirs);
+      const { relativePathOrBasename } = findUriInDirs(entry[0], workspaceDirs);
       return {
         uri: entry[0],
         type: entry[1],
         basename: getUriPathBasename(entry[0]),
         relativePath:
-          relativePath + (entry[1] === FileType.Directory ? "/" : ""),
+          relativePathOrBasename + (entry[1] === FileType.Directory ? "/" : ""),
       };
     });
 
diff --git a/core/commands/util.ts b/core/commands/util.ts
index de7b6b97c3..ff53061c7e 100644
--- a/core/commands/util.ts
+++ b/core/commands/util.ts
@@ -1,12 +1,12 @@
 import { ContextItemWithId, RangeInFileWithContents } from "../";
-import { getRelativePath, getUriPathBasename } from "../util/uri";
+import { findUriInDirs, getUriPathBasename } from "../util/uri";
 import { v4 as uuidv4 } from "uuid";
 
 export function rifWithContentsToContextItem(
   rif: RangeInFileWithContents,
 ): ContextItemWithId {
   const basename = getUriPathBasename(rif.filepath);
-  const relativePath = getRelativePath(
+  const { relativePathOrBasename } = findUriInDirs(
     rif.filepath,
     window.workspacePaths ?? [],
   );
@@ -16,7 +16,7 @@ export function rifWithContentsToContextItem(
   return {
     content: rif.contents,
     name: itemName,
-    description: `${relativePath} ${rangeStr}`, // This is passed to the LLM - do not pass full URI
+    description: `${relativePathOrBasename} ${rangeStr}`, // This is passed to the LLM - do not pass full URI
     id: {
       providerTitle: "code",
       itemId: uuidv4(),
diff --git a/core/context/providers/FileTreeContextProvider.ts b/core/context/providers/FileTreeContextProvider.ts
index 0a8bbd5526..aadd11eec4 100644
--- a/core/context/providers/FileTreeContextProvider.ts
+++ b/core/context/providers/FileTreeContextProvider.ts
@@ -4,7 +4,11 @@ import {
   ContextProviderExtras,
 } from "../../index.js";
 import { walkDir } from "../../indexing/walkDir.js";
-import { getRelativePath, getUriPathBasename } from "../../util/uri.js";
+import {
+  findUriInDirs,
+  getRelativePath,
+  getUriPathBasename,
+} from "../../util/uri.js";
 import { BaseContextProvider } from "../index.js";
 
 interface Directory {
@@ -51,8 +55,8 @@ class FileTreeContextProvider extends BaseContextProvider {
       };
 
       const uris = await walkDir(workspaceDir, extras.ide);
-      const relativePaths = uris.map((uri) =>
-        getRelativePath(uri, [workspaceDir]),
+      const relativePaths = uris.map(
+        (uri) => findUriInDirs(uri, [workspaceDir]).relativePathOrBasename,
       );
 
       for (const path of relativePaths) {
diff --git a/core/context/providers/OpenFilesContextProvider.ts b/core/context/providers/OpenFilesContextProvider.ts
index 8b68332c3f..318ebb6480 100644
--- a/core/context/providers/OpenFilesContextProvider.ts
+++ b/core/context/providers/OpenFilesContextProvider.ts
@@ -3,7 +3,7 @@ import {
   ContextProviderDescription,
   ContextProviderExtras,
 } from "../../index.js";
-import { getBasename, getRelativePath } from "../../util/uri.js";
+import { findUriInDirs, getUriPathBasename } from "../../util/uri.js";
 import { BaseContextProvider } from "../index.js";
 
 class OpenFilesContextProvider extends BaseContextProvider {
@@ -28,11 +28,10 @@ class OpenFilesContextProvider extends BaseContextProvider {
       openFiles.map(async (filepath: string) => {
         return {
           description: filepath,
-          content: `\`\`\`${await getRelativePath(
-            filepath,
-            workspaceDirs,
-          )}\n${await ide.readFile(filepath)}\n\`\`\``,
-          name: getBasename(filepath),
+          content: `\`\`\`${
+            findUriInDirs(filepath, workspaceDirs).relativePathOrBasename
+          }\n${await ide.readFile(filepath)}\n\`\`\``,
+          name: getUriPathBasename(filepath),
           uri: {
             type: "file",
             value: filepath,
diff --git a/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts b/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts
index 87fb84c0ec..af72c06392 100644
--- a/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts
+++ b/core/context/retrieval/pipelines/NoRerankerRetrievalPipeline.ts
@@ -1,5 +1,5 @@
 import { Chunk } from "../../../";
-import { isUriWithinDirectory } from "../../../util/uri";
+import { findUriInDirs } from "../../../util/uri";
 import { requestFilesFromRepoMap } from "../repoMapRequest";
 import { deduplicateChunks } from "../util";
 
@@ -45,8 +45,9 @@ export default class NoRerankerRetrievalPipeline extends BaseRetrievalPipeline {
 
     if (filterDirectory) {
       // Backup if the individual retrieval methods don't listen
-      retrievalResults = retrievalResults.filter((chunk) =>
-        isUriWithinDirectory(chunk.filepath, filterDirectory),
+      retrievalResults = retrievalResults.filter(
+        (chunk) =>
+          !!findUriInDirs(chunk.filepath, [filterDirectory]).foundInDir,
       );
     }
 
diff --git a/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts b/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts
index 33676936d8..7c7e698600 100644
--- a/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts
+++ b/core/context/retrieval/pipelines/RerankerRetrievalPipeline.ts
@@ -1,6 +1,6 @@
 import { Chunk } from "../../..";
 import { RETRIEVAL_PARAMS } from "../../../util/parameters";
-import { isUriWithinDirectory } from "../../../util/uri";
+import { findUriInDirs } from "../../../util/uri";
 import { requestFilesFromRepoMap } from "../repoMapRequest";
 import { deduplicateChunks } from "../util";
 
@@ -41,8 +41,8 @@ export default class RerankerRetrievalPipeline extends BaseRetrievalPipeline {
 
     if (filterDirectory) {
       // Backup if the individual retrieval methods don't listen
-      retrievalResults = retrievalResults.filter((chunk) =>
-        isUriWithinDirectory(chunk.filepath, filterDirectory),
+      retrievalResults = retrievalResults.filter(
+        (chunk) => !!findUriInDirs(chunk.filepath, [filterDirectory]),
       );
     }
 
diff --git a/core/core.ts b/core/core.ts
index faf58fc82f..09cded385b 100644
--- a/core/core.ts
+++ b/core/core.ts
@@ -44,7 +44,8 @@ import type { FromCoreProtocol, ToCoreProtocol } from "./protocol";
 
 import { SYSTEM_PROMPT_DOT_FILE } from "./config/getSystemPromptDotFile";
 import type { IMessenger, Message } from "./protocol/messenger";
-import { getFullPath } from "./util/uri";
+import URI from "uri-js";
+import { localPathToUri } from "./util/uri";
 
 export class Core {
   // implements IMessenger
@@ -730,8 +731,7 @@ export class Core {
         for (const uri of data.uris) {
           // Listen for file changes in the workspace
           // URI TODO is this equality statement valid?
-          const filePath = getFullPath(uri);
-          if (filePath === getConfigJsonPath()) {
+          if (URI.equal(uri, localPathToUri(getConfigJsonPath()))) {
             // Trigger a toast notification to provide UI feedback that config has been updated
             const showToast =
               this.globalContext.get("showConfigUpdateToast") ?? true;
diff --git a/core/test/testDir.ts b/core/test/testDir.ts
index dcd410b759..b79624e1df 100644
--- a/core/test/testDir.ts
+++ b/core/test/testDir.ts
@@ -1,12 +1,7 @@
 import fs from "fs";
 import os from "os";
 import path from "path";
-import {
-  localPathOrUriToPath,
-  localPathToUri,
-  pathOrUriToUri,
-} from "../util/uri";
-import { fileURLToPath } from "url";
+import { localPathOrUriToPath, localPathToUri } from "../util/uri";
 
 // Want this outside of the git repository so we can change branches in tests
 const TEST_DIR_PATH = path.join(os.tmpdir(), "testWorkspaceDir");
diff --git a/core/util/ideUtils.ts b/core/util/ideUtils.ts
index d720afb3f2..48561be749 100644
--- a/core/util/ideUtils.ts
+++ b/core/util/ideUtils.ts
@@ -1,5 +1,5 @@
 import { IDE } from "..";
-import { isUriWithinDirectory, joinPathsToUri } from "./uri";
+import { findUriInDirs, joinPathsToUri } from "./uri";
 
 /*
   This function takes a relative filepath
@@ -33,13 +33,13 @@ export async function inferResolvedUriFromRelativePath(
   path: string,
   ide: IDE,
 ): Promise {
-  for (const workspaceUri of workspaceDirs) {
-    if (isUriWithinDirectory(path))
-      const fullUri = joinPathsToUri(workspaceUri, path);
-    if (await ide.fileExists(fullUri)) {
-      return fullUri;
-    }
-  }
-  // console.warn("No meaninful filepath inferred from relative path " + path)
-  return `${workspaces[0]}/${cleanPath}`;
+  // for (const workspaceUri of workspaceDirs) {
+  //   if (isUriWithinDirectory(path))
+  //     const fullUri = joinPathsToUri(workspaceUri, path);
+  //   if (await ide.fileExists(fullUri)) {
+  //     return fullUri;
+  //   }
+  // }
+  // // console.warn("No meaninful filepath inferred from relative path " + path)
+  // return `${workspaces[0]}/${cleanPath}`;
 }
diff --git a/core/util/uri.test.ts b/core/util/uri.test.ts
index 09cb27c6c0..d4f82f3f74 100644
--- a/core/util/uri.test.ts
+++ b/core/util/uri.test.ts
@@ -4,7 +4,6 @@ import {
   getUniqueUriPath,
   getUriPathBasename,
   groupByLastNPathParts,
-  splitUriPath,
 } from "./uri";
 
 describe("getUriPathBasename", () => {
@@ -90,69 +89,6 @@ describe("getUniqueUriPath", () => {
   });
 });
 
-describe("splitUriPath", () => {
-  it("should split Unix-style paths", () => {
-    const path = "/a/b/c/d/e.txt";
-    const output = splitUriPath(path);
-    expect(output).toEqual(["", "a", "b", "c", "d", "e.txt"]);
-  });
-
-  it("should split Windows-style paths", () => {
-    const path = "C:\\Users\\User\\Documents\\file.txt";
-    const output = splitUriPath(path);
-    expect(output).toEqual(["C:", "Users", "User", "Documents", "file.txt"]);
-  });
-
-  it("should handle empty path", () => {
-    const path = "";
-    const output = splitUriPath(path);
-    expect(output).toEqual([""]);
-  });
-
-  it("should handle paths with multiple consecutive separators", () => {
-    const path = "/a//b/c/d/e.txt";
-    const output = splitUriPath(path);
-    expect(output).toEqual(["", "a", "", "b", "c", "d", "e.txt"]);
-  });
-});
-
-describe("getRelativePath", () => {
-  it("should return the relative path with respect to workspace directories", () => {
-    const filepath = "/workspace/project/src/file.ts";
-    const workspaceDirs = ["/workspace/project"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("src/file.ts");
-  });
-
-  it("should return the filename if not in any workspace", () => {
-    const filepath = "/other/place/file.ts";
-    const workspaceDirs = ["/workspace/project"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("file.ts");
-  });
-
-  it("should handle multiple workspace directories", () => {
-    const filepath = "/workspace2/project/src/file.ts";
-    const workspaceDirs = ["/workspace/project", "/workspace2/project"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("src/file.ts");
-  });
-
-  it("should handle Windows-style paths", () => {
-    const filepath = "C:\\workspace\\project\\src\\file.ts";
-    const workspaceDirs = ["C:\\workspace\\project"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("src/file.ts");
-  });
-
-  it("should handle paths with spaces or special characters", () => {
-    const filepath = "/workspace/project folder/src/file.ts";
-    const workspaceDirs = ["/workspace/project folder"];
-    const output = getRelativePath(filepath, workspaceDirs);
-    expect(output).toBe("src/file.ts");
-  });
-});
-
 describe("getLastNPathParts", () => {
   test("returns the last N parts of a filepath with forward slashes", () => {
     const filepath = "home/user/documents/project/file.txt";
diff --git a/core/util/uri.ts b/core/util/uri.ts
index e638762cef..ebd496134a 100644
--- a/core/util/uri.ts
+++ b/core/util/uri.ts
@@ -1,96 +1,110 @@
 import URI from "uri-js";
 import { fileURLToPath, pathToFileURL } from "url";
 
-export function getFullPath(uri: string): string {
-  try {
-    return URI.parse(uri).path ?? "";
-  } catch (error) {
-    console.error(`Invalid URI: ${uri}`, error);
-    return "";
-  }
-}
-
+// Converts a local path to a file:// URI
 export function localPathToUri(path: string) {
   const url = pathToFileURL(path);
   return URI.normalize(url.toString());
 }
 
+export function localPathOrUriToPath(localPathOrUri: string): string {
+  try {
+    return fileURLToPath(localPathOrUri);
+  } catch (e) {
+    console.log("Received local filepath", localPathOrUri);
+
+    return localPathOrUri;
+  }
+}
+
+/** Converts any OS path to cleaned up URI path segment format with no leading/trailing slashes
+   e.g. \path\to\folder\ -> path/to/folder
+        \this\is\afile.ts -> this/is/afile.ts
+        is/already/clean -> is/already/clean
+  **/
 export function pathToUriPathSegment(path: string) {
-  // Converts any OS path to cleaned up URI path segment format with no leading/trailing slashes
-  // e.g. \path\to\folder\ -> path/to/folder
-  //      \this\is\afile.ts -> this/is/afile.ts
-  //      is/already/clean -> is/already/clean
   let clean = path.replace(/[\\]/g, "/"); // backslashes -> forward slashes
   clean = clean.replace(/^\//, ""); // remove start slash
   clean = clean.replace(/\/$/, ""); // remove end slash
-  return clean;
+  return encodeURIComponent(clean);
 }
 
-// Whenever working with partial URIs
-// Should always first get the path relative to the workspaces
-// If a matching workspace is not found, ONLY the file name should be used
-export function getRelativePath(
-  fileUri: string,
-  workspaceUris: string[],
-): string {
-  const fullPath = getFullPath(fileUri);
+export function findUriInDirs(
+  uri: string,
+  dirUriCandidates: string[],
+): {
+  relativePathOrBasename: string;
+  foundInDir: string | null;
+} {
+  const uriComps = URI.parse(uri);
+  if (!uriComps.scheme) {
+    throw new Error(`Invalid uri: ${uri}`);
+  }
+  for (const dir of dirUriCandidates) {
+    const dirComps = URI.parse(dir);
+
+    if (!dirComps.scheme) {
+      throw new Error(`Invalid uri: ${dir}`);
+    }
+
+    if (uriComps.scheme !== dirComps.scheme) {
+      continue;
+    }
+    if (uriComps.path === dirComps.path) {
+      continue;
+    }
+    // Can't just use starts with because e.g.
+    // file:///folder/file is not within file:///fold
+    return uriComps.path.startsWith(dirComps.path);
+  }
+  return {
+    relativePathOrBasename: getUriPathBasename(uri),
+    foundInDir: null,
+  };
 }
 
 /*
-  
+  To smooth out the transition from path to URI will use this function to warn when path is used
+  This will NOT work consistently with full OS paths like c:\blah\blah or ~/Users/etc
 */
-export function localPathOrUriToPath(localPathOrUri: string): string {
-  try {
-    return fileURLToPath(localPathOrUri);
-  } catch (e) {
-    console.log("Converted url to path");
-    return localPathOrUri;
+export function relativePathOrUriToUri(
+  relativePathOrUri: string,
+  defaultDirUri: string,
+): string {
+  const out = URI.parse(relativePathOrUri);
+  if (out.scheme) {
+    return relativePathOrUri;
   }
+  console.trace("Received path with no scheme");
+  return joinPathsToUri(defaultDirUri, out.path ?? "");
 }
 
 /*
-  To smooth out the transition from path to URI will use this function to warn when path is used
+  Returns just the file or folder name of a URI
 */
-export function pathOrUriToUri(
-  pathOrUri: string,
-  workspaceDirUris: string[],
-  showTraceOnPath = true,
-): string {
-  try {
-    // URI.parse(pathOrUri);
+export function getUriPathBasename(uri: string): string {
+  return URI.parse(uri).path?.split("/")?.pop() || "";
+}
 
-    return pathOrUri;
-  } catch (e) {
-    if (showTraceOnPath) {
-      console.trace("Received relative path", e);
-    }
-  }
+/*
+  Returns the file extension of a URI
+*/
+export function getUriFileExtension(uri: string) {
+  const baseName = getUriPathBasename(uri);
+  return baseName.split(".")[-1] ?? "";
 }
 
-export function splitUriPath(uri: string): string[] {
-  let parts = path.includes("/") ? path.split("/") : path.split("\\");
-  if (withRoot !== undefined) {
-    const rootParts = splitPath(withRoot);
-    parts = parts.slice(rootParts.length - 1);
-  }
-  return parts;
+export function getFileExtensionFromBasename(filename: string) {
+  return filename.split(".").pop() ?? "";
 }
 
 export function getLastNUriRelativePathParts(
-  workspaceDirs: string[],
+  dirUriCandidates: string[],
   uri: string,
   n: number,
 ): string {
-  const path = getRelativePath(uri, workspaceDirs);
-  return getLastNPathParts(path, n);
-}
-
-export function getUriPathBasename(uri: string): string {
-  return getPath();
-}
-
-export function getFileExtensionFromBasename(filename: string) {
-  return filename.split(".").pop() ?? "";
+  const { relativePathOrBasename } = findUriInDirs(uri, dirUriCandidates);
+  return getLastNPathParts(relativePathOrBasename, n);
 }
 
 export function joinPathsToUri(uri: string, ...pathSegments: string[]) {
@@ -116,11 +130,12 @@ export function getUniqueUriPath(
 
 export function shortestRelativeUriPaths(
   uris: string[],
-  workspaceUris: string[],
+  dirUriCandidates: string[],
 ): string[] {
   if (uris.length === 0) {
     return [];
   }
+  const relativeUris;
 
   const partsLengths = uris.map((x) => x.split("/").length);
   const currentRelativeUris = uris.map(getB);
@@ -162,25 +177,6 @@ export function shortestRelativeUriPaths(
 
   return currentRelativeUris;
 }
-export function isUriWithinDirectory(
-  uri: string,
-  directoryUri: string,
-): boolean {
-  const uriPath = getFullPath(uri);
-  const directoryPath = getFullPath(directoryUri);
-
-  if (uriPath === directoryPath) {
-    return false;
-  }
-  return uriPath.startsWith(directoryPath);
-}
-
-export function getUriFileExtension(uri: string) {
-  const baseName = getUriPathBasename(uri);
-  return baseName.split(".")[-1] ?? "";
-}
-
-const SEP_REGEX = /[\\/]/;
 
 // export function getBasename(filepath: string): string {
 //   return filepath.split(SEP_REGEX).pop() ?? "";
@@ -190,7 +186,7 @@ export function getLastNPathParts(filepath: string, n: number): string {
   if (n <= 0) {
     return "";
   }
-  return filepath.split(SEP_REGEX).slice(-n).join("/");
+  return filepath.split(/[\\/]/).slice(-n).join("/");
 }
 
 // export function groupByLastNPathParts(
@@ -241,20 +237,3 @@ export function getLastNPathParts(filepath: string, n: number): string {
 //   }
 //   return parts;
 // }
-
-// export function getRelativePath(
-//   filepath: string,
-//   workspaceDirs: string[],
-// ): string {
-//   for (const workspaceDir of workspaceDirs) {
-//     const filepathParts = splitPath(filepath);
-//     const workspaceDirParts = splitPath(workspaceDir);
-//     if (
-//       filepathParts.slice(0, workspaceDirParts.length).join("/") ===
-//       workspaceDirParts.join("/")
-//     ) {
-//       return filepathParts.slice(workspaceDirParts.length).join("/");
-//     }
-//   }
-//   return splitPath(filepath).pop() ?? ""; // If the file is not in any of the workspaces, return the plain filename
-// }
diff --git a/extensions/vscode/src/util/ideUtils.ts b/extensions/vscode/src/util/ideUtils.ts
index 4b6e723125..6d79b9a356 100644
--- a/extensions/vscode/src/util/ideUtils.ts
+++ b/extensions/vscode/src/util/ideUtils.ts
@@ -15,7 +15,7 @@ import {
 import { getUniqueId, openEditorAndRevealRange } from "./vscode";
 
 import type { Range, RangeInFile, Thread } from "core";
-import { isUriWithinDirectory } from "core/util/uri";
+import { findUriInDirs } from "core/util/uri";
 
 const util = require("node:util");
 const asyncExec = util.promisify(require("node:child_process").exec);
@@ -450,13 +450,16 @@ export class VsCodeIdeUtils {
   private static secondsToWaitForGitToLoad =
     process.env.NODE_ENV === "test" ? 1 : 20;
   async getRepo(forDirectory: vscode.Uri): Promise {
-    const workspaceDirs = this.getWorkspaceDirectories();
-    const parentDir = workspaceDirs.find((dir) =>
-      isUriWithinDirectory(forDirectory.toString(), dir.toString()),
+    const workspaceDirs = this.getWorkspaceDirectories().map((dir) =>
+      dir.toString(),
     );
-    if (parentDir) {
+    const { foundInDir } = findUriInDirs(
+      forDirectory.toString(),
+      workspaceDirs,
+    );
+    if (foundInDir) {
       // Check if the repository is already cached
-      const cachedRepo = this.repoCache.get(parentDir.toString());
+      const cachedRepo = this.repoCache.get(foundInDir);
       if (cachedRepo) {
         return cachedRepo;
       }
@@ -479,9 +482,9 @@ export class VsCodeIdeUtils {
       repo = await this._getRepo(forDirectory);
     }
 
-    if (parentDir) {
+    if (foundInDir) {
       // Cache the repository for the parent directory
-      this.repoCache.set(parentDir.toString(), repo);
+      this.repoCache.set(foundInDir, repo);
     }
 
     return repo;
diff --git a/gui/src/redux/slices/sessionSlice.ts b/gui/src/redux/slices/sessionSlice.ts
index 6e3e38884a..c866590454 100644
--- a/gui/src/redux/slices/sessionSlice.ts
+++ b/gui/src/redux/slices/sessionSlice.ts
@@ -27,7 +27,7 @@ import { v4 as uuidv4 } from "uuid";
 import { RootState } from "../store";
 import { streamResponseThunk } from "../thunks/streamResponse";
 import { findCurrentToolCall } from "../util";
-import { getBasename, getRelativePath } from "core/util/uri";
+import { findUriInDirs, getUriPathBasename } from "core/util/uri";
 
 // We need this to handle reorderings (e.g. a mid-array deletion) of the messages array.
 // The proper fix is adding a UUID to all chat messages, but this is the temp workaround.
@@ -430,11 +430,13 @@ export const sessionSlice = createSlice({
         return { ...item, editing: false };
       });
 
-      const relativeFilepath = getRelativePath(
+      const { relativePathOrBasename } = findUriInDirs(
         payload.rangeInFileWithContents.filepath,
         window.workspacePaths ?? [],
       );
-      const fileName = getBasename(payload.rangeInFileWithContents.filepath);
+      const fileName = getUriPathBasename(
+        payload.rangeInFileWithContents.filepath,
+      );
 
       const lineNums = `(${
         payload.rangeInFileWithContents.range.start.line + 1
@@ -442,7 +444,7 @@ export const sessionSlice = createSlice({
 
       contextItems.push({
         name: `${fileName} ${lineNums}`,
-        description: relativeFilepath,
+        description: relativePathOrBasename,
         id: {
           providerTitle: "code",
           itemId: uuidv4(),
diff --git a/gui/src/redux/thunks/gatherContext.ts b/gui/src/redux/thunks/gatherContext.ts
index d1eb556d69..4129868ef2 100644
--- a/gui/src/redux/thunks/gatherContext.ts
+++ b/gui/src/redux/thunks/gatherContext.ts
@@ -12,7 +12,7 @@ import resolveEditorContent, {
 import { ThunkApiType } from "../store";
 import { selectDefaultModel } from "../slices/configSlice";
 import { setIsGatheringContext } from "../slices/sessionSlice";
-import { getRelativePath, getUriPathBasename } from "core/util/uri";
+import { findUriInDirs, getUriPathBasename } from "core/util/uri";
 import { updateFileSymbolsFromNewContextItems } from "./updateFileSymbols";
 
 export const gatherContext = createAsyncThunk<
@@ -81,10 +81,12 @@ export const gatherContext = createAsyncThunk<
         ) {
           // don't add the file if it's already in the context items
           selectedContextItems.unshift({
-            content: `The following file is currently open. Don't reference it if it's not relevant to the user's message.\n\n\`\`\`${getRelativePath(
-              currentFile.path,
-              await extra.ideMessenger.ide.getWorkspaceDirs(),
-            )}\n${currentFileContents}\n\`\`\``,
+            content: `The following file is currently open. Don't reference it if it's not relevant to the user's message.\n\n\`\`\`${
+              findUriInDirs(
+                currentFile.path,
+                await extra.ideMessenger.ide.getWorkspaceDirs(),
+              ).relativePathOrBasename
+            }\n${currentFileContents}\n\`\`\``,
             name: `Active file: ${getUriPathBasename(currentFile.path)}`,
             description: currentFile.path,
             id: {

From 19802493eb3cf01c7b5496e4d87583fb626f047c Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 11:12:56 -0800
Subject: [PATCH 27/84] group by last n path parts

---
 core/commands/slash/share.ts                  |  7 +-
 core/context/providers/FileContextProvider.ts |  9 ++-
 .../providers/FolderContextProvider.ts        | 13 +++-
 .../providers/RepoMapContextProvider.ts       | 13 +++-
 core/edit/lazy/applyCodeBlock.ts              |  5 +-
 core/indexing/walkDir.ts                      |  3 +-
 core/util/uri.test.ts                         |  9 ++-
 core/util/uri.ts                              | 74 +++++++++++--------
 gui/src/context/SubmenuContextProviders.tsx   |  3 +-
 9 files changed, 82 insertions(+), 54 deletions(-)

diff --git a/core/commands/slash/share.ts b/core/commands/slash/share.ts
index d840f7db1e..01d5bea969 100644
--- a/core/commands/slash/share.ts
+++ b/core/commands/slash/share.ts
@@ -1,6 +1,6 @@
 import fs from "fs";
 import { homedir } from "node:os";
-import { fileURLToPath } from "node:url";
+import { fileURLToPath, pathToFileURL } from "node:url";
 import path from "path";
 
 import { languageForFilepath } from "../../autocomplete/constants/AutocompleteLanguageInfo.js";
@@ -90,8 +90,9 @@ const ShareSlashCommand: SlashCommand = {
     const dtString = asBasicISOString(getOffsetDatetime(now));
     const outPath = path.join(outputDir, `${dtString}_session.md`); //TODO: more flexible naming?
 
-    await ide.writeFile(outPath, content);
-    await ide.openFile(outPath);
+    const fileUrl = pathToFileURL(outPath).toString(); // TODO switch from path to URI above ^
+    await ide.writeFile(fileUrl, content);
+    await ide.openFile(fileUrl);
 
     yield `The session transcript has been saved to a markdown file at \`${outPath}\`.`;
   },
diff --git a/core/context/providers/FileContextProvider.ts b/core/context/providers/FileContextProvider.ts
index ad277d3201..e1918a107a 100644
--- a/core/context/providers/FileContextProvider.ts
+++ b/core/context/providers/FileContextProvider.ts
@@ -46,9 +46,14 @@ class FileContextProvider extends BaseContextProvider {
   async loadSubmenuItems(
     args: LoadSubmenuItemsArgs,
   ): Promise {
-    const results = await walkDirInWorkspaces(args.ide);
+    const workspaceDirs = await args.ide.getWorkspaceDirs();
+    const results = await walkDirInWorkspaces(
+      args.ide,
+      undefined,
+      workspaceDirs,
+    );
     const files = results.flat().slice(-MAX_SUBMENU_ITEMS);
-    const fileGroups = groupByLastNPathParts(files, 2);
+    const fileGroups = groupByLastNPathParts(workspaceDirs, files, 2);
 
     return files.map((uri) => {
       return {
diff --git a/core/context/providers/FolderContextProvider.ts b/core/context/providers/FolderContextProvider.ts
index 59f537b44c..b521aaf194 100644
--- a/core/context/providers/FolderContextProvider.ts
+++ b/core/context/providers/FolderContextProvider.ts
@@ -32,10 +32,15 @@ class FolderContextProvider extends BaseContextProvider {
   async loadSubmenuItems(
     args: LoadSubmenuItemsArgs,
   ): Promise {
-    const folders = await walkDirInWorkspaces(args.ide, {
-      onlyDirs: true,
-    });
-    const folderGroups = groupByLastNPathParts(folders, 2);
+    const workspaceDirs = await args.ide.getWorkspaceDirs();
+    const folders = await walkDirInWorkspaces(
+      args.ide,
+      {
+        onlyDirs: true,
+      },
+      workspaceDirs,
+    );
+    const folderGroups = groupByLastNPathParts(workspaceDirs, folders, 2);
 
     return folders.map((folder) => {
       return {
diff --git a/core/context/providers/RepoMapContextProvider.ts b/core/context/providers/RepoMapContextProvider.ts
index 73d0bf012d..be25999fa9 100644
--- a/core/context/providers/RepoMapContextProvider.ts
+++ b/core/context/providers/RepoMapContextProvider.ts
@@ -46,10 +46,15 @@ class RepoMapContextProvider extends BaseContextProvider {
   async loadSubmenuItems(
     args: LoadSubmenuItemsArgs,
   ): Promise {
-    const folders = await walkDirInWorkspaces(args.ide, {
-      onlyDirs: true,
-    });
-    const folderGroups = groupByLastNPathParts(folders, 2);
+    const workspaceDirs = await args.ide.getWorkspaceDirs();
+    const folders = await walkDirInWorkspaces(
+      args.ide,
+      {
+        onlyDirs: true,
+      },
+      workspaceDirs,
+    );
+    const folderGroups = groupByLastNPathParts(workspaceDirs, folders, 2);
 
     return [
       ENTIRE_PROJECT_ITEM,
diff --git a/core/edit/lazy/applyCodeBlock.ts b/core/edit/lazy/applyCodeBlock.ts
index 86b2d67b43..924d1c282a 100644
--- a/core/edit/lazy/applyCodeBlock.ts
+++ b/core/edit/lazy/applyCodeBlock.ts
@@ -1,14 +1,13 @@
-import path from "path";
-
 import { DiffLine, ILLM } from "../..";
 import { generateLines } from "../../diff/util";
 import { supportedLanguages } from "../../util/treeSitter";
+import { getUriFileExtension } from "../../util/uri";
 
 import { deterministicApplyLazyEdit } from "./deterministic";
 import { streamLazyApply } from "./streamLazyApply";
 
 function canUseInstantApply(filename: string) {
-  const fileExtension = path.extname(filename).toLowerCase().slice(1);
+  const fileExtension = getUriFileExtension(filename);
   return supportedLanguages[fileExtension] !== undefined;
 }
 
diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts
index d41ee41f8a..b5e951dcb6 100644
--- a/core/indexing/walkDir.ts
+++ b/core/indexing/walkDir.ts
@@ -227,8 +227,9 @@ export async function* walkDirAsync(
 export async function walkDirInWorkspaces(
   ide: IDE,
   _optionOverrides?: WalkerOptions,
+  dirs?: string[], // Can pass dirs to prevent duplicate calls
 ): Promise {
-  const workspaceDirs = await ide.getWorkspaceDirs();
+  const workspaceDirs = dirs ?? (await ide.getWorkspaceDirs());
   const results = await Promise.all(
     workspaceDirs.map((dir) => walkDir(dir, ide, _optionOverrides)),
   );
diff --git a/core/util/uri.test.ts b/core/util/uri.test.ts
index d4f82f3f74..42ecd4a025 100644
--- a/core/util/uri.test.ts
+++ b/core/util/uri.test.ts
@@ -1,6 +1,6 @@
+import { TEST_DIR } from "../test/testDir";
 import {
   getLastNPathParts,
-  getRelativePath,
   getUniqueUriPath,
   getUriPathBasename,
   groupByLastNPathParts,
@@ -33,13 +33,14 @@ describe("getUriPathBasename", () => {
 });
 
 describe("groupByLastNPathParts", () => {
+  const workspaceDirs = [TEST_DIR];
   it("should group filepaths by their last N parts", () => {
     const filepaths = [
       "/a/b/c/d/file1.txt",
       "/x/y/z/file1.txt",
       "/a/b/c/d/file2.txt",
     ];
-    const output = groupByLastNPathParts(filepaths, 2);
+    const output = groupByLastNPathParts(workspaceDirs, filepaths, 2);
     expect(output).toEqual({
       "d/file1.txt": ["/a/b/c/d/file1.txt"],
       "z/file1.txt": ["/x/y/z/file1.txt"],
@@ -49,13 +50,13 @@ describe("groupByLastNPathParts", () => {
 
   it("should handle an empty array", () => {
     const filepaths: string[] = [];
-    const output = groupByLastNPathParts(filepaths, 2);
+    const output = groupByLastNPathParts(workspaceDirs, filepaths, 2);
     expect(output).toEqual({});
   });
 
   it("should handle N greater than path parts", () => {
     const filepaths = ["/file.txt"];
-    const output = groupByLastNPathParts(filepaths, 5);
+    const output = groupByLastNPathParts(workspaceDirs, filepaths, 5);
     expect(output).toEqual({ "/file.txt": ["/file.txt"] });
   });
 });
diff --git a/core/util/uri.ts b/core/util/uri.ts
index ebd496134a..2b001533f3 100644
--- a/core/util/uri.ts
+++ b/core/util/uri.ts
@@ -50,13 +50,37 @@ export function findUriInDirs(
     if (uriComps.scheme !== dirComps.scheme) {
       continue;
     }
-    if (uriComps.path === dirComps.path) {
+    // Can't just use startsWith because e.g.
+    // file:///folder/file is not within file:///fold
+
+    // At this point we break the path up and check if each dir path part matches
+    const dirPathParts = (dirComps.path ?? "")
+      .split("/")
+      .map((part) => encodeURIComponent(part));
+    const uriPathParts = (uriComps.path ?? "")
+      .split("/")
+      .map((part) => encodeURIComponent(part));
+
+    if (uriPathParts.length > dirPathParts.length - 1) {
       continue;
     }
-    // Can't just use starts with because e.g.
-    // file:///folder/file is not within file:///fold
-    return uriComps.path.startsWith(dirComps.path);
+    let allDirPartsMatch = true;
+    for (let i = 0; i < dirPathParts.length; i++) {
+      if (dirPathParts[i] !== uriPathParts[i]) {
+        allDirPartsMatch = false;
+      }
+    }
+    if (allDirPartsMatch) {
+      return {
+        relativePathOrBasename: uriPathParts
+          .slice(dirPathParts.length)
+          .join("/"),
+        foundInDir: dir,
+      };
+    }
   }
+  // Not found
+  console.warn("Directory not found for uri", uri, dirUriCandidates);
   return {
     relativePathOrBasename: getUriPathBasename(uri),
     foundInDir: null,
@@ -91,7 +115,7 @@ export function getUriPathBasename(uri: string): string {
 */
 export function getUriFileExtension(uri: string) {
   const baseName = getUriPathBasename(uri);
-  return baseName.split(".")[-1] ?? "";
+  return (baseName.split(".")[-1] ?? "").toLowerCase();
 }
 
 export function getFileExtensionFromBasename(filename: string) {
@@ -114,11 +138,23 @@ export function joinPathsToUri(uri: string, ...pathSegments: string[]) {
   return URI.serialize(components);
 }
 
+// Note, will show workspace
 export function groupByLastNPathParts(
+  workspaceDirs: string[],
   uris: string[],
   n: number,
 ): Record {
-  return [];
+  return uris.reduce(
+    (groups, uri) => {
+      const lastNParts = getLastNUriRelativePathParts(workspaceDirs, uri, n);
+      if (!groups[lastNParts]) {
+        groups[lastNParts] = [];
+      }
+      groups[lastNParts].push(uri);
+      return groups;
+    },
+    {} as Record,
+  );
 }
 
 export function getUniqueUriPath(
@@ -189,23 +225,6 @@ export function getLastNPathParts(filepath: string, n: number): string {
   return filepath.split(/[\\/]/).slice(-n).join("/");
 }
 
-// export function groupByLastNPathParts(
-//   filepaths: string[],
-//   n: number,
-// ): Record {
-//   return filepaths.reduce(
-//     (groups, item) => {
-//       const lastNParts = getLastNPathParts(item, n);
-//       if (!groups[lastNParts]) {
-//         groups[lastNParts] = [];
-//       }
-//       groups[lastNParts].push(item);
-//       return groups;
-//     },
-//     {} as Record,
-//   );
-// }
-
 // export function getUniqueFilePath(
 //   item: string,
 //   itemGroups: Record,
@@ -228,12 +247,3 @@ export function getLastNPathParts(filepath: string, n: number): string {
 
 //   return getLastNPathParts(item, n);
 // }
-
-// export function splitPath(path: string, withRoot?: string): string[] {
-//   let parts = path.includes("/") ? path.split("/") : path.split("\\");
-//   if (withRoot !== undefined) {
-//     const rootParts = splitPath(withRoot);
-//     parts = parts.slice(rootParts.length - 1);
-//   }
-//   return parts;
-// }
diff --git a/gui/src/context/SubmenuContextProviders.tsx b/gui/src/context/SubmenuContextProviders.tsx
index b82f9dcaeb..da87d56c40 100644
--- a/gui/src/context/SubmenuContextProviders.tsx
+++ b/gui/src/context/SubmenuContextProviders.tsx
@@ -89,7 +89,8 @@ export const SubmenuContextProvidersProvider = ({
 
   const getOpenFilesItems = useCallback(async () => {
     const openFiles = await ideMessenger.ide.getOpenFiles();
-    const openFileGroups = groupByLastNPathParts(openFiles, 2);
+    const workspaceDirs = await ideMessenger.ide.getWorkspaceDirs();
+    const openFileGroups = groupByLastNPathParts(workspaceDirs, openFiles, 2);
 
     return openFiles.map((file) => ({
       id: file,

From 7621abcdcc4689ef25c8fecffd28a9770e674537 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 12:08:56 -0800
Subject: [PATCH 28/84] getShortestUniqueRelativeUriPaths

---
 core/context/providers/FileContextProvider.ts |  16 ++-
 .../providers/FolderContextProvider.ts        |  16 ++-
 .../providers/RepoMapContextProvider.ts       |  16 ++-
 core/util/uri.test.ts                         | 105 +-------------
 core/util/uri.ts                              | 134 +++++-------------
 gui/src/context/SubmenuContextProviders.tsx   |  18 +--
 6 files changed, 76 insertions(+), 229 deletions(-)

diff --git a/core/context/providers/FileContextProvider.ts b/core/context/providers/FileContextProvider.ts
index e1918a107a..ebdc4afe6c 100644
--- a/core/context/providers/FileContextProvider.ts
+++ b/core/context/providers/FileContextProvider.ts
@@ -8,9 +8,8 @@ import {
 } from "../../";
 import { walkDirInWorkspaces } from "../../indexing/walkDir";
 import {
-  getUniqueUriPath,
   getUriPathBasename,
-  groupByLastNPathParts,
+  getShortestUniqueRelativeUriPaths,
 } from "../../util/uri";
 
 const MAX_SUBMENU_ITEMS = 10_000;
@@ -53,13 +52,16 @@ class FileContextProvider extends BaseContextProvider {
       workspaceDirs,
     );
     const files = results.flat().slice(-MAX_SUBMENU_ITEMS);
-    const fileGroups = groupByLastNPathParts(workspaceDirs, files, 2);
+    const withUniquePaths = getShortestUniqueRelativeUriPaths(
+      files,
+      workspaceDirs,
+    );
 
-    return files.map((uri) => {
+    return withUniquePaths.map((file) => {
       return {
-        id: uri,
-        title: getUriPathBasename(uri),
-        description: getUniqueUriPath(uri, fileGroups),
+        id: file.uri,
+        title: getUriPathBasename(file.uri),
+        description: file.uniquePath,
       };
     });
   }
diff --git a/core/context/providers/FolderContextProvider.ts b/core/context/providers/FolderContextProvider.ts
index b521aaf194..260101863b 100644
--- a/core/context/providers/FolderContextProvider.ts
+++ b/core/context/providers/FolderContextProvider.ts
@@ -7,9 +7,8 @@ import {
 } from "../../index.js";
 import { walkDirInWorkspaces } from "../../indexing/walkDir.js";
 import {
-  getUniqueUriPath,
+  getShortestUniqueRelativeUriPaths,
   getUriPathBasename,
-  groupByLastNPathParts,
 } from "../../util/uri.js";
 import { BaseContextProvider } from "../index.js";
 import { retrieveContextItemsFromEmbeddings } from "../retrieval/retrieval.js";
@@ -40,13 +39,16 @@ class FolderContextProvider extends BaseContextProvider {
       },
       workspaceDirs,
     );
-    const folderGroups = groupByLastNPathParts(workspaceDirs, folders, 2);
+    const withUniquePaths = getShortestUniqueRelativeUriPaths(
+      folders,
+      workspaceDirs,
+    );
 
-    return folders.map((folder) => {
+    return withUniquePaths.map((folder) => {
       return {
-        id: folder,
-        title: getUriPathBasename(folder),
-        description: getUniqueUriPath(folder, folderGroups),
+        id: folder.uri,
+        title: getUriPathBasename(folder.uri),
+        description: folder.uniquePath,
       };
     });
   }
diff --git a/core/context/providers/RepoMapContextProvider.ts b/core/context/providers/RepoMapContextProvider.ts
index be25999fa9..ac12d1e309 100644
--- a/core/context/providers/RepoMapContextProvider.ts
+++ b/core/context/providers/RepoMapContextProvider.ts
@@ -9,9 +9,8 @@ import {
 import { walkDirInWorkspaces } from "../../indexing/walkDir";
 import generateRepoMap from "../../util/generateRepoMap";
 import {
-  getUniqueUriPath,
+  getShortestUniqueRelativeUriPaths,
   getUriPathBasename,
-  groupByLastNPathParts,
 } from "../../util/uri";
 
 const ENTIRE_PROJECT_ITEM: ContextSubmenuItem = {
@@ -54,14 +53,17 @@ class RepoMapContextProvider extends BaseContextProvider {
       },
       workspaceDirs,
     );
-    const folderGroups = groupByLastNPathParts(workspaceDirs, folders, 2);
+    const withUniquePaths = getShortestUniqueRelativeUriPaths(
+      folders,
+      workspaceDirs,
+    );
 
     return [
       ENTIRE_PROJECT_ITEM,
-      ...folders.map((folder) => ({
-        id: folder,
-        title: getUriPathBasename(folder),
-        description: getUniqueUriPath(folder, folderGroups),
+      ...withUniquePaths.map((folder) => ({
+        id: folder.uri,
+        title: getUriPathBasename(folder.uri),
+        description: folder.uniquePath,
       })),
     ];
   }
diff --git a/core/util/uri.test.ts b/core/util/uri.test.ts
index 42ecd4a025..016fd06685 100644
--- a/core/util/uri.test.ts
+++ b/core/util/uri.test.ts
@@ -1,10 +1,5 @@
 import { TEST_DIR } from "../test/testDir";
-import {
-  getLastNPathParts,
-  getUniqueUriPath,
-  getUriPathBasename,
-  groupByLastNPathParts,
-} from "./uri";
+import { getLastNPathParts, getUriPathBasename } from "./uri";
 
 describe("getUriPathBasename", () => {
   it("should return the base name of a Unix-style path", () => {
@@ -32,64 +27,6 @@ describe("getUriPathBasename", () => {
   });
 });
 
-describe("groupByLastNPathParts", () => {
-  const workspaceDirs = [TEST_DIR];
-  it("should group filepaths by their last N parts", () => {
-    const filepaths = [
-      "/a/b/c/d/file1.txt",
-      "/x/y/z/file1.txt",
-      "/a/b/c/d/file2.txt",
-    ];
-    const output = groupByLastNPathParts(workspaceDirs, filepaths, 2);
-    expect(output).toEqual({
-      "d/file1.txt": ["/a/b/c/d/file1.txt"],
-      "z/file1.txt": ["/x/y/z/file1.txt"],
-      "d/file2.txt": ["/a/b/c/d/file2.txt"],
-    });
-  });
-
-  it("should handle an empty array", () => {
-    const filepaths: string[] = [];
-    const output = groupByLastNPathParts(workspaceDirs, filepaths, 2);
-    expect(output).toEqual({});
-  });
-
-  it("should handle N greater than path parts", () => {
-    const filepaths = ["/file.txt"];
-    const output = groupByLastNPathParts(workspaceDirs, filepaths, 5);
-    expect(output).toEqual({ "/file.txt": ["/file.txt"] });
-  });
-});
-
-describe("getUniqueUriPath", () => {
-  it("should return a unique file path within the group", () => {
-    const item = "/a/b/c/file.txt";
-    const itemGroups = {
-      "c/file.txt": ["/a/b/c/file.txt", "/x/y/c/file.txt"],
-    };
-    const output = getUniqueUriPath(item, itemGroups);
-    expect(output).toBe("b/c/file.txt");
-  });
-
-  it("should return the last two parts if unique", () => {
-    const item = "/a/b/c/file.txt";
-    const itemGroups = {
-      "c/file.txt": ["/a/b/c/file.txt"],
-    };
-    const output = getUniqueUriPath(item, itemGroups);
-    expect(output).toBe("c/file.txt");
-  });
-
-  it("should handle when additional parts are needed to make it unique", () => {
-    const item = "/a/b/c/d/e/file.txt";
-    const itemGroups = {
-      "e/file.txt": ["/a/b/c/d/e/file.txt", "/x/y/z/e/file.txt"],
-    };
-    const output = getUniqueUriPath(item, itemGroups);
-    expect(output).toBe("d/e/file.txt");
-  });
-});
-
 describe("getLastNPathParts", () => {
   test("returns the last N parts of a filepath with forward slashes", () => {
     const filepath = "home/user/documents/project/file.txt";
@@ -128,43 +65,3 @@ describe("getLastNPathParts", () => {
     expect(getLastNPathParts(filepath, 2)).toBe("");
   });
 });
-
-describe("shortestRelativePaths", () => {
-  it("should return shortest unique paths", () => {
-    const paths = [
-      "/a/b/c/file.txt",
-      "/a/b/d/file.txt",
-      "/a/b/d/file2.txt",
-      "/x/y/z/file.txt",
-    ];
-    const output = shortestRelativePaths(paths);
-    expect(output).toEqual([
-      "c/file.txt",
-      "d/file.txt",
-      "file2.txt",
-      "z/file.txt",
-    ]);
-  });
-
-  it("should handle empty array", () => {
-    const paths: string[] = [];
-    const output = shortestRelativePaths(paths);
-    expect(output).toEqual([]);
-  });
-
-  it("should handle paths with same base names", () => {
-    const paths = [
-      "/a/b/c/d/file.txt",
-      "/a/b/c/e/file.txt",
-      "/a/b/f/g/h/file.txt",
-    ];
-    const output = shortestRelativePaths(paths);
-    expect(output).toEqual(["d/file.txt", "e/file.txt", "h/file.txt"]);
-  });
-
-  it("should handle paths where entire path is needed", () => {
-    const paths = ["/a/b/c/file.txt", "/a/b/c/file.txt", "/a/b/c/file.txt"];
-    const output = shortestRelativePaths(paths);
-    expect(output).toEqual(["file.txt", "file.txt", "file.txt"]);
-  });
-});
diff --git a/core/util/uri.ts b/core/util/uri.ts
index 2b001533f3..f110a870a0 100644
--- a/core/util/uri.ts
+++ b/core/util/uri.ts
@@ -33,6 +33,7 @@ export function findUriInDirs(
   uri: string,
   dirUriCandidates: string[],
 ): {
+  uri: string;
   relativePathOrBasename: string;
   foundInDir: string | null;
 } {
@@ -72,6 +73,7 @@ export function findUriInDirs(
     }
     if (allDirPartsMatch) {
       return {
+        uri,
         relativePathOrBasename: uriPathParts
           .slice(dirPathParts.length)
           .join("/"),
@@ -82,6 +84,7 @@ export function findUriInDirs(
   // Not found
   console.warn("Directory not found for uri", uri, dirUriCandidates);
   return {
+    uri,
     relativePathOrBasename: getUriPathBasename(uri),
     foundInDir: null,
   };
@@ -138,112 +141,51 @@ export function joinPathsToUri(uri: string, ...pathSegments: string[]) {
   return URI.serialize(components);
 }
 
-// Note, will show workspace
-export function groupByLastNPathParts(
-  workspaceDirs: string[],
-  uris: string[],
-  n: number,
-): Record {
-  return uris.reduce(
-    (groups, uri) => {
-      const lastNParts = getLastNUriRelativePathParts(workspaceDirs, uri, n);
-      if (!groups[lastNParts]) {
-        groups[lastNParts] = [];
-      }
-      groups[lastNParts].push(uri);
-      return groups;
-    },
-    {} as Record,
-  );
-}
-
-export function getUniqueUriPath(
-  item: string,
-  itemGroups: Record,
-): string {
-  return "hi";
-}
-
-export function shortestRelativeUriPaths(
+export function getShortestUniqueRelativeUriPaths(
   uris: string[],
   dirUriCandidates: string[],
-): string[] {
-  if (uris.length === 0) {
-    return [];
-  }
-  const relativeUris;
-
-  const partsLengths = uris.map((x) => x.split("/").length);
-  const currentRelativeUris = uris.map(getB);
-  const currentNumParts = uris.map(() => 1);
-  const isDuplicated = currentRelativeUris.map(
-    (x, i) =>
-      currentRelativeUris.filter((y, j) => y === x && paths[i] !== paths[j])
-        .length > 1,
-  );
-
-  while (isDuplicated.some(Boolean)) {
-    const firstDuplicatedPath = currentRelativeUris.find(
-      (x, i) => isDuplicated[i],
-    );
-    if (!firstDuplicatedPath) {
-      break;
+): {
+  uri: string;
+  uniquePath: string;
+}[] {
+  // Split all URIs into segments and count occurrences of each suffix combination
+  const segmentCombinationsMap = new Map();
+  const segmentsInfo = uris.map((uri) => {
+    const { relativePathOrBasename } = findUriInDirs(uri, dirUriCandidates);
+    const segments = relativePathOrBasename.split("/");
+    const suffixes: string[] = [];
+
+    // Generate all possible suffix combinations
+    for (let i = segments.length - 1; i >= 0; i--) {
+      const suffix = segments.slice(i).join("/");
+      suffixes.unshift(suffix);
+
+      // Count occurrences of each suffix
+      segmentCombinationsMap.set(
+        suffix,
+        (segmentCombinationsMap.get(suffix) || 0) + 1,
+      );
     }
 
-    currentRelativeUris.forEach((x, i) => {
-      if (x === firstDuplicatedPath) {
-        currentNumParts[i] += 1;
-        currentRelativeUris[i] = getLastNUriRelativePathParts(
-          paths[i],
-          currentNumParts[i],
-        );
-      }
-    });
-
-    isDuplicated.forEach((x, i) => {
-      if (x) {
-        isDuplicated[i] =
-          // Once we've used up all the parts, we can't make it longer
-          currentNumParts[i] < partsLengths[i] &&
-          currentRelativeUris.filter((y) => y === currentRelativeUris[i])
-            .length > 1;
-      }
-    });
-  }
+    return { uri, segments, suffixes, relativePathOrBasename };
+  });
 
-  return currentRelativeUris;
-}
+  // Find shortest unique path for each URI
+  return segmentsInfo.map(({ uri, suffixes, relativePathOrBasename }) => {
+    // Find the first (shortest) unique suffix
+    const uniquePath =
+      suffixes.find((suffix) => segmentCombinationsMap.get(suffix) === 1) ??
+      relativePathOrBasename; // case where not unique - perhaps basename
 
-// export function getBasename(filepath: string): string {
-//   return filepath.split(SEP_REGEX).pop() ?? "";
-// }
+    return { uri, uniquePath };
+  });
+}
 
+// Only used when working with system paths and relative paths
+// Since doesn't account for URI segements before workspace
 export function getLastNPathParts(filepath: string, n: number): string {
   if (n <= 0) {
     return "";
   }
   return filepath.split(/[\\/]/).slice(-n).join("/");
 }
-
-// export function getUniqueFilePath(
-//   item: string,
-//   itemGroups: Record,
-// ): string {
-//   const lastTwoParts = getLastNPathParts(item, 2);
-//   const group = itemGroups[lastTwoParts];
-
-//   let n = 2;
-//   if (group.length > 1) {
-//     while (
-//       group.some(
-//         (otherItem) =>
-//           otherItem !== item &&
-//           getLastNPathParts(otherItem, n) === getLastNPathParts(item, n),
-//       )
-//     ) {
-//       n++;
-//     }
-//   }
-
-//   return getLastNPathParts(item, n);
-// }
diff --git a/gui/src/context/SubmenuContextProviders.tsx b/gui/src/context/SubmenuContextProviders.tsx
index da87d56c40..c8ba17c774 100644
--- a/gui/src/context/SubmenuContextProviders.tsx
+++ b/gui/src/context/SubmenuContextProviders.tsx
@@ -15,9 +15,8 @@ import { selectContextProviderDescriptions } from "../redux/selectors";
 import { useWebviewListener } from "../hooks/useWebviewListener";
 import { useAppSelector } from "../redux/hooks";
 import {
-  getUniqueUriPath,
+  getShortestUniqueRelativeUriPaths,
   getUriPathBasename,
-  groupByLastNPathParts,
 } from "core/util/uri";
 
 const MINISEARCH_OPTIONS = {
@@ -90,12 +89,15 @@ export const SubmenuContextProvidersProvider = ({
   const getOpenFilesItems = useCallback(async () => {
     const openFiles = await ideMessenger.ide.getOpenFiles();
     const workspaceDirs = await ideMessenger.ide.getWorkspaceDirs();
-    const openFileGroups = groupByLastNPathParts(workspaceDirs, openFiles, 2);
-
-    return openFiles.map((file) => ({
-      id: file,
-      title: getUriPathBasename(file),
-      description: getUniqueUriPath(file, openFileGroups),
+    const withUniquePaths = getShortestUniqueRelativeUriPaths(
+      openFiles,
+      workspaceDirs,
+    );
+
+    return withUniquePaths.map((file) => ({
+      id: file.uri,
+      title: getUriPathBasename(file.uri),
+      description: file.uniquePath,
       providerTitle: "file",
     }));
   }, [ideMessenger]);

From 8113e1d1cbac2f87f31c433b29962ec0522498db Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 12:47:27 -0800
Subject: [PATCH 29/84] inferResolvedUriFromRelativePath

---
 core/context/retrieval/retrieval.ts           |  9 ---
 core/promptFiles/v2/renderPromptFile.ts       |  7 +-
 .../tools/implementations/viewSubdirectory.ts |  4 +-
 core/util/generateRepoMap.ts                  |  5 ++
 core/util/ideUtils.test.ts                    | 45 -----------
 core/util/ideUtils.ts                         | 74 +++++++++++++------
 gui/src/components/mainInput/resolveInput.ts  |  1 -
 7 files changed, 62 insertions(+), 83 deletions(-)
 delete mode 100644 core/util/ideUtils.test.ts

diff --git a/core/context/retrieval/retrieval.ts b/core/context/retrieval/retrieval.ts
index 1ee33af5ce..dba06f5136 100644
--- a/core/context/retrieval/retrieval.ts
+++ b/core/context/retrieval/retrieval.ts
@@ -1,6 +1,5 @@
 import { BranchAndDir, ContextItem, ContextProviderExtras } from "../../";
 import TransformersJsEmbeddingsProvider from "../../llm/llms/TransformersJsEmbeddingsProvider";
-import { resolveRelativePathInWorkspace } from "../../util/ideUtils";
 import { getUriPathBasename } from "../../util/uri";
 import { INSTRUCTIONS_BASE_ITEM } from "../providers/utils";
 
@@ -70,14 +69,6 @@ export async function retrieveContextItemsFromEmbeddings(
     ? RerankerRetrievalPipeline
     : NoRerankerRetrievalPipeline;
 
-  if (filterDirectory) {
-    // Handle relative paths
-    filterDirectory = await resolveRelativePathInWorkspace(
-      filterDirectory,
-      extras.ide,
-    );
-  }
-
   const pipelineOptions: RetrievalPipelineOptions = {
     nFinal,
     nRetrieve,
diff --git a/core/promptFiles/v2/renderPromptFile.ts b/core/promptFiles/v2/renderPromptFile.ts
index 22abf35768..ace5a96df7 100644
--- a/core/promptFiles/v2/renderPromptFile.ts
+++ b/core/promptFiles/v2/renderPromptFile.ts
@@ -1,7 +1,7 @@
 import { ContextItem, ContextProviderExtras } from "../..";
 import { contextProviderClassFromName } from "../../context/providers";
 import URLContextProvider from "../../context/providers/URLContextProvider";
-import { resolveRelativePathInWorkspace } from "../../util/ideUtils";
+import { resolveRelativePathInDir } from "../../util/ideUtils";
 import { getUriPathBasename } from "../../util/uri";
 import { getPreambleAndBody } from "./parse";
 
@@ -26,10 +26,7 @@ async function resolveAttachment(
   }
 
   // Files
-  const resolvedFileUri = await resolveRelativePathInWorkspace(
-    name,
-    extras.ide,
-  );
+  const resolvedFileUri = await resolveRelativePathInDir(name, extras.ide);
   if (resolvedFileUri) {
     const content = `\`\`\`${name}\n${await extras.ide.readFile(resolvedFileUri)}\n\`\`\``;
     return [
diff --git a/core/tools/implementations/viewSubdirectory.ts b/core/tools/implementations/viewSubdirectory.ts
index e762858ff7..9bd4583f2c 100644
--- a/core/tools/implementations/viewSubdirectory.ts
+++ b/core/tools/implementations/viewSubdirectory.ts
@@ -1,11 +1,11 @@
 import generateRepoMap from "../../util/generateRepoMap";
-import { resolveRelativePathInWorkspace } from "../../util/ideUtils";
+import { resolveRelativePathInDir } from "../../util/ideUtils";
 
 import { ToolImpl } from ".";
 
 export const viewSubdirectoryImpl: ToolImpl = async (args: any, extras) => {
   const { directory_path } = args;
-  const absoluteUri = await resolveRelativePathInWorkspace(
+  const absoluteUri = await resolveRelativePathInDir(
     directory_path,
     extras.ide,
   );
diff --git a/core/util/generateRepoMap.ts b/core/util/generateRepoMap.ts
index 6aad1f95ab..47a796b48b 100644
--- a/core/util/generateRepoMap.ts
+++ b/core/util/generateRepoMap.ts
@@ -40,6 +40,11 @@ class RepoMapGenerator {
       llm.contextLength * this.REPO_MAX_CONTEXT_LENGTH_RATIO;
   }
 
+  /*
+    Note, the repo map is usually USED as relative file paths (passed to LLM)
+    So for now, using URIs INPUTS to generate the map, but OUTPUTing relative paths
+    The exception if requestFilesFromRepoMap, which now converts the relative paths back to URIs
+  */
   async generate(): Promise {
     this.repoMapDirs = this.options.dirs ?? (await this.ide.getWorkspaceDirs());
     this.allPathsInDirs = await this.getAllPathsInDirs();
diff --git a/core/util/ideUtils.test.ts b/core/util/ideUtils.test.ts
deleted file mode 100644
index a9069f4258..0000000000
--- a/core/util/ideUtils.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-// Generated by continue
-
-import { resolveRelativePathInWorkspace } from "./ideUtils";
-import { testIde } from "../test/fixtures";
-import { IDE } from "..";
-
-describe("resolveRelativePathInWorkspace", () => {
-  let mockIde: IDE;
-
-  beforeEach(() => {
-    mockIde = testIde;
-  });
-
-  it("should return the full path if the path is already absolute", async () => {
-    const mockPath = "/absolute/path/to/file.txt";
-
-    const result = await resolveRelativePathInWorkspace(mockPath, mockIde);
-    expect(result).toBe(mockPath);
-  });
-
-  it("should resolve a relative path to a full path within workspace directories", async () => {
-    const relativePath = "relative/path/to/file.txt";
-    const workspaces = ["/workspace/one", "/workspace/two"];
-    const expectedFullPath = `/workspace/one/${relativePath}`;
-
-    jest.spyOn(mockIde, "getWorkspaceDirs").mockResolvedValue(workspaces);
-    jest
-      .spyOn(mockIde, "fileExists")
-      .mockImplementation(async (path) => path === expectedFullPath);
-
-    const result = await resolveRelativePathInWorkspace(relativePath, mockIde);
-    expect(result).toBe(expectedFullPath);
-  });
-
-  it("should return undefined if the relative path does not exist in any workspace directory", async () => {
-    const relativePath = "non/existent/path.txt";
-    const workspaces = ["/workspace/one", "/workspace/two"];
-
-    jest.spyOn(mockIde, "getWorkspaceDirs").mockResolvedValue(workspaces);
-    jest.spyOn(mockIde, "fileExists").mockResolvedValue(false);
-
-    const result = await resolveRelativePathInWorkspace(relativePath, mockIde);
-    expect(result).toBeUndefined();
-  });
-});
diff --git a/core/util/ideUtils.ts b/core/util/ideUtils.ts
index 48561be749..26b4b75ab7 100644
--- a/core/util/ideUtils.ts
+++ b/core/util/ideUtils.ts
@@ -1,18 +1,19 @@
 import { IDE } from "..";
-import { findUriInDirs, joinPathsToUri } from "./uri";
+import { joinPathsToUri, pathToUriPathSegment } from "./uri";
 
 /*
-  This function takes a relative filepath
+  This function takes a relative (to workspace) filepath
   And checks each workspace for if it exists or not
   Only returns fully resolved URI if it exists
 */
-export async function resolveRelativePathInWorkspace(
+export async function resolveRelativePathInDir(
   path: string,
   ide: IDE,
+  dirUriCandidates?: string[],
 ): Promise {
-  const workspaces = await ide.getWorkspaceDirs();
-  for (const workspaceUri of workspaces) {
-    const fullUri = joinPathsToUri(workspaceUri, path);
+  const dirs = dirUriCandidates ?? (await ide.getWorkspaceDirs());
+  for (const dirUri of dirs) {
+    const fullUri = joinPathsToUri(dirUri, path);
     if (await ide.fileExists(fullUri)) {
       return fullUri;
     }
@@ -22,24 +23,55 @@ export async function resolveRelativePathInWorkspace(
 }
 
 /*
-  This function takes a relative filepath (which may not exist)
-  And, based on which workspace has the closest matching path
-  Guesses which workspace and returns resolved URI
-
-  Original use case of for tools trying to create new files
-  If no meaninful path match just concatenates to first workspace's uri
+  Same as above but in this case the relative path does not need to exist (e.g. file to be created, etc)
+  Checks closes match with the dirs, path segment by segment
+  and based on which workspace has the closest matching path, returns resolved URI
+  If no meaninful path match just concatenates to first dir's uri
 */
 export async function inferResolvedUriFromRelativePath(
   path: string,
   ide: IDE,
+  dirCandidates?: string[],
 ): Promise {
-  // for (const workspaceUri of workspaceDirs) {
-  //   if (isUriWithinDirectory(path))
-  //     const fullUri = joinPathsToUri(workspaceUri, path);
-  //   if (await ide.fileExists(fullUri)) {
-  //     return fullUri;
-  //   }
-  // }
-  // // console.warn("No meaninful filepath inferred from relative path " + path)
-  // return `${workspaces[0]}/${cleanPath}`;
+  const dirs = dirCandidates ?? (await ide.getWorkspaceDirs());
+  if (dirs.length === 0) {
+    throw new Error("inferResolvedUriFromRelativePath: no dirs provided");
+  }
+  const segments = pathToUriPathSegment(path).split("/");
+
+  // Generate all possible suffixes from shortest to longest
+  const suffixes: string[] = [];
+  for (let i = segments.length - 1; i >= 0; i--) {
+    suffixes.push(segments.slice(i).join("/"));
+  }
+
+  // For each suffix, try to find a unique matching directory
+  for (const suffix of suffixes) {
+    const uris = dirs.map((dir) => ({
+      dir,
+      partialUri: joinPathsToUri(dir, suffix),
+    }));
+    const existenceChecks = await Promise.all(
+      uris.map(async ({ partialUri, dir }) => ({
+        dir,
+        partialUri,
+        exists: await ide.fileExists(partialUri),
+      })),
+    );
+
+    const existingUris = existenceChecks.filter(({ exists }) => exists);
+
+    // If exactly one directory matches, use it
+    if (existingUris.length === 1) {
+      return joinPathsToUri(existingUris[0].dir, segments.join("/"));
+    }
+  }
+
+  // If no unique match found, use the first directory
+  return joinPathsToUri(dirs[0], segments.join("/"));
+}
+
+interface ResolveResult {
+  resolvedUri: string;
+  matchedDir: string;
 }
diff --git a/gui/src/components/mainInput/resolveInput.ts b/gui/src/components/mainInput/resolveInput.ts
index 6f52327173..8f8cbb6f90 100644
--- a/gui/src/components/mainInput/resolveInput.ts
+++ b/gui/src/components/mainInput/resolveInput.ts
@@ -11,7 +11,6 @@ import { stripImages } from "core/util/messageContent";
 import { IIdeMessenger } from "../../context/IdeMessenger";
 import { Dispatch } from "@reduxjs/toolkit";
 import { setIsGatheringContext } from "../../redux/slices/sessionSlice";
-import { inferResolvedUriFromRelativePath } from "core/util/ideUtils";
 import { ctxItemToRifWithContents } from "core/commands/util";
 import { getUriFileExtension } from "core/util/uri";
 

From 96e970682f87f05b96d34450e2c252afb8c32460 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 12:50:12 -0800
Subject: [PATCH 30/84] minor fix

---
 core/autocomplete/templating/formatting.ts        | 2 +-
 core/context/providers/FileTreeContextProvider.ts | 6 +-----
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/core/autocomplete/templating/formatting.ts b/core/autocomplete/templating/formatting.ts
index 7724522ebc..63bc156ce8 100644
--- a/core/autocomplete/templating/formatting.ts
+++ b/core/autocomplete/templating/formatting.ts
@@ -1,4 +1,4 @@
-import { getLastNUriRelativePathParts, getRelativePath } from "../../util/uri";
+import { getLastNUriRelativePathParts } from "../../util/uri";
 import {
   AutocompleteClipboardSnippet,
   AutocompleteCodeSnippet,
diff --git a/core/context/providers/FileTreeContextProvider.ts b/core/context/providers/FileTreeContextProvider.ts
index aadd11eec4..3b98dd6cb5 100644
--- a/core/context/providers/FileTreeContextProvider.ts
+++ b/core/context/providers/FileTreeContextProvider.ts
@@ -4,11 +4,7 @@ import {
   ContextProviderExtras,
 } from "../../index.js";
 import { walkDir } from "../../indexing/walkDir.js";
-import {
-  findUriInDirs,
-  getRelativePath,
-  getUriPathBasename,
-} from "../../util/uri.js";
+import { findUriInDirs, getUriPathBasename } from "../../util/uri.js";
 import { BaseContextProvider } from "../index.js";
 
 interface Directory {

From e40c4751e15fa64317816313c4b94dd7fd2b407c Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 15:20:35 -0800
Subject: [PATCH 31/84] path->uri more fixes

---
 core/context/providers/FileContextProvider.ts |   8 +-
 .../providers/FolderContextProvider.ts        |   4 +-
 .../providers/RepoMapContextProvider.ts       |   8 +-
 core/context/retrieval/repoMapRequest.ts      |  33 +--
 core/indexing/CodeSnippetsIndex.ts            |  12 +-
 core/indexing/FullTextSearchCodebaseIndex.ts  |   4 +-
 core/indexing/docs/article.ts                 |  12 +-
 core/indexing/docs/suggestions/index.ts       |   4 +-
 core/indexing/walkDir.ts                      |   2 +-
 core/tools/implementations/viewRepoMap.ts     |   5 +-
 .../tools/implementations/viewSubdirectory.ts |  12 +-
 core/util/generateRepoMap.test.ts             |   9 +-
 core/util/generateRepoMap.ts                  | 212 +++++++-----------
 extensions/vscode/src/util/FileSearch.ts      |   4 +-
 14 files changed, 147 insertions(+), 182 deletions(-)

diff --git a/core/context/providers/FileContextProvider.ts b/core/context/providers/FileContextProvider.ts
index ebdc4afe6c..674cc1a009 100644
--- a/core/context/providers/FileContextProvider.ts
+++ b/core/context/providers/FileContextProvider.ts
@@ -6,7 +6,7 @@ import {
   ContextSubmenuItem,
   LoadSubmenuItemsArgs,
 } from "../../";
-import { walkDirInWorkspaces } from "../../indexing/walkDir";
+import { walkDirs } from "../../indexing/walkDir";
 import {
   getUriPathBasename,
   getShortestUniqueRelativeUriPaths,
@@ -46,11 +46,7 @@ class FileContextProvider extends BaseContextProvider {
     args: LoadSubmenuItemsArgs,
   ): Promise {
     const workspaceDirs = await args.ide.getWorkspaceDirs();
-    const results = await walkDirInWorkspaces(
-      args.ide,
-      undefined,
-      workspaceDirs,
-    );
+    const results = await walkDirs(args.ide, undefined, workspaceDirs);
     const files = results.flat().slice(-MAX_SUBMENU_ITEMS);
     const withUniquePaths = getShortestUniqueRelativeUriPaths(
       files,
diff --git a/core/context/providers/FolderContextProvider.ts b/core/context/providers/FolderContextProvider.ts
index 260101863b..dc52575683 100644
--- a/core/context/providers/FolderContextProvider.ts
+++ b/core/context/providers/FolderContextProvider.ts
@@ -5,7 +5,7 @@ import {
   ContextSubmenuItem,
   LoadSubmenuItemsArgs,
 } from "../../index.js";
-import { walkDirInWorkspaces } from "../../indexing/walkDir.js";
+import { walkDirs } from "../../indexing/walkDir.js";
 import {
   getShortestUniqueRelativeUriPaths,
   getUriPathBasename,
@@ -32,7 +32,7 @@ class FolderContextProvider extends BaseContextProvider {
     args: LoadSubmenuItemsArgs,
   ): Promise {
     const workspaceDirs = await args.ide.getWorkspaceDirs();
-    const folders = await walkDirInWorkspaces(
+    const folders = await walkDirs(
       args.ide,
       {
         onlyDirs: true,
diff --git a/core/context/providers/RepoMapContextProvider.ts b/core/context/providers/RepoMapContextProvider.ts
index ac12d1e309..ae7d92bf9b 100644
--- a/core/context/providers/RepoMapContextProvider.ts
+++ b/core/context/providers/RepoMapContextProvider.ts
@@ -6,7 +6,7 @@ import {
   ContextSubmenuItem,
   LoadSubmenuItemsArgs,
 } from "../../";
-import { walkDirInWorkspaces } from "../../indexing/walkDir";
+import { walkDirs } from "../../indexing/walkDir";
 import generateRepoMap from "../../util/generateRepoMap";
 import {
   getShortestUniqueRelativeUriPaths,
@@ -36,7 +36,9 @@ class RepoMapContextProvider extends BaseContextProvider {
         name: "Repository Map",
         description: "Overview of the repository structure",
         content: await generateRepoMap(extras.llm, extras.ide, {
-          dirs: query === ENTIRE_PROJECT_ITEM.id ? undefined : [query],
+          dirUris: query === ENTIRE_PROJECT_ITEM.id ? undefined : [query],
+          outputRelativeUriPaths: true,
+          includeSignatures: false,
         }),
       },
     ];
@@ -46,7 +48,7 @@ class RepoMapContextProvider extends BaseContextProvider {
     args: LoadSubmenuItemsArgs,
   ): Promise {
     const workspaceDirs = await args.ide.getWorkspaceDirs();
-    const folders = await walkDirInWorkspaces(
+    const folders = await walkDirs(
       args.ide,
       {
         onlyDirs: true,
diff --git a/core/context/retrieval/repoMapRequest.ts b/core/context/retrieval/repoMapRequest.ts
index a6a77127a8..f70f0a5941 100644
--- a/core/context/retrieval/repoMapRequest.ts
+++ b/core/context/retrieval/repoMapRequest.ts
@@ -47,8 +47,9 @@ export async function requestFilesFromRepoMap(
 
   try {
     const repoMap = await generateRepoMap(llm, ide, {
+      dirUris: filterDirUri ? [filterDirUri] : undefined,
       includeSignatures: false,
-      dirs: filterDirUri ? [filterDirUri] : undefined,
+      outputRelativeUriPaths: false,
     });
 
     const prompt = `${repoMap}
@@ -72,26 +73,26 @@ This is the question that you should select relevant files for: "${input}"`;
       return [];
     }
 
-    const subDirPrefix = filterDirUri
-      ? getUriPathBasename(filterDirUri) + "/"
-      : "";
-    const files =
-      content
-        .split("")[1]
-        ?.split("")[0]
-        ?.split("\n")
-        .filter(Boolean)
-        .map((file) => file.trim())
-        .map((file) => subDirPrefix + file) ?? [];
+    // IMPORANT
+
+    // const subDirPrefix = filterDirUri
+    //   ? getUriPathBasename(filterDirUri) + "/"
+    //   : "";
+    const fileUris = content
+      .split("")[1]
+      ?.split("")[0]
+      ?.split("\n")
+      .filter(Boolean)
+      .map((uri) => uri.trim());
 
     const chunks = await Promise.all(
-      files.map(async (file) => {
-        const content = await ide.readFile(file);
+      fileUris.map(async (uri) => {
+        const content = await ide.readFile(uri);
         const lineCount = content.split("\n").length;
         const chunk: Chunk = {
-          digest: file,
+          digest: uri,
           content,
-          filepath: file,
+          filepath: uri,
           endLine: lineCount - 1,
           startLine: 0,
           index: 0,
diff --git a/core/indexing/CodeSnippetsIndex.ts b/core/indexing/CodeSnippetsIndex.ts
index 48d60083b3..fa210ed00a 100644
--- a/core/indexing/CodeSnippetsIndex.ts
+++ b/core/indexing/CodeSnippetsIndex.ts
@@ -390,7 +390,7 @@ export class CodeSnippetsCodebaseIndex implements CodebaseIndex {
     offset: number = 0,
     batchSize: number = 100,
   ): Promise<{
-    groupedByPath: { [path: string]: string[] };
+    groupedByUri: { [path: string]: string[] };
     hasMore: boolean;
   }> {
     const db = await SqliteDb.get();
@@ -411,17 +411,17 @@ export class CodeSnippetsCodebaseIndex implements CodebaseIndex {
 
     const rows = await db.all(query, [...likePatterns, batchSize, offset]);
 
-    const groupedByPath: { [path: string]: string[] } = {};
+    const groupedByUri: { [path: string]: string[] } = {};
 
     for (const { path, signature } of rows) {
-      if (!groupedByPath[path]) {
-        groupedByPath[path] = [];
+      if (!groupedByUri[path]) {
+        groupedByUri[path] = [];
       }
-      groupedByPath[path].push(signature);
+      groupedByUri[path].push(signature);
     }
 
     const hasMore = rows.length === batchSize;
 
-    return { groupedByPath, hasMore };
+    return { groupedByUri, hasMore };
   }
 }
diff --git a/core/indexing/FullTextSearchCodebaseIndex.ts b/core/indexing/FullTextSearchCodebaseIndex.ts
index 82e4e822f6..7ff54781c8 100644
--- a/core/indexing/FullTextSearchCodebaseIndex.ts
+++ b/core/indexing/FullTextSearchCodebaseIndex.ts
@@ -1,6 +1,6 @@
 import { BranchAndDir, Chunk, IndexTag, IndexingProgressUpdate } from "../";
-import { getBasename } from "../util/index";
 import { RETRIEVAL_PARAMS } from "../util/parameters";
+import { getUriPathBasename } from "../util/uri";
 
 import { ChunkCodebaseIndex } from "./chunk/ChunkCodebaseIndex";
 import { DatabaseConnection, SqliteDb, tagToString } from "./refreshIndex";
@@ -79,7 +79,7 @@ export class FullTextSearchCodebaseIndex implements CodebaseIndex {
 
       yield {
         progress: i / results.compute.length,
-        desc: `Indexing ${getBasename(item.path)}`,
+        desc: `Indexing ${getUriPathBasename(item.path)}`,
         status: "indexing",
       };
       await markComplete([item], IndexResultType.Compute);
diff --git a/core/indexing/docs/article.ts b/core/indexing/docs/article.ts
index 2ae0e79953..8026280c9c 100644
--- a/core/indexing/docs/article.ts
+++ b/core/indexing/docs/article.ts
@@ -31,6 +31,11 @@ function breakdownArticleComponent(
   let content = "";
   let index = 0;
 
+  const fullUrl = new URL(
+    `${subpath}#${cleanFragment(article.title)}`,
+    url,
+  ).toString();
+
   const createChunk = (
     chunkContent: string,
     chunkStartLine: number,
@@ -44,11 +49,8 @@ function breakdownArticleComponent(
         title: cleanHeader(article.title),
       },
       index: index++,
-      filepath: new URL(
-        `${subpath}#${cleanFragment(article.title)}`,
-        url,
-      ).toString(),
-      digest: subpath,
+      filepath: fullUrl,
+      digest: fullUrl,
     });
   };
 
diff --git a/core/indexing/docs/suggestions/index.ts b/core/indexing/docs/suggestions/index.ts
index d32d7c0d09..fac5091fd8 100644
--- a/core/indexing/docs/suggestions/index.ts
+++ b/core/indexing/docs/suggestions/index.ts
@@ -7,7 +7,7 @@ import {
   ParsedPackageInfo,
 } from "../../..";
 import { getUriPathBasename } from "../../../util/uri";
-import { walkDir, walkDirInWorkspaces } from "../../walkDir";
+import { walkDir, walkDirs } from "../../walkDir";
 
 import { PythonPackageCrawler } from "./packageCrawlers/Python";
 import { NodePackageCrawler } from "./packageCrawlers/TsJs";
@@ -25,7 +25,7 @@ export interface PackageCrawler {
 }
 
 export async function getAllSuggestedDocs(ide: IDE) {
-  const allFileUris = await walkDirInWorkspaces(ide);
+  const allFileUris = await walkDirs(ide);
   const allFiles = allFileUris.map((uri) => ({
     path: uri,
     name: getUriPathBasename(uri),
diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts
index b5e951dcb6..a1ec2eba3d 100644
--- a/core/indexing/walkDir.ts
+++ b/core/indexing/walkDir.ts
@@ -224,7 +224,7 @@ export async function* walkDirAsync(
   yield* new DFSWalker(path, ide, options).walk();
 }
 
-export async function walkDirInWorkspaces(
+export async function walkDirs(
   ide: IDE,
   _optionOverrides?: WalkerOptions,
   dirs?: string[], // Can pass dirs to prevent duplicate calls
diff --git a/core/tools/implementations/viewRepoMap.ts b/core/tools/implementations/viewRepoMap.ts
index 56c2654162..69f624feb4 100644
--- a/core/tools/implementations/viewRepoMap.ts
+++ b/core/tools/implementations/viewRepoMap.ts
@@ -3,7 +3,10 @@ import generateRepoMap from "../../util/generateRepoMap";
 import { ToolImpl } from ".";
 
 export const viewRepoMapImpl: ToolImpl = async (args, extras) => {
-  const repoMap = await generateRepoMap(extras.llm, extras.ide, {});
+  const repoMap = await generateRepoMap(extras.llm, extras.ide, {
+    outputRelativeUriPaths: true,
+    includeSignatures: false,
+  });
   return [
     {
       name: "Repo map",
diff --git a/core/tools/implementations/viewSubdirectory.ts b/core/tools/implementations/viewSubdirectory.ts
index 9bd4583f2c..c62887dc5b 100644
--- a/core/tools/implementations/viewSubdirectory.ts
+++ b/core/tools/implementations/viewSubdirectory.ts
@@ -5,18 +5,18 @@ import { ToolImpl } from ".";
 
 export const viewSubdirectoryImpl: ToolImpl = async (args: any, extras) => {
   const { directory_path } = args;
-  const absoluteUri = await resolveRelativePathInDir(
-    directory_path,
-    extras.ide,
-  );
+  const uri = await resolveRelativePathInDir(directory_path, extras.ide);
 
-  if (!absoluteUri) {
+  if (!uri) {
     throw new Error(`Directory path "${directory_path}" does not exist.`);
   }
 
   const repoMap = await generateRepoMap(extras.llm, extras.ide, {
-    dirs: [absoluteUri],
+    dirUris: [uri],
+    outputRelativeUriPaths: true,
+    includeSignatures: false,
   });
+
   return [
     {
       name: "Repo map",
diff --git a/core/util/generateRepoMap.test.ts b/core/util/generateRepoMap.test.ts
index d1d130d316..536d3fcac2 100644
--- a/core/util/generateRepoMap.test.ts
+++ b/core/util/generateRepoMap.test.ts
@@ -39,7 +39,7 @@ describe.skip("generateRepoMap", () => {
       .mockImplementation(async (dirs, offset, limit) => {
         // Return test data
         return {
-          groupedByPath: {
+          groupedByUri: {
             [path.join(TEST_DIR, "file1.js")]: [
               "function foo() {}",
               "function bar() {}",
@@ -60,6 +60,7 @@ describe.skip("generateRepoMap", () => {
     // Act
     const repoMapContent = await generateRepoMap(testLLM, testIde, {
       includeSignatures: true,
+      outputRelativeUriPaths: true,
     });
 
     // Assert
@@ -103,7 +104,7 @@ describe.skip("generateRepoMap", () => {
       .mockImplementation(async (dirs, offset, limit) => {
         // Return test data
         return {
-          groupedByPath: {
+          groupedByUri: {
             [path.join(TEST_DIR, "file1.js")]: [],
             [path.join(TEST_DIR, "subdir/file2.py")]: [],
           },
@@ -118,6 +119,7 @@ describe.skip("generateRepoMap", () => {
     // Act
     const repoMapContent = await generateRepoMap(testLLM, testIde, {
       includeSignatures: false,
+      outputRelativeUriPaths: true,
     });
 
     // Assert
@@ -155,7 +157,7 @@ describe.skip("generateRepoMap", () => {
       .mockImplementation(async (dirs, offset, limit) => {
         // Return test data
         return {
-          groupedByPath: {
+          groupedByUri: {
             [path.join(TEST_DIR, "file1.js")]: ["function foo() {}"],
             [path.join(TEST_DIR, "subdir/file2.py")]: ["def foo():"],
           },
@@ -185,6 +187,7 @@ describe.skip("generateRepoMap", () => {
     // Act
     const repoMapContent = await generateRepoMap(testLLM, testIde, {
       includeSignatures: true,
+      outputRelativeUriPaths: true,
     });
 
     // Assert
diff --git a/core/util/generateRepoMap.ts b/core/util/generateRepoMap.ts
index 47a796b48b..b0af8079ad 100644
--- a/core/util/generateRepoMap.ts
+++ b/core/util/generateRepoMap.ts
@@ -1,16 +1,17 @@
 import fs from "node:fs";
-import path from "node:path";
 
 import { IDE, ILLM } from "..";
 import { CodeSnippetsCodebaseIndex } from "../indexing/CodeSnippetsIndex";
-import { walkDirAsync } from "../indexing/walkDir";
+import { walkDirs } from "../indexing/walkDir";
 import { pruneLinesFromTop } from "../llm/countTokens";
 
 import { getRepoMapFilePath } from "./paths";
+import { findUriInDirs } from "./uri";
 
 export interface RepoMapOptions {
   includeSignatures?: boolean;
-  dirs?: string[];
+  dirUris?: string[];
+  outputRelativeUriPaths: boolean;
 }
 
 class RepoMapGenerator {
@@ -19,8 +20,8 @@ class RepoMapGenerator {
   private repoMapPath: string = getRepoMapFilePath();
   private writeStream: fs.WriteStream = fs.createWriteStream(this.repoMapPath);
   private contentTokens: number = 0;
-  private repoMapDirs: string[] = [];
-  private allPathsInDirs: Set = new Set();
+  private dirs: string[] = [];
+  private allUris: string[] = [];
   private pathsInDirsWithSnippets: Set = new Set();
 
   private BATCH_SIZE = 100;
@@ -40,123 +41,100 @@ class RepoMapGenerator {
       llm.contextLength * this.REPO_MAX_CONTEXT_LENGTH_RATIO;
   }
 
-  /*
-    Note, the repo map is usually USED as relative file paths (passed to LLM)
-    So for now, using URIs INPUTS to generate the map, but OUTPUTing relative paths
-    The exception if requestFilesFromRepoMap, which now converts the relative paths back to URIs
-  */
-  async generate(): Promise {
-    this.repoMapDirs = this.options.dirs ?? (await this.ide.getWorkspaceDirs());
-    this.allPathsInDirs = await this.getAllPathsInDirs();
-
-    await this.initializeWriteStream();
-    await this.processPathsAndSignatures();
-
-    this.writeStream.end();
-    this.logRepoMapGeneration();
-
-    return fs.readFileSync(this.repoMapPath, "utf8");
+  private getUriForWrite(uri: string) {
+    if (this.options.outputRelativeUriPaths) {
+      return findUriInDirs(uri, this.dirs).relativePathOrBasename;
+    }
+    return uri;
   }
 
-  private async initializeWriteStream(): Promise {
-    await this.writeToStream(this.PREAMBLE);
-    this.contentTokens += this.llm.countTokens(this.PREAMBLE);
-  }
+  async generate(): Promise {
+    this.dirs = this.options.dirUris ?? (await this.ide.getWorkspaceDirs());
+    this.allUris = await walkDirs(this.ide, undefined, this.dirs);
 
-  private async getAllPathsInDirs(): Promise> {
-    const paths = new Set();
+    // Initialize
+    await this.writeToStream(this.PREAMBLE);
 
-    for (const dir of this.repoMapDirs) {
-      for await (const filepath of walkDirAsync(dir, this.ide)) {
-        paths.add(filepath.replace(dir, "").slice(1));
+    if (this.options.includeSignatures) {
+      // Process uris and signatures
+      let offset = 0;
+      while (true) {
+        const { groupedByUri, hasMore } =
+          await CodeSnippetsCodebaseIndex.getPathsAndSignatures(
+            this.allUris,
+            offset,
+            this.BATCH_SIZE,
+          );
+        // process batch
+        for (const [uri, signatures] of Object.entries(groupedByUri)) {
+          let fileContent: string;
+
+          try {
+            fileContent = await this.ide.readFile(uri);
+          } catch (err) {
+            console.error(
+              "Failed to read file:\n" +
+                `  Uri: ${uri}\n` +
+                `  Error: ${err instanceof Error ? err.message : String(err)}`,
+            );
+
+            continue;
+          }
+
+          const filteredSignatures = signatures.filter(
+            (signature) => signature.trim() !== fileContent.trim(),
+          );
+
+          if (filteredSignatures.length > 0) {
+            this.pathsInDirsWithSnippets.add(uri);
+          }
+
+          let content = `${this.getUriForWrite(uri)}:\n`;
+
+          for (const signature of signatures.slice(0, -1)) {
+            content += `${this.indentMultilineString(signature)}\n\t...\n`;
+          }
+
+          content += `${this.indentMultilineString(
+            signatures[signatures.length - 1],
+          )}\n\n`;
+
+          if (content) {
+            await this.writeToStream(content);
+          }
+        }
+        if (!hasMore || this.contentTokens >= this.maxRepoMapTokens) {
+          break;
+        }
+        offset += this.BATCH_SIZE;
       }
-    }
 
-    return paths;
-  }
+      // Remaining Uris just so that written repo map isn't incomplete
+      const urisWithoutSnippets = this.allUris.filter(
+        (uri) => !this.pathsInDirsWithSnippets.has(uri),
+      );
 
-  private async processPathsAndSignatures(): Promise {
-    let offset = 0;
-    while (true) {
-      const { groupedByPath, hasMore } =
-        await CodeSnippetsCodebaseIndex.getPathsAndSignatures(
-          this.repoMapDirs,
-          offset,
-          this.BATCH_SIZE,
+      if (urisWithoutSnippets.length > 0) {
+        await this.writeToStream(
+          urisWithoutSnippets.map((uri) => this.getUriForWrite(uri)).join("\n"),
         );
-      await this.processBatch(groupedByPath);
-      if (!hasMore || this.contentTokens >= this.maxRepoMapTokens) {
-        break;
-      }
-      offset += this.BATCH_SIZE;
-    }
-    await this.writeRemainingPaths();
-  }
-
-  private async processBatch(
-    groupedByPath: Record,
-  ): Promise {
-    for (const [absolutePath, signatures] of Object.entries(groupedByPath)) {
-      const content = await this.processFile(absolutePath, signatures);
-
-      if (content) {
-        await this.writeToStream(content);
       }
-    }
-  }
-
-  private async processFile(
-    absolutePath: string,
-    signatures: string[],
-  ): Promise {
-    const workspaceDir =
-      this.repoMapDirs.find((dir) => absolutePath.startsWith(dir)) || "";
-    const relativePath = path.relative(workspaceDir, absolutePath);
-
-    let fileContent: string;
-
-    try {
-      fileContent = await fs.promises.readFile(absolutePath, "utf8");
-    } catch (err) {
-      console.error(
-        "Failed to read file:\n" +
-          `  Path: ${absolutePath}\n` +
-          `  Error: ${err instanceof Error ? err.message : String(err)}`,
+    } else {
+      // Only process uris
+      await this.writeToStream(
+        this.allUris.map((uri) => this.getUriForWrite(uri)).join("\n"),
       );
-
-      return;
-    }
-
-    const filteredSignatures = signatures.filter(
-      (signature) => signature.trim() !== fileContent.trim(),
-    );
-
-    if (filteredSignatures.length > 0) {
-      this.pathsInDirsWithSnippets.add(relativePath);
-    }
-
-    return this.generateContentForPath(relativePath, filteredSignatures);
-  }
-
-  private generateContentForPath(
-    relativePath: string,
-    signatures: string[],
-  ): string {
-    if (this.options.includeSignatures === false) {
-      return `${relativePath}\n`;
     }
 
-    let content = `${relativePath}:\n`;
+    this.writeStream.end();
 
-    for (const signature of signatures.slice(0, -1)) {
-      content += `${this.indentMultilineString(signature)}\n\t...\n`;
+    if (this.contentTokens >= this.maxRepoMapTokens) {
+      console.debug(
+        "Full repo map was unable to be generated due to context window limitations",
+      );
     }
 
-    content += `${this.indentMultilineString(
-      signatures[signatures.length - 1],
-    )}\n\n`;
-
-    return content;
+    return fs.readFileSync(this.repoMapPath, "utf8");
   }
 
   private async writeToStream(content: string): Promise {
@@ -175,26 +153,6 @@ class RepoMapGenerator {
     await new Promise((resolve) => this.writeStream.write(content, resolve));
   }
 
-  private async writeRemainingPaths(): Promise {
-    const pathsWithoutSnippets = new Set(
-      [...this.allPathsInDirs].filter(
-        (x) => !this.pathsInDirsWithSnippets.has(x),
-      ),
-    );
-
-    if (pathsWithoutSnippets.size > 0) {
-      await this.writeToStream(Array.from(pathsWithoutSnippets).join("\n"));
-    }
-  }
-
-  private logRepoMapGeneration(): void {
-    if (this.contentTokens >= this.maxRepoMapTokens) {
-      console.debug(
-        "Full repo map was unable to be generated due to context window limitations",
-      );
-    }
-  }
-
   private indentMultilineString(str: string) {
     return str
       .split("\n")
diff --git a/extensions/vscode/src/util/FileSearch.ts b/extensions/vscode/src/util/FileSearch.ts
index fe977e4e53..c6707fa511 100644
--- a/extensions/vscode/src/util/FileSearch.ts
+++ b/extensions/vscode/src/util/FileSearch.ts
@@ -1,5 +1,5 @@
 import { IDE } from "core";
-import { walkDirInWorkspaces } from "core/indexing/walkDir";
+import { walkDirs } from "core/indexing/walkDir";
 // @ts-ignore
 import MiniSearch from "minisearch";
 import * as vscode from "vscode";
@@ -24,7 +24,7 @@ export class FileSearch {
     },
   });
   private async initializeFileSearchState() {
-    const results = await walkDirInWorkspaces(this.ide);
+    const results = await walkDirs(this.ide);
     this.miniSearch.addAll(
       results.flat().map((uri) => ({
         id: uri,

From a3b5dd9e0c74e40e121a346a322cd8381c9f58ce Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 15:41:12 -0800
Subject: [PATCH 32/84] fix config yaml conflicts

---
 core/config/yaml/loadYaml.ts                  | 27 +++-------------
 .../providers/CurrentFileContextProvider.ts   |  4 +--
 core/indexing/CodeSnippetsIndex.ts            |  3 ++
 core/indexing/CodebaseIndexer.ts              | 31 ++++++++-----------
 core/llm/llms/llm.ts                          |  4 +--
 .../components/mainInput/ContextItemsPeek.tsx |  4 +--
 .../StepContainerPreToolbar/FileInfo.tsx      |  4 +--
 7 files changed, 29 insertions(+), 48 deletions(-)

diff --git a/core/config/yaml/loadYaml.ts b/core/config/yaml/loadYaml.ts
index cc219e9521..ad33ea4d85 100644
--- a/core/config/yaml/loadYaml.ts
+++ b/core/config/yaml/loadYaml.ts
@@ -32,14 +32,11 @@ import {
   readAllGlobalPromptFiles,
 } from "../../util/paths";
 import { getSystemPromptDotFile } from "../getSystemPromptDotFile";
-import {
-  DEFAULT_PROMPTS_FOLDER,
-  getPromptFiles,
-  slashCommandFromPromptFile,
-} from "../promptFile.js";
 import { ConfigValidationError } from "../validation.js";
 
 import { llmsFromModelConfig } from "./models";
+import { getAllPromptFiles } from "../../promptFiles/v2/getPromptFiles";
+import { slashCommandFromPromptFileV1 } from "../../promptFiles/v1/slashCommandFromPromptFile";
 
 export interface ConfigResult {
   config: T | undefined;
@@ -102,25 +99,11 @@ async function slashCommandsFromV1PromptFiles(
   ide: IDE,
 ): Promise {
   const slashCommands: SlashCommand[] = [];
-  const workspaceDirs = await ide.getWorkspaceDirs();
-
-  // v1 prompt files
-  let promptFiles: { path: string; content: string }[] = [];
-  promptFiles = (
-    await Promise.all(
-      workspaceDirs.map((dir) =>
-        getPromptFiles(ide, path.join(dir, DEFAULT_PROMPTS_FOLDER)),
-      ),
-    )
-  )
-    .flat()
-    .filter(({ path }) => path.endsWith(".prompt"));
-
-  // Also read from ~/.continue/.prompts
-  promptFiles.push(...readAllGlobalPromptFiles());
+
+  const promptFiles = await getAllPromptFiles(ide, undefined, true);
 
   for (const file of promptFiles) {
-    const slashCommand = slashCommandFromPromptFile(file.path, file.content);
+    const slashCommand = slashCommandFromPromptFileV1(file.path, file.content);
     if (slashCommand) {
       slashCommands.push(slashCommand);
     }
diff --git a/core/context/providers/CurrentFileContextProvider.ts b/core/context/providers/CurrentFileContextProvider.ts
index 67593b064c..d3cef2dc11 100644
--- a/core/context/providers/CurrentFileContextProvider.ts
+++ b/core/context/providers/CurrentFileContextProvider.ts
@@ -4,7 +4,7 @@ import {
   ContextProviderDescription,
   ContextProviderExtras,
 } from "../../";
-import { getBasename } from "../../util/uri";
+import { getUriPathBasename } from "../../util/uri";
 
 class CurrentFileContextProvider extends BaseContextProvider {
   static description: ContextProviderDescription = {
@@ -23,7 +23,7 @@ class CurrentFileContextProvider extends BaseContextProvider {
     if (!currentFile) {
       return [];
     }
-    const baseName = getBasename(currentFile.path);
+    const baseName = getUriPathBasename(currentFile.path);
     return [
       {
         description: currentFile.path,
diff --git a/core/indexing/CodeSnippetsIndex.ts b/core/indexing/CodeSnippetsIndex.ts
index fa210ed00a..bd1f08cf90 100644
--- a/core/indexing/CodeSnippetsIndex.ts
+++ b/core/indexing/CodeSnippetsIndex.ts
@@ -190,6 +190,9 @@ export class CodeSnippetsCodebaseIndex implements CodebaseIndex {
     const ast = parser.parse(contents);
 
     const language = getFullLanguageName(filepath);
+    if (!language) {
+      return [];
+    }
     const query = await getQueryForFile(
       filepath,
       `code-snippet-queries/${language}.scm`,
diff --git a/core/indexing/CodebaseIndexer.ts b/core/indexing/CodebaseIndexer.ts
index cd074f7ad2..4e8355c9be 100644
--- a/core/indexing/CodebaseIndexer.ts
+++ b/core/indexing/CodebaseIndexer.ts
@@ -17,7 +17,7 @@ import {
   RefreshIndexResults,
 } from "./types.js";
 import { walkDirAsync } from "./walkDir.js";
-import { getUriPathBasename } from "../util/uri.js";
+import { findUriInDirs, getUriPathBasename } from "../util/uri.js";
 
 export class PauseToken {
   constructor(private _paused: boolean) {}
@@ -94,23 +94,26 @@ export class CodebaseIndexer {
     return indexes;
   }
 
-  public async refreshFile(file: string): Promise {
+  public async refreshFile(
+    file: string,
+    workspaceDirs: string[],
+  ): Promise {
     if (this.pauseToken.paused) {
       // NOTE: by returning here, there is a chance that while paused a file is modified and
       // then after unpausing the file is not reindexed
       return;
     }
-    const workspaceDir = await this.getWorkspaceDir(file);
-    if (!workspaceDir) {
+    const { foundInDir } = findUriInDirs(file, workspaceDirs);
+    if (!foundInDir) {
       return;
     }
-    const branch = await this.ide.getBranch(workspaceDir);
-    const repoName = await this.ide.getRepoName(workspaceDir);
+    const branch = await this.ide.getBranch(foundInDir);
+    const repoName = await this.ide.getRepoName(foundInDir);
     const indexesToBuild = await this.getIndexesToBuild();
     const stats = await this.ide.getLastModified([file]);
     for (const index of indexesToBuild) {
       const tag = {
-        directory: workspaceDir,
+        directory: foundInDir,
         branch,
         artifactId: index.artifactId,
       };
@@ -151,6 +154,8 @@ export class CodebaseIndexer {
       };
     }
 
+    const workspaceDirs = await this.ide.getWorkspaceDirs();
+
     const progressPer = 1 / files.length;
     try {
       for (const file of files) {
@@ -159,7 +164,7 @@ export class CodebaseIndexer {
           desc: `Indexing file ${file}...`,
           status: "indexing",
         };
-        await this.refreshFile(file);
+        await this.refreshFile(file, workspaceDirs);
 
         progress += progressPer;
 
@@ -443,14 +448,4 @@ export class CodebaseIndexer {
       completedIndexCount += 1;
     }
   }
-
-  private async getWorkspaceDir(filepath: string): Promise {
-    const workspaceDirs = await this.ide.getWorkspaceDirs();
-    for (const workspaceDir of workspaceDirs) {
-      if (filepath.startsWith(workspaceDir)) {
-        return workspaceDir;
-      }
-    }
-    return undefined;
-  }
 }
diff --git a/core/llm/llms/llm.ts b/core/llm/llms/llm.ts
index 4cd4332322..e472b73170 100644
--- a/core/llm/llms/llm.ts
+++ b/core/llm/llms/llm.ts
@@ -1,5 +1,5 @@
 import { Chunk } from "../../index.js";
-import { getBasename } from "../../util/index.js";
+import { getUriPathBasename } from "../../util/uri.js";
 import { BaseLLM } from "../index.js";
 
 const RERANK_PROMPT = (
@@ -47,7 +47,7 @@ export class LLMReranker extends BaseLLM {
 
   async scoreChunk(chunk: Chunk, query: string): Promise {
     const completion = await this.complete(
-      RERANK_PROMPT(query, getBasename(chunk.filepath), chunk.content),
+      RERANK_PROMPT(query, getUriPathBasename(chunk.filepath), chunk.content),
       new AbortController().signal,
       {
         maxTokens: 1,
diff --git a/gui/src/components/mainInput/ContextItemsPeek.tsx b/gui/src/components/mainInput/ContextItemsPeek.tsx
index 226d87a6db..77a9f222d5 100644
--- a/gui/src/components/mainInput/ContextItemsPeek.tsx
+++ b/gui/src/components/mainInput/ContextItemsPeek.tsx
@@ -5,7 +5,6 @@ import {
 } from "@heroicons/react/24/outline";
 import { ContextItemWithId } from "core";
 import { ctxItemToRifWithContents } from "core/commands/util";
-import { getBasename } from "core/util";
 import { useContext, useMemo, useState } from "react";
 import { AnimatedEllipsis, lightGray, vscBackground } from "..";
 import { IdeMessengerContext } from "../../context/IdeMessenger";
@@ -14,6 +13,7 @@ import { selectIsGatheringContext } from "../../redux/slices/sessionSlice";
 import FileIcon from "../FileIcon";
 import SafeImg from "../SafeImg";
 import { getIconFromDropdownItem } from "./MentionList";
+import { getUriPathBasename } from "core/util/uri";
 
 interface ContextItemsPeekProps {
   contextItems?: ContextItemWithId[];
@@ -119,7 +119,7 @@ function ContextItemsPeekItem({ contextItem }: ContextItemsPeekItemProps) {
             className={`min-w-0 flex-1 overflow-hidden truncate whitespace-nowrap text-xs text-gray-400 ${isUrl ? "hover:underline" : ""}`}
           >
             {contextItem.uri?.type === "file"
-              ? getBasename(contextItem.description)
+              ? getUriPathBasename(contextItem.description)
               : contextItem.description}
           
         
diff --git a/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx b/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx
index 5534b8e5ca..05d1eed575 100644
--- a/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx
+++ b/gui/src/components/markdown/StepContainerPreToolbar/FileInfo.tsx
@@ -1,7 +1,7 @@
 import FileIcon from "../../FileIcon";
 import { useContext } from "react";
 import { IdeMessengerContext } from "../../../context/IdeMessenger";
-import { getBasename } from "core/util/uri";
+import { getUriPathBasename } from "core/util/uri";
 
 export interface FileInfoProps {
   filepath: string;
@@ -25,7 +25,7 @@ const FileInfo = ({ filepath, range }: FileInfoProps) => {
       >
         
         
-          {getBasename(filepath)}
+          {getUriPathBasename(filepath)}
           {range && ` ${range}`}
         
       

From aa72fcd64df97e3bc085843f4eb42bc8956c5813 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 15:55:22 -0800
Subject: [PATCH 33/84] path -> uri build error fixes

---
 core/core.ts                                  |  2 +-
 core/test/testDir.ts                          |  2 +-
 core/util/filesystem.ts                       |  2 --
 core/util/pathToUri.ts                        | 21 +++++++++++++++++++
 core/util/uri.ts                              | 17 ---------------
 .../lang-server/codeLens/providers/index.ts   |  1 -
 6 files changed, 23 insertions(+), 22 deletions(-)
 create mode 100644 core/util/pathToUri.ts

diff --git a/core/core.ts b/core/core.ts
index 09cded385b..a9a45634b7 100644
--- a/core/core.ts
+++ b/core/core.ts
@@ -45,7 +45,7 @@ import type { FromCoreProtocol, ToCoreProtocol } from "./protocol";
 import { SYSTEM_PROMPT_DOT_FILE } from "./config/getSystemPromptDotFile";
 import type { IMessenger, Message } from "./protocol/messenger";
 import URI from "uri-js";
-import { localPathToUri } from "./util/uri";
+import { localPathToUri } from "./util/pathToUri";
 
 export class Core {
   // implements IMessenger
diff --git a/core/test/testDir.ts b/core/test/testDir.ts
index b79624e1df..098075c344 100644
--- a/core/test/testDir.ts
+++ b/core/test/testDir.ts
@@ -1,7 +1,7 @@
 import fs from "fs";
 import os from "os";
 import path from "path";
-import { localPathOrUriToPath, localPathToUri } from "../util/uri";
+import { localPathToUri, localPathOrUriToPath } from "../util/pathToUri";
 
 // Want this outside of the git repository so we can change branches in tests
 const TEST_DIR_PATH = path.join(os.tmpdir(), "testWorkspaceDir");
diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts
index 886e58e104..1dc5e556c3 100644
--- a/core/util/filesystem.ts
+++ b/core/util/filesystem.ts
@@ -15,8 +15,6 @@ import {
   ToastType,
 } from "../index.d.js";
 import { GetGhTokenArgs } from "../protocol/ide.js";
-
-import { getContinueGlobalPath } from "./paths.js";
 import { fileURLToPath } from "node:url";
 
 class FileSystemIde implements IDE {
diff --git a/core/util/pathToUri.ts b/core/util/pathToUri.ts
new file mode 100644
index 0000000000..a8d1b3a7f6
--- /dev/null
+++ b/core/util/pathToUri.ts
@@ -0,0 +1,21 @@
+import { fileURLToPath, pathToFileURL } from "url";
+
+import URI from "uri-js";
+
+// CAN ONLY BE USED IN CORE
+
+// Converts a local path to a file:// URI
+export function localPathToUri(path: string) {
+  const url = pathToFileURL(path);
+  return URI.normalize(url.toString());
+}
+
+export function localPathOrUriToPath(localPathOrUri: string): string {
+  try {
+    return fileURLToPath(localPathOrUri);
+  } catch (e) {
+    console.log("Received local filepath", localPathOrUri);
+
+    return localPathOrUri;
+  }
+}
diff --git a/core/util/uri.ts b/core/util/uri.ts
index f110a870a0..25d42efba1 100644
--- a/core/util/uri.ts
+++ b/core/util/uri.ts
@@ -1,21 +1,4 @@
 import URI from "uri-js";
-import { fileURLToPath, pathToFileURL } from "url";
-
-// Converts a local path to a file:// URI
-export function localPathToUri(path: string) {
-  const url = pathToFileURL(path);
-  return URI.normalize(url.toString());
-}
-
-export function localPathOrUriToPath(localPathOrUri: string): string {
-  try {
-    return fileURLToPath(localPathOrUri);
-  } catch (e) {
-    console.log("Received local filepath", localPathOrUri);
-
-    return localPathOrUri;
-  }
-}
 
 /** Converts any OS path to cleaned up URI path segment format with no leading/trailing slashes
    e.g. \path\to\folder\ -> path/to/folder
diff --git a/extensions/vscode/src/lang-server/codeLens/providers/index.ts b/extensions/vscode/src/lang-server/codeLens/providers/index.ts
index 8285b1fe5e..00a309e62a 100644
--- a/extensions/vscode/src/lang-server/codeLens/providers/index.ts
+++ b/extensions/vscode/src/lang-server/codeLens/providers/index.ts
@@ -1,4 +1,3 @@
-export { DiffViewerCodeLensProvider } from "./DiffViewerCodeLensProvider";
 export { QuickActionsCodeLensProvider } from "./QuickActionsCodeLensProvider";
 export { SuggestionsCodeLensProvider } from "./SuggestionsCodeLensProvider";
 export { VerticalDiffCodeLensProvider as VerticalPerLineCodeLensProvider } from "./VerticalPerLineCodeLensProvider";

From c3aa7c6d636435c35f5567e7c96c09f02c9b2844 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 16:26:09 -0800
Subject: [PATCH 34/84] build fixes

---
 core/autocomplete/templating/AutocompleteTemplate.ts | 6 +++---
 core/index.d.ts                                      | 5 +++--
 core/indexing/walkDir.ts                             | 6 +++---
 core/util/filesystem.ts                              | 2 +-
 4 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts
index 25b4620fab..5d2e5fee73 100644
--- a/core/autocomplete/templating/AutocompleteTemplate.ts
+++ b/core/autocomplete/templating/AutocompleteTemplate.ts
@@ -3,7 +3,7 @@
 import { CompletionOptions } from "../../index.js";
 import {
   getLastNUriRelativePathParts,
-  shortestRelativeUriPaths,
+  getShortestUniqueRelativeUriPaths,
 } from "../../util/uri.js";
 import {
   AutocompleteCodeSnippet,
@@ -95,7 +95,7 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = {
       return [prefix, suffix];
     }
 
-    const relativePaths = shortestRelativeUriPaths(
+    const relativePaths = getShortestUniqueRelativeUriPaths(
       [
         ...snippets.map((snippet) =>
           "filepath" in snippet ? snippet.filepath : "Untitled.txt",
@@ -214,7 +214,7 @@ const codegeexFimTemplate: AutocompleteTemplate = {
       (snippet) => snippet.type === AutocompleteSnippetType.Code,
     ) as AutocompleteCodeSnippet[];
 
-    const relativePaths = shortestRelativeUriPaths(
+    const relativePaths = getShortestUniqueRelativeUriPaths(
       [...snippets.map((snippet) => snippet.filepath), filepath],
       workspaceUris,
     );
diff --git a/core/index.d.ts b/core/index.d.ts
index 3612f1354f..5f15f939a6 100644
--- a/core/index.d.ts
+++ b/core/index.d.ts
@@ -1,5 +1,6 @@
 import Parser from "web-tree-sitter";
 import { GetGhTokenArgs } from "./protocol/ide";
+
 declare global {
   interface Window {
     ide?: "vscode";
@@ -535,13 +536,13 @@ export interface DiffLine {
   line: string;
 }
 
-export class Problem {
+export interface Problem {
   filepath: string;
   range: Range;
   message: string;
 }
 
-export class Thread {
+export interface Thread {
   name: string;
   id: number;
 }
diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts
index a1ec2eba3d..979f4d6f47 100644
--- a/core/indexing/walkDir.ts
+++ b/core/indexing/walkDir.ts
@@ -1,6 +1,6 @@
 import ignore, { Ignore } from "ignore";
 
-import { FileType, IDE } from "../index.d.js";
+import { FileType, IDE } from "..";
 
 import {
   DEFAULT_IGNORE_DIRS,
@@ -9,8 +9,8 @@ import {
   defaultIgnoreFile,
   getGlobalContinueIgArray,
   gitIgArrayFromFile,
-} from "./ignore.js";
-import { joinPathsToUri } from "../util/uri.js";
+} from "./ignore";
+import { joinPathsToUri } from "../util/uri";
 
 export interface WalkerOptions {
   ignoreFiles?: string[];
diff --git a/core/util/filesystem.ts b/core/util/filesystem.ts
index 1dc5e556c3..d0821b6ef7 100644
--- a/core/util/filesystem.ts
+++ b/core/util/filesystem.ts
@@ -13,7 +13,7 @@ import {
   RangeInFile,
   Thread,
   ToastType,
-} from "../index.d.js";
+} from "../index.js";
 import { GetGhTokenArgs } from "../protocol/ide.js";
 import { fileURLToPath } from "node:url";
 

From 4d2af1654330e8efeb26f4003a2a744437a65b4b Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 16:58:18 -0800
Subject: [PATCH 35/84] export more types

---
 core/index.d.ts                             | 32 ++++++++++-----------
 extensions/vscode/package-lock.json         |  4 +--
 extensions/vscode/src/util/expandSnippet.ts | 14 ++++-----
 3 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/core/index.d.ts b/core/index.d.ts
index 5f15f939a6..ec6af891f6 100644
--- a/core/index.d.ts
+++ b/core/index.d.ts
@@ -403,7 +403,7 @@ export interface PromptLog {
   completion: string;
 }
 
-type MessageModes = "chat" | "edit";
+export type MessageModes = "chat" | "edit";
 
 export type ToolStatus =
   | "generating"
@@ -492,7 +492,7 @@ export interface LLMOptions {
   deploymentId?: string;
 }
 
-type RequireAtLeastOne = Pick<
+export type RequireAtLeastOne = Pick<
   T,
   Exclude
 > &
@@ -700,7 +700,7 @@ export interface SlashCommand {
 
 // Config
 
-type StepName =
+export type StepName =
   | "AnswerQuestionChroma"
   | "GenerateShellCommandStep"
   | "EditHighlightedCodeStep"
@@ -712,7 +712,7 @@ type StepName =
   | "GenerateShellCommandStep"
   | "DraftIssueStep";
 
-type ContextProviderName =
+export type ContextProviderName =
   | "diff"
   | "github"
   | "terminal"
@@ -743,7 +743,7 @@ type ContextProviderName =
   | "url"
   | string;
 
-type TemplateType =
+export type TemplateType =
   | "llama2"
   | "alpaca"
   | "zephyr"
@@ -806,7 +806,7 @@ export interface CustomCommand {
   description: string;
 }
 
-interface Prediction {
+export interface Prediction {
   type: "content";
   content:
     | string
@@ -837,7 +837,7 @@ export interface Tool {
   uri?: string;
 }
 
-interface BaseCompletionOptions {
+export interface BaseCompletionOptions {
   temperature?: number;
   topP?: number;
   topK?: number;
@@ -926,23 +926,23 @@ export interface TabAutocompleteOptions {
   showWhateverWeHaveAtXMs?: number;
 }
 
-interface StdioOptions {
+export interface StdioOptions {
   type: "stdio";
   command: string;
   args: string[];
 }
 
-interface WebSocketOptions {
+export interface WebSocketOptions {
   type: "websocket";
   url: string;
 }
 
-interface SSEOptions {
+export interface SSEOptions {
   type: "sse";
   url: string;
 }
 
-type TransportOptions = StdioOptions | WebSocketOptions | SSEOptions;
+export type TransportOptions = StdioOptions | WebSocketOptions | SSEOptions;
 
 export interface MCPOptions {
   transport: TransportOptions;
@@ -957,7 +957,7 @@ export interface ContinueUIConfig {
   codeWrap?: boolean;
 }
 
-interface ContextMenuConfig {
+export interface ContextMenuConfig {
   comment?: string;
   docstring?: string;
   fix?: string;
@@ -965,7 +965,7 @@ interface ContextMenuConfig {
   fixGrammar?: string;
 }
 
-interface ModelRoles {
+export interface ModelRoles {
   inlineEdit?: string;
   applyCodeBlock?: string;
   repoMapFileSelection?: string;
@@ -1006,7 +1006,7 @@ export type CodeToEdit = RangeInFileWithContents | FileWithContents;
  * Represents the configuration for a quick action in the Code Lens.
  * Quick actions are custom commands that can be added to function and class declarations.
  */
-interface QuickActionConfig {
+export interface QuickActionConfig {
   /**
    * The title of the quick action that will display in the Code Lens.
    */
@@ -1031,7 +1031,7 @@ export type DefaultContextProvider = ContextProviderWithParams & {
   query?: string;
 };
 
-interface ExperimentalConfig {
+export interface ExperimentalConfig {
   contextMenuPrompts?: ContextMenuConfig;
   modelRoles?: ModelRoles;
   defaultContext?: DefaultContextProvider[];
@@ -1058,7 +1058,7 @@ interface ExperimentalConfig {
   modelContextProtocolServers?: MCPOptions[];
 }
 
-interface AnalyticsConfig {
+export interface AnalyticsConfig {
   type: string;
   url?: string;
   clientKey?: string;
diff --git a/extensions/vscode/package-lock.json b/extensions/vscode/package-lock.json
index 3e213c9998..3b0d6b3713 100644
--- a/extensions/vscode/package-lock.json
+++ b/extensions/vscode/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.9.244",
+  "version": "0.9.245",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.9.244",
+      "version": "0.9.245",
       "license": "Apache-2.0",
       "dependencies": {
         "@continuedev/fetch": "^1.0.3",
diff --git a/extensions/vscode/src/util/expandSnippet.ts b/extensions/vscode/src/util/expandSnippet.ts
index 3b9c45bb10..2c41235980 100644
--- a/extensions/vscode/src/util/expandSnippet.ts
+++ b/extensions/vscode/src/util/expandSnippet.ts
@@ -3,23 +3,23 @@ import { languageForFilepath } from "core/autocomplete/constants/AutocompleteLan
 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(
-  filepath: string,
+  fileUri: string,
   startLine: number,
   endLine: number,
   ide: IDE,
 ): Promise {
-  const parser = await getParserForFile(filepath);
+  const parser = await getParserForFile(fileUri);
   if (!parser) {
     return [];
   }
 
-  const fullFileContents = await ide.readFile(filepath);
+  const fullFileContents = await ide.readFile(fileUri);
   const root: SyntaxNode = parser.parse(fullFileContents).rootNode;
 
   // Find all nodes contained in the range
@@ -53,10 +53,10 @@ export async function expandSnippet(
     await Promise.all(
       callExpressions.map(async (node) => {
         return getDefinitionsForNode(
-          filepath,
+          vscode.Uri.parse(fileUri),
           node,
           ide,
-          languageForFilepath(filepath),
+          languageForFilepath(fileUri),
         );
       }),
     )
@@ -79,7 +79,7 @@ export async function expandSnippet(
   // Filter out definitions already in selected range
   callExpressionDefinitions = callExpressionDefinitions.filter((def) => {
     return !(
-      def.filepath === filepath &&
+      def.filepath === fileUri &&
       def.range.start.line >= startLine &&
       def.range.end.line <= endLine
     );

From 6f12051dec3aa0e5958716bbdd0ba15feb3d3be6 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 17:15:13 -0800
Subject: [PATCH 36/84] fix build error

---
 core/commands/slash/onboard.ts           | 2 +-
 core/context/retrieval/repoMapRequest.ts | 5 -----
 core/index.d.ts                          | 2 +-
 core/indexing/walkDir.ts                 | 2 +-
 4 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/core/commands/slash/onboard.ts b/core/commands/slash/onboard.ts
index 3252a5bbb6..7895101f89 100644
--- a/core/commands/slash/onboard.ts
+++ b/core/commands/slash/onboard.ts
@@ -1,6 +1,6 @@
 import ignore from "ignore";
 
-import { FileType, IDE, SlashCommand } from "../..";
+import type { FileType, IDE, SlashCommand } from "../..";
 import {
   defaultIgnoreDir,
   defaultIgnoreFile,
diff --git a/core/context/retrieval/repoMapRequest.ts b/core/context/retrieval/repoMapRequest.ts
index f70f0a5941..39223321bd 100644
--- a/core/context/retrieval/repoMapRequest.ts
+++ b/core/context/retrieval/repoMapRequest.ts
@@ -73,11 +73,6 @@ This is the question that you should select relevant files for: "${input}"`;
       return [];
     }
 
-    // IMPORANT
-
-    // const subDirPrefix = filterDirUri
-    //   ? getUriPathBasename(filterDirUri) + "/"
-    //   : "";
     const fileUris = content
       .split("")[1]
       ?.split("")[0]
diff --git a/core/index.d.ts b/core/index.d.ts
index ec6af891f6..f9e6aa0e7c 100644
--- a/core/index.d.ts
+++ b/core/index.d.ts
@@ -492,7 +492,7 @@ export interface LLMOptions {
   deploymentId?: string;
 }
 
-export type RequireAtLeastOne = Pick<
+type RequireAtLeastOne = Pick<
   T,
   Exclude
 > &
diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts
index 979f4d6f47..1d8aa485a6 100644
--- a/core/indexing/walkDir.ts
+++ b/core/indexing/walkDir.ts
@@ -1,6 +1,6 @@
 import ignore, { Ignore } from "ignore";
 
-import { FileType, IDE } from "..";
+import type { FileType, IDE } from "..";
 
 import {
   DEFAULT_IGNORE_DIRS,

From b324f808becc8a39f7026ea6701c96a8fb4c4a12 Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 17:20:49 -0800
Subject: [PATCH 37/84] index.d.ts fixes

---
 core/commands/slash/onboard.ts | 9 ++++++---
 core/indexing/walkDir.ts       | 5 ++++-
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/core/commands/slash/onboard.ts b/core/commands/slash/onboard.ts
index 7895101f89..d6fedcafb1 100644
--- a/core/commands/slash/onboard.ts
+++ b/core/commands/slash/onboard.ts
@@ -78,7 +78,9 @@ async function getEntriesFilteredByIgnore(dir: string, ide: IDE) {
 
   const withRelativePaths = entries
     .filter(
-      (entry) => entry[1] === FileType.File || entry[1] === FileType.Directory,
+      (entry) =>
+        entry[1] === (1 as FileType.File) ||
+        entry[1] === (2 as FileType.Directory),
     )
     .map((entry) => {
       const { relativePathOrBasename } = findUriInDirs(entry[0], workspaceDirs);
@@ -87,7 +89,8 @@ async function getEntriesFilteredByIgnore(dir: string, ide: IDE) {
         type: entry[1],
         basename: getUriPathBasename(entry[0]),
         relativePath:
-          relativePathOrBasename + (entry[1] === FileType.Directory ? "/" : ""),
+          relativePathOrBasename +
+          (entry[1] === (2 as FileType.Directory) ? "/" : ""),
       };
     });
 
@@ -107,7 +110,7 @@ async function gatherProjectContext(
     const entries = await getEntriesFilteredByIgnore(dir, ide);
 
     for (const entry of entries) {
-      if (entry.type === FileType.Directory) {
+      if (entry.type === (2 as FileType.Directory)) {
         context += `\nFolder: ${entry.relativePath}\n`;
         await exploreDirectory(entry.uri, currentDepth + 1);
       } else {
diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts
index 1d8aa485a6..d50e3b6358 100644
--- a/core/indexing/walkDir.ts
+++ b/core/indexing/walkDir.ts
@@ -189,7 +189,10 @@ class DFSWalker {
   }
 
   private entryIsIgnoreFile(e: Entry): boolean {
-    if (e[1] === FileType.Directory || e[1] === FileType.SymbolicLink) {
+    if (
+      e[1] === (2 as FileType.Directory) ||
+      e[1] === (64 as FileType.SymbolicLink)
+    ) {
       return false;
     }
     return this.ignoreFileNames.has(e[0]);

From 4481cacac594d664645ae40f12b5a61e5b40890b Mon Sep 17 00:00:00 2001
From: Dallin Romney 
Date: Fri, 13 Dec 2024 17:45:10 -0800
Subject: [PATCH 38/84] path->ide fixes

---
 core/core.ts                                          |  2 +-
 core/indexing/walkDir.ts                              |  1 +
 core/util/pathToUri.ts                                |  2 +-
 core/util/uri.ts                                      |  6 +++---
 extensions/vscode/src/VsCodeIde.ts                    |  2 +-
 .../vscode/src/autocomplete/completionProvider.ts     |  2 +-
 extensions/vscode/src/autocomplete/lsp.ts             |  2 +-
 extensions/vscode/src/diff/vertical/handler.ts        |  2 +-
 extensions/vscode/src/diff/vertical/manager.ts        |  2 +-
 extensions/vscode/src/suggestions.ts                  |  2 +-
 extensions/vscode/src/util/ideUtils.ts                |  2 +-
 extensions/vscode/src/util/vscode.ts                  |  2 +-
 gui/src/components/History/HistoryTableRow.tsx        | 11 +++++------
 13 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/core/core.ts b/core/core.ts
index a9a45634b7..6849e7902f 100644
--- a/core/core.ts
+++ b/core/core.ts
@@ -44,7 +44,7 @@ import type { FromCoreProtocol, ToCoreProtocol } from "./protocol";
 
 import { SYSTEM_PROMPT_DOT_FILE } from "./config/getSystemPromptDotFile";
 import type { IMessenger, Message } from "./protocol/messenger";
-import URI from "uri-js";
+import * as URI from "uri-js";
 import { localPathToUri } from "./util/pathToUri";
 
 export class Core {
diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts
index d50e3b6358..04f446ca04 100644
--- a/core/indexing/walkDir.ts
+++ b/core/indexing/walkDir.ts
@@ -170,6 +170,7 @@ class DFSWalker {
     for (const ig of ignoreContexts) {
       // remove the directory name and path seperator from the match path, unless this an ignore file
       // in the root directory
+      console.log("IGNORE", ig, relPath);
       const prefixLength = ig.dirname.length === 0 ? 0 : ig.dirname.length + 1;
       // The ignore library expects a path relative to the ignore file location
       const matchPath = relPath.substring(prefixLength);
diff --git a/core/util/pathToUri.ts b/core/util/pathToUri.ts
index a8d1b3a7f6..e70af5d271 100644
--- a/core/util/pathToUri.ts
+++ b/core/util/pathToUri.ts
@@ -1,6 +1,6 @@
 import { fileURLToPath, pathToFileURL } from "url";
 
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 // CAN ONLY BE USED IN CORE
 
diff --git a/core/util/uri.ts b/core/util/uri.ts
index 25d42efba1..d2ab8476dc 100644
--- a/core/util/uri.ts
+++ b/core/util/uri.ts
@@ -1,4 +1,4 @@
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 /** Converts any OS path to cleaned up URI path segment format with no leading/trailing slashes
    e.g. \path\to\folder\ -> path/to/folder
@@ -45,7 +45,7 @@ export function findUriInDirs(
       .split("/")
       .map((part) => encodeURIComponent(part));
 
-    if (uriPathParts.length > dirPathParts.length - 1) {
+    if (uriPathParts.length < dirPathParts.length) {
       continue;
     }
     let allDirPartsMatch = true;
@@ -65,7 +65,7 @@ export function findUriInDirs(
     }
   }
   // Not found
-  console.warn("Directory not found for uri", uri, dirUriCandidates);
+  console.trace("Directory not found for uri", uri, dirUriCandidates);
   return {
     uri,
     relativePathOrBasename: getUriPathBasename(uri),
diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts
index 22814e3a3b..2dc769ffde 100644
--- a/extensions/vscode/src/VsCodeIde.ts
+++ b/extensions/vscode/src/VsCodeIde.ts
@@ -13,7 +13,7 @@ import { Repository } from "./otherExtensions/git";
 import { VsCodeIdeUtils } from "./util/ideUtils";
 import { getExtensionUri, openEditorAndRevealRange } from "./util/vscode";
 import { VsCodeWebviewProtocol } from "./webviewProtocol";
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 import type {
   ContinueRcJson,
diff --git a/extensions/vscode/src/autocomplete/completionProvider.ts b/extensions/vscode/src/autocomplete/completionProvider.ts
index a3a8ff8c05..f5388e397d 100644
--- a/extensions/vscode/src/autocomplete/completionProvider.ts
+++ b/extensions/vscode/src/autocomplete/completionProvider.ts
@@ -19,7 +19,7 @@ import {
   setupStatusBar,
   stopStatusBarLoading,
 } from "./statusBar";
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 import type { IDE } from "core";
 import type { TabAutocompleteModel } from "../util/loadAutocompleteModel";
diff --git a/extensions/vscode/src/autocomplete/lsp.ts b/extensions/vscode/src/autocomplete/lsp.ts
index 18ec44ce17..6d3b27543c 100644
--- a/extensions/vscode/src/autocomplete/lsp.ts
+++ b/extensions/vscode/src/autocomplete/lsp.ts
@@ -14,7 +14,7 @@ import {
   AutocompleteCodeSnippet,
   AutocompleteSnippetType,
 } from "core/autocomplete/snippets/types";
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 type GotoProviderName =
   | "vscode.executeDefinitionProvider"
diff --git a/extensions/vscode/src/diff/vertical/handler.ts b/extensions/vscode/src/diff/vertical/handler.ts
index 4290589021..024aea2d21 100644
--- a/extensions/vscode/src/diff/vertical/handler.ts
+++ b/extensions/vscode/src/diff/vertical/handler.ts
@@ -10,7 +10,7 @@ import {
 
 import type { VerticalDiffCodeLens } from "./manager";
 import type { ApplyState, DiffLine } from "core";
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 export interface VerticalDiffHandlerOptions {
   input?: string;
diff --git a/extensions/vscode/src/diff/vertical/manager.ts b/extensions/vscode/src/diff/vertical/manager.ts
index ee6b0d8f46..ee47fd1732 100644
--- a/extensions/vscode/src/diff/vertical/manager.ts
+++ b/extensions/vscode/src/diff/vertical/manager.ts
@@ -9,7 +9,7 @@ import EditDecorationManager from "../../quickEdit/EditDecorationManager";
 import { VsCodeWebviewProtocol } from "../../webviewProtocol";
 
 import { VerticalDiffHandler, VerticalDiffHandlerOptions } from "./handler";
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 export interface VerticalDiffCodeLens {
   start: number;
diff --git a/extensions/vscode/src/suggestions.ts b/extensions/vscode/src/suggestions.ts
index 5a6a1fdd9c..06c47552f0 100644
--- a/extensions/vscode/src/suggestions.ts
+++ b/extensions/vscode/src/suggestions.ts
@@ -1,7 +1,7 @@
 import * as vscode from "vscode";
 
 import { openEditorAndRevealRange, translate } from "./util/vscode";
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 export interface SuggestionRanges {
   oldRange: vscode.Range;
diff --git a/extensions/vscode/src/util/ideUtils.ts b/extensions/vscode/src/util/ideUtils.ts
index 6d79b9a356..71fdda56b7 100644
--- a/extensions/vscode/src/util/ideUtils.ts
+++ b/extensions/vscode/src/util/ideUtils.ts
@@ -1,7 +1,7 @@
 import { EXTENSION_NAME } from "core/control-plane/env";
 import _ from "lodash";
 import * as vscode from "vscode";
-import URI from "uri-js";
+import * as URI from "uri-js";
 import { threadStopped } from "../debug/debug";
 import { VsCodeExtension } from "../extension/VsCodeExtension";
 import { GitExtension, Repository } from "../otherExtensions/git";
diff --git a/extensions/vscode/src/util/vscode.ts b/extensions/vscode/src/util/vscode.ts
index 29aedb6358..d8cb9cffd2 100644
--- a/extensions/vscode/src/util/vscode.ts
+++ b/extensions/vscode/src/util/vscode.ts
@@ -1,6 +1,6 @@
 import { machineIdSync } from "node-machine-id";
 import * as vscode from "vscode";
-import URI from "uri-js";
+import * as URI from "uri-js";
 
 export function translate(range: vscode.Range, lines: number): vscode.Range {
   return new vscode.Range(
diff --git a/gui/src/components/History/HistoryTableRow.tsx b/gui/src/components/History/HistoryTableRow.tsx
index 7f80819c7d..11a5a36df3 100644
--- a/gui/src/components/History/HistoryTableRow.tsx
+++ b/gui/src/components/History/HistoryTableRow.tsx
@@ -12,7 +12,10 @@ import {
   updateSession,
 } from "../../redux/thunks/session";
 import HeaderButtonWithToolTip from "../gui/HeaderButtonWithToolTip";
-import { getLastNUriRelativePathParts } from "core/util/uri";
+import {
+  getLastNUriRelativePathParts,
+  getUriPathBasename,
+} from "core/util/uri";
 
 export function HistoryTableRow({
   sessionMetadata,
@@ -104,11 +107,7 @@ export function HistoryTableRow({
 
             
- {getLastNUriRelativePathParts( - window.workspacePaths ?? [], - sessionMetadata.workspaceDirectory || "", - 1, - )} + {getUriPathBasename(sessionMetadata.workspaceDirectory || "")} {/* Uncomment to show the date */} {/* From f4e7398d702dd33ed2f344c221383650d21cf1c5 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Fri, 13 Dec 2024 18:51:28 -0800 Subject: [PATCH 39/84] minor fixes --- core/indexing/walkDir.ts | 3 +-- gui/src/components/markdown/StyledMarkdownPreview.tsx | 10 ++++------ manual-testing-sandbox/AdvancedPage.tsx | 5 ++++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts index 04f446ca04..d0a90aad37 100644 --- a/core/indexing/walkDir.ts +++ b/core/indexing/walkDir.ts @@ -116,7 +116,7 @@ class DFSWalker { ): Promise { const entries = await this.ide.listDir(walkableEntry.uri); return entries.map((e) => ({ - relativeUriPath: `${walkableEntry.relativeUriPath}/${e[0]}`, + relativeUriPath: `${walkableEntry.relativeUriPath}${walkableEntry.relativeUriPath ? "/" : ""}${e[0]}`, uri: joinPathsToUri(walkableEntry.uri, e[0]), type: e[1], entry: e, @@ -170,7 +170,6 @@ class DFSWalker { for (const ig of ignoreContexts) { // remove the directory name and path seperator from the match path, unless this an ignore file // in the root directory - console.log("IGNORE", ig, relPath); const prefixLength = ig.dirname.length === 0 ? 0 : ig.dirname.length + 1; // The ignore library expects a path relative to the ignore file location const matchPath = relPath.substring(prefixLength); diff --git a/gui/src/components/markdown/StyledMarkdownPreview.tsx b/gui/src/components/markdown/StyledMarkdownPreview.tsx index 9c06164a13..3a72f16450 100644 --- a/gui/src/components/markdown/StyledMarkdownPreview.tsx +++ b/gui/src/components/markdown/StyledMarkdownPreview.tsx @@ -201,10 +201,8 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( if (node.meta) { let meta = node.meta.split(" "); - node.data.hProperties.fileUri = inferResolvedUriFromRelativePath( - meta[0], - ideMessenger.ide, - ); + node.data.hProperties["data-fileuri"] = + inferResolvedUriFromRelativePath(meta[0], ideMessenger.ide); node.data.hProperties.range = meta[1]; // E.g } }); @@ -244,8 +242,8 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( }, pre: ({ node, ...preProps }) => { const preChildProps = preProps?.children?.[0]?.props; - const { className, fileUri, range } = preProps?.children?.[0]?.props; - + const { className, range } = preChildProps ?? {}; + const fileUri = preChildProps["data-fileuri"]; const codeBlockContent = preChildProps["data-codeblockcontent"]; const isGeneratingCodeBlock = preChildProps["data-isgeneratingcodeblock"]; diff --git a/manual-testing-sandbox/AdvancedPage.tsx b/manual-testing-sandbox/AdvancedPage.tsx index 6d17ef4634..5b5ccbde6f 100644 --- a/manual-testing-sandbox/AdvancedPage.tsx +++ b/manual-testing-sandbox/AdvancedPage.tsx @@ -13,7 +13,10 @@ const AdvancedPage = () => { className={`${isDarkMode ? "bg-gray-800 text-white" : "bg-white text-black"} min-h-screen p-4`} >
-

Welcome to the Advanced Page

+ I apologize, but there seems to be a misunderstanding. The user's + request doesn't appear to be related to rewriting the code section you + provided. The request mentions editing a readme.md file, which is not + part of the React component code you've shown.
diff --git a/gui/src/components/markdown/StepContainerPreToolbar/StepContainerPreToolbar.tsx b/gui/src/components/markdown/StepContainerPreToolbar/StepContainerPreToolbar.tsx index 45c5a335c2..7339bd83a5 100644 --- a/gui/src/components/markdown/StepContainerPreToolbar/StepContainerPreToolbar.tsx +++ b/gui/src/components/markdown/StepContainerPreToolbar/StepContainerPreToolbar.tsx @@ -19,6 +19,7 @@ import { selectApplyStateByStreamId, selectIsInEditMode, } from "../../../redux/slices/sessionSlice"; +import { inferResolvedUriFromRelativePath } from "core/util/ideUtils"; const TopDiv = styled.div` outline: 1px solid rgba(153, 153, 152); @@ -44,7 +45,7 @@ const ToolbarDiv = styled.div<{ isExpanded: boolean }>` export interface StepContainerPreToolbarProps { codeBlockContent: string; language: string; - fileUri: string; + relativeFilepath: string; isGeneratingCodeBlock: boolean; codeBlockIndex: number; // To track which codeblock we are applying range?: string; @@ -84,17 +85,23 @@ export default function StepContainerPreToolbar( : props.isGeneratingCodeBlock; const isNextCodeBlock = nextCodeBlockIndex === props.codeBlockIndex; - const hasFileExtension = /\.[0-9a-z]+$/i.test(props.fileUri); + const hasFileExtension = /\.[0-9a-z]+$/i.test(props.relativeFilepath); const defaultModel = useAppSelector(selectDefaultModel); - function onClickApply() { + async function onClickApply() { if (!defaultModel) { return; } + console.log(props.relativeFilepath); + const fileUri = await inferResolvedUriFromRelativePath( + props.relativeFilepath, + ideMessenger.ide, + ); + console.log(fileUri); ideMessenger.post("applyToFile", { streamId: streamIdRef.current, - filepath: props.fileUri, + filepath: fileUri, text: codeBlockContent, curSelectedModelTitle: defaultModel.title, }); @@ -137,16 +144,24 @@ export default function StepContainerPreToolbar( wasGeneratingRef.current = isGeneratingCodeBlock; }, [isGeneratingCodeBlock]); - function onClickAcceptApply() { + async function onClickAcceptApply() { + const fileUri = await inferResolvedUriFromRelativePath( + props.relativeFilepath, + ideMessenger.ide, + ); ideMessenger.post("acceptDiff", { - filepath: props.fileUri, + filepath: fileUri, streamId: streamIdRef.current, }); } - function onClickRejectApply() { + async function onClickRejectApply() { + const fileUri = await inferResolvedUriFromRelativePath( + props.relativeFilepath, + ideMessenger.ide, + ); ideMessenger.post("rejectDiff", { - filepath: props.fileUri, + filepath: fileUri, streamId: streamIdRef.current, }); } @@ -172,7 +187,10 @@ export default function StepContainerPreToolbar( }`} />
- +
diff --git a/gui/src/components/markdown/StyledMarkdownPreview.tsx b/gui/src/components/markdown/StyledMarkdownPreview.tsx index 3a72f16450..ebaf1e4f98 100644 --- a/gui/src/components/markdown/StyledMarkdownPreview.tsx +++ b/gui/src/components/markdown/StyledMarkdownPreview.tsx @@ -201,9 +201,8 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( if (node.meta) { let meta = node.meta.split(" "); - node.data.hProperties["data-fileuri"] = - inferResolvedUriFromRelativePath(meta[0], ideMessenger.ide); - node.data.hProperties.range = meta[1]; // E.g + node.data.hProperties["data-relativefilepath"] = meta[0]; + node.data.hProperties.range = meta[1]; } }); }, @@ -224,7 +223,7 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( return (tree) => { visit(tree, { tagName: "pre" }, (node: any) => { // Add an index (0, 1, 2, etc...) to each code block. - node.properties = { codeBlockIndex }; + node.properties = { "data-codeblockindex": codeBlockIndex }; codeBlockIndex++; }); }; @@ -241,9 +240,11 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( ); }, pre: ({ node, ...preProps }) => { - const preChildProps = preProps?.children?.[0]?.props; - const { className, range } = preChildProps ?? {}; - const fileUri = preChildProps["data-fileuri"]; + const codeBlockIndex = preProps["data-codeblockindex"]; + + const preChildProps = preProps?.children?.[0]?.props ?? {}; + const { className, range } = preChildProps; + const relativeFilePath = preChildProps["data-relativefilepath"]; const codeBlockContent = preChildProps["data-codeblockcontent"]; const isGeneratingCodeBlock = preChildProps["data-isgeneratingcodeblock"]; @@ -258,12 +259,12 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( // that is just action buttons on hover. // We also use this in JB since we haven't yet implemented // the logic forfileUri lazy apply. - if (!fileUri || isJetBrains()) { + if (!relativeFilePath || isJetBrains()) { return ( @@ -274,9 +275,9 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview( return ( diff --git a/gui/src/context/IdeMessenger.ts b/gui/src/context/IdeMessenger.ts index 8b47fbf9bc..8bb2362899 100644 --- a/gui/src/context/IdeMessenger.ts +++ b/gui/src/context/IdeMessenger.ts @@ -65,7 +65,11 @@ export class IdeMessenger implements IIdeMessenger { ); } - private _postToIde(messageType: string, data: any, messageId?: string) { + private _postToIde( + messageType: string, + data: FromWebviewProtocol[T][0], + messageId?: string, + ) { if (typeof vscode === "undefined") { if (isJetBrains()) { if (window.postIntellijMessage === undefined) { From 24834edb659ac0faefd70c93d79e9938d0b96d8b Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Fri, 13 Dec 2024 21:32:41 -0800 Subject: [PATCH 41/84] uri util fixes --- core/commands/util.ts | 2 +- core/util/ideUtils.ts | 3 +-- core/util/uri.ts | 7 ++++--- extensions/vscode/src/VsCodeIde.ts | 1 - gui/src/components/CodeToEditCard/CodeToEditCard.tsx | 8 ++++---- gui/src/components/markdown/CodeSnippetPreview.tsx | 7 ++++++- gui/src/components/markdown/StyledMarkdownPreview.tsx | 4 ---- gui/src/context/IdeMessenger.ts | 6 +----- 8 files changed, 17 insertions(+), 21 deletions(-) diff --git a/core/commands/util.ts b/core/commands/util.ts index ff53061c7e..7921c2b515 100644 --- a/core/commands/util.ts +++ b/core/commands/util.ts @@ -6,7 +6,7 @@ export function rifWithContentsToContextItem( rif: RangeInFileWithContents, ): ContextItemWithId { const basename = getUriPathBasename(rif.filepath); - const { relativePathOrBasename } = findUriInDirs( + const { relativePathOrBasename, foundInDir, uri } = findUriInDirs( rif.filepath, window.workspacePaths ?? [], ); diff --git a/core/util/ideUtils.ts b/core/util/ideUtils.ts index 8172167d8f..570f562e4b 100644 --- a/core/util/ideUtils.ts +++ b/core/util/ideUtils.ts @@ -34,6 +34,7 @@ export async function inferResolvedUriFromRelativePath( dirCandidates?: string[], ): Promise { const dirs = dirCandidates ?? (await ide.getWorkspaceDirs()); + console.log(path, dirs); if (dirs.length === 0) { throw new Error("inferResolvedUriFromRelativePath: no dirs provided"); } @@ -50,7 +51,6 @@ export async function inferResolvedUriFromRelativePath( dir, partialUri: joinPathsToUri(dir, suffix), })); - console.log("here"); const promises = uris.map(async ({ partialUri, dir }) => { const exists = await ide.fileExists(partialUri); return { @@ -59,7 +59,6 @@ export async function inferResolvedUriFromRelativePath( exists, }; }); - console.log("INFERRING", suffix, promises); const existenceChecks = await Promise.all(promises); const existingUris = existenceChecks.filter(({ exists }) => exists); diff --git a/core/util/uri.ts b/core/util/uri.ts index 7cb17a5cd4..1ca0a3923e 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -42,9 +42,11 @@ export function findUriInDirs( // At this point we break the path up and check if each dir path part matches const dirPathParts = (dirComps.path ?? "") + .replace(/^\//, "") .split("/") .map((part) => encodeURIComponent(part)); const uriPathParts = (uriComps.path ?? "") + .replace(/^\//, "") .split("/") .map((part) => encodeURIComponent(part)); @@ -58,11 +60,10 @@ export function findUriInDirs( } } if (allDirPartsMatch) { + const relativePath = uriPathParts.slice(dirPathParts.length).join("/"); return { uri, - relativePathOrBasename: uriPathParts - .slice(dirPathParts.length) - .join("/"), + relativePathOrBasename: relativePath, foundInDir: dir, }; } diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts index a812fe86dd..cd0ddc8419 100644 --- a/extensions/vscode/src/VsCodeIde.ts +++ b/extensions/vscode/src/VsCodeIde.ts @@ -39,7 +39,6 @@ class VsCodeIde implements IDE { } async fileExists(uri: string): Promise { - console.log("VSCODE FILE EXISTS"); try { await vscode.workspace.fs.stat(vscode.Uri.parse(uri)); return true; diff --git a/gui/src/components/CodeToEditCard/CodeToEditCard.tsx b/gui/src/components/CodeToEditCard/CodeToEditCard.tsx index cac18f4e15..7bd469c240 100644 --- a/gui/src/components/CodeToEditCard/CodeToEditCard.tsx +++ b/gui/src/components/CodeToEditCard/CodeToEditCard.tsx @@ -41,10 +41,10 @@ export default function CodeToEditCard() { } } - async function onSelectFilesToAdd(filepaths: string[]) { - const filePromises = filepaths.map(async (filepath) => { - const contents = await ideMessenger.ide.readFile(filepath); - return { contents, filepath }; + async function onSelectFilesToAdd(uris: string[]) { + const filePromises = uris.map(async (uri) => { + const contents = await ideMessenger.ide.readFile(uri); + return { contents, filepath: uri }; }); const fileResults = await Promise.all(filePromises); diff --git a/gui/src/components/markdown/CodeSnippetPreview.tsx b/gui/src/components/markdown/CodeSnippetPreview.tsx index 2a1fe556a3..a463c421ee 100644 --- a/gui/src/components/markdown/CodeSnippetPreview.tsx +++ b/gui/src/components/markdown/CodeSnippetPreview.tsx @@ -122,8 +122,13 @@ function CodeSnippetPreview(props: CodeSnippetPreviewProps) { )}
Date: Mon, 16 Dec 2024 12:11:12 +0100 Subject: [PATCH 42/84] Adds Qwen coder models to Ollama.ts --- core/llm/llms/Ollama.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/llm/llms/Ollama.ts b/core/llm/llms/Ollama.ts index c6be1c98bf..3e9af9d38a 100644 --- a/core/llm/llms/Ollama.ts +++ b/core/llm/llms/Ollama.ts @@ -152,6 +152,12 @@ class Ollama extends BaseLLM { "llama3.2-90b": "llama3.2:90b", "phi-2": "phi:2.7b", "phind-codellama-34b": "phind-codellama:34b-v2", + "qwen2.5-coder-0.5b": "qwen2.5-coder:0.5b", + "qwen2.5-coder-1.5b": "qwen2.5-coder:1.5b", + "qwen2.5-coder-3b": "qwen2.5-coder:3b", + "qwen2.5-coder-7b": "qwen2.5-coder:7b", + "qwen2.5-coder-14b": "qwen2.5-coder:14b", + "qwen2.5-coder-32b": "qwen2.5-coder:32b", "wizardcoder-7b": "wizardcoder:7b-python", "wizardcoder-13b": "wizardcoder:13b-python", "wizardcoder-34b": "wizardcoder:34b-python", From 7c415459f044f58943ae9ae7978035ae056ed25a Mon Sep 17 00:00:00 2001 From: Nate Date: Mon, 16 Dec 2024 12:31:37 -0800 Subject: [PATCH 43/84] autodetect mistral API key type --- core/llm/index.ts | 18 +++++++++++------- core/llm/llms/Mistral.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/core/llm/index.ts b/core/llm/index.ts index 041693363c..c5a0d5c79c 100644 --- a/core/llm/index.ts +++ b/core/llm/index.ts @@ -138,7 +138,7 @@ export abstract class BaseLLM implements ILLM { private _llmOptions: LLMOptions; - private openaiAdapter?: BaseLlmApi; + protected openaiAdapter?: BaseLlmApi; constructor(_options: LLMOptions) { this._llmOptions = _options; @@ -212,12 +212,7 @@ export abstract class BaseLLM implements ILLM { this.projectId = options.projectId; this.profile = options.profile; - this.openaiAdapter = constructLlmApi({ - provider: this.providerName as any, - apiKey: this.apiKey ?? "", - apiBase: this.apiBase, - requestOptions: this.requestOptions, - }); + this.openaiAdapter = this.createOpenAiAdapter(); this.maxEmbeddingBatchSize = options.maxEmbeddingBatchSize ?? DEFAULT_MAX_BATCH_SIZE; @@ -226,6 +221,15 @@ export abstract class BaseLLM implements ILLM { this.embeddingId = `${this.constructor.name}::${this.model}::${this.maxEmbeddingChunkSize}`; } + protected createOpenAiAdapter() { + return constructLlmApi({ + provider: this.providerName as any, + apiKey: this.apiKey ?? "", + apiBase: this.apiBase, + requestOptions: this.requestOptions, + }); + } + listModels(): Promise { return Promise.resolve([]); } diff --git a/core/llm/llms/Mistral.ts b/core/llm/llms/Mistral.ts index b723016fe6..66d00da007 100644 --- a/core/llm/llms/Mistral.ts +++ b/core/llm/llms/Mistral.ts @@ -3,6 +3,8 @@ import { gptEditPrompt } from "../templates/edit.js"; import OpenAI from "./OpenAI.js"; +type MistralApiKeyType = "mistral" | "codestral"; + class Mistral extends OpenAI { static providerName = "mistral"; static defaultOptions: Partial = { @@ -14,6 +16,17 @@ class Mistral extends OpenAI { maxEmbeddingBatchSize: 128, }; + private async autodetectApiKeyType(): Promise { + const mistralResp = await fetch("https://api.mistral.ai/v1/models", { + method: "GET", + headers: this._getHeaders(), + }); + if (mistralResp.status === 401) { + return "codestral"; + } + return "mistral"; + } + constructor(options: LLMOptions) { super(options); if ( @@ -26,6 +39,28 @@ class Mistral extends OpenAI { if (!this.apiBase?.endsWith("/")) { this.apiBase += "/"; } + + // Unless the user explicitly specifies, we will autodetect the API key type and adjust the API base accordingly + if (!options.apiBase) { + this.autodetectApiKeyType() + .then((keyType) => { + switch (keyType) { + case "codestral": + this.apiBase = "https://codestral.mistral.ai/v1/"; + break; + case "mistral": + this.apiBase = "https://api.mistral.ai/v1/"; + break; + } + + this.openaiAdapter = this.createOpenAiAdapter(); + }) + .catch((err: any) => { + console.log( + `Error autodetecting API key type for Mistral: ${err.message}`, + ); + }); + } } private static modelConversion: { [key: string]: string } = { From 7673757f6f0ff28787d203d21111e3034463b930 Mon Sep 17 00:00:00 2001 From: Nate Date: Mon, 16 Dec 2024 14:31:04 -0800 Subject: [PATCH 44/84] remove console log causing failing tests --- core/llm/llms/Mistral.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/llm/llms/Mistral.ts b/core/llm/llms/Mistral.ts index 66d00da007..7bacb54a6c 100644 --- a/core/llm/llms/Mistral.ts +++ b/core/llm/llms/Mistral.ts @@ -55,11 +55,7 @@ class Mistral extends OpenAI { this.openaiAdapter = this.createOpenAiAdapter(); }) - .catch((err: any) => { - console.log( - `Error autodetecting API key type for Mistral: ${err.message}`, - ); - }); + .catch((err: any) => {}); } } From cdd3212bcda9c81f2138c3c265d5d0089c59d9f3 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Mon, 16 Dec 2024 18:03:47 -0800 Subject: [PATCH 45/84] fix get uri extension --- core/util/treeSitter.ts | 2 -- core/util/uri.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/util/treeSitter.ts b/core/util/treeSitter.ts index 2efb03114a..bc7c098273 100644 --- a/core/util/treeSitter.ts +++ b/core/util/treeSitter.ts @@ -228,7 +228,6 @@ export async function getSymbolsForFile( contents: string, ): Promise { const parser = await getParserForFile(filepath); - if (!parser) { return; } @@ -288,7 +287,6 @@ export async function getSymbolsForFile( node.children.forEach(findNamedNodesRecursive); } findNamedNodesRecursive(tree.rootNode); - return symbols; } diff --git a/core/util/uri.ts b/core/util/uri.ts index 1ca0a3923e..f4f62e5c4c 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -105,7 +105,7 @@ export function getUriPathBasename(uri: string): string { */ export function getUriFileExtension(uri: string) { const baseName = getUriPathBasename(uri); - return (baseName.split(".")[-1] ?? "").toLowerCase(); + return (baseName.split(".").slice(-1)[0] ?? "").toLowerCase(); } export function getFileExtensionFromBasename(filename: string) { From 435f5837de1cbfca6f9a306029694234281c0100 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Mon, 16 Dec 2024 18:42:12 -0800 Subject: [PATCH 46/84] tip tap fixes --- core/util/ideUtils.ts | 3 ++- extensions/vscode/src/extension/VsCodeMessenger.ts | 2 +- gui/src/components/mainInput/TipTapEditor.tsx | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/util/ideUtils.ts b/core/util/ideUtils.ts index 570f562e4b..2dbf201473 100644 --- a/core/util/ideUtils.ts +++ b/core/util/ideUtils.ts @@ -44,6 +44,7 @@ export async function inferResolvedUriFromRelativePath( for (let i = segments.length - 1; i >= 0; i--) { suffixes.push(segments.slice(i).join("/")); } + console.log(suffixes); // For each suffix, try to find a unique matching directory for (const suffix of suffixes) { @@ -70,7 +71,7 @@ export async function inferResolvedUriFromRelativePath( } // If no unique match found, use the first directory - return joinPathsToUri(dirs[0], segments.join("/")); + return joinPathsToUri(dirs[0], path); } interface ResolveResult { diff --git a/extensions/vscode/src/extension/VsCodeMessenger.ts b/extensions/vscode/src/extension/VsCodeMessenger.ts index 7fa90e2c80..87c49583d6 100644 --- a/extensions/vscode/src/extension/VsCodeMessenger.ts +++ b/extensions/vscode/src/extension/VsCodeMessenger.ts @@ -134,7 +134,7 @@ export class VsCodeMessenger { status: "streaming", fileContent: data.text, }); - + console.log("applyToFile", data); if (data.filepath) { const fileExists = await this.ide.fileExists(data.filepath); if (!fileExists) { diff --git a/gui/src/components/mainInput/TipTapEditor.tsx b/gui/src/components/mainInput/TipTapEditor.tsx index f77cbb671c..3ffa249d02 100644 --- a/gui/src/components/mainInput/TipTapEditor.tsx +++ b/gui/src/components/mainInput/TipTapEditor.tsx @@ -662,7 +662,9 @@ function TipTapEditor(props: TipTapEditorProps) { if (!props.isMainInput || !mainInputContentTrigger) { return; } - editor.commands.setContent(mainInputContentTrigger); + queueMicrotask(() => { + editor.commands.setContent(mainInputContentTrigger); + }); dispatch(setMainEditorContentTrigger(undefined)); }, [editor, props.isMainInput, mainInputContentTrigger]); From 4d030f532f18e975c849137601f986708bfb9a72 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Mon, 16 Dec 2024 18:43:42 -0800 Subject: [PATCH 47/84] don't add empty tools array --- core/llm/openaiTypeConverters.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/llm/openaiTypeConverters.ts b/core/llm/openaiTypeConverters.ts index eaf4b00777..1a4d590d68 100644 --- a/core/llm/openaiTypeConverters.ts +++ b/core/llm/openaiTypeConverters.ts @@ -67,7 +67,7 @@ export function toChatBody( }, })); - return { + const params: ChatCompletionCreateParams = { messages: messages.map(toChatMessage), model: options.model, max_tokens: options.maxTokens, @@ -77,9 +77,12 @@ export function toChatBody( presence_penalty: options.presencePenalty, stream: options.stream ?? true, stop: options.stop, - tools, prediction: options.prediction, }; + if (tools?.length) { + params.tools = tools; + } + return params; } export function toCompleteBody( From 59918b3207a3ced4e7ddecce81f1d3e16a10d67f Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Mon, 16 Dec 2024 19:22:32 -0800 Subject: [PATCH 48/84] uri util tests --- core/util/uri.test.ts | 210 ++++++++++++++++++++++++++++++++---------- core/util/uri.ts | 50 ++++++---- 2 files changed, 193 insertions(+), 67 deletions(-) diff --git a/core/util/uri.test.ts b/core/util/uri.test.ts index 016fd06685..9e24710542 100644 --- a/core/util/uri.test.ts +++ b/core/util/uri.test.ts @@ -1,67 +1,181 @@ -import { TEST_DIR } from "../test/testDir"; -import { getLastNPathParts, getUriPathBasename } from "./uri"; - -describe("getUriPathBasename", () => { - it("should return the base name of a Unix-style path", () => { - const filepath = "/home/user/documents/file.txt"; - const output = getUriPathBasename(filepath); - expect(output).toBe("file.txt"); - }); +// Generated by continue +import * as URI from "uri-js"; +import { + pathToUriPathSegment, + findUriInDirs, + relativePathOrUriToUri, + getUriPathBasename, + getUriFileExtension, + getFileExtensionFromBasename, + getLastNUriRelativePathParts, + joinPathsToUri, + getShortestUniqueRelativeUriPaths, + getLastNPathParts, +} from "./uri"; - it("should return the base name of a Windows-style path", () => { - const filepath = "C:\\Users\\User\\Documents\\file.txt"; - const output = getUriPathBasename(filepath); - expect(output).toBe("file.txt"); - }); +describe("uri utils", () => { + describe("pathToUriPathSegment", () => { + it("should convert Windows paths to URI segments", () => { + expect(pathToUriPathSegment("\\path\\to\\folder\\")).toBe( + "path/to/folder", + ); + expect(pathToUriPathSegment("\\this\\is\\afile.ts")).toBe( + "this/is/afile.ts", + ); + }); - it("should handle paths with mixed separators", () => { - const filepath = "C:/Users\\User/Documents/file.txt"; - const output = getUriPathBasename(filepath); - expect(output).toBe("file.txt"); - }); + it("should clean Unix paths", () => { + expect(pathToUriPathSegment("/path/to/folder/")).toBe("path/to/folder"); + expect(pathToUriPathSegment("is/already/clean")).toBe("is/already/clean"); + }); - it("should return an empty string for empty input", () => { - const filepath = ""; - const output = getUriPathBasename(filepath); - expect(output).toBe(""); + it("should encode URI special characters", () => { + expect(pathToUriPathSegment("path/with spaces/file")).toBe( + "path/with%20spaces/file", + ); + expect(pathToUriPathSegment("special#chars?in&path")).toBe( + "special%23chars%3Fin%26path", + ); + }); }); -}); -describe("getLastNPathParts", () => { - test("returns the last N parts of a filepath with forward slashes", () => { - const filepath = "home/user/documents/project/file.txt"; - expect(getLastNPathParts(filepath, 2)).toBe("project/file.txt"); + describe("findUriInDirs", () => { + const dirUris = [ + "file:///workspace/project1", + "file:///workspace/project2", + ]; + + it("should find URI in directory", () => { + const result = findUriInDirs( + "file:///workspace/project1/src/file.ts", + dirUris, + ); + expect(result).toEqual({ + uri: "file:///workspace/project1/src/file.ts", + relativePathOrBasename: "src/file.ts", + foundInDir: "file:///workspace/project1", + }); + }); + + it("should return basename when URI not in directories", () => { + const result = findUriInDirs("file:///other/location/file.ts", dirUris); + expect(result).toEqual({ + uri: "file:///other/location/file.ts", + relativePathOrBasename: "file.ts", + foundInDir: null, + }); + }); + + it("should throw error for invalid URIs", () => { + expect(() => findUriInDirs("invalid-uri", dirUris)).toThrow( + "Invalid uri: invalid-uri", + ); + }); }); - test("returns the last N parts of a filepath with backward slashes", () => { - const filepath = "C:\\home\\user\\documents\\project\\file.txt"; - expect(getLastNPathParts(filepath, 3)).toBe("documents/project/file.txt"); + describe("URI path operations", () => { + it("should get URI path basename", () => { + expect(getUriPathBasename("file:///path/to/file.txt")).toBe("file.txt"); + expect(getUriPathBasename("file:///path/to/folder/")).toBe("folder"); + }); + + it("should get URI file extension", () => { + expect(getUriFileExtension("file:///path/to/file.TXT")).toBe("txt"); + expect(getUriFileExtension("file:///path/to/file")).toBe(""); + }); + + it("should get file extension from basename", () => { + expect(getFileExtensionFromBasename("file.txt")).toBe("txt"); + expect(getFileExtensionFromBasename("file")).toBe(""); + }); }); - test("returns the last part if N is 1", () => { - const filepath = "/home/user/documents/project/file.txt"; - expect(getLastNPathParts(filepath, 1)).toBe("file.txt"); + describe("joinPathsToUri", () => { + it("should join paths to base URI", () => { + expect(joinPathsToUri("file:///base", "path", "to", "file.txt")).toBe( + "file:///base/path/to/file.txt", + ); + }); + + it("should handle paths with special characters", () => { + expect( + joinPathsToUri("file:///base", "path with spaces", "file.txt"), + ).toBe("file:///base/path%20with%20spaces/file.txt"); + }); }); - test("returns the entire path if N is greater than the number of parts", () => { - const filepath = "home/user/documents/project/file.txt"; - expect(getLastNPathParts(filepath, 10)).toBe( - "home/user/documents/project/file.txt", - ); + describe("getShortestUniqueRelativeUriPaths", () => { + it("should find shortest unique paths", () => { + const uris = [ + "file:///workspace/project1/src/components/Button.tsx", + "file:///workspace/project1/src/utils/Button.tsx", + ]; + const dirUris = ["file:///workspace/project1"]; + + const result = getShortestUniqueRelativeUriPaths(uris, dirUris); + expect(result).toEqual([ + { + uri: "file:///workspace/project1/src/components/Button.tsx", + uniquePath: "components/Button.tsx", + }, + { + uri: "file:///workspace/project1/src/utils/Button.tsx", + uniquePath: "utils/Button.tsx", + }, + ]); + }); }); - test("returns an empty string if N is 0", () => { - const filepath = "home/user/documents/project/file.txt"; - expect(getLastNPathParts(filepath, 0)).toBe(""); + describe("getLastNPathParts", () => { + it("should get last N parts of path", () => { + expect(getLastNPathParts("path/to/some/file.txt", 2)).toBe( + "some/file.txt", + ); + expect(getLastNPathParts("path/to/some/file.txt", 0)).toBe(""); + }); + + it("should handle Windows paths", () => { + expect(getLastNPathParts("path\\to\\some\\file.txt", 2)).toBe( + "some/file.txt", + ); + }); }); - test("handles paths with mixed forward and backward slashes", () => { - const filepath = "home\\user/documents\\project/file.txt"; - expect(getLastNPathParts(filepath, 3)).toBe("documents/project/file.txt"); + describe("relativePathOrUriToUri", () => { + const consoleSpy = jest.spyOn(console, "trace").mockImplementation(); + + afterEach(() => { + consoleSpy.mockClear(); + }); + + it("should return original URI if scheme exists", () => { + const uri = "file:///path/to/file.txt"; + expect(relativePathOrUriToUri(uri, "file:///base")).toBe(uri); + }); + + it("should convert relative path to URI", () => { + expect(relativePathOrUriToUri("path/to/file.txt", "file:///base")).toBe( + "file:///base/path/to/file.txt", + ); + expect(consoleSpy).toHaveBeenCalledWith("Received path with no scheme"); + }); }); - test("handles edge case with empty filepath", () => { - const filepath = ""; - expect(getLastNPathParts(filepath, 2)).toBe(""); + describe("getLastNUriRelativePathParts", () => { + it("should get last N parts of URI relative path", () => { + const dirUris = ["file:///workspace/project1"]; + const uri = "file:///workspace/project1/src/components/Button.tsx"; + + expect(getLastNUriRelativePathParts(dirUris, uri, 2)).toBe( + "components/Button.tsx", + ); + }); + + it("should handle URI not in directories", () => { + const dirUris = ["file:///workspace/project1"]; + const uri = "file:///other/path/file.txt"; + + expect(getLastNUriRelativePathParts(dirUris, uri, 2)).toBe("file.txt"); + }); }); }); diff --git a/core/util/uri.ts b/core/util/uri.ts index f4f62e5c4c..49a9e8389d 100644 --- a/core/util/uri.ts +++ b/core/util/uri.ts @@ -5,6 +5,7 @@ import * as URI from "uri-js"; \this\is\afile.ts -> this/is/afile.ts is/already/clean -> is/already/clean **/ + export function pathToUriPathSegment(path: string) { let clean = path.replace(/[\\]/g, "/"); // backslashes -> forward slashes clean = clean.replace(/^\//, ""); // remove start slash @@ -15,6 +16,14 @@ export function pathToUriPathSegment(path: string) { .join("/"); } +export function getCleanUriPath(uri: string) { + const path = URI.parse(uri).path; + if (!path) { + return ""; + } + return pathToUriPathSegment(path); +} + export function findUriInDirs( uri: string, dirUriCandidates: string[], @@ -97,7 +106,16 @@ export function relativePathOrUriToUri( Returns just the file or folder name of a URI */ export function getUriPathBasename(uri: string): string { - return URI.parse(uri).path?.split("/")?.pop() || ""; + const cleanPath = getCleanUriPath(uri); + return cleanPath.split("/")?.pop() || ""; +} + +export function getFileExtensionFromBasename(basename: string) { + const parts = basename.split("."); + if (parts.length < 2) { + return ""; + } + return (parts.slice(-1)[0] ?? "").toLowerCase(); } /* @@ -105,11 +123,7 @@ export function getUriPathBasename(uri: string): string { */ export function getUriFileExtension(uri: string) { const baseName = getUriPathBasename(uri); - return (baseName.split(".").slice(-1)[0] ?? "").toLowerCase(); -} - -export function getFileExtensionFromBasename(filename: string) { - return filename.split(".").pop() ?? ""; + return getFileExtensionFromBasename(baseName); } export function getLastNUriRelativePathParts( @@ -139,14 +153,14 @@ export function getShortestUniqueRelativeUriPaths( const segmentCombinationsMap = new Map(); const segmentsInfo = uris.map((uri) => { const { relativePathOrBasename } = findUriInDirs(uri, dirUriCandidates); - const segments = relativePathOrBasename.split("/"); + const cleanPath = pathToUriPathSegment(relativePathOrBasename); + const segments = cleanPath.split("/"); const suffixes: string[] = []; - // Generate all possible suffix combinations + // Generate all possible suffix combinations, starting from the shortest (basename) for (let i = segments.length - 1; i >= 0; i--) { const suffix = segments.slice(i).join("/"); - suffixes.unshift(suffix); - + suffixes.push(suffix); // Now pushing in order from shortest to longest // Count occurrences of each suffix segmentCombinationsMap.set( suffix, @@ -154,20 +168,18 @@ export function getShortestUniqueRelativeUriPaths( ); } - return { uri, segments, suffixes, relativePathOrBasename }; + return { uri, segments, suffixes, cleanPath }; }); - // Find shortest unique path for each URI - return segmentsInfo.map(({ uri, suffixes, relativePathOrBasename }) => { - // Find the first (shortest) unique suffix - const uniquePath = + return segmentsInfo.map(({ uri, suffixes, cleanPath }) => { + // Since suffixes are now ordered from shortest to longest, + // the first unique one we find will be the shortest + const uniqueCleanPath = suffixes.find((suffix) => segmentCombinationsMap.get(suffix) === 1) ?? - relativePathOrBasename; // case where not unique - perhaps basename - - return { uri, uniquePath }; + cleanPath; // fallback to full path if no unique suffix found + return { uri, uniquePath: decodeURIComponent(uniqueCleanPath) }; }); } - // Only used when working with system paths and relative paths // Since doesn't account for URI segements before workspace export function getLastNPathParts(filepath: string, n: number): string { From 7ee2a356a30744da7b38144781daea8ff7a290a2 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Mon, 16 Dec 2024 19:38:10 -0800 Subject: [PATCH 49/84] remove duplicate gather context, fix dispatch -> false --- gui/src/components/mainInput/resolveInput.ts | 2 +- gui/src/redux/thunks/gatherContext.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/gui/src/components/mainInput/resolveInput.ts b/gui/src/components/mainInput/resolveInput.ts index e3af025000..52f06a4a81 100644 --- a/gui/src/components/mainInput/resolveInput.ts +++ b/gui/src/components/mainInput/resolveInput.ts @@ -194,7 +194,7 @@ async function resolveEditorContent({ } if (shouldGatherContext) { - dispatch(setIsGatheringContext(true)); + dispatch(setIsGatheringContext(false)); } return [contextItems, selectedCode, parts]; diff --git a/gui/src/redux/thunks/gatherContext.ts b/gui/src/redux/thunks/gatherContext.ts index 3e92025516..fa2c1ee3d5 100644 --- a/gui/src/redux/thunks/gatherContext.ts +++ b/gui/src/redux/thunks/gatherContext.ts @@ -44,13 +44,6 @@ export const gatherContext = createAsyncThunk< } // Resolve context providers and construct new history - const shouldGatherContext = - modifiers.useCodebase || hasSlashCommandOrContextProvider(editorState); - - if (shouldGatherContext) { - dispatch(setIsGatheringContext(true)); - } - let [selectedContextItems, selectedCode, content] = await resolveEditorContent({ editorState, From ab2f6761ee9fe593073c182aef5265898b434335 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Mon, 16 Dec 2024 20:12:29 -0800 Subject: [PATCH 50/84] fix codeblock error --- core/commands/util.ts | 3 +-- gui/src/components/mainInput/TipTapEditor.tsx | 18 ++++++++++++++++++ gui/src/components/mainInput/resolveInput.ts | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/core/commands/util.ts b/core/commands/util.ts index 7921c2b515..2d0c8257e3 100644 --- a/core/commands/util.ts +++ b/core/commands/util.ts @@ -12,10 +12,9 @@ export function rifWithContentsToContextItem( ); const rangeStr = `(${rif.range.start.line + 1}-${rif.range.end.line + 1})`; - const itemName = `${basename} ${rangeStr}`; return { content: rif.contents, - name: itemName, + name: `${basename} ${rangeStr}`, description: `${relativePathOrBasename} ${rangeStr}`, // This is passed to the LLM - do not pass full URI id: { providerTitle: "code", diff --git a/gui/src/components/mainInput/TipTapEditor.tsx b/gui/src/components/mainInput/TipTapEditor.tsx index 3ffa249d02..b4e6616d78 100644 --- a/gui/src/components/mainInput/TipTapEditor.tsx +++ b/gui/src/components/mainInput/TipTapEditor.tsx @@ -746,9 +746,27 @@ function TipTapEditor(props: TipTapEditorProps) { if (!props.isMainInput || !editor) { return; } + + // const rif: RangeInFile & { contents: string } = + // data.rangeInFileWithContents; + // const basename = getBasename(rif.filepath); + // const relativePath = getRelativePath( + // rif.filepath, + // await ideMessenger.ide.getWorkspaceDirs(), + // const rangeStr = `(${rif.range.start.line + 1}-${ + // rif.range.end.line + 1 + // })`; + + // const itemName = `${basename} ${rangeStr}`; + // const item: ContextItemWithId = { + // content: rif.contents, + // name: itemName + // } + const contextItem = rifWithContentsToContextItem( data.rangeInFileWithContents, ); + console.log(contextItem); let index = 0; for (const el of editor.getJSON()?.content ?? []) { if (el.attrs?.item?.name === contextItem.name) { diff --git a/gui/src/components/mainInput/resolveInput.ts b/gui/src/components/mainInput/resolveInput.ts index 8f8cbb6f90..190dfbc80d 100644 --- a/gui/src/components/mainInput/resolveInput.ts +++ b/gui/src/components/mainInput/resolveInput.ts @@ -71,7 +71,7 @@ async function resolveEditorContent({ } } else if (p.type === "codeBlock") { if (p.attrs?.item) { - const contextItem = p.atts.item as ContextItemWithId; + const contextItem = p.attrs.item as ContextItemWithId; const rif = ctxItemToRifWithContents(contextItem); // If not editing, include codeblocks in the prompt // If editing is handled by selectedCode below From e1ab08009bd529de4d937e5adc894a903b6396b2 Mon Sep 17 00:00:00 2001 From: Mark Espinoza Date: Mon, 16 Dec 2024 22:59:52 -0600 Subject: [PATCH 51/84] Added new models and Linked Ask Sage Documentation to the preIndexedDocs. --- core/indexing/docs/preIndexedDocs.ts | 5 + core/llm/llms/Asksage.ts | 37 +++-- .../customize/model-providers/more/asksage.md | 39 +++-- gui/src/pages/AddNewModel/configs/models.ts | 135 +++++++++++++++++- .../pages/AddNewModel/configs/providers.ts | 14 +- 5 files changed, 198 insertions(+), 32 deletions(-) diff --git a/core/indexing/docs/preIndexedDocs.ts b/core/indexing/docs/preIndexedDocs.ts index 93eaa84111..1e2a8dbc0d 100644 --- a/core/indexing/docs/preIndexedDocs.ts +++ b/core/indexing/docs/preIndexedDocs.ts @@ -348,6 +348,11 @@ const preIndexedDocs: Record< rootUrl: "https://api.dart.dev", faviconUrl: "https://api.dart.dev/static-assets/favicon.png", }, + "https://docs.asksage.ai/": { + title: "Ask Sage", + startUrl: "https://docs.asksage.ai/", + rootUrl: "https://docs.asksage.ai/", + }, }; export default preIndexedDocs; diff --git a/core/llm/llms/Asksage.ts b/core/llm/llms/Asksage.ts index 316540c487..544b947c25 100644 --- a/core/llm/llms/Asksage.ts +++ b/core/llm/llms/Asksage.ts @@ -9,17 +9,27 @@ class Asksage extends BaseLLM { }; private static modelConversion: { [key: string]: string } = { - "gpt-4o": "gpt-4o", - "gpt-4o-mini": "gpt-4o-mini", - "gpt4-gov": "gpt4-gov", - "gpt-4o-gov": "gpt-4o-gov", - "gpt-3.5-turbo": "gpt35-16k", - "mistral-large-latest": "mistral-large", - "llama3-70b": "llma3", - "gemini-1.5-pro-latest": "google-gemini-pro", - "claude-3-5-sonnet-20240620": "claude-35-sonnet", - "claude-3-opus-20240229": "claude-3-opus", - "claude-3-sonnet-20240229": "claude-3-sonnet", + "gpt-4o-gov": "gpt-4o-gov", // Works + "gpt-4o-mini-gov": "gpt-4o-mini-gov", + "gpt4-gov": "gpt4-gov", // Works + "gpt-gov": "gpt-gov", // Works + "gpt-4o": "gpt-4o", // Works + "gpt-4o-mini": "gpt-4o-mini", // Works + "gpt4": "gpt4", // Works + "gpt4-32k": "gpt4-32k", + "gpt-o1": "gpt-o1", // Works + "gpt-o1-mini": "gpt-o1-mini", // Works + "gpt-3.5-turbo": "gpt35-16k", // Works + "aws-bedrock-claude-35-sonnet-gov": "aws-bedrock-claude-35-sonnet-gov", // Works + "claude-3-5-sonnet-latest": "claude-35-sonnet", // Works + "claude-3-opus-20240229": "claude-3-opus", // Works + "claude-3-sonnet-20240229": "claude-3-sonnet", // Works + "grok-beta": "xai-grok", + "groq-llama33": "groq-llama33", + "groq-70b": "groq-70b", + "mistral-large-latest": "mistral-large", // Works + "llama3-70b": "llma3", // Works + "gemini-1.5-pro-latest": "google-gemini-pro", // Works }; constructor(options: LLMOptions) { @@ -28,7 +38,10 @@ class Asksage extends BaseLLM { } protected _convertModelName(model: string): string { - return Asksage.modelConversion[model] ?? model; + console.log("Converting model:", model); + const convertedModel = Asksage.modelConversion[model] ?? model; + console.log("Converted model:", convertedModel); + return convertedModel; } protected _convertMessage(message: ChatMessage) { diff --git a/docs/docs/customize/model-providers/more/asksage.md b/docs/docs/customize/model-providers/more/asksage.md index c5ba9218cc..e21133cb8f 100644 --- a/docs/docs/customize/model-providers/more/asksage.md +++ b/docs/docs/customize/model-providers/more/asksage.md @@ -41,21 +41,34 @@ More models, functionalities and documentation will be added in the future for A > We recommend to utilize the`OpenAI` or `Anthropic` models for the best performance and results for the `Chat` and `Edit` functionalities. +## Ask Sage Documentation +Ask Sage documentation is not available with Continue.Dev, so if you have any questions or need help with Ask Sage type `@ask` and select the `Ask Sage` option in the chat. Then procced to ask your question about Ask Sage. + ## Current Models From Ask Sage Supported The current Models available provided by Ask Sage are: -| Model | Added | -|--------------------|-------| -| Gov GPT-4.0 | Yes | -| Gov GPT-4o | Yes | -| GPT-4o | Yes | -| GPT-4o-mini | Yes | -| GPT-3.5-16K | Yes | -| Calude 3 Opus | Yes | -| Calude 3 Sonet | Yes | -| Calude 3.5 Sonnet | Yes | -| Gemini Pro | Yes | -| llama 3 | Yes | -| Mistral Large | Yes | +| Index | Model | Added | Status | +|-------|------------------------|-------|--------| +| 1 | GPT-4 Gov | Yes | ✅ | +| 2 | GPT-4o Gov | Yes | ✅ | +| 3 | GPT-4o-mini Gov | Yes | ✅ | +| 4 | GPT-3.5-Turbo Gov | Yes | ✅ | +| 5 | GPT-4o | Yes | ✅ | +| 6 | GPT-4o-mini | Yes | ✅ | +| 7 | GPT-4 | Yes | ✅ | +| 8 | GPT-4-32K | Yes | ✅ | +| 9 | GPT-o1 | Yes | ✅ | +| 10 | GPT-o1-mini | Yes | ✅ | +| 11 | GPT-3.5-turbo | Yes | ✅ | +| 12 | Calude 3.5 Sonnet Gov | Yes | ✅ | +| 13 | Calude 3 Opus | Yes | ✅ | +| 14 | Calude 3 Sonet | Yes | ✅ | +| 15 | Calude 3.5 Sonnet | Yes | ✅ | +| 16 | Grok (xAI) | Yes | ✅ | +| 17 | Groq Llama 3.3 | Yes | ✅ | +| 18 | Groq 70B | Yes | ✅ | +| 19 | Gemini Pro | Yes | ✅ | +| 20 | llama 3 | Yes | ✅ | +| 21 | Mistral Large | Yes | ✅ | diff --git a/gui/src/pages/AddNewModel/configs/models.ts b/gui/src/pages/AddNewModel/configs/models.ts index f3e3203051..a9802ef12f 100644 --- a/gui/src/pages/AddNewModel/configs/models.ts +++ b/gui/src/pages/AddNewModel/configs/models.ts @@ -1053,10 +1053,10 @@ export const models: { [key: string]: ModelPackage } = { providerOptions: ["vertexai"], isOpenSource: false, }, - gpt4gov: { + asksagegpt4gov: { title: "GPT-4 gov", description: - "U.S. Government. Most capable model today - which is similar to GPT-4o but approved for use by the U.S. Government.", + "U.S. Government. Most capable model today - which is similar to GPT-4 but approved for use by the U.S. Government.", params: { model: "gpt4-gov", contextLength: 128_000, @@ -1068,14 +1068,14 @@ export const models: { [key: string]: ModelPackage } = { icon: "openai.png", isOpenSource: false, }, - gpt4ogov: { + asksagegpt4ogov: { title: "GPT-4o gov", description: "U.S. Government. Most capable model today - which is similar to GPT-4o but approved for use by the U.S. Government.", params: { model: "gpt-4o-gov", contextLength: 128_000, - title: "GPT-4o", + title: "GPT-4o-gov", systemMessage: "You are an expert software developer. You give helpful and concise responses.", // Need to set this on the Ask Sage side or just configure it in here to be discussed }, @@ -1083,6 +1083,131 @@ export const models: { [key: string]: ModelPackage } = { icon: "openai.png", isOpenSource: false, }, + asksagegpt35gov: { + title: "GPT-3.5-Turbo gov", + description: + "U.S. Government. Inexpensive and good ROI.", + params: { + model: "gpt-gov", + contextLength: 8096, + title: "GPT-3.5-Turbo gov", + systemMessage: + "You are an expert software developer. You give helpful and concise responses.", // Need to set this on the Ask Sage side or just configure it in here to be discussed + }, + providerOptions: ["askSage"], + icon: "openai.png", + isOpenSource: false, + }, + asksagegpt4ominigov: { + title: "GPT-4o-mini gov", + description: + "U.S. Government. Latest OpenAI GPT 4o-mini model. More inexpensive than GPT4. Capable of ingesting and analyzing images (JPG, PNG, GIF (20MB files max)). 16,384 token response max.", + params: { + model: "gpt-4o-mini-gov", + contextLength: 128_000, + title: "GPT-4o-mini gov", + systemMessage: + "You are an expert software developer. You give helpful and concise responses.", // Need to set this on the Ask Sage side or just configure it in here to be discussed + }, + providerOptions: ["askSage"], + icon: "openai.png", + isOpenSource: false, + }, + asksagegpt4: { + title: "GPT-4", + description: + "GPT4 is about 5X more expensive than Ask Sage tokens and 50X more expensive than GPT3.5", + params: { + model: "gpt4", + contextLength: 8_192, + title: "GPT-4", + }, + providerOptions: ["openai",], + icon: "openai.png", + isOpenSource: false, + }, + asksagegpt432: { + title: "GPT-4-32k", + description: + "The GPT-4-32k model is a variant of the GPT-4 model developed by OpenAI. It is designed to handle a larger context window, capable of processing up to 32,768 tokens, which makes it suitable for scenarios that require extensive information integration and data analysis", + params: { + model: "gpt4-32k", + contextLength: 32_768, + title: "GPT-4-32k", + }, + providerOptions: ["openai",], + icon: "openai.png", + isOpenSource: false, + }, + asksagegpto1: { + title: "GPT-o1", + description: + "Latest OpenAI GPT-o1 model. More inexpensive than GPT4. Capable of ingesting and analyzing images (JPG, PNG, GIF (20MB files max)).", + params: { + model: "gpt-o1", + contextLength: 128_000, + title: "GPT-o1", + systemMessage: + "You are an expert software developer. You give helpful and concise responses.", + }, + providerOptions: ["askSage"], + icon: "openai.png", + isOpenSource: false, + }, + asksagegpto1mini: { + title: "GPT-o1-mini", + description: + "Latest OpenAI GPT-o1-mini model. More inexpensive than GPT-o1. Capable of ingesting and analyzing images (JPG, PNG, GIF (20MB files max)). 16,384 token response max.", + params: { + model: "gpt-o1-mini", + contextLength: 128_000, + title: "GPT-o1-mini", + systemMessage: + "You are an expert software developer. You give helpful and concise responses.", + }, + providerOptions: ["askSage"], + icon: "openai.png", + isOpenSource: false, + }, + asksageclaude35gov: { + title: "Claude 3.5 Sonnet gov", + description: + "Anthropic's most intelligent model, but much less expensive than Claude 3 Opus", + params: { + model: "aws-bedrock-claude-35-sonnet-gov", + contextLength: 200_000, + title: "Claude 3.5 Sonnet gov", + systemMessage: + "You are an expert software developer. You give helpful and concise responses.", + }, + providerOptions: ["askSage"], + icon: "anthropic.png", + isOpenSource: false, + }, + asksagegroqllama33: { + title: "Llama 3.3", + description: + "Llama-3.3 is a large language model customized by Groq.", + params: { + title: "Llama 3.3", + model: "groq-llama33", + contextLength: 128_000, + }, + icon: "groq.png", + isOpenSource: true, + }, + asksagegroq70b: { + title: "Groq-70B", + description: + "A large language model customized by Groq.", + params: { + title: "Groq-70B", + model: "groq-70b", + contextLength: 8_192, + }, + icon: "groq.png", + isOpenSource: true, + }, Qwen2Coder: { title: "Qwen 2.5 Coder 7b", description: @@ -1131,7 +1256,7 @@ export const models: { [key: string]: ModelPackage } = { contextLength: 128_000, }, icon: "xAI.png", - providerOptions: ["xAI"], + providerOptions: ["xAI", "askSage"], isOpenSource: false, }, gemma2_2b: { diff --git a/gui/src/pages/AddNewModel/configs/providers.ts b/gui/src/pages/AddNewModel/configs/providers.ts index b3b80e6e13..f3d3a9ccb0 100644 --- a/gui/src/pages/AddNewModel/configs/providers.ts +++ b/gui/src/pages/AddNewModel/configs/providers.ts @@ -782,14 +782,24 @@ To get started, [register](https://dataplatform.cloud.ibm.com/registration/stepo ...completionParamsInputsConfigs, ], packages: [ - models.gpt4gov, - models.gpt4ogov, + models.asksagegpt4ogov, + models.asksagegpt4ominigov, + models.asksagegpt4gov, + models.asksagegpt35gov, models.gpt4o, models.gpt4omini, + models.asksagegpt4, + models.asksagegpt432, + models.asksagegpto1, + models.asksagegpto1mini, models.gpt35turbo, + models.asksageclaude35gov, models.claude35Sonnet, models.claude3Opus, models.claude3Sonnet, + models.grokBeta, + models.asksagegroqllama33, + models.asksagegroq70b, models.mistralLarge, models.llama370bChat, models.gemini15Pro, From 6657d100535b8a10a7d1bebe285da0ab78a936a8 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Mon, 16 Dec 2024 21:40:29 -0800 Subject: [PATCH 52/84] walkdir tests and path fixes --- core/commands/util.test.ts | 20 +- core/indexing/CodebaseIndexer.test.ts | 23 +- core/indexing/walkDir.test.ts | 636 +++++++++++++------------- core/indexing/walkDir.ts | 2 +- core/test/testDir.ts | 4 +- core/util/pathToUri.ts | 2 +- 6 files changed, 349 insertions(+), 338 deletions(-) diff --git a/core/commands/util.test.ts b/core/commands/util.test.ts index 447ec0e600..7947a7db80 100644 --- a/core/commands/util.test.ts +++ b/core/commands/util.test.ts @@ -14,8 +14,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "/path/to/file", range: { - start: { line: 10, character: 0 }, - end: { line: 20, character: 0 }, + start: { line: 9, character: 0 }, + end: { line: 19, character: 0 }, }, contents: "function content", }; @@ -57,8 +57,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "", range: { - start: { line: 10, character: 0 }, - end: { line: 20, character: 0 }, + start: { line: 9, character: 0 }, + end: { line: 19, character: 0 }, }, contents: "function content", }; @@ -78,8 +78,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "", range: { - start: { line: 10, character: 0 }, - end: { line: 20, character: 0 }, + start: { line: 9, character: 0 }, + end: { line: 19, character: 0 }, }, contents: "function content", }; @@ -115,8 +115,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "/path/to/file", range: { - start: { line: 10, character: 0 }, - end: { line: 20, character: 0 }, + start: { line: 9, character: 0 }, + end: { line: 19, character: 0 }, }, contents: "function content", }; @@ -137,8 +137,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "/path/to/file", range: { - start: { line: 10, character: 0 }, - end: { line: 20, character: 0 }, + start: { line: 9, character: 0 }, + end: { line: 19, character: 0 }, }, contents: "function content", }; diff --git a/core/indexing/CodebaseIndexer.test.ts b/core/indexing/CodebaseIndexer.test.ts index cc01625620..ae935669f7 100644 --- a/core/indexing/CodebaseIndexer.test.ts +++ b/core/indexing/CodebaseIndexer.test.ts @@ -1,7 +1,6 @@ import { execSync } from "node:child_process"; import fs from "node:fs"; -import path from "node:path"; - +import path from "path"; import { jest } from "@jest/globals"; import { ContinueServerClient } from "../continueServer/stubs/client.js"; @@ -11,6 +10,7 @@ import { setUpTestDir, tearDownTestDir, TEST_DIR, + TEST_DIR_PATH, } from "../test/testDir.js"; import { getIndexSqlitePath } from "../util/paths.js"; @@ -19,6 +19,8 @@ import { getComputeDeleteAddRemove } from "./refreshIndex.js"; import { TestCodebaseIndex } from "./TestCodebaseIndex.js"; import { CodebaseIndex } from "./types.js"; import { walkDir } from "./walkDir.js"; +import { pathToFileURL } from "node:url"; +import { joinPathsToUri } from "../util/uri.js"; jest.useFakeTimers(); @@ -155,7 +157,10 @@ describe("CodebaseIndexer", () => { }); test("should have created index folder with all necessary files", async () => { - expect(fs.existsSync(getIndexSqlitePath())).toBe(true); + const exists = await testIde.fileExists( + pathToFileURL(getIndexSqlitePath()).toString(), + ); + expect(exists).toBe(true); }); test("should have indexed all of the files", async () => { @@ -189,7 +194,7 @@ describe("CodebaseIndexer", () => { }); test("should successfully re-index after deleting a file", async () => { - fs.rmSync(path.join(TEST_DIR, "main.rs")); + fs.rmSync(path.join(TEST_DIR_PATH, "main.rs")); await expectPlan(0, 0, 0, 1); @@ -210,23 +215,25 @@ describe("CodebaseIndexer", () => { test("should create git repo for testing", async () => { execSync( - `cd ${TEST_DIR} && git init && git checkout -b main && git add -A && git commit -m "First commit"`, + `cd ${TEST_DIR_PATH} && git init && git checkout -b main && git add -A && git commit -m "First commit"`, ); }); test.skip("should only re-index the changed files when changing branches", async () => { - execSync(`cd ${TEST_DIR} && git checkout -b test2`); + execSync(`cd ${TEST_DIR_PATH} && git checkout -b test2`); // Rewriting the file addToTestDir([["test.ts", "// This is different"]]); // Should re-compute test.ts, but just re-tag the .py file await expectPlan(1, 1, 0, 0); - execSync(`cd ${TEST_DIR} && git add -A && git commit -m "Change .ts file"`); + execSync( + `cd ${TEST_DIR_PATH} && git add -A && git commit -m "Change .ts file"`, + ); }); test.skip("shouldn't re-index anything when changing back to original branch", async () => { - execSync(`cd ${TEST_DIR} && git checkout main`); + execSync(`cd ${TEST_DIR_PATH} && git checkout main`); await expectPlan(0, 0, 0, 0); }); }); diff --git a/core/indexing/walkDir.test.ts b/core/indexing/walkDir.test.ts index 92df792fab..8a9d26e7e3 100644 --- a/core/indexing/walkDir.test.ts +++ b/core/indexing/walkDir.test.ts @@ -1,353 +1,357 @@ -import path from "path"; - -import { walkDir, WalkerOptions } from "../indexing/walkDir"; +// Generated by continue +import { testIde } from "../test/fixtures"; import { - TEST_DIR, setUpTestDir, tearDownTestDir, addToTestDir, + TEST_DIR_PATH, } from "../test/testDir"; -import FileSystemIde from "../util/filesystem"; - -const ide = new FileSystemIde(TEST_DIR); +import { walkDir, walkDirAsync, walkDirs } from "../indexing/walkDir"; +import fs from "fs"; +import path from "path"; -async function walkTestDir( - options?: WalkerOptions, -): Promise { - return walkDir(TEST_DIR, ide, { - returnRelativeUrisPaths: true, - ...options, - }); -} - -async function expectPaths( - toExist: string[], - toNotExist: string[], - options?: WalkerOptions, -) { - const result = await walkTestDir(options); - - for (const p of toExist) { - expect(result).toContain(p); - } - for (const p of toNotExist) { - expect(result).not.toContain(p); - } -} - -describe("walkDir", () => { - beforeEach(() => { +describe("walkDir functions", () => { + beforeEach(async () => { setUpTestDir(); }); - afterEach(() => { + afterEach(async () => { tearDownTestDir(); }); - test("should return nothing for empty dir", async () => { - const result = await walkTestDir(); - expect(result).toEqual([]); - }); - - test("should return all files in flat dir", async () => { - const files = ["a.txt", "b.py", "c.ts"]; - 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"]; - 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"]; - addToTestDir(files); - await expectPaths(["b.py"], [".gitignore", "a.txt", "c.ts"]); - }); - - test("should get all files in nested folder structure", async () => { - const files = [ - "a.txt", - "b.py", - "c.ts", - "d/", - "d/e.txt", - "d/f.py", - "d/g/", - "d/g/h.ts", - ]; - addToTestDir(files); - await expectPaths( - files.filter((files) => !files.endsWith("/")), - [], - ); - }); - - test("should ignore ignored files in nested folder structure", async () => { - const files = [ - "a.txt", - "b.py", - "c.ts", - "d/", - "d/e.txt", - "d/f.py", - "d/g/", - "d/g/h.ts", - ["d/.gitignore", "*.py"], - ]; - addToTestDir(files); - await expectPaths( - ["a.txt", "b.py", "c.ts", "d/e.txt", "d/g/h.ts"], - ["d/f.py"], - ); - }); - - test("should use gitignore in parent directory for subdirectory", async () => { - const files = [ - "a.txt", - "b.py", - "d/", - "d/e.txt", - "d/f.py", - "d/g/", - "d/g/h.ts", - "d/g/i.py", - [".gitignore", "*.py"], - ]; - addToTestDir(files); - await expectPaths(["a.txt", "d/e.txt", "d/g/h.ts"], ["d/f.py", "d/g/i.py"]); - }); - - test("should handle leading slash in gitignore", async () => { - const files = [[".gitignore", "/no.txt"], "a.txt", "b.py", "no.txt"]; - addToTestDir(files); - await expectPaths(["a.txt", "b.py"], ["no.txt"]); - }); + describe("walkDir", () => { + it("should walk a simple directory structure", async () => { + addToTestDir([ + "src/", + ["src/file1.ts", "content1"], + ["src/file2.ts", "content2"], + "src/nested/", + ["src/nested/file3.ts", "content3"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + ); + + expect(files.sort()).toEqual( + [ + expect.stringContaining("src/file1.ts"), + expect.stringContaining("src/file2.ts"), + expect.stringContaining("src/nested/file3.ts"), + ].sort(), + ); + }); - test("should not ignore leading slash when in subfolder", async () => { - const files = [ - [".gitignore", "/no.txt"], - "a.txt", - "b.py", - "no.txt", - "sub/", - "sub/no.txt", - ]; - addToTestDir(files); - await expectPaths(["a.txt", "b.py", "sub/no.txt"], ["no.txt"]); - }); + it("should respect .gitignore", async () => { + addToTestDir([ + ["src/file1.ts", "content1"], + ["src/file2.ts", "content2"], + "node_modules/", + ["node_modules/pkg/file.js", "ignored"], + [".gitignore", "node_modules/"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + ); + + expect(files).toEqual( + expect.not.arrayContaining([ + expect.stringContaining("node_modules/pkg/file.js"), + ]), + ); + expect(files.sort()).toEqual( + [ + expect.stringContaining("src/file1.ts"), + expect.stringContaining("src/file2.ts"), + ].sort(), + ); + }); - test("should handle multiple .gitignore files in nested structure", async () => { - const files = [ - [".gitignore", "*.txt"], - "a.py", - "b.txt", - "c/", - "c/d.txt", - "c/e.py", - ["c/.gitignore", "*.py"], - ]; - addToTestDir(files); - await expectPaths(["a.py"], ["b.txt", "c/e.py", "c/d.txt"]); + it("should handle onlyDirs option", async () => { + addToTestDir([ + "src/", + ["src/file1.ts", "content1"], + "src/nested/", + ["src/nested/file2.ts", "content2"], + ]); + + const dirs = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + onlyDirs: true, + }, + ); + + expect(dirs.sort()).toEqual( + [ + expect.stringContaining("src"), + expect.stringContaining("src/nested"), + ].sort(), + ); + }); }); - test("should handle wildcards in .gitignore", async () => { - const files = [ - [".gitignore", "*.txt\n*.py"], - "a.txt", - "b.py", - "c.ts", - "d/", - "d/e.txt", - "d/f.py", - "d/g.ts", - ]; - addToTestDir(files); - await expectPaths( - ["c.ts", "d/g.ts"], - ["a.txt", "b.py", "d/e.txt", "d/f.py"], - ); - }); + describe("walkDirAsync", () => { + it("should yield files asynchronously", async () => { + addToTestDir([ + "src/", + ["src/file1.ts", "content1"], + ["src/file2.ts", "content2"], + ]); + + const files: string[] = []; + for await (const file of walkDirAsync( + (await testIde.getWorkspaceDirs())[0], + testIde, + )) { + files.push(file); + } + + expect(files.sort()).toEqual( + [ + expect.stringContaining("src/file1.ts"), + expect.stringContaining("src/file2.ts"), + ].sort(), + ); + }); - test("should handle directory ignores in .gitignore", async () => { - const files = [ - [".gitignore", "ignored_dir/"], - "a.txt", - "ignored_dir/", - "ignored_dir/b.txt", - "ignored_dir/c/", - "ignored_dir/c/d.py", - ]; - addToTestDir(files); - await expectPaths(["a.txt"], ["ignored_dir/b.txt", "ignored_dir/c/d.py"]); + it("should handle relative paths option", async () => { + addToTestDir([ + "src/", + ["src/file1.ts", "content1"], + ["src/file2.ts", "content2"], + ]); + + const files: string[] = []; + for await (const file of walkDirAsync( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + returnRelativeUrisPaths: true, + }, + )) { + files.push(file); + } + + expect(files.sort()).toEqual(["src/file1.ts", "src/file2.ts"].sort()); + }); }); - test("gitignore in sub directory should only apply to subdirectory", async () => { - const files = [ - [".gitignore", "abc"], - "a.txt", - "abc", - "xyz/", - ["xyz/.gitignore", "xyz"], - "xyz/b.txt", - "xyz/c/", - "xyz/c/d.py", - "xyz/xyz", - ]; - addToTestDir(files); - await expectPaths(["a.txt", "xyz/b.txt", "xyz/c/d.py"], ["abc", "xyz/xyz"]); - }); + describe("walkDirs", () => { + it("should walk multiple workspace directories", async () => { + addToTestDir([ + "workspace1/src/", + ["workspace1/src/file1.ts", "content1"], + "workspace2/src/", + ["workspace2/src/file2.ts", "content2"], + ]); + + const files = await walkDirs(testIde, undefined, [ + (await testIde.getWorkspaceDirs())[0] + "/workspace1", + (await testIde.getWorkspaceDirs())[0] + "/workspace2", // Second workspace dir + ]); + + expect(files).toContainEqual(expect.stringContaining("file1.ts")); + expect(files).toContainEqual(expect.stringContaining("file2.ts")); + }); - test("should handle complex patterns in .gitignore", async () => { - const files = [ - [".gitignore", "*.what\n!important.what\ntemp/\n/root_only.txt"], - "a.what", - "important.what", - "root_only.txt", - "subdir/", - "subdir/root_only.txt", - "subdir/b.what", - "temp/", - "temp/c.txt", - ]; - addToTestDir(files); - await expectPaths( - ["important.what", "subdir/root_only.txt"], - ["a.what", "root_only.txt", "subdir/b.what", "temp/c.txt"], - ); - }); + it("should handle empty directories", async () => { + addToTestDir(["empty/"]); - test("should listen to both .gitignore and .continueignore", async () => { - const files = [ - [".gitignore", "*.py"], - [".continueignore", "*.ts"], - "a.txt", - "b.py", - "c.ts", - "d.js", - ]; - addToTestDir(files); - await expectPaths(["a.txt", "d.js"], ["b.py", "c.ts"]); - }); + const files = await walkDirs(testIde); + console.log("EMPTY DIR", files); - test("should return dirs and only dirs in onlyDirs mode", async () => { - const files = [ - "a.txt", - "b.py", - "c.ts", - "d/", - "d/e.txt", - "d/f.py", - "d/g/", - "d/g/h.ts", - ]; - addToTestDir(files); - await expectPaths( - ["d", "d/g"], - ["a.txt", "b.py", "c.ts", "d/e.txt", "d/f.py", "d/g/h.ts"], - { onlyDirs: true }, - ); - }); + expect(files).toEqual([]); + }); - test("should return valid paths in absolute path mode", async () => { - const files = ["a.txt", "b/", "b/c.txt"]; - addToTestDir(files); - await expectPaths( - [path.join(TEST_DIR, "a.txt"), path.join(TEST_DIR, "b", "c.txt")], - [], - { - returnRelativeUrisPaths: false, - }, - ); - }); + it("should skip symlinks", async () => { + const filePath = path.join(TEST_DIR_PATH, "real.ts"); + addToTestDir([["real.ts", "content"]]); + fs.symlink( + filePath, + path.join(TEST_DIR_PATH, "symlink.ts"), + "file", + () => {}, + ); - test("should skip .git and node_modules folders", async () => { - const files = [ - "a.txt", - ".git/", - ".git/config", - ".git/HEAD", - ".git/objects/", - ".git/objects/1234567890abcdef", - "node_modules/", - "node_modules/package/", - "node_modules/package/index.js", - "src/", - "src/index.ts", - ]; - addToTestDir(files); - await expectPaths( - ["a.txt", "src/index.ts"], - [ - ".git/config", - ".git/HEAD", - "node_modules/package/index.js", - ".git/objects/1234567890abcdef", - ], - ); - }); + const files = await walkDirs(testIde); - 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(files).not.toContainEqual(expect.stringContaining("symlink.ts")); }); - 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); }); - // This test is passing when this file is ran individually, but failing with `directory not found` error - // when the full test suite is ran - test.skip("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"], + describe("walkDir ignore patterns", () => { + it("should handle negation patterns in gitignore", async () => { + addToTestDir([ + [".gitignore", "**/*\n!*.py"], + ["a.txt", "content"], + ["b.py", "content"], + ["c.ts", "content"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + returnRelativeUrisPaths: true, + }, + ); + + expect(files).toContain("b.py"); + expect(files).not.toContain("a.txt"); + expect(files).not.toContain("c.ts"); }); - expect(results.length).toBeGreaterThan(0); - expect(results.some((file) => file.includes("/textmate-syntaxes/"))).toBe( - false, - ); - expect(results.some((file) => file.includes(".tmLanguage"))).toBe(false); - }); - // This test is passing when this file is ran individually, but failing with `jest not found` error - // when the full test suite is ran - test.skip("should perform the same number of dir reads as 1 + the number of dirs that contain files", async () => { - const files = [ - "a.txt", - "b.py", - "c.ts", - "d/", - "d/e.txt", - "d/f.py", - "d/g/", - "d/g/h.ts", - "d/g/i/", - "d/g/i/j.ts", - ]; + it("should handle leading slash patterns", async () => { + addToTestDir([ + [".gitignore", "/no.txt"], + ["no.txt", "content"], + "sub/", + ["sub/no.txt", "content"], + ["a.txt", "content"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + returnRelativeUrisPaths: true, + }, + ); + + expect(files).not.toContain("no.txt"); + expect(files).toContain("sub/no.txt"); + expect(files).toContain("a.txt"); + }); - const numDirs = files.filter((file) => !file.includes(".")).length; - const numDirsPlusTopLevelRead = numDirs + 1; + it("should handle multiple gitignore files in nested structure", async () => { + addToTestDir([ + [".gitignore", "*.txt"], + ["a.py", "content"], + ["b.txt", "content"], + "c/", + ["c/.gitignore", "*.py"], + ["c/d.txt", "content"], + ["c/e.py", "content"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + returnRelativeUrisPaths: true, + }, + ); + + expect(files).toContain("a.py"); + expect(files).not.toContain("b.txt"); + expect(files).not.toContain("c/d.txt"); + expect(files).not.toContain("c/e.py"); + }); - addToTestDir(files); + it("should handle both gitignore and continueignore", async () => { + addToTestDir([ + [".gitignore", "*.py"], + [".continueignore", "*.ts"], + ["a.txt", "content"], + ["b.py", "content"], + ["c.ts", "content"], + ["d.js", "content"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + returnRelativeUrisPaths: true, + }, + ); + + expect(files).toContain("a.txt"); + expect(files).toContain("d.js"); + expect(files).not.toContain("b.py"); + expect(files).not.toContain("c.ts"); + }); - const mockListDir = jest.spyOn(ide, "listDir"); + it("should handle complex wildcard patterns", async () => { + addToTestDir([ + [".gitignore", "*.what\n!important.what\ntemp/\n/root_only.txt"], + ["a.what", "content"], + ["important.what", "content"], + ["root_only.txt", "content"], + "subdir/", + ["subdir/root_only.txt", "content"], + ["subdir/b.what", "content"], + "temp/", + ["temp/c.txt", "content"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + returnRelativeUrisPaths: true, + }, + ); + + expect(files).toContain("important.what"); + expect(files).toContain("subdir/root_only.txt"); + expect(files).not.toContain("a.what"); + expect(files).not.toContain("root_only.txt"); + expect(files).not.toContain("subdir/b.what"); + expect(files).not.toContain("temp/c.txt"); + }); - await walkTestDir(); + it("should skip common system directories by default", async () => { + addToTestDir([ + ["normal/file.txt", "content"], + [".git/config", "content"], + ["node_modules/package/index.js", "content"], + ["dist/bundle.js", "content"], + ["coverage/lcov.env", "content"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + returnRelativeUrisPaths: true, + }, + ); + + expect(files).toContain("normal/file.txt"); + expect(files).not.toContain(".git/config"); + expect(files).not.toContain("node_modules/package/index.js"); + expect(files).not.toContain("coverage/lcov.info"); + }); - expect(mockListDir).toHaveBeenCalledTimes(numDirsPlusTopLevelRead); + it("should handle directory-specific ignore patterns correctly", async () => { + addToTestDir([ + [".gitignore", "/abc"], + ["abc", "content"], + "xyz/", + ["xyz/.gitignore", "xyz"], + ["xyz/abc", "content"], + ["xyz/xyz", "content"], + ["xyz/normal.txt", "content"], + ]); + + const files = await walkDir( + (await testIde.getWorkspaceDirs())[0], + testIde, + { + returnRelativeUrisPaths: true, + }, + ); + + expect(files).not.toContain("abc"); + expect(files).toContain("xyz/abc"); + expect(files).not.toContain("xyz/xyz"); + expect(files).toContain("xyz/normal.txt"); + }); }); }); diff --git a/core/indexing/walkDir.ts b/core/indexing/walkDir.ts index d0a90aad37..4e97e01d42 100644 --- a/core/indexing/walkDir.ts +++ b/core/indexing/walkDir.ts @@ -168,7 +168,7 @@ class DFSWalker { } } for (const ig of ignoreContexts) { - // remove the directory name and path seperator from the match path, unless this an ignore file + // remove the directory name and path separator from the match path, unless this an ignore file // in the root directory const prefixLength = ig.dirname.length === 0 ? 0 : ig.dirname.length + 1; // The ignore library expects a path relative to the ignore file location diff --git a/core/test/testDir.ts b/core/test/testDir.ts index 098075c344..9b0473b468 100644 --- a/core/test/testDir.ts +++ b/core/test/testDir.ts @@ -4,7 +4,7 @@ import path from "path"; import { localPathToUri, localPathOrUriToPath } from "../util/pathToUri"; // Want this outside of the git repository so we can change branches in tests -const TEST_DIR_PATH = path.join(os.tmpdir(), "testWorkspaceDir"); +export const TEST_DIR_PATH = path.join(os.tmpdir(), "testWorkspaceDir"); export const TEST_DIR = localPathToUri(TEST_DIR_PATH); // URI export function setUpTestDir() { @@ -43,7 +43,7 @@ export function addToTestDir(pathsOrUris: (string | string[])[]) { if (Array.isArray(p)) { fs.writeFileSync(filepath, p[1]); } else if (p.endsWith("/")) { - fs.mkdirSync(filepath, { recursive: true }); + fs.mkdirSync(p, { recursive: true }); } else { fs.writeFileSync(filepath, ""); } diff --git a/core/util/pathToUri.ts b/core/util/pathToUri.ts index e70af5d271..804b4fa999 100644 --- a/core/util/pathToUri.ts +++ b/core/util/pathToUri.ts @@ -14,7 +14,7 @@ export function localPathOrUriToPath(localPathOrUri: string): string { try { return fileURLToPath(localPathOrUri); } catch (e) { - console.log("Received local filepath", localPathOrUri); + // console.log("Received local filepath", localPathOrUri); return localPathOrUri; } From 1567d21dc02f00b32b935f968831bff5384a18ef Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Mon, 16 Dec 2024 21:51:05 -0800 Subject: [PATCH 53/84] fix core tests --- core/indexing/CodebaseIndexer.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/indexing/CodebaseIndexer.test.ts b/core/indexing/CodebaseIndexer.test.ts index ae935669f7..7db40d75ed 100644 --- a/core/indexing/CodebaseIndexer.test.ts +++ b/core/indexing/CodebaseIndexer.test.ts @@ -77,9 +77,11 @@ describe("CodebaseIndexer", () => { tearDownTestDir(); setUpTestDir(); - execSync("git init", { cwd: TEST_DIR }); - execSync('git config user.email "test@example.com"', { cwd: TEST_DIR }); - execSync('git config user.name "Test"', { cwd: TEST_DIR }); + execSync("git init", { cwd: TEST_DIR_PATH }); + execSync('git config user.email "test@example.com"', { + cwd: TEST_DIR_PATH, + }); + execSync('git config user.name "Test"', { cwd: TEST_DIR_PATH }); }); afterAll(async () => { From 49c5a538f6d9fba78971c497fc3527ac42b66a91 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 10:55:05 -0800 Subject: [PATCH 54/84] jetbrains p1 --- .../continue/IntelliJIde.kt | 13 ------------- .../continuedev/continueintellijextension/types.kt | 2 -- 2 files changed, 15 deletions(-) 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 e883df441f..d1e340784a 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 @@ -168,19 +168,6 @@ class IntelliJIDE( throw NotImplementedError("getAvailableThreads not implemented yet") } - override suspend fun listFolders(): List { - val workspacePath = this.workspacePath ?: return emptyList() - val folders = mutableListOf() - fun findNestedFolders(dirPath: String) { - val dir = File(dirPath) - val nestedFolders = dir.listFiles { file -> file.isDirectory }?.map { it.absolutePath } ?: emptyList() - folders.addAll(nestedFolders) - nestedFolders.forEach { folder -> findNestedFolders(folder) } - } - findNestedFolders(workspacePath) - return folders - } - override suspend fun getWorkspaceDirs(): List { return workspaceDirectories().toList() } 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 0747284233..4b632f07e1 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 @@ -194,8 +194,6 @@ interface IDE { // Callbacks fun onDidChangeActiveTextEditor(callback: (filepath: String) -> Unit) - - suspend fun pathSep(): String } data class GetGhTokenArgs( From 07d03ceef7ac79417404b3f0b28ef2c253f82797 Mon Sep 17 00:00:00 2001 From: Test Date: Tue, 17 Dec 2024 11:17:16 -0800 Subject: [PATCH 55/84] update intellij ide --- .../activities/ContinuePluginStartupActivity.kt | 6 ++---- .../continue/IdeProtocolClient.kt | 5 ----- .../continue/IntelliJIde.kt | 13 +++---------- .../continuedev/continueintellijextension/types.kt | 2 -- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt index c74c45548f..a208ea2cd7 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/activities/ContinuePluginStartupActivity.kt @@ -173,7 +173,7 @@ class ContinuePluginStartupActivity : StartupActivity, DumbAware { // Collect all relevant URIs for content changes val changedURIs = events.filterIsInstance() .map { event -> event.file.url } - + // Send "files/changed" message if there are any content changes if (changedURIs.isNotEmpty()) { val data = mapOf("files" to changedURIs) @@ -220,12 +220,10 @@ class ContinuePluginStartupActivity : StartupActivity, DumbAware { // Reload the WebView continuePluginService?.let { pluginService -> val allModulePaths = ModuleManager.getInstance(project).modules - .flatMap { module -> ModuleRootManager.getInstance(module).contentRoots.map { it.path } } - .map { Paths.get(it).normalize() } + .flatMap { module -> ModuleRootManager.getInstance(module).contentRoots.map { it.url } } val topLevelModulePaths = allModulePaths .filter { modulePath -> allModulePaths.none { it != modulePath && modulePath.startsWith(it) } } - .map { it.toString() } pluginService.workspacePaths = topLevelModulePaths.toTypedArray() } 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 0c771a68c7..3b7e19b8c5 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 @@ -390,11 +390,6 @@ class IdeProtocolClient( respond(null) } - "pathSep" -> { - val sep = ide.pathSep() - respond(sep) - } - "insertAtCursor" -> { val params = Gson().fromJson( dataElement.toString(), 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 d1e340784a..99ca7afd3c 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 @@ -1,6 +1,5 @@ import com.github.continuedev.continueintellijextension.* import com.github.continuedev.continueintellijextension.constants.getContinueGlobalPath -import com.github.continuedev.continueintellijextension.`continue`.DiffManager import com.github.continuedev.continueintellijextension.services.ContinueExtensionSettings import com.github.continuedev.continueintellijextension.services.ContinuePluginService import com.github.continuedev.continueintellijextension.utils.OS @@ -178,16 +177,14 @@ class IntelliJIDE( val configs = mutableListOf() for (workspaceDir in workspaceDirs) { - val workspacePath = File(workspaceDir) - val dir = VirtualFileManager.getInstance().findFileByUrl("file://$workspacePath") + val dir = VirtualFileManager.getInstance().findFileByUrl(workspaceDir) if (dir != null) { - val contents = dir.children.map { it.name } + val contents = dir.children.map { it.url } // Find any .continuerc.json files for (file in contents) { if (file.endsWith(".continuerc.json")) { - val filePath = workspacePath.resolve(file) - val fileContent = File(filePath.toString()).readText() + val fileContent = File(URI(file)).readText() configs.add(fileContent) } } @@ -540,10 +537,6 @@ class IntelliJIDE( throw NotImplementedError("onDidChangeActiveTextEditor not implemented yet") } - override suspend fun pathSep(): String { - return File.separator - } - private fun setFileOpen(filepath: String, open: Boolean = true) { val file = LocalFileSystem.getInstance().findFileByPath(filepath) 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 4b632f07e1..b1e051777b 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 @@ -115,8 +115,6 @@ interface IDE { suspend fun getAvailableThreads(): List - suspend fun listFolders(): List - suspend fun getWorkspaceDirs(): List suspend fun getWorkspaceConfigs(): List From 412877dc52acb5feaa2781cfa94b1262f1dd3fcb Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 11:17:24 -0800 Subject: [PATCH 56/84] eliminate pathsep from intellij --- .../autocomplete/AutocompleteService.kt | 2 +- .../continue/IdeProtocolClient.kt | 7 +------ .../continueintellijextension/continue/IntelliJIde.kt | 4 ---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/autocomplete/AutocompleteService.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/autocomplete/AutocompleteService.kt index d4e5e1092b..6279e03f74 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/autocomplete/AutocompleteService.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/autocomplete/AutocompleteService.kt @@ -69,7 +69,7 @@ class AutocompleteService(private val project: Project) { val column = editor.caretModel.primaryCaret.logicalPosition.column val input = mapOf( "completionId" to completionId, - "filepath" to virtualFile?.path, + "filepath" to virtualFile?.url, "pos" to mapOf( "line" to editor.caretModel.primaryCaret.logicalPosition.line, "character" to column 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 0c771a68c7..6549a895e9 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 @@ -389,12 +389,7 @@ class IdeProtocolClient( ide.openUrl(url) respond(null) } - - "pathSep" -> { - val sep = ide.pathSep() - respond(sep) - } - + "insertAtCursor" -> { val params = Gson().fromJson( dataElement.toString(), 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 d1e340784a..556e050091 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 @@ -540,10 +540,6 @@ class IntelliJIDE( throw NotImplementedError("onDidChangeActiveTextEditor not implemented yet") } - override suspend fun pathSep(): String { - return File.separator - } - private fun setFileOpen(filepath: String, open: Boolean = true) { val file = LocalFileSystem.getInstance().findFileByPath(filepath) From 647be1161585297f4bcba88b348737d0e2625b83 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 11:23:38 -0800 Subject: [PATCH 57/84] configjson plugin --- .../continue/ConfigJsonSchemaProviderFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/ConfigJsonSchemaProviderFactory.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/ConfigJsonSchemaProviderFactory.kt index 2056d216e1..677506f6e6 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/ConfigJsonSchemaProviderFactory.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/ConfigJsonSchemaProviderFactory.kt @@ -22,7 +22,7 @@ class ConfigJsonSchemaProviderFactory : JsonSchemaProviderFactory { class ConfigJsonSchemaFileProvider : JsonSchemaFileProvider { override fun isAvailable(file: VirtualFile): Boolean { - return file.path.endsWith("/.continue/config.json") + return file.path.endsWith("/.continue/config.json") || file.path.endsWith("\\.continue\\config.json") } override fun getName(): String { From 6e97179f44befd480a97006cdbefdd2f94a2d82d Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 11:49:41 -0800 Subject: [PATCH 58/84] ideporotocol client --- .../continueintellijextension/continue/IdeProtocolClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6549a895e9..7efc44c79d 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 @@ -548,7 +548,7 @@ class IdeProtocolClient( val endChar = endOffset - document.getLineStartOffset(endLine) return@runReadAction RangeInFileWithContents( - virtualFile.path, Range( + virtualFile.url, Range( Position(startLine, startChar), Position(endLine, endChar) ), selectedText From 9116818cdd38623cbba8a3c5013e517de5bfeda1 Mon Sep 17 00:00:00 2001 From: Test Date: Tue, 17 Dec 2024 11:51:32 -0800 Subject: [PATCH 59/84] Update IntelliJIde.kt --- .../continue/IntelliJIde.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 99ca7afd3c..cc4113bf24 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 @@ -107,7 +107,7 @@ class IntelliJIDE( } else { ProcessBuilder("git", "diff", "--cached") } - builder.directory(File(workspaceDir)) + builder.directory(File(URI(workspaceDir))) val process = withContext(Dispatchers.IO) { builder.start() } @@ -195,12 +195,12 @@ class IntelliJIDE( } override suspend fun fileExists(filepath: String): Boolean { - val file = File(filepath) + val file = File(URI(filepath)) return file.exists() } override suspend fun writeFile(path: String, contents: String) { - val file = File(path) + val file = File(URI(path)) file.writeText(contents) } @@ -216,7 +216,7 @@ class IntelliJIDE( } override suspend fun openFile(path: String) { - val file = LocalFileSystem.getInstance().findFileByPath(path) + val file = LocalFileSystem.getInstance().findFileByPath(URI(path).path) file?.let { ApplicationManager.getApplication().invokeLater { FileEditorManager.getInstance(project).openFile(it, true) @@ -236,7 +236,7 @@ class IntelliJIDE( override suspend fun saveFile(filepath: String) { ApplicationManager.getApplication().invokeLater { - val file = LocalFileSystem.getInstance().findFileByPath(filepath) ?: return@invokeLater + val file = LocalFileSystem.getInstance().findFileByPath(URI(filepath).path) ?: return@invokeLater val fileDocumentManager = FileDocumentManager.getInstance() val document = fileDocumentManager.getDocument(file) @@ -249,7 +249,7 @@ class IntelliJIDE( override suspend fun readFile(filepath: String): String { return try { val content = ApplicationManager.getApplication().runReadAction { - val virtualFile = LocalFileSystem.getInstance().findFileByPath(filepath) + val virtualFile = LocalFileSystem.getInstance().findFileByPath(URI(filepath).path) if (virtualFile != null && FileDocumentManager.getInstance().isFileModified(virtualFile)) { return@runReadAction FileDocumentManager.getInstance().getDocument(virtualFile)?.text } @@ -259,7 +259,7 @@ class IntelliJIDE( if (content != null) { content } else { - val file = File(filepath) + val file = File(URI(filepath)) if (!file.exists()) return "" withContext(Dispatchers.IO) { FileInputStream(file).use { fis -> @@ -406,7 +406,7 @@ class IntelliJIDE( return withContext(Dispatchers.IO) { try { val builder = ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD") - builder.directory(File(dir)) + builder.directory(File(URI(dir))) val process = builder.start() val reader = BufferedReader(InputStreamReader(process.inputStream)) val output = reader.readLine() @@ -436,7 +436,7 @@ class IntelliJIDE( override suspend fun getRepoName(dir: String): String? { return withContext(Dispatchers.IO) { - val directory = File(dir) + val directory = File(URI(dir)) val targetDir = if (directory.isFile) directory.parentFile else directory val builder = ProcessBuilder("git", "config", "--get", "remote.origin.url") builder.directory(targetDir) @@ -500,7 +500,7 @@ class IntelliJIDE( override suspend fun getGitRootPath(dir: String): String? { return withContext(Dispatchers.IO) { val builder = ProcessBuilder("git", "rev-parse", "--show-toplevel") - builder.directory(File(dir)) + builder.directory(File(URI(dir))) val process = builder.start() val reader = BufferedReader(InputStreamReader(process.inputStream)) @@ -511,7 +511,7 @@ class IntelliJIDE( } override suspend fun listDir(dir: String): List> { - val files = File(dir).listFiles()?.map { + val files = File(URI(dir)).listFiles()?.map { listOf(it.name, if (it.isDirectory) FileType.DIRECTORY else FileType.FILE) } ?: emptyList() @@ -520,7 +520,7 @@ class IntelliJIDE( override suspend fun getLastModified(files: List): Map { return files.associateWith { file -> - File(file).lastModified() + File(URI(file)).lastModified() } } From fdb108eecc1d91ddd1c4a0fe3f53ce6b6fb3b1be Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 11:59:41 -0800 Subject: [PATCH 60/84] diff manager --- .../continueintellijextension/continue/Diffs.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/Diffs.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/Diffs.kt index d7e9124998..812355db2f 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/Diffs.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/Diffs.kt @@ -61,7 +61,7 @@ class DiffManager(private val project: Project) : DumbAware { file.createNewFile() } file.writeText(replacement) - openDiffWindow(filepath, file.path, stepIndex) + openDiffWindow(filepath, file.url, stepIndex) } private fun cleanUpFile(file2: String) { @@ -79,7 +79,7 @@ class DiffManager(private val project: Project) : DumbAware { val diffInfo = diffInfoMap[file] ?: return // Write contents to original file - val virtualFile = LocalFileSystem.getInstance().findFileByPath(diffInfo.originalFilepath) ?: return + val virtualFile = LocalFileSystem.getInstance().findFileByPath(URI(diffInfo.originalFilepath).path) ?: return val document = FileDocumentManager.getInstance().getDocument(virtualFile) ?: return WriteCommandAction.runWriteCommandAction(project) { document.setText(File(file).readText()) @@ -118,8 +118,8 @@ class DiffManager(private val project: Project) : DumbAware { lastFile2 = file2 // Create a DiffContent for each of the texts you want to compare - val content1: DiffContent = DiffContentFactory.getInstance().create(File(file1).readText()) - val content2: DiffContent = DiffContentFactory.getInstance().create(File(file2).readText()) + val content1: DiffContent = DiffContentFactory.getInstance().create(File(URI(file1)).readText()) + val content2: DiffContent = DiffContentFactory.getInstance().create(File(URI(file2)).readText()) // Create a SimpleDiffRequest and populate it with the DiffContents and titles val diffRequest = SimpleDiffRequest("Continue Diff", content1, content2, "Old", "New") From 735e134ad586b563189f91c7624ec2d28e2dbe39 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 12:08:59 -0800 Subject: [PATCH 61/84] jetbrains diffs --- .../continuedev/continueintellijextension/continue/Diffs.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/Diffs.kt b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/Diffs.kt index 812355db2f..750737f1bc 100644 --- a/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/Diffs.kt +++ b/extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/continue/Diffs.kt @@ -18,6 +18,7 @@ import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.LocalFileSystem import java.awt.Toolkit import java.io.File +import java.net.URI import java.nio.file.Paths import javax.swing.Action import javax.swing.JComponent @@ -61,7 +62,7 @@ class DiffManager(private val project: Project) : DumbAware { file.createNewFile() } file.writeText(replacement) - openDiffWindow(filepath, file.url, stepIndex) + openDiffWindow(URI(filepath).toString(), file.toURI().toString(), stepIndex) } private fun cleanUpFile(file2: String) { From ed998cb41f676fd5d49778230f1fbb90ef227d87 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 12:25:06 -0800 Subject: [PATCH 62/84] fix ide protocol --- .../continueintellijextension/continue/IntelliJIde.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 cc4113bf24..3198247d65 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 @@ -306,7 +306,7 @@ class IntelliJIDE( override suspend fun getOpenFiles(): List { val fileEditorManager = FileEditorManager.getInstance(project) - return fileEditorManager.openFiles.map { it.path }.toList() + return fileEditorManager.openFiles.map { it.url }.toList() } override suspend fun getCurrentFile(): Map? { @@ -315,7 +315,7 @@ class IntelliJIDE( val virtualFile = editor?.document?.let { FileDocumentManager.getInstance().getFile(it) } return virtualFile?.let { mapOf( - "path" to it.path, + "path" to it.url, "contents" to editor.document.text, "isUntitled" to false ) @@ -381,7 +381,7 @@ class IntelliJIDE( problems.add( Problem( - filepath = psiFile.virtualFile?.path ?: "", + filepath = psiFile.virtualFile?.url ?: "", range = Range( start = Position( line = startLineNumber, @@ -538,7 +538,7 @@ class IntelliJIDE( } private fun setFileOpen(filepath: String, open: Boolean = true) { - val file = LocalFileSystem.getInstance().findFileByPath(filepath) + val file = LocalFileSystem.getInstance().findFileByPath(URI(filepath).path) file?.let { if (open) { From ab74a4efbae2064877dde317f94de9d4fef0d28c Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 14:24:57 -0800 Subject: [PATCH 63/84] off by one range rif error - fix clickable context items and symbols --- core/commands/util.ts | 5 +++-- extensions/vscode/package-lock.json | 4 ++-- gui/src/components/mainInput/resolveInput.ts | 2 +- gui/src/components/markdown/CodeSnippetPreview.tsx | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/commands/util.ts b/core/commands/util.ts index 2d0c8257e3..1cd493fad7 100644 --- a/core/commands/util.ts +++ b/core/commands/util.ts @@ -29,6 +29,7 @@ export function rifWithContentsToContextItem( export function ctxItemToRifWithContents( item: ContextItemWithId, + linesOffByOne = false, ): RangeInFileWithContents { let startLine = 0; let endLine = 0; @@ -37,8 +38,8 @@ export function ctxItemToRifWithContents( if (nameSplit.length > 1) { const lines = nameSplit[1].split(")")[0].split("-"); - startLine = Number.parseInt(lines[0], 10) - 1; - endLine = Number.parseInt(lines[1], 10) - 1; + startLine = Number.parseInt(lines[0], 10) - (linesOffByOne ? 1 : 0); + endLine = Number.parseInt(lines[1], 10) - (linesOffByOne ? 1 : 0); } const rif: RangeInFileWithContents = { diff --git a/extensions/vscode/package-lock.json b/extensions/vscode/package-lock.json index 3b0d6b3713..ca4edfc38b 100644 --- a/extensions/vscode/package-lock.json +++ b/extensions/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.9.245", + "version": "0.9.246", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "continue", - "version": "0.9.245", + "version": "0.9.246", "license": "Apache-2.0", "dependencies": { "@continuedev/fetch": "^1.0.3", diff --git a/gui/src/components/mainInput/resolveInput.ts b/gui/src/components/mainInput/resolveInput.ts index 190dfbc80d..1ee6ed86a7 100644 --- a/gui/src/components/mainInput/resolveInput.ts +++ b/gui/src/components/mainInput/resolveInput.ts @@ -72,7 +72,7 @@ async function resolveEditorContent({ } else if (p.type === "codeBlock") { if (p.attrs?.item) { const contextItem = p.attrs.item as ContextItemWithId; - const rif = ctxItemToRifWithContents(contextItem); + const rif = ctxItemToRifWithContents(contextItem, true); // If not editing, include codeblocks in the prompt // If editing is handled by selectedCode below if (!contextItem.editing) { diff --git a/gui/src/components/markdown/CodeSnippetPreview.tsx b/gui/src/components/markdown/CodeSnippetPreview.tsx index a463c421ee..816ba82a63 100644 --- a/gui/src/components/markdown/CodeSnippetPreview.tsx +++ b/gui/src/components/markdown/CodeSnippetPreview.tsx @@ -89,7 +89,7 @@ function CodeSnippetPreview(props: CodeSnippetPreviewProps) { filepath: props.item.uri.value, }); } else if (props.item.id.providerTitle === "code") { - const rif = ctxItemToRifWithContents(props.item); + const rif = ctxItemToRifWithContents(props.item, true); ideMessenger.ide.showLines( rif.filepath, rif.range.start.line, From a43c31a1432b90e0854faae8e006d0572bae5108 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 14:32:09 -0800 Subject: [PATCH 64/84] untitled file url : --- core/autocomplete/templating/AutocompleteTemplate.ts | 2 +- core/autocomplete/templating/formatting.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index 5d2e5fee73..e3f087102c 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -98,7 +98,7 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { const relativePaths = getShortestUniqueRelativeUriPaths( [ ...snippets.map((snippet) => - "filepath" in snippet ? snippet.filepath : "Untitled.txt", + "filepath" in snippet ? snippet.filepath : "file:///Untitled.txt", ), filepath, ], diff --git a/core/autocomplete/templating/formatting.ts b/core/autocomplete/templating/formatting.ts index 63bc156ce8..2a91ad4384 100644 --- a/core/autocomplete/templating/formatting.ts +++ b/core/autocomplete/templating/formatting.ts @@ -27,7 +27,7 @@ const formatClipboardSnippet = ( ): AutocompleteCodeSnippet => { return formatCodeSnippet( { - filepath: "Untitled.txt", + filepath: "file:///Untitled.txt", content: snippet.content, type: AutocompleteSnippetType.Code, }, From 4bd83ad8c0a6469ffe2c99de458650b6dd8f9432 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 14:38:43 -0800 Subject: [PATCH 65/84] reinstate codeblock max height --- gui/src/components/markdown/CodeSnippetPreview.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/gui/src/components/markdown/CodeSnippetPreview.tsx b/gui/src/components/markdown/CodeSnippetPreview.tsx index 816ba82a63..62565e5ea6 100644 --- a/gui/src/components/markdown/CodeSnippetPreview.tsx +++ b/gui/src/components/markdown/CodeSnippetPreview.tsx @@ -124,11 +124,9 @@ function CodeSnippetPreview(props: CodeSnippetPreviewProps) { contentEditable={false} className={`m-0 ${collapsed ? "overflow-hidden" : "overflow-auto"}`} ref={codeBlockRef} - style={ - { - // maxHeight: collapsed ? MAX_PREVIEW_HEIGHT : undefined, // Could switch to max-h-[33vh] but then chevron icon shows when height can't change - } - } + style={{ + maxHeight: collapsed ? MAX_PREVIEW_HEIGHT : undefined, // Could switch to max-h-[33vh] but then chevron icon shows when height can't change + }} > Date: Tue, 17 Dec 2024 21:40:59 -0800 Subject: [PATCH 66/84] ctxItemToRifWithContents test --- core/commands/util.test.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/commands/util.test.ts b/core/commands/util.test.ts index 7947a7db80..447ec0e600 100644 --- a/core/commands/util.test.ts +++ b/core/commands/util.test.ts @@ -14,8 +14,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "/path/to/file", range: { - start: { line: 9, character: 0 }, - end: { line: 19, character: 0 }, + start: { line: 10, character: 0 }, + end: { line: 20, character: 0 }, }, contents: "function content", }; @@ -57,8 +57,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "", range: { - start: { line: 9, character: 0 }, - end: { line: 19, character: 0 }, + start: { line: 10, character: 0 }, + end: { line: 20, character: 0 }, }, contents: "function content", }; @@ -78,8 +78,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "", range: { - start: { line: 9, character: 0 }, - end: { line: 19, character: 0 }, + start: { line: 10, character: 0 }, + end: { line: 20, character: 0 }, }, contents: "function content", }; @@ -115,8 +115,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "/path/to/file", range: { - start: { line: 9, character: 0 }, - end: { line: 19, character: 0 }, + start: { line: 10, character: 0 }, + end: { line: 20, character: 0 }, }, contents: "function content", }; @@ -137,8 +137,8 @@ describe("ctxItemToRifWithContents", () => { const expected: RangeInFileWithContents = { filepath: "/path/to/file", range: { - start: { line: 9, character: 0 }, - end: { line: 19, character: 0 }, + start: { line: 10, character: 0 }, + end: { line: 20, character: 0 }, }, contents: "function content", }; From 62f0a13d92328b2f91aee5ff2486f0fbe9c42b67 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 21:49:21 -0800 Subject: [PATCH 67/84] fix searchDir --- extensions/vscode/src/VsCodeIde.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts index cd0ddc8419..bee4a3771f 100644 --- a/extensions/vscode/src/VsCodeIde.ts +++ b/extensions/vscode/src/VsCodeIde.ts @@ -27,6 +27,7 @@ import type { RangeInFile, Thread, } from "core"; +import { findUriInDirs } from "core/util/uri"; class VsCodeIde implements IDE { ideUtils: VsCodeIdeUtils; @@ -486,7 +487,7 @@ class VsCodeIde implements IDE { } private async _searchDir(query: string, dir: string): Promise { - const relativeDir = vscode.workspace.asRelativePath(dir); + const relativeDir = vscode.Uri.parse(dir).fsPath; const ripGrepUri = vscode.Uri.joinPath( getExtensionUri(), "out/node_modules/@vscode/ripgrep/bin/rg", From 504bc7c5e7c7484c8de889e930a8d43a1acf59f3 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 17 Dec 2024 22:20:41 -0800 Subject: [PATCH 68/84] no folder results message --- core/context/retrieval/retrieval.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/context/retrieval/retrieval.ts b/core/context/retrieval/retrieval.ts index dba06f5136..14dba33415 100644 --- a/core/context/retrieval/retrieval.ts +++ b/core/context/retrieval/retrieval.ts @@ -89,9 +89,17 @@ export async function retrieveContextItemsFromEmbeddings( }); if (results.length === 0) { - throw new Error( - "Warning: No results found for @codebase context provider.", - ); + if (extras.config.disableIndexing) { + void extras.ide.showToast("warning", "No embeddings results found."); + return []; + } else { + void extras.ide.showToast( + "warning", + "No embeddings results found. If you think this is an error, re-index your codebase.", + ); + // TODO - add "re-index" option to warning message which clears and reindexes codebase + } + return []; } return [ From 1f9bb5490b39d71226dcf6250a613aed8b9eab6d Mon Sep 17 00:00:00 2001 From: Test Date: Wed, 18 Dec 2024 09:14:15 -0800 Subject: [PATCH 69/84] Update onboarding.ts --- core/config/onboarding.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/config/onboarding.ts b/core/config/onboarding.ts index 6b05ca59ff..426a7a4ba8 100644 --- a/core/config/onboarding.ts +++ b/core/config/onboarding.ts @@ -4,7 +4,7 @@ import { FREE_TRIAL_MODELS } from "./default"; export const TRIAL_FIM_MODEL = "codestral-latest"; export const ONBOARDING_LOCAL_MODEL_TITLE = "Ollama"; -export const LOCAL_ONBOARDING_FIM_MODEL = "qwen2.5-coder:1.5b"; +export const LOCAL_ONBOARDING_FIM_MODEL = "qwen2.5-coder:1.5b-base"; export const LOCAL_ONBOARDING_CHAT_MODEL = "llama3.1:8b"; export const LOCAL_ONBOARDING_CHAT_TITLE = "Llama 3.1 8B"; From 98296ff125fdb104759f10ae9a016bbde5a49046 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 18 Dec 2024 10:36:50 -0800 Subject: [PATCH 70/84] thinking indicator --- .../StepContainer/StepContainer.tsx | 6 ++- .../StepContainer/ThinkingIndicator.tsx | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 gui/src/components/StepContainer/ThinkingIndicator.tsx diff --git a/gui/src/components/StepContainer/StepContainer.tsx b/gui/src/components/StepContainer/StepContainer.tsx index a497a0b22c..6fb4603913 100644 --- a/gui/src/components/StepContainer/StepContainer.tsx +++ b/gui/src/components/StepContainer/StepContainer.tsx @@ -10,6 +10,7 @@ import ResponseActions from "./ResponseActions"; import { useAppSelector } from "../../redux/hooks"; import { selectUIConfig } from "../../redux/slices/configSlice"; import { deleteMessage } from "../../redux/slices/sessionSlice"; +import ThinkingIndicator from "./ThinkingIndicator"; interface StepContainerProps { item: ChatHistoryItem; @@ -95,8 +96,11 @@ export default function StepContainer(props: StepContainerProps) { itemIndex={props.index} /> )} + {props.isLast && ( + + )} - {/* We want to occupy space in the DOM regardless of whether the actions are visible to avoid jank on */} + {/* We want to occupy space in the DOM regardless of whether the actions are visible to avoid jank on stream complete */}
{!shouldHideActions && ( { + // Animation for thinking ellipses + const [animation, setAnimation] = useState(2); + useEffect(() => { + const interval = setInterval(() => { + setAnimation((prevState) => (prevState === 2 ? 0 : prevState + 1)); + }, 600); + return () => { + clearInterval(interval); + }; + }, []); + + const selectedModel = useAppSelector(selectDefaultModel); + const isStreaming = useAppSelector((state) => state.session.isStreaming); + + const hasContent = Array.isArray(messageContent) + ? !!messageContent.length + : !!messageContent; + const isO1 = selectedModel?.model.startsWith("o1"); + const isThinking = isStreaming && !hasContent; + if (!isThinking || !isO1) { + return null; + } + + return ( +
+ {`Thinking.${".".repeat(animation)}`} +
+ ); +}; + +export default ThinkingIndicator; From 5cff82297451a10a6d589c1e3fa48a300e464f8d Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 18 Dec 2024 10:57:39 -0800 Subject: [PATCH 71/84] don't show while gathering context --- .../components/StepContainer/StepContainer.tsx | 4 +--- .../StepContainer/ThinkingIndicator.tsx | 15 ++++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/gui/src/components/StepContainer/StepContainer.tsx b/gui/src/components/StepContainer/StepContainer.tsx index 6fb4603913..470b426673 100644 --- a/gui/src/components/StepContainer/StepContainer.tsx +++ b/gui/src/components/StepContainer/StepContainer.tsx @@ -96,9 +96,7 @@ export default function StepContainer(props: StepContainerProps) { itemIndex={props.index} /> )} - {props.isLast && ( - - )} + {props.isLast && } {/* We want to occupy space in the DOM regardless of whether the actions are visible to avoid jank on stream complete */}
diff --git a/gui/src/components/StepContainer/ThinkingIndicator.tsx b/gui/src/components/StepContainer/ThinkingIndicator.tsx index 9e7d710f4d..8757cae838 100644 --- a/gui/src/components/StepContainer/ThinkingIndicator.tsx +++ b/gui/src/components/StepContainer/ThinkingIndicator.tsx @@ -1,16 +1,16 @@ import { useEffect, useState } from "react"; import { useAppSelector } from "../../redux/hooks"; import { selectDefaultModel } from "../../redux/slices/configSlice"; -import { MessageContent } from "core"; +import { ChatHistoryItem } from "core"; interface ThinkingIndicatorProps { - messageContent: MessageContent; + historyItem: ChatHistoryItem; } /* Thinking animation Only for reasoning (long load time) models for now */ -const ThinkingIndicator = ({ messageContent }: ThinkingIndicatorProps) => { +const ThinkingIndicator = ({ historyItem }: ThinkingIndicatorProps) => { // Animation for thinking ellipses const [animation, setAnimation] = useState(2); useEffect(() => { @@ -25,11 +25,12 @@ const ThinkingIndicator = ({ messageContent }: ThinkingIndicatorProps) => { const selectedModel = useAppSelector(selectDefaultModel); const isStreaming = useAppSelector((state) => state.session.isStreaming); - const hasContent = Array.isArray(messageContent) - ? !!messageContent.length - : !!messageContent; + const hasContent = Array.isArray(historyItem.message.content) + ? !!historyItem.message.content.length + : !!historyItem.message.content; const isO1 = selectedModel?.model.startsWith("o1"); - const isThinking = isStreaming && !hasContent; + const isThinking = + isStreaming && !historyItem.isGatheringContext && !hasContent; if (!isThinking || !isO1) { return null; } From 5db1b558ac920a854a8076236764d5dd4f79e09a Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Wed, 18 Dec 2024 15:23:25 -0800 Subject: [PATCH 72/84] ci: further parallelize --- .github/workflows/pr_checks.yaml | 221 +++++++++++++++++++++++++++---- extensions/vscode/package.json | 2 +- 2 files changed, 196 insertions(+), 27 deletions(-) diff --git a/.github/workflows/pr_checks.yaml b/.github/workflows/pr_checks.yaml index b2578aa5e3..17fa5a8944 100644 --- a/.github/workflows/pr_checks.yaml +++ b/.github/workflows/pr_checks.yaml @@ -39,7 +39,7 @@ jobs: if: steps.root-cache.outputs.cache-hit != 'true' run: npm ci - core-checks: + install-core: needs: install-root runs-on: ubuntu-latest steps: @@ -66,6 +66,27 @@ jobs: cd core npm ci + core-checks: + needs: install-core + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - uses: actions/cache@v4 + id: root-cache + with: + path: node_modules + key: ${{ runner.os }}-root-node-modules-${{ hashFiles('package-lock.json') }} + + - uses: actions/cache@v4 + id: core-cache + with: + path: core/node_modules + key: ${{ runner.os }}-core-node-modules-${{ hashFiles('core/package-lock.json') }} + - name: Type check and lint run: | cd core @@ -74,8 +95,8 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - gui-checks: - needs: [install-root, core-checks] + install-gui: + needs: [install-root, install-core] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -105,13 +126,38 @@ jobs: cd gui npm ci + gui-checks: + needs: install-gui + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-root-node-modules-${{ hashFiles('package-lock.json') }} + + - uses: actions/cache@v4 + with: + path: core/node_modules + key: ${{ runner.os }}-core-node-modules-${{ hashFiles('core/package-lock.json') }} + + - uses: actions/cache@v4 + id: gui-cache + with: + path: gui/node_modules + key: ${{ runner.os }}-gui-node-modules-${{ hashFiles('gui/package-lock.json') }} + - name: Type check run: | cd gui npx tsc --noEmit binary-checks: - needs: [install-root, core-checks] + needs: [install-root, install-core] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -146,8 +192,8 @@ jobs: cd binary npx tsc --noEmit - vscode-checks: - needs: [install-root, core-checks] + install-vscode: + needs: [install-root, install-core] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -179,6 +225,31 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} + vscode-checks: + needs: install-vscode + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - uses: actions/cache@v4 + with: + path: node_modules + key: ${{ runner.os }}-root-node-modules-${{ hashFiles('package-lock.json') }} + + - uses: actions/cache@v4 + with: + path: core/node_modules + key: ${{ runner.os }}-core-node-modules-${{ hashFiles('core/package-lock.json') }} + + - uses: actions/cache@v4 + id: vscode-cache + with: + path: extensions/vscode/node_modules + key: ${{ runner.os }}-vscode-node-modules-${{ hashFiles('extensions/vscode/package-lock.json') }} + - name: Type check and lint run: | cd extensions/vscode @@ -186,7 +257,7 @@ jobs: npm run lint core-tests: - needs: [core-checks] + needs: install-core runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -204,9 +275,38 @@ jobs: cd core npm test - vscode-tests: - needs: [vscode-checks, core-checks] + vscode-get-test-file-matrix: runs-on: ubuntu-latest + needs: [install-root, install-vscode] + outputs: + test_file_matrix: ${{ steps.vscode-get-test-file-matrix.outputs.test_file_matrix }} + steps: + - uses: actions/checkout@v4 + + - name: Cache node modules + uses: actions/cache@v3 + with: + path: extensions/vscode/node_modules + key: ${{ runner.os }}-vscode-node-modules-${{ hashFiles('extensions/vscode/package-lock.json') }} + + - name: Get test files + id: vscode-get-test-file-matrix + run: | + cd extensions/vscode + npm ci + npm run e2e:compile + FILES=$(ls -1 e2e/_output/tests/*.test.js | jq -R . | jq -s .) + echo "test_file_matrix<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Debug Outputs + run: | + echo "Test files: ${{ steps.vscode-get-test-file-matrix.outputs.test_file_matrix }}" + + vscode-package-extension: + runs-on: ubuntu-latest + needs: [install-vscode, install-core] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -225,18 +325,88 @@ jobs: path: core/node_modules key: ${{ runner.os }}-core-node-modules-${{ hashFiles('core/package-lock.json') }} + - name: Package extension + run: | + cd extensions/vscode + npm run package + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: vscode-extension-build + path: extensions/vscode/build + + vscode-download-e2e-dependencies: + runs-on: ubuntu-latest + needs: [install-vscode, install-core] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + - uses: actions/cache@v4 id: storage-cache with: path: extensions/vscode/e2e/storage key: ${{ runner.os }}-vscode-storage-${{ hashFiles('extensions/vscode/package-lock.json') }} - - name: Download Dependencies if Cache Miss + - name: Download Dependencies if: steps.storage-cache.outputs.cache-hit != 'true' run: | cd extensions/vscode npm run e2e:ci:download + - name: Upload e2e dependencies + uses: actions/upload-artifact@v4 + with: + name: vscode-e2e-dependencies + path: extensions/vscode/e2e/storage + + vscode-e2e-tests: + name: ${{ matrix.test_file }}" + needs: + [ + vscode-download-e2e-dependencies, + vscode-get-test-file-matrix, + vscode-package-extension, + install-vscode, + install-core, + ] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + test_file: ${{ fromJson(needs.vscode-get-test-file-matrix.outputs.test_file_matrix) }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - uses: actions/cache@v4 + id: vscode-node-modules-cache + with: + path: extensions/vscode/node_modules + key: ${{ runner.os }}-vscode-node-modules-${{ hashFiles('extensions/vscode/package-lock.json') }} + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: vscode-extension-build + path: extensions/vscode/build + + - name: Download e2e dependencies + uses: actions/download-artifact@v4 + with: + name: vscode-e2e-dependencies + path: extensions/vscode/e2e/storage + + - name: Fix VSCode binary permissions + run: | + chmod +x extensions/vscode/e2e/storage/VSCode-linux-x64/code + chmod +x extensions/vscode/e2e/storage/chromedriver-linux64/chromedriver + - name: Set up SSH env: SSH_KEY: ${{ secrets.GH_ACTIONS_SSH_TEST_KEY_PEM }} @@ -248,24 +418,28 @@ jobs: ssh-keyscan -H "$SSH_HOST" >> ~/.ssh/known_hosts echo -e "Host ssh-test-container\n\tHostName $SSH_HOST\n\tUser ec2-user\n\tIdentityFile ~/.ssh/id_rsa" >> ~/.ssh/config - - name: Install Xvfb for Linux and run e2e tests + - name: Set up Xvfb + run: | + Xvfb :99 & + export DISPLAY=:99 + + - name: Run e2e tests run: | - sudo apt-get install -y xvfb # Install Xvfb - Xvfb :99 & # Start Xvfb - export DISPLAY=:99 # Export the display number to the environment cd extensions/vscode - npm run package - npm run e2e:ci:run + TEST_FILE="${{ matrix.test_file }}" npm run e2e:ci:run + + env: + DISPLAY: :99 - name: Upload e2e test screenshots if: failure() uses: actions/upload-artifact@v4 with: - name: e2e-screenshots + name: e2e-screenshots-${{ matrix.test_file }} path: extensions/vscode/e2e/storage/screenshots gui-tests: - needs: [gui-checks, core-checks] + needs: [install-gui, install-core] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -296,7 +470,7 @@ jobs: npm test jetbrains-tests: - needs: [install-root, core-checks] + needs: [install-root, install-core] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -331,12 +505,7 @@ jobs: path: gui/node_modules key: ${{ runner.os }}-gui-node-modules-${{ hashFiles('gui/package-lock.json') }} - - name: Build GUI - run: | - cd gui - npm ci - npm run build - + # We can shave off another minute off our CI script by finding a way to share this with vscode-tests - name: Run prepackage script run: | cd extensions/vscode @@ -371,7 +540,7 @@ jobs: with: url: http://127.0.0.1:8082 max-attempts: 15 - retry-delay: 30s + retry-delay: 5s - name: Run tests run: | diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 06c7337be7..dbca6152d5 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -629,7 +629,7 @@ "e2e:copy-vsix": "chmod +x ./e2e/get-latest-vsix.sh && bash ./e2e/get-latest-vsix.sh", "e2e:install-vsix": "extest install-vsix -f ./e2e/vsix/continue.vsix --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage", "e2e:install-extensions": "extest install-from-marketplace ms-vscode-remote.remote-ssh --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage && extest install-from-marketplace ms-vscode-remote.remote-containers --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage && extest install-from-marketplace ms-vscode-remote.remote-wsl --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage", - "e2e:test": "extest run-tests './e2e/_output/tests/*.test.js' --code_settings settings.json --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage", + "e2e:test": "extest run-tests ${TEST_FILE:-'./e2e/_output/tests/*.test.js'} --code_settings settings.json --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage", "e2e:clean": "rm -rf ./e2e/_output", "e2e:all": "npm run e2e:build && npm run e2e:compile && npm run e2e:create-storage && npm run e2e:get-chromedriver && npm run e2e:get-vscode && npm run e2e:sign-vscode && npm run e2e:copy-vsix && npm run e2e:install-vsix && npm run e2e:install-extensions && CONTINUE_GLOBAL_DIR=e2e/test-continue npm run e2e:test && npm run e2e:clean", "e2e:quick": "npm run e2e:compile && CONTINUE_GLOBAL_DIR=e2e/test-continue npm run e2e:test && npm run e2e:clean", From 1e7aa1a879c80c0acddf09fbe0d887b7ec9bd31d Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Wed, 18 Dec 2024 15:27:20 -0800 Subject: [PATCH 73/84] fix: caching --- .github/workflows/pr_checks.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/pr_checks.yaml b/.github/workflows/pr_checks.yaml index 17fa5a8944..e5f958aca5 100644 --- a/.github/workflows/pr_checks.yaml +++ b/.github/workflows/pr_checks.yaml @@ -345,6 +345,12 @@ jobs: with: node-version-file: ".nvmrc" + - uses: actions/cache@v4 + id: vscode-node-modules-cache + with: + path: extensions/vscode/node_modules + key: ${{ runner.os }}-vscode-node-modules-${{ hashFiles('extensions/vscode/package-lock.json') }} + - uses: actions/cache@v4 id: storage-cache with: From 1039e777c8a081ed9362f29e20c7c6133f3b07cd Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Wed, 18 Dec 2024 15:36:10 -0800 Subject: [PATCH 74/84] fix: artifact upload --- .github/workflows/pr_checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_checks.yaml b/.github/workflows/pr_checks.yaml index e5f958aca5..d15ab46429 100644 --- a/.github/workflows/pr_checks.yaml +++ b/.github/workflows/pr_checks.yaml @@ -441,7 +441,7 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: e2e-screenshots-${{ matrix.test_file }} + name: e2e-screenshots-${{ replace(matrix.test_file, '/', '_') }} path: extensions/vscode/e2e/storage/screenshots gui-tests: From 85e3fbc1c73872b1dfd8aee1f1dcef26b9f37761 Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Wed, 18 Dec 2024 15:39:23 -0800 Subject: [PATCH 75/84] fix: artifact name --- .github/workflows/pr_checks.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_checks.yaml b/.github/workflows/pr_checks.yaml index d15ab46429..b608698948 100644 --- a/.github/workflows/pr_checks.yaml +++ b/.github/workflows/pr_checks.yaml @@ -437,11 +437,16 @@ jobs: env: DISPLAY: :99 + - name: Set artifact name + shell: bash + run: | + echo "ARTIFACT_NAME=e2e-screenshots-$(echo '${{ matrix.test_file }}' | tr '/' '_')" >> $GITHUB_ENV + - name: Upload e2e test screenshots if: failure() uses: actions/upload-artifact@v4 with: - name: e2e-screenshots-${{ replace(matrix.test_file, '/', '_') }} + name: ${{ env.ARTIFACT_NAME }} path: extensions/vscode/e2e/storage/screenshots gui-tests: From 0bf9169a968a42dfc083696b3e8be5f1997ad7e5 Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Wed, 18 Dec 2024 15:49:12 -0800 Subject: [PATCH 76/84] fix: timeouts --- extensions/vscode/e2e/tests/SSH.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/extensions/vscode/e2e/tests/SSH.test.ts b/extensions/vscode/e2e/tests/SSH.test.ts index 664776a9cb..49a1547e7e 100644 --- a/extensions/vscode/e2e/tests/SSH.test.ts +++ b/extensions/vscode/e2e/tests/SSH.test.ts @@ -25,7 +25,7 @@ describe("SSH", function () { await TestUtils.waitForSuccess( () => SSHSelectors.connectedToRemoteConfirmationMessage(), - DEFAULT_TIMEOUT.XL, + DEFAULT_TIMEOUT.MD, ); await TestUtils.waitForSuccess(async () => { @@ -34,7 +34,7 @@ describe("SSH", function () { await inputBox.setText("/home/ec2-user/test-folder/main.py"); await inputBox.selectQuickPick("main.py"); await inputBox.sendKeys(Key.ENTER); - }, DEFAULT_TIMEOUT.XL); + }, DEFAULT_TIMEOUT.MD); const editor = await TestUtils.waitForSuccess( async () => (await new EditorView().openEditor("main.py")) as TextEditor, @@ -44,7 +44,6 @@ describe("SSH", function () { await editor.clearText(); await AutocompleteActions.testCompletions(editor); await editor.setText(text); - }) - .timeout(DEFAULT_TIMEOUT.XL) - .retries(2); + }).timeout(DEFAULT_TIMEOUT.XL); + // .retries(2); }); From f6f263375be129e438ab46a3ddbbba0722d3eb24 Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Wed, 18 Dec 2024 15:50:12 -0800 Subject: [PATCH 77/84] fix: ci --- .github/workflows/pr_checks.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_checks.yaml b/.github/workflows/pr_checks.yaml index b608698948..dd155fbdb7 100644 --- a/.github/workflows/pr_checks.yaml +++ b/.github/workflows/pr_checks.yaml @@ -438,15 +438,17 @@ jobs: DISPLAY: :99 - name: Set artifact name + id: set_artifact_name shell: bash run: | - echo "ARTIFACT_NAME=e2e-screenshots-$(echo '${{ matrix.test_file }}' | tr '/' '_')" >> $GITHUB_ENV + ARTIFACT_NAME="e2e-screenshots-$(echo '${{ matrix.test_file }}' | tr '/' '_')" + echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT - name: Upload e2e test screenshots if: failure() uses: actions/upload-artifact@v4 with: - name: ${{ env.ARTIFACT_NAME }} + name: ${{ steps.set_artifact_name.outputs.artifact_name }} path: extensions/vscode/e2e/storage/screenshots gui-tests: From 42a03ad020634a7c6be719a70bf40144582682dc Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Wed, 18 Dec 2024 15:56:08 -0800 Subject: [PATCH 78/84] fix: naming --- .github/workflows/pr_checks.yaml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/pr_checks.yaml b/.github/workflows/pr_checks.yaml index dd155fbdb7..e5f44c03e3 100644 --- a/.github/workflows/pr_checks.yaml +++ b/.github/workflows/pr_checks.yaml @@ -437,18 +437,11 @@ jobs: env: DISPLAY: :99 - - name: Set artifact name - id: set_artifact_name - shell: bash - run: | - ARTIFACT_NAME="e2e-screenshots-$(echo '${{ matrix.test_file }}' | tr '/' '_')" - echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT - - name: Upload e2e test screenshots if: failure() uses: actions/upload-artifact@v4 with: - name: ${{ steps.set_artifact_name.outputs.artifact_name }} + name: e2e-failure-screenshots path: extensions/vscode/e2e/storage/screenshots gui-tests: From adb7d3a85005a1a49683dfbfdd7fdb0c282fc48b Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Wed, 18 Dec 2024 15:56:42 -0800 Subject: [PATCH 79/84] test: restore retries --- extensions/vscode/e2e/tests/SSH.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/vscode/e2e/tests/SSH.test.ts b/extensions/vscode/e2e/tests/SSH.test.ts index 49a1547e7e..1abf66db9a 100644 --- a/extensions/vscode/e2e/tests/SSH.test.ts +++ b/extensions/vscode/e2e/tests/SSH.test.ts @@ -44,6 +44,7 @@ describe("SSH", function () { await editor.clearText(); await AutocompleteActions.testCompletions(editor); await editor.setText(text); - }).timeout(DEFAULT_TIMEOUT.XL); - // .retries(2); + }) + .timeout(DEFAULT_TIMEOUT.XL) + .retries(2); }); From 925dc0a6b824d134c7eadb4388de377a93d0f544 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 18 Dec 2024 16:31:16 -0800 Subject: [PATCH 80/84] more common errors --- gui/src/pages/gui/StreamError.tsx | 136 +++++++++++++++++++++++------- 1 file changed, 104 insertions(+), 32 deletions(-) diff --git a/gui/src/pages/gui/StreamError.tsx b/gui/src/pages/gui/StreamError.tsx index 47fc790dea..de5e0b719d 100644 --- a/gui/src/pages/gui/StreamError.tsx +++ b/gui/src/pages/gui/StreamError.tsx @@ -56,27 +56,115 @@ const StreamErrorDialog = ({ error }: StreamErrorProps) => { } } } - let errorContent: React.ReactNode = ( - Error while streaming chat response. + + const checkKeysButton = apiKeyUrl ? ( + + ) : null; + + const configButton = ( + { + ideMessenger.post("config/openProfile", { + profileId: undefined, + }); + }} + > +
+ +
+ Open config +
); + let errorContent: React.ReactNode = <>; + + // Display components for specific errors if (statusCode === 429) { errorContent = (
- {`This likely means your ${modelTitle} usage has been rate limited + {`This might mean your ${modelTitle} usage has been rate limited by ${providerName}.`} - {apiKeyUrl ? ( - - ) : null} +
+ {checkKeysButton} + {configButton} +
+
+ ); + } + + if (statusCode === 404) { + errorContent = ( +
+ Likely causes: +
    +
  • + Invalid + apiBase + {selectedModel && ( + <> + {`: `} + {selectedModel.apiBase} + + )} +
  • +
  • + Model/deployment not found + {selectedModel && ( + <> + {` for: `} + {selectedModel.model} + + )} +
  • +
+
{configButton}
+
+ ); + } + + if (statusCode === 401) { + errorContent = ( +
+ {`Likely cause: your API key is invalid.`} +
+ {checkKeysButton} + {configButton} +
+
+ ); + } + + if (statusCode === 403) { + errorContent = ( +
+ {`Likely cause: not authorized to access the model deployment.`} +
+ {checkKeysButton} + {configButton} +
+
+ ); + } + + if ( + message && + (message.toLowerCase().includes("overloaded") || + message.toLowerCase().includes("malformed")) + ) { + errorContent = ( +
+ {`Most likely, the provider${selectedModel ? " " + selectedModel.provider : ""}'s servers are overloaded and streaming was interrupted. Try again later`} + {/* TODO: status page links for providers? */}
); } @@ -84,13 +172,10 @@ const StreamErrorDialog = ({ error }: StreamErrorProps) => { return (

{`${statusCode ? statusCode + " " : ""}Error`}

-
{errorContent}
{message ? (
- - {message} - + {message}
{ @@ -101,22 +186,9 @@ const StreamErrorDialog = ({ error }: StreamErrorProps) => {
) : null} +
{errorContent}
+
-
- { - ideMessenger.post("config/openProfile", { - profileId: undefined, - }); - }} - > -
- -
- Open config -
-
Report this error:
Date: Wed, 18 Dec 2024 16:38:52 -0800 Subject: [PATCH 81/84] chat errors cleanup --- gui/src/pages/gui/StreamError.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gui/src/pages/gui/StreamError.tsx b/gui/src/pages/gui/StreamError.tsx index de5e0b719d..1694fe4109 100644 --- a/gui/src/pages/gui/StreamError.tsx +++ b/gui/src/pages/gui/StreamError.tsx @@ -163,7 +163,13 @@ const StreamErrorDialog = ({ error }: StreamErrorProps) => { ) { errorContent = (
- {`Most likely, the provider${selectedModel ? " " + selectedModel.provider : ""}'s servers are overloaded and streaming was interrupted. Try again later`} + {`Most likely, the provider's server(s) are overloaded and streaming was interrupted. Try again later`} + {selectedModel ? ( + + {`Provider: `} + {selectedModel.provider} + + ) : null} {/* TODO: status page links for providers? */}
); From e0385d354355db935d93fc76a09079d660d19f22 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Wed, 18 Dec 2024 17:54:28 -0800 Subject: [PATCH 82/84] relative path on filenamelink hover --- gui/src/components/markdown/FilenameLink.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gui/src/components/markdown/FilenameLink.tsx b/gui/src/components/markdown/FilenameLink.tsx index f1013b42f6..4e4d934e96 100644 --- a/gui/src/components/markdown/FilenameLink.tsx +++ b/gui/src/components/markdown/FilenameLink.tsx @@ -2,7 +2,7 @@ import { RangeInFile } from "core"; import { useContext } from "react"; import { IdeMessengerContext } from "../../context/IdeMessenger"; import FileIcon from "../FileIcon"; -import { getUriPathBasename } from "core/util/uri"; +import { findUriInDirs, getUriPathBasename } from "core/util/uri"; import { ToolTip } from "../gui/Tooltip"; import { v4 as uuidv4 } from "uuid"; @@ -23,6 +23,11 @@ function FilenameLink({ rif }: FilenameLinkProps) { const id = uuidv4(); + const { relativePathOrBasename } = findUriInDirs( + rif.filepath, + window.workspacePaths ?? [], + ); + return ( <> - {rif.filepath} + {"/" + relativePathOrBasename} ); From d232c87e792d54332009a24c854f76b0d0f0c596 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Thu, 19 Dec 2024 10:48:37 -0800 Subject: [PATCH 83/84] Update SambaNova.md --- docs/docs/customize/model-providers/more/SambaNova.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/customize/model-providers/more/SambaNova.md b/docs/docs/customize/model-providers/more/SambaNova.md index 9c20ba4d76..f35c56dd52 100644 --- a/docs/docs/customize/model-providers/more/SambaNova.md +++ b/docs/docs/customize/model-providers/more/SambaNova.md @@ -1,6 +1,6 @@ # SambaNova Cloud -The SambaNova Cloud is a cloud platform for running large AI models with the world record Llama 3.1 70B/405B performance. You can sign up [here](https://cloud.sambanova.ai/), copy your API key on the initial welcome screen, and then hit the play button on any model from the [model list](https://community.sambanova.ai/t/quick-start-guide/104). +The SambaNova Cloud is a cloud platform for running large AI models with the world record Llama 3.1 70B/405B performance. You can follow the instructions in [this blog post](https://sambanova.ai/blog/accelerating-coding-with-sambanova-cloud?ref=blog.continue.dev) to configure your setup. ```json title="config.json" { From 93c95075425179d27421c7b54d92c9235d914c3c Mon Sep 17 00:00:00 2001 From: tomasz-io Date: Thu, 19 Dec 2024 11:02:14 -0800 Subject: [PATCH 84/84] fix: close sidebar when cmd+l pressed on focus --- extensions/vscode/e2e/TestUtils.ts | 9 +++- extensions/vscode/e2e/actions/GUI.actions.ts | 40 +++++++++++++++-- .../e2e/actions/KeyboardShortcuts.actions.ts | 17 ++++---- extensions/vscode/e2e/tests/GUI.test.ts | 2 +- .../e2e/tests/KeyboardShortcuts.test.ts | 35 +++++++++++---- extensions/vscode/src/commands.ts | 43 ++++++++++++------- 6 files changed, 108 insertions(+), 38 deletions(-) diff --git a/extensions/vscode/e2e/TestUtils.ts b/extensions/vscode/e2e/TestUtils.ts index 92986e498d..e1ae22227d 100644 --- a/extensions/vscode/e2e/TestUtils.ts +++ b/extensions/vscode/e2e/TestUtils.ts @@ -37,8 +37,8 @@ export class TestUtils { throw new Error(`Element not found after ${timeout}ms timeout`); } - public static async expectNoElement( - locatorFn: () => Promise, + public static async expectNoElement( + locatorFn: () => Promise, timeout: number = 1000, interval: number = 200, ): Promise { @@ -48,6 +48,7 @@ export class TestUtils { while (Date.now() - startTime < timeout) { try { const element = await locatorFn(); + console.log("ELEMENT", element); if (element) { elementFound = true; break; @@ -71,6 +72,10 @@ export class TestUtils { }; } + public static waitForTimeout(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + public static get isMacOS(): boolean { return process.platform === "darwin"; } diff --git a/extensions/vscode/e2e/actions/GUI.actions.ts b/extensions/vscode/e2e/actions/GUI.actions.ts index 402176b20c..567a2f5721 100644 --- a/extensions/vscode/e2e/actions/GUI.actions.ts +++ b/extensions/vscode/e2e/actions/GUI.actions.ts @@ -1,8 +1,33 @@ -import { Key, WebElement, WebView, Workbench } from "vscode-extension-tester"; +import { + InputBox, + Key, + WebDriver, + WebElement, + WebView, + Workbench, +} from "vscode-extension-tester"; import { GUISelectors } from "../selectors/GUI.selectors"; import { TestUtils } from "../TestUtils"; +import { DEFAULT_TIMEOUT } from "../constants"; export class GUIActions { + public static moveContinueToSidebar = async (driver: WebDriver) => { + await GUIActions.toggleGui(); + await TestUtils.waitForSuccess(async () => { + await new Workbench().executeCommand("View: Move View"); + await (await InputBox.create(DEFAULT_TIMEOUT.MD)).selectQuickPick(4); + await (await InputBox.create(DEFAULT_TIMEOUT.MD)).selectQuickPick(14); + }); + + // first call focuses the input + await TestUtils.waitForTimeout(DEFAULT_TIMEOUT.XS); + await GUIActions.executeFocusContinueInputShortcut(driver); + + // second call closes the gui + await TestUtils.waitForTimeout(DEFAULT_TIMEOUT.XS); + await GUIActions.executeFocusContinueInputShortcut(driver); + }; + public static switchToReactIframe = async () => { const view = new WebView(); const driver = view.getDriver(); @@ -19,7 +44,7 @@ export class GUIActions { } if (!continueIFrame) { - throw new Error("Could not find continue iframe"); + throw new Error("Could not find Continue iframe"); } await driver.switchTo().frame(continueIFrame); @@ -41,7 +66,7 @@ export class GUIActions { }; }; - public static openGui = async () => { + public static toggleGui = async () => { return TestUtils.waitForSuccess(() => new Workbench().executeCommand("continue.focusContinueInput"), ); @@ -77,4 +102,13 @@ export class GUIActions { await editor.sendKeys(message); await editor.sendKeys(Key.ENTER); } + + public static async executeFocusContinueInputShortcut(driver: WebDriver) { + return driver + .actions() + .keyDown(TestUtils.osControlKey) + .sendKeys("l") + .keyUp(TestUtils.osControlKey) + .perform(); + } } diff --git a/extensions/vscode/e2e/actions/KeyboardShortcuts.actions.ts b/extensions/vscode/e2e/actions/KeyboardShortcuts.actions.ts index dfa6dee150..0a4cc51ad5 100644 --- a/extensions/vscode/e2e/actions/KeyboardShortcuts.actions.ts +++ b/extensions/vscode/e2e/actions/KeyboardShortcuts.actions.ts @@ -1,14 +1,15 @@ -import { WebDriver } from "vscode-extension-tester"; +import { TextEditor, WebDriver, WebView } from "vscode-extension-tester"; import { TestUtils } from "../TestUtils"; export class KeyboardShortcutsActions { - public static async executeFocusContinueInput(driver: WebDriver) { - return await driver - .actions() - .keyDown(TestUtils.osControlKey) - .sendKeys("l") - .keyUp(TestUtils.osControlKey) - .perform(); + /** + * For some reason Selenium-simulated keyboard shortcuts don't perfectly + * mimic the behavior of real shortcuts unless some text is highlighted first. + */ + public static async HACK__typeWithSelect(editor: TextEditor, text: string) { + await editor.typeText(text); + await editor.selectText(text); + await editor.typeText(text); } } diff --git a/extensions/vscode/e2e/tests/GUI.test.ts b/extensions/vscode/e2e/tests/GUI.test.ts index 9b3eca3e07..60b6723b87 100644 --- a/extensions/vscode/e2e/tests/GUI.test.ts +++ b/extensions/vscode/e2e/tests/GUI.test.ts @@ -18,7 +18,7 @@ describe("GUI Test", () => { beforeEach(async function () { this.timeout(DEFAULT_TIMEOUT.XL); - await GUIActions.openGui(); + await GUIActions.toggleGui(); ({ view, driver } = await GUIActions.switchToReactIframe()); await GUIActions.selectModelFromDropdown(view, "TEST LLM"); diff --git a/extensions/vscode/e2e/tests/KeyboardShortcuts.test.ts b/extensions/vscode/e2e/tests/KeyboardShortcuts.test.ts index b15d4ae81b..837372bab9 100644 --- a/extensions/vscode/e2e/tests/KeyboardShortcuts.test.ts +++ b/extensions/vscode/e2e/tests/KeyboardShortcuts.test.ts @@ -6,19 +6,24 @@ import { TextEditor, Workbench, WebView, + WebElement, } from "vscode-extension-tester"; import { DEFAULT_TIMEOUT } from "../constants"; import { GUISelectors } from "../selectors/GUI.selectors"; import { GUIActions } from "../actions/GUI.actions"; import { expect } from "chai"; import { TestUtils } from "../TestUtils"; -import { KeyboardShortcutsActions } from "../actions/KeyboardShortcuts.actions"; describe("Cmd+L Shortcut Test", () => { let driver: WebDriver; let editor: TextEditor; let view: WebView; + before(async function () { + this.timeout(DEFAULT_TIMEOUT.XL); + await GUIActions.moveContinueToSidebar(VSBrowser.instance.driver); + }); + beforeEach(async function () { this.timeout(DEFAULT_TIMEOUT.XL); @@ -38,11 +43,6 @@ describe("Cmd+L Shortcut Test", () => { this.timeout(DEFAULT_TIMEOUT.XL * 1000); await view.switchBack(); await editor.clearText(); - await TestUtils.waitForSuccess( - async () => (await GUISelectors.getContinueExtensionBadge(view)).click(), - DEFAULT_TIMEOUT.XS, - ); - await new EditorView().closeAllEditors(); }); @@ -51,13 +51,30 @@ describe("Cmd+L Shortcut Test", () => { await editor.setText(text); - await KeyboardShortcutsActions.executeFocusContinueInput(driver); + await GUIActions.executeFocusContinueInputShortcut(driver); ({ view } = await GUIActions.switchToReactIframe()); await TestUtils.expectNoElement(async () => { return GUISelectors.getInputBoxCodeBlockAtIndex(view, 0); }, DEFAULT_TIMEOUT.XS); + await GUIActions.executeFocusContinueInputShortcut(driver); + }).timeout(DEFAULT_TIMEOUT.XL); + + it("Fresh VS Code window → sidebar closed → cmd+L with no code highlighted → opens sidebar and focuses input → cmd+L closes sidebar", async () => { + await GUIActions.executeFocusContinueInputShortcut(driver); + ({ view } = await GUIActions.switchToReactIframe()); + const textInput = await TestUtils.waitForSuccess(() => + GUISelectors.getMessageInputFieldAtIndex(view, 0), + ); + const activeElement: WebElement = await driver.switchTo().activeElement(); + const textInputHtml = await textInput.getAttribute("outerHTML"); + const activeElementHtml = await activeElement.getAttribute("outerHTML"); + expect(textInputHtml).to.equal(activeElementHtml); + expect(await textInput.isDisplayed()).to.equal(true); + + await GUIActions.executeFocusContinueInputShortcut(driver); + expect(await textInput.isDisplayed()).to.equal(false); }).timeout(DEFAULT_TIMEOUT.XL); it("Should create a code block when Cmd+L is pressed with text highlighted", async () => { @@ -66,7 +83,7 @@ describe("Cmd+L Shortcut Test", () => { await editor.setText(text); await editor.selectText(text); - await KeyboardShortcutsActions.executeFocusContinueInput(driver); + await GUIActions.executeFocusContinueInputShortcut(driver); ({ view } = await GUIActions.switchToReactIframe()); @@ -78,5 +95,7 @@ describe("Cmd+L Shortcut Test", () => { ); expect(codeblockContent).to.equal(text); + + await GUIActions.executeFocusContinueInputShortcut(driver); }).timeout(DEFAULT_TIMEOUT.XL); }); diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts index ffb0b400d1..690063628f 100644 --- a/extensions/vscode/src/commands.ts +++ b/extensions/vscode/src/commands.ts @@ -432,6 +432,12 @@ const getCommandsMap: ( core.invoke("context/indexDocs", { reIndex: true }); }, "continue.focusContinueInput": async () => { + const isContinueInputFocused = await sidebar.webviewProtocol.request( + "isContinueInputFocused", + undefined, + false, + ); + // This is a temporary fix—sidebar.webviewProtocol.request is blocking // when the GUI hasn't yet been setup and we should instead be // immediately throwing an error, or returning a Result object @@ -448,11 +454,6 @@ const getCommandsMap: ( undefined, false, ); - const isContinueInputFocused = await sidebar.webviewProtocol.request( - "isContinueInputFocused", - undefined, - false, - ); if (isContinueInputFocused) { if (historyLength === 0) { @@ -475,19 +476,23 @@ const getCommandsMap: ( } }, "continue.focusContinueInputWithoutClear": async () => { + const isContinueInputFocused = await sidebar.webviewProtocol.request( + "isContinueInputFocused", + undefined, + false, + ); + // This is a temporary fix—sidebar.webviewProtocol.request is blocking // when the GUI hasn't yet been setup and we should instead be // immediately throwing an error, or returning a Result object + focusGUI(); if (!sidebar.isReady) { - focusGUI(); - return; + const isReady = await waitForSidebarReady(sidebar, 5000, 100); + if (!isReady) { + return; + } } - const isContinueInputFocused = await sidebar.webviewProtocol.request( - "isContinueInputFocused", - undefined, - ); - if (isContinueInputFocused) { hideGUI(); } else { @@ -721,7 +726,10 @@ const getCommandsMap: ( "continue.toggleFullScreen": async () => { focusGUI(); - const sessionId = await sidebar.webviewProtocol.request("getCurrentSessionId", undefined); + const sessionId = await sidebar.webviewProtocol.request( + "getCurrentSessionId", + undefined, + ); // Check if full screen is already open by checking open tabs const fullScreenTab = getFullScreenTab(); @@ -755,11 +763,14 @@ const getCommandsMap: ( undefined, true, ); - + panel.onDidChangeViewState(() => { vscode.commands.executeCommand("continue.newSession"); - if(sessionId){ - vscode.commands.executeCommand("continue.focusContinueSessionId", sessionId); + if (sessionId) { + vscode.commands.executeCommand( + "continue.focusContinueSessionId", + sessionId, + ); } });