diff --git a/CodeiumVS/LanguageServer/LanguageServer.cs b/CodeiumVS/LanguageServer/LanguageServer.cs index c371379..f94cac2 100644 --- a/CodeiumVS/LanguageServer/LanguageServer.cs +++ b/CodeiumVS/LanguageServer/LanguageServer.cs @@ -754,14 +754,12 @@ private async Task InitializeTrackedWorkspaceAsync() foreach (EnvDTE.Document doc in documents) { ProjectItem projectItem = doc.ProjectItem; - await _package.LogAsync($"projectITEM: {projectItem}"); if (projectItem != null) { EnvDTE.Project project = projectItem.ContainingProject; if (project != null && !openFileProjects.Contains(project)) { openFileProjects.Add(project); - await _package.LogAsync($"Open File FOUNDD: {project}, {project.FullName}, {project.Name}"); } } } @@ -828,12 +826,13 @@ async Task AddFilesToIndexLists(EnvDTE.Project project) return; } string projectFullName = project.FullName; - string projectName = Path.GetFileNameWithoutExtension(projectFullName); - IEnumerable commonDirs = Enumerable.Empty(); - + await _package.LogAsync($"Adding files to index of project: {projectFullName}"); if (!string.IsNullOrEmpty(projectFullName) && !processedProjects.Any(p => projectFullName.StartsWith(p))) { - // Parse the csproj file to find all source directories + string projectName = Path.GetFileNameWithoutExtension(projectFullName); + IEnumerable commonDirs = Enumerable.Empty(); + string projectDir = Path.GetDirectoryName(projectFullName); + // Parse the proj file to find all source directories if (File.Exists(projectFullName) && (projectFullName.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) || projectFullName.EndsWith(".vcxproj", StringComparison.OrdinalIgnoreCase))) { try @@ -907,7 +906,7 @@ async Task AddFilesToIndexLists(EnvDTE.Project project) } catch (Exception ex) { - await _package.LogAsync($"Failed to process project: {ex.Message}"); + await _package.LogAsync($"Failed to process open project: {ex.Message}"); continue; } } @@ -923,7 +922,7 @@ async Task AddFilesToIndexLists(EnvDTE.Project project) } catch (Exception ex) { - await _package.LogAsync($"Failed to process project: {ex.Message}"); + await _package.LogAsync($"Failed to process remaining project: {ex.Message}"); continue; } if (remainingToFind <=0) diff --git a/CodeiumVS/Utilities/FileUtilities.cs b/CodeiumVS/Utilities/FileUtilities.cs index 556278c..247082e 100644 --- a/CodeiumVS/Utilities/FileUtilities.cs +++ b/CodeiumVS/Utilities/FileUtilities.cs @@ -58,53 +58,91 @@ internal static List FindMinimumEncompassingDirectories(IEnumerable(); + // Get the directory paths of the file paths + var directoryPaths = filePaths.Select(Path.GetDirectoryName).Distinct().ToList(); + CodeiumVSPackage.Instance?.Log($"Directories before minimization: {string.Join(", ", directoryPaths)}"); + var result = GetMinimumDirectoryCover(directoryPaths); + CodeiumVSPackage.Instance?.Log($"Directories after minimization: {string.Join(", ", result)}"); + return result.Where(dir => CountPathSegments(dir) > 1).ToList(); + } + + + public static List GetMinimumDirectoryCover(IEnumerable directories) + { + // 1. Normalize all paths to full/absolute paths and remove duplicates + var normalizedDirs = directories + .Select(d => NormalizePath(d)) + .Distinct() + .ToList(); - // Get all parent directories for each file - var allPaths = filePaths.Select(path => + // 2. Sort by ascending number of path segments (shallow first) + normalizedDirs.Sort((a, b) => + CountPathSegments(a).CompareTo(CountPathSegments(b))); + + var coverSet = new List(); + + // 3. Greedy selection + foreach (var dir in normalizedDirs) { - var parents = new List(); - var dir = Path.GetDirectoryName(path); - while (!string.IsNullOrEmpty(dir)) + bool isCovered = false; + + // Check if 'dir' is already covered by any directory in coverSet + foreach (var coverDir in coverSet) { - parents.Add(dir); - dir = Path.GetDirectoryName(dir); + if (IsSubdirectoryOrSame(coverDir, dir)) + { + isCovered = true; + break; + } } - return parents; - }).ToList(); - // Find directories that contain files - var directoryCounts = new Dictionary>(); - for (int i = 0; i < allPaths.Count; i++) - { - foreach (var dir in allPaths[i]) + // If not covered, add it to the cover set + if (!isCovered) { - if (!directoryCounts.ContainsKey(dir)) - directoryCounts[dir] = new HashSet(); - directoryCounts[dir].Add(i); + coverSet.Add(dir); } } - var result = new List(); - var coveredFiles = new HashSet(); - - // While we haven't covered all files - while (coveredFiles.Count < allPaths.Count) - { - // Find directory that covers most uncovered files - var bestDir = directoryCounts - .Where(kvp => kvp.Value.Except(coveredFiles).Any()) - .OrderByDescending(kvp => kvp.Value.Except(coveredFiles).Count()) - .ThenBy(kvp => kvp.Key.Count(c => c == Path.DirectorySeparatorChar)) // Prefer deeper directories - .FirstOrDefault(); - - if (bestDir.Key == null) - break; - - result.Add(bestDir.Key); - coveredFiles.UnionWith(bestDir.Value); - } + return coverSet; + } - // Filter out paths that are too shallow (less than 3 levels deep) - return result.Where(dir => dir.Count(c => c == Path.DirectorySeparatorChar) >= 2).ToList(); + /// + /// Checks if 'child' is the same or a subdirectory of 'parent'. + /// + private static bool IsSubdirectoryOrSame(string parent, string child) + { + // 1. Normalize both directories to their full path (remove extra slashes, etc.). + string parentFull = Path.GetFullPath(parent) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + string childFull = Path.GetFullPath(child) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + // 2. Append a directory separator at the end of each path to ensure + // that "C:\Folder" won’t incorrectly match "C:\Folder2". + // e.g. "C:\Folder" -> "C:\Folder\" + parentFull += Path.DirectorySeparatorChar; + childFull += Path.DirectorySeparatorChar; + + // 3. On Windows, paths are case-insensitive. Use OrdinalIgnoreCase + // to compare. On non-Windows systems, consider using Ordinal. + return childFull.StartsWith(parentFull, StringComparison.OrdinalIgnoreCase); +} + + /// + /// Normalize a directory path by getting its full path (removing trailing slash, etc). + /// + private static string NormalizePath(string path) + { + return Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + } + + /// + /// Count path segments based on splitting by directory separators. + /// E.g. "C:\Folder\Sub" -> 3 segments (on Windows). + /// + private static int CountPathSegments(string path) + { + return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .Count(segment => !string.IsNullOrEmpty(segment)); } }