diff --git a/src/common/Helpers/FileHelpers.cs b/src/common/Helpers/FileHelpers.cs index 404ebe9a..bce34cf3 100644 --- a/src/common/Helpers/FileHelpers.cs +++ b/src/common/Helpers/FileHelpers.cs @@ -18,6 +18,7 @@ public static IEnumerable FindFiles(string path, string pattern) public static IEnumerable FindFiles(string fileNames) { + fileNames = PathHelpers.ExpandPath(fileNames); var currentDir = Directory.GetCurrentDirectory(); foreach (var item in fileNames.Split(new char[] { ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) { @@ -103,11 +104,14 @@ public static IEnumerable FindFilesInOsPath(string fileName) public static bool FileExists(string? fileName) { - return !string.IsNullOrEmpty(fileName) && (File.Exists(fileName) || fileName == "-"); + if (string.IsNullOrEmpty(fileName)) return false; + fileName = PathHelpers.ExpandPath(fileName); + return File.Exists(fileName) || fileName == "-"; } public static bool IsFileMatch(string fileName, List includeFileContainsPatternList, List excludeFileContainsPatternList) { + fileName = PathHelpers.ExpandPath(fileName); var checkContent = includeFileContainsPatternList.Any() || excludeFileContainsPatternList.Any(); if (!checkContent) return true; @@ -185,6 +189,7 @@ public static bool IsFileMatch(string fileName, List includeFileContainsP public static void ReadIgnoreFile(string ignoreFile, out List excludeGlobs, out List excludeFileNamePatternList) { + ignoreFile = PathHelpers.ExpandPath(ignoreFile); ConsoleHelpers.WriteDebugLine($"ReadIgnoreFile: ignoreFile: {ignoreFile}"); excludeGlobs = new List(); @@ -355,6 +360,7 @@ public static bool IsFileTimeMatch(string fileName, DateTime? accessedAfter, DateTime? accessedBefore, DateTime? anyTimeAfter, DateTime? anyTimeBefore) { + fileName = PathHelpers.ExpandPath(fileName); try { var fileInfo = new FileInfo(fileName); @@ -436,6 +442,7 @@ public static string MakeRelativePath(string fullPath) public static string ReadAllText(string fileName) { + fileName = PathHelpers.ExpandPath(fileName); var content = ConsoleHelpers.IsStandardInputReference(fileName) ? string.Join("\n", ConsoleHelpers.GetAllLinesFromStdin()) : File.ReadAllText(fileName, Encoding.UTF8); @@ -445,6 +452,7 @@ public static string ReadAllText(string fileName) public static string[] ReadAllLines(string fileName) { + fileName = PathHelpers.ExpandPath(fileName); var lines = ConsoleHelpers.IsStandardInputReference(fileName) ? ConsoleHelpers.GetAllLinesFromStdin().ToArray() : File.ReadAllLines(fileName, Encoding.UTF8); @@ -454,6 +462,7 @@ public static string[] ReadAllLines(string fileName) public static string WriteAllText(string fileName, string content, string? saveToFolderOnAccessDenied = null) { + fileName = PathHelpers.ExpandPath(fileName); try { DirectoryHelpers.EnsureDirectoryForFileExists(fileName); @@ -478,6 +487,7 @@ public static string WriteAllText(string fileName, string content, string? saveT public static void AppendAllText(string fileName, string trajectoryContent) { + fileName = PathHelpers.ExpandPath(fileName); DirectoryHelpers.EnsureDirectoryForFileExists(fileName); File.AppendAllText(fileName, trajectoryContent, Encoding.UTF8); } @@ -638,6 +648,9 @@ private static char[] GetInvalidFileNameCharsForWeb() { if (fileNames == null || fileNames.Length == 0) return null; + + // Expand tilde paths for all filenames + fileNames = fileNames.Select(PathHelpers.ExpandPath).ToArray(); var currentPath = Directory.GetCurrentDirectory(); diff --git a/src/common/Helpers/PathHelpers.cs b/src/common/Helpers/PathHelpers.cs index eca4681a..b48226d5 100644 --- a/src/common/Helpers/PathHelpers.cs +++ b/src/common/Helpers/PathHelpers.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.IO; + public class PathHelpers { public static string? Combine(string path1, string path2) @@ -47,4 +51,32 @@ public static string NormalizePath(string outputDirectory) ? normalized.Substring(cwd.Length + 1) : normalized; } + + /// + /// Expands tilde (~) paths to full paths using the user's home directory. + /// Handles both "~" (home directory) and "~/path" (home directory + path). + /// + /// The path that may contain a tilde + /// The expanded path with tilde replaced by the home directory + public static string ExpandPath(string path) + { + if (string.IsNullOrEmpty(path)) + return path; + + // Handle exact "~" case + if (path == "~") + { + return Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + } + + // Handle "~/..." case + if (path.StartsWith("~/")) + { + var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return Path.Combine(homeDir, path.Substring(2)); + } + + // Return unchanged if no tilde expansion needed + return path; + } } diff --git a/src/cycod/FunctionCallingTools/StrReplaceEditorHelperFunctions.cs b/src/cycod/FunctionCallingTools/StrReplaceEditorHelperFunctions.cs index a32b5602..e5c851f7 100644 --- a/src/cycod/FunctionCallingTools/StrReplaceEditorHelperFunctions.cs +++ b/src/cycod/FunctionCallingTools/StrReplaceEditorHelperFunctions.cs @@ -12,6 +12,8 @@ public class StrReplaceEditorHelperFunctions [Description("Returns a list of non-hidden files and directories up to 2 levels deep.")] public string ListFiles([Description("Absolute or relative path to directory.")] string path) { + path = PathHelpers.ExpandPath(path); + if (Directory.Exists(path)) { path = Path.GetFullPath(path); @@ -54,6 +56,8 @@ public string ViewFile( [Description("Maximum number of characters to display per line.")] int maxCharsPerLine = 500, [Description("Maximum total number of characters to display.")] int maxTotalChars = 100000) { + path = PathHelpers.ExpandPath(path); + // Basic file validation var noFile = Directory.Exists(path) || !File.Exists(path); if (noFile) return $"Path {path} does not exist or is not a file."; @@ -296,6 +300,7 @@ public string CreateFile( [Description("Absolute or relative path to file.")] string path, [Description("Content to be written to the file.")] string fileText) { + if (File.Exists(path)) { return $"Path {path} already exists; cannot create file. Use ViewFile and then ReplaceOneInFile to edit the file."; @@ -312,6 +317,7 @@ public string ReplaceFileContent( [Description("New content to replace the entire file.")] string newContent, [Description("Current line count of the file (for verification).")] int oldContentLineCount) { + if (!File.Exists(path)) { return $"File {path} does not exist. Use CreateFile to create a new file."; @@ -392,6 +398,7 @@ public string ReplaceOneInFile( [Description("Existing text in the file that should be replaced. Must match exactly one occurrence.")] string oldStr, [Description("New string content that will replace the old string.")] string newStr) { + if (!File.Exists(path)) { return $"File {path} does not exist."; @@ -422,6 +429,7 @@ public string ReplaceMultipleInFile( [Description("Array of old strings to be replaced. Each must match exactly one occurrence.")] string[] oldStrings, [Description("Array of new strings to replace with. Must be same length as oldStrings.")] string[] newStrings) { + if (!File.Exists(path)) { return $"File {path} does not exist."; @@ -491,6 +499,7 @@ public string Insert( [Description("Line number (1-indexed) after which to insert the new string.")] int insertLine, [Description("The string to insert into the file.")] string newStr) { + if (!File.Exists(path)) { return $"File {path} does not exist."; @@ -518,6 +527,7 @@ public string Insert( public string UndoEdit( [Description("Absolute or relative path to file.")] string path) { + if (!EditHistory.ContainsKey(path)) { return $"No previous edit found for {path}.";