diff --git a/src/ClaudeDev.ts b/src/ClaudeDev.ts index d6e28fd..28ce829 100644 --- a/src/ClaudeDev.ts +++ b/src/ClaudeDev.ts @@ -29,6 +29,7 @@ import { regexSearchFiles } from "./utils/ripgrep" import { parseMentions } from "./utils/context-mentions" import { UrlContentFetcher } from "./utils/UrlContentFetcher" import { diagnosticsToProblemsString, getNewDiagnostics } from "./utils/diagnostics" +import { arePathsEqual } from "./utils/path-helpers" const SYSTEM_PROMPT = async ( supportsImages: boolean @@ -786,7 +787,9 @@ export class ClaudeDev { // if the file is already open, ensure it's not dirty before getting its contents if (fileExists) { - const existingDocument = vscode.workspace.textDocuments.find((doc) => doc.uri.fsPath === absolutePath) + const existingDocument = vscode.workspace.textDocuments.find((doc) => + arePathsEqual(doc.uri.fsPath, absolutePath) + ) if (existingDocument && existingDocument.isDirty) { await existingDocument.save() } @@ -861,7 +864,10 @@ export class ClaudeDev { const tabs = vscode.window.tabGroups.all .map((tg) => tg.tabs) .flat() - .filter((tab) => tab.input instanceof vscode.TabInputText && tab.input.uri.fsPath === absolutePath) + .filter( + (tab) => + tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, absolutePath) + ) for (const tab of tabs) { await vscode.window.tabGroups.close(tab) // console.log(`Closed tab for ${absolutePath}`) @@ -1291,11 +1297,11 @@ export class ClaudeDev { getReadablePath(relPath: string): string { // path.resolve is flexible in that it will resolve relative paths like '../../' to the cwd and even ignore the cwd if the relPath is actually an absolute path const absolutePath = path.resolve(cwd, relPath) - if (cwd === path.join(os.homedir(), "Desktop")) { + if (arePathsEqual(cwd, path.join(os.homedir(), "Desktop"))) { // User opened vscode without a workspace, so cwd is the Desktop. Show the full absolute path to keep the user aware of where files are being created return absolutePath.toPosix() } - if (path.normalize(absolutePath) === path.normalize(cwd)) { + if (arePathsEqual(path.normalize(absolutePath), path.normalize(cwd))) { return path.basename(absolutePath).toPosix() } else { // show the relative path to the cwd @@ -2107,7 +2113,7 @@ ${this.customInstructions.trim()} if (includeFileDetails) { details += `\n\n# Current Working Directory (${cwd.toPosix()}) Files\n` - const isDesktop = cwd === path.join(os.homedir(), "Desktop") + const isDesktop = arePathsEqual(cwd, path.join(os.homedir(), "Desktop")) if (isDesktop) { // don't want to immediately access desktop since it would show permission popup details += "(Desktop files not shown automatically. Use list_files to explore if needed.)" diff --git a/src/integrations/TerminalManager.ts b/src/integrations/TerminalManager.ts index 0f8f28f..5508a15 100644 --- a/src/integrations/TerminalManager.ts +++ b/src/integrations/TerminalManager.ts @@ -2,6 +2,7 @@ import { EventEmitter } from "events" import pWaitFor from "p-wait-for" import stripAnsi from "strip-ansi" import * as vscode from "vscode" +import { arePathsEqual } from "../utils/path-helpers" /* TerminalManager: @@ -225,7 +226,7 @@ export class TerminalManager { if (!terminalCwd) { return false } - return vscode.Uri.file(cwd).fsPath === terminalCwd.fsPath + return arePathsEqual(vscode.Uri.file(cwd).fsPath, terminalCwd.fsPath) }) if (availableTerminal) { this.terminalIds.add(availableTerminal.id) diff --git a/src/parse-source-code/index.ts b/src/parse-source-code/index.ts index dc5b522..31f2988 100644 --- a/src/parse-source-code/index.ts +++ b/src/parse-source-code/index.ts @@ -3,6 +3,7 @@ import { globby, Options } from "globby" import os from "os" import * as path from "path" import { LanguageParser, loadRequiredLanguageParsers } from "./languageParser" +import { arePathsEqual } from "../utils/path-helpers" // TODO: implement caching behavior to avoid having to keep analyzing project for new tasks. export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Promise { @@ -57,12 +58,12 @@ export async function listFiles(dirPath: string, recursive: boolean, limit: numb const absolutePath = path.resolve(dirPath) // Do not allow listing files in root or home directory, which Claude tends to want to do when the user's prompt is vague. const root = process.platform === "win32" ? path.parse(absolutePath).root : "/" - const isRoot = absolutePath === root + const isRoot = arePathsEqual(absolutePath, root) if (isRoot) { return [[root], false] } const homeDir = os.homedir() - const isHomeDir = absolutePath === homeDir + const isHomeDir = arePathsEqual(absolutePath, homeDir) if (isHomeDir) { return [[homeDir], false] } diff --git a/src/utils/open-file.ts b/src/utils/open-file.ts index ce3f590..91e121a 100644 --- a/src/utils/open-file.ts +++ b/src/utils/open-file.ts @@ -1,6 +1,7 @@ import * as path from "path" import * as os from "os" import * as vscode from "vscode" +import { arePathsEqual } from "./path-helpers" export async function openImage(dataUri: string) { const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/) @@ -27,7 +28,7 @@ export async function openFile(absolutePath: string) { try { for (const group of vscode.window.tabGroups.all) { const existingTab = group.tabs.find( - (tab) => tab.input instanceof vscode.TabInputText && tab.input.uri.fsPath === uri.fsPath + (tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, uri.fsPath) ) if (existingTab) { const activeColumn = vscode.window.activeTextEditor?.viewColumn diff --git a/src/utils/path-helpers.ts b/src/utils/path-helpers.ts index 94530f2..9b87e7c 100644 --- a/src/utils/path-helpers.ts +++ b/src/utils/path-helpers.ts @@ -46,30 +46,30 @@ String.prototype.toPosix = function (this: string): string { } // Safe path comparison that works across different platforms -// export function arePathsEqual(path1?: string, path2?: string): boolean { -// if (!path1 && !path2) { -// return true -// } -// if (!path1 || !path2) { -// return false -// } +export function arePathsEqual(path1?: string, path2?: string): boolean { + if (!path1 && !path2) { + return true + } + if (!path1 || !path2) { + return false + } -// path1 = normalizePath(path1) -// path2 = normalizePath(path2) + path1 = normalizePath(path1) + path2 = normalizePath(path2) -// if (process.platform === "win32") { -// return path1.toLowerCase() === path2.toLowerCase() -// } -// return path1 === path2 -// } + if (process.platform === "win32") { + return path1.toLowerCase() === path2.toLowerCase() + } + return path1 === path2 +} -// function normalizePath(p: string): string { -// // normalize resolve ./.. segments, removes duplicate slashes, and standardizes path separators -// let normalized = path.normalize(p) -// // however it doesn't remove trailing slashes -// // remove trailing slash, except for root paths -// if (normalized.length > 1 && (normalized.endsWith("/") || normalized.endsWith("\\"))) { -// normalized = normalized.slice(0, -1) -// } -// return normalized -// } +function normalizePath(p: string): string { + // normalize resolve ./.. segments, removes duplicate slashes, and standardizes path separators + let normalized = path.normalize(p) + // however it doesn't remove trailing slashes + // remove trailing slash, except for root paths + if (normalized.length > 1 && (normalized.endsWith("/") || normalized.endsWith("\\"))) { + normalized = normalized.slice(0, -1) + } + return normalized +}