diff --git a/IncludeToolbox.vsix b/IncludeToolbox.vsix index 1a1e168..70c5917 100644 --- a/IncludeToolbox.vsix +++ b/IncludeToolbox.vsix @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50ac3952952fee7141aabb2a03e9cfd968a164a403c8d86529748c7df89d545f -size 128269 +oid sha256:0c432712fbe792d75f5dff593f0cdad3f2d72cbcd081a6e5a4b6c13c51a07ef8 +size 128533 diff --git a/IncludeToolbox/Formatter/IncludeFormatter.cs b/IncludeToolbox/Formatter/IncludeFormatter.cs index 0b5f61f..05d1042 100644 --- a/IncludeToolbox/Formatter/IncludeFormatter.cs +++ b/IncludeToolbox/Formatter/IncludeFormatter.cs @@ -8,12 +8,21 @@ namespace IncludeToolbox.Formatter { public static class IncludeFormatter { - public static string FormatPath(string absoluteIncludeFilename, FormatterOptionsPage.PathMode pathformat, IEnumerable includeDirectories) + public static string FormatPath(string absoluteIncludeFilename, + FormatterOptionsPage.PathMode pathformat, + IEnumerable includeDirectories, + string includeRootDirectory = null) { if (pathformat == FormatterOptionsPage.PathMode.Absolute) { return absoluteIncludeFilename; } + else if (pathformat == FormatterOptionsPage.PathMode.Absolute_FromParentDirWithFile && + null != absoluteIncludeFilename && + null != includeRootDirectory) + { + return Utils.MakeRelative(includeRootDirectory, absoluteIncludeFilename); + } else { // todo: Treat std library files special? @@ -47,16 +56,44 @@ public static string FormatPath(string absoluteIncludeFilename, FormatterOptions /// /// Formats the paths of a given list of include line info. /// - private static void FormatPaths(IEnumerable lines, FormatterOptionsPage.PathMode pathformat, IEnumerable includeDirectories) + private static void FormatPaths(IEnumerable lines, + FormatterOptionsPage.PathMode pathformat, + FormatterOptionsPage.IgnoreFileRelativeMode ignoreFileRelativeMode, + IEnumerable includeDirectories, + string documentDir, + string includeRootDirectory = null) { if (pathformat == FormatterOptionsPage.PathMode.Unchanged) return; foreach (var line in lines) { - string absoluteIncludeDir = line.TryResolveInclude(includeDirectories, out bool resolvedPath); + string absoluteIncludePath = line.TryResolveInclude(includeDirectories, out bool resolvedPath); if (resolvedPath) - line.IncludeContent = FormatPath(absoluteIncludeDir, pathformat, includeDirectories) ?? line.IncludeContent; + { + if (pathformat == FormatterOptionsPage.PathMode.Absolute_FromParentDirWithFile && + includeRootDirectory != null && + !absoluteIncludePath.StartsWith(includeRootDirectory)) + { + continue; + } + + var currentPathFormat = pathformat; + var currentIncludeRootDirectory = includeRootDirectory; + string includeFilename = Path.GetFileName(absoluteIncludePath); + if ((ignoreFileRelativeMode == FormatterOptionsPage.IgnoreFileRelativeMode.InSameDirectory && + Path.Combine(documentDir, includeFilename) == absoluteIncludePath) || + (ignoreFileRelativeMode == FormatterOptionsPage.IgnoreFileRelativeMode.InSameOrSubDirectory && + absoluteIncludePath.StartsWith(documentDir))) + { + currentPathFormat = FormatterOptionsPage.PathMode.Absolute_FromParentDirWithFile; + currentIncludeRootDirectory = documentDir; + } + line.IncludeContent = FormatPath(absoluteIncludePath, + currentPathFormat, + includeDirectories, + currentIncludeRootDirectory) ?? line.IncludeContent; + } } } @@ -225,8 +262,32 @@ private static bool SortIncludeBatch(FormatterOptionsPage settings, string[] pre /// Formated text. public static string FormatIncludes(string text, string documentPath, IEnumerable includeDirectories, FormatterOptionsPage settings) { - string documentDir = Path.GetDirectoryName(documentPath); + string documentDir = Utils.GetExactPathName(Path.GetDirectoryName(documentPath)); string documentName = Path.GetFileNameWithoutExtension(documentPath); + string includeRootDirectory = null; + if (settings.PathFormat == FormatterOptionsPage.PathMode.Absolute_FromParentDirWithFile && + !String.IsNullOrWhiteSpace(settings.FromParentDirWithFile)) + { + var dir = new DirectoryInfo(documentDir); + while (dir != null) + { + if (File.Exists(Path.Combine(dir.FullName, settings.FromParentDirWithFile))) + { + includeRootDirectory = Utils.GetExactPathName(dir.FullName); + break; + } + + try + { + dir = dir.Parent; + } + catch (System.Security.SecurityException) + { + // Permission denied + break; + } + } + } includeDirectories = new string[] { Microsoft.VisualStudio.PlatformUI.PathUtil.Normalize(documentDir) + Path.DirectorySeparatorChar }.Concat(includeDirectories); @@ -236,11 +297,16 @@ public static string FormatIncludes(string text, string documentPath, IEnumerabl // Format. IEnumerable formatingDirs = includeDirectories; - if (settings.IgnoreFileRelative) + if (settings.IgnoreFileRelative == FormatterOptionsPage.IgnoreFileRelativeMode.Always) { formatingDirs = formatingDirs.Skip(1); } - FormatPaths(lines, settings.PathFormat, formatingDirs); + FormatPaths(lines, + settings.PathFormat, + settings.IgnoreFileRelative, + formatingDirs, + documentDir, + includeRootDirectory); FormatDelimiters(lines, settings.DelimiterFormatting); FormatSlashes(lines, settings.SlashFormatting); diff --git a/IncludeToolbox/Options/FormatterOptionsPage.cs b/IncludeToolbox/Options/FormatterOptionsPage.cs index eee752b..b156182 100644 --- a/IncludeToolbox/Options/FormatterOptionsPage.cs +++ b/IncludeToolbox/Options/FormatterOptionsPage.cs @@ -19,16 +19,29 @@ public enum PathMode Shortest, Shortest_AvoidUpSteps, Absolute, + Absolute_FromParentDirWithFile } [Category("Path")] [DisplayName("Mode")] [Description("Changes the path mode to the given pattern.")] public PathMode PathFormat { get; set; } = PathMode.Shortest_AvoidUpSteps; + [Category("Path")] + [DisplayName("Filename in parent directory for absolute include paths")] + [Description("The Absolute_FromParentDirWithFile mode will look for this file in parent directories and make include paths absolute from its location.")] + public string FromParentDirWithFile { get; set; } = "build.root"; + + public enum IgnoreFileRelativeMode + { + Never, + Always, + InSameDirectory, + InSameOrSubDirectory + } [Category("Path")] [DisplayName("Ignore File Relative")] - [Description("If true, include directives will not take the path of the file into account.")] - public bool IgnoreFileRelative { get; set; } = false; + [Description("Whether to ignore include paths relative to the current file.")] + public IgnoreFileRelativeMode IgnoreFileRelative { get; set; } = IgnoreFileRelativeMode.Never; //[Category("Path")] //[DisplayName("Ignore Standard Library")] @@ -115,7 +128,8 @@ public override void SaveSettingsToStorage() settingsStore.CreateCollection(collectionName); settingsStore.SetInt32(collectionName, nameof(PathFormat), (int)PathFormat); - settingsStore.SetBoolean(collectionName, nameof(IgnoreFileRelative), IgnoreFileRelative); + settingsStore.SetString(collectionName, nameof(FromParentDirWithFile), FromParentDirWithFile); + settingsStore.SetInt32(collectionName, nameof(IgnoreFileRelative), (int)IgnoreFileRelative); settingsStore.SetInt32(collectionName, nameof(DelimiterFormatting), (int)DelimiterFormatting); settingsStore.SetInt32(collectionName, nameof(SlashFormatting), (int)SlashFormatting); @@ -135,8 +149,10 @@ public override void LoadSettingsFromStorage() if (settingsStore.PropertyExists(collectionName, nameof(PathFormat))) PathFormat = (PathMode)settingsStore.GetInt32(collectionName, nameof(PathFormat)); + if (settingsStore.PropertyExists(collectionName, nameof(FromParentDirWithFile))) + FromParentDirWithFile = settingsStore.GetString(collectionName, nameof(FromParentDirWithFile)); if (settingsStore.PropertyExists(collectionName, nameof(IgnoreFileRelative))) - IgnoreFileRelative = settingsStore.GetBoolean(collectionName, nameof(IgnoreFileRelative)); + IgnoreFileRelative = (IgnoreFileRelativeMode)settingsStore.GetInt32(collectionName, nameof(IgnoreFileRelative)); if (settingsStore.PropertyExists(collectionName, nameof(DelimiterFormatting))) DelimiterFormatting = (DelimiterMode) settingsStore.GetInt32(collectionName, nameof(DelimiterFormatting)); diff --git a/IncludeToolbox/Package/source.extension.vsixmanifest b/IncludeToolbox/Package/source.extension.vsixmanifest index 3dade45..eda7790 100644 --- a/IncludeToolbox/Package/source.extension.vsixmanifest +++ b/IncludeToolbox/Package/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + IncludeToolbox Various tools for managing C/C++ #includes: Formatting, sorting, exploring, pruning. license.txt diff --git a/IncludeToolbox/Utils.cs b/IncludeToolbox/Utils.cs index ea49990..29cab16 100644 --- a/IncludeToolbox/Utils.cs +++ b/IncludeToolbox/Utils.cs @@ -6,7 +6,7 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.VCProjectEngine; +//using Microsoft.VisualStudio.VCProjectEngine; namespace IncludeToolbox { @@ -35,23 +35,61 @@ public static string MakeRelative(string absoluteRoot, string absoluteTarget) return relativePath; } - public static string GetExactPathName(string pathName) + // Shamelessly stolen from https://stackoverflow.com/a/5076517/153079 + private static string GetFileSystemCasing(string path) { - if (!File.Exists(pathName) && !Directory.Exists(pathName)) - return pathName; + if (Path.IsPathRooted(path)) + { + path = path.TrimEnd(Path.DirectorySeparatorChar); // if you type c:\foo\ instead of c:\foo + try + { + string name = Path.GetFileName(path); + if (name == "") + return path.ToUpper() + Path.DirectorySeparatorChar; // root reached - var di = new DirectoryInfo(pathName); + string parent = Path.GetDirectoryName(path); - if (di.Parent != null) - { - return Path.Combine( - GetExactPathName(di.Parent.FullName), - di.Parent.GetFileSystemInfos(di.Name)[0].Name); + parent = GetFileSystemCasing(parent); + + DirectoryInfo diParent = new DirectoryInfo(parent); + FileSystemInfo[] fsiChildren = diParent.GetFileSystemInfos(name); + FileSystemInfo fsiChild = fsiChildren.First(); + return fsiChild.FullName; + } + catch (Exception ex) + { + Output.Instance.ErrorMsg("Invalid path: '{0}'\nError:\n{1}", path, ex.Message); + } } else { - return di.Name.ToUpper(); + Output.Instance.ErrorMsg("Absolute path needed, not relative: '{0}'", path); + } + + return ""; + } + + /// + /// Gets the path name as it exists it the file system, normalizing it. + /// + /// Path name + /// Normalized path name. Directories are returned with trailing separator. + public static string GetExactPathName(string pathName) + { + if (!File.Exists(pathName) && !Directory.Exists(pathName)) + { + return pathName; } + + string exactPathName = GetFileSystemCasing(Path.GetFullPath(pathName).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)); + if (exactPathName == "") + return pathName; + + // Add trailing slash for directories + exactPathName = exactPathName.TrimEnd(Path.DirectorySeparatorChar); + if (File.GetAttributes(exactPathName).HasFlag(FileAttributes.Directory)) + return exactPathName + Path.DirectorySeparatorChar; + return exactPathName; } ///