private static bool SortIncludeBatch(FormatterOptionsPage settings, string[] precedenceRegexes, List <IncludeLineInfo> outSortedList, IEnumerable <IncludeLineInfo> includeBatch) { // Get enumerator and cancel if batch is empty. if (!includeBatch.Any()) { return(false); } // Fetch settings. FormatterOptionsPage.TypeSorting typeSorting = settings.SortByType; bool regexIncludeDelimiter = settings.RegexIncludeDelimiter; bool blankAfterRegexGroupMatch = settings.BlankAfterRegexGroupMatch; // Select only valid include lines and sort them. They'll stay in this relative sorted // order when rearranged by regex precedence groups. var includeLines = includeBatch .Where(x => x.ContainsActiveInclude) .OrderBy(x => x.IncludeContent) .ToList(); if (settings.RemoveDuplicates) { HashSet <string> uniqueIncludes = new HashSet <string>(); includeLines.RemoveAll(x => !x.ShouldBePreserved && !uniqueIncludes.Add(x.GetIncludeContentWithDelimiters())); } // Group the includes by the index of the precedence regex they match, or // precedenceRegexes.Length for no match, and sort the groups by index. var includeGroups = includeLines .GroupBy(x => { var includeContent = regexIncludeDelimiter ? x.GetIncludeContentWithDelimiters() : x.IncludeContent; for (int precedence = 0; precedence < precedenceRegexes.Count(); ++precedence) { if (Regex.Match(includeContent, precedenceRegexes[precedence]).Success) { return(precedence); } } return(precedenceRegexes.Length); }, x => x) .OrderBy(x => x.Key); // Optional newlines between regex match groups var groupStarts = new HashSet <IncludeLineInfo>(); if (blankAfterRegexGroupMatch && precedenceRegexes.Length > 0 && includeLines.Count() > 1) { // Set flag to prepend a newline to each group's first include foreach (var grouping in includeGroups) { groupStarts.Add(grouping.First()); } } // Flatten the groups var sortedIncludes = includeGroups.SelectMany(x => x.Select(y => y)); // Sort by angle or quoted delimiters if either of those options were selected if (typeSorting == FormatterOptionsPage.TypeSorting.AngleBracketsFirst) { sortedIncludes = sortedIncludes.OrderBy(x => x.LineDelimiterType == IncludeLineInfo.DelimiterType.AngleBrackets ? 0 : 1); } else if (typeSorting == FormatterOptionsPage.TypeSorting.QuotedFirst) { sortedIncludes = sortedIncludes.OrderBy(x => x.LineDelimiterType == IncludeLineInfo.DelimiterType.Quotes ? 0 : 1); } // Merge sorted includes with original non-include lines var sortedIncludeEnumerator = sortedIncludes.GetEnumerator(); var sortedLines = includeBatch.Select(originalLine => { if (originalLine.ContainsActiveInclude) { // Replace original include with sorted includes return(sortedIncludeEnumerator.MoveNext() ? sortedIncludeEnumerator.Current : new IncludeLineInfo()); } return(originalLine); }); if (settings.RemoveEmptyLines) { // Removing duplicates may have introduced new empty lines sortedLines = sortedLines.Where(sortedLine => !string.IsNullOrWhiteSpace(sortedLine.RawLine)); } // Finally, update the actual lines { bool firstLine = true; foreach (var sortedLine in sortedLines) { // Handle prepending a newline if requested, as long as: // - this include is the begin of a new group // - it's not the first line // - the previous line isn't already a non-include if (groupStarts.Contains(sortedLine) && !firstLine && outSortedList[outSortedList.Count - 1].ContainsActiveInclude) { outSortedList.Add(new IncludeLineInfo()); } outSortedList.Add(sortedLine); firstLine = false; } } return(true); }
private static List <IncludeLineInfo> SortIncludes(IList <IncludeLineInfo> lines, FormatterOptionsPage settings, string documentName) { string[] precedenceRegexes = RegexUtils.FixupRegexes(settings.PrecedenceRegexes, documentName); List <IncludeLineInfo> outSortedList = new List <IncludeLineInfo>(lines.Count); IEnumerable <IncludeLineInfo> includeBatch; int numConsumedItems = 0; do { // Fill in all non-include items between batches. var nonIncludeItems = lines.Skip(numConsumedItems).TakeWhile(x => !x.ContainsActiveInclude); numConsumedItems += nonIncludeItems.Count(); outSortedList.AddRange(nonIncludeItems); // Process until we hit a preprocessor directive that is not an include. // Those are boundaries for the sorting which we do not want to cross. includeBatch = lines.Skip(numConsumedItems).TakeWhile(x => x.ContainsActiveInclude || !x.ContainsPreProcessorDirective); numConsumedItems += includeBatch.Count(); } while (SortIncludeBatch(settings, precedenceRegexes, outSortedList, includeBatch) && numConsumedItems != lines.Count); return(outSortedList); }
private static void SortIncludes(ref List <IncludeLineInfo> lines, FormatterOptionsPage settings, string documentName) { FormatterOptionsPage.TypeSorting typeSorting = settings.SortByType; bool regexIncludeDelimiter = settings.RegexIncludeDelimiter; bool blankAfterRegexGroupMatch = settings.BlankAfterRegexGroupMatch; bool removeEmptyLines = settings.RemoveEmptyLines; string[] precedenceRegexes = FixupRegexes(settings.PrecedenceRegexes, documentName); // Select only valid include lines and sort them. They'll stay in this relative sorted // order when rearranged by regex precedence groups. var includeLines = lines .Where(x => x.LineType != IncludeLineInfo.Type.NoInclude) .OrderBy(x => x.IncludeContent); // Group the includes by the index of the precedence regex they match, or // precedenceRegexes.Length for no match, and sort the groups by index. var includeGroups = includeLines .GroupBy(x => { var includeContent = regexIncludeDelimiter ? x.GetIncludeContentWithDelimiters() : x.IncludeContent; for (int precedence = 0; precedence < precedenceRegexes.Count(); ++precedence) { if (Regex.Match(includeContent, precedenceRegexes[precedence]).Success) { return(precedence); } } return(precedenceRegexes.Length); }, x => x) .OrderBy(x => x.Key); // Optional newlines between regex match groups var groupStarts = new HashSet <IncludeLineInfo>(); if (blankAfterRegexGroupMatch && precedenceRegexes.Length > 0 && includeLines.Count() > 1) { // Set flag to prepend a newline to each group's first include foreach (var grouping in includeGroups) { groupStarts.Add(grouping.First()); } } // Flatten the groups var sortedIncludes = includeGroups.SelectMany(x => x.Select(y => y)); // Sort by angle or quoted delimiters if either of those options were selected if (typeSorting == FormatterOptionsPage.TypeSorting.AngleBracketsFirst) { sortedIncludes = sortedIncludes.OrderBy(x => x.LineType == IncludeLineInfo.Type.AngleBrackets ? 0 : 1); } else if (typeSorting == FormatterOptionsPage.TypeSorting.QuotedFirst) { sortedIncludes = sortedIncludes.OrderBy(x => x.LineType == IncludeLineInfo.Type.Quotes ? 0 : 1); } // Finally, update the actual lines List <IncludeLineInfo> extendedLineList = new List <IncludeLineInfo>(lines.Count); { var sortedIncludesArray = sortedIncludes.ToArray(); int sortedIndex = 0; for (int i = 0; i < lines.Count; ++i) { if (lines[i].ContainsActiveInclude) { var includeLine = sortedIncludesArray[sortedIndex++]; // Handle prepending a newline if requested, as long as: // - It's not the first line, and // - We'll remove empty lines or the previous line isn't already a NoInclude if (groupStarts.Contains(includeLine) && i > 0 && (removeEmptyLines || !extendedLineList[i - 1].ContainsActiveInclude)) { extendedLineList.Add(new IncludeLineInfo()); } lines[i] = includeLine; } extendedLineList.Add(lines[i]); } } // Only overwrite original array if we've added lines. if (lines.Count != extendedLineList.Count) { lines = extendedLineList; } }
/// <summary> /// Formats all includes in a given piece of text. /// </summary> /// <param name="text">Text to be parsed for includes.</param> /// <param name="documentName">Path to the document the edit is occuring in.</param> /// <param name="includeDirectories">A list of include directories</param> /// <param name="settings">Settings that determine how the formating should be done.</param> /// <returns>Formated text.</returns> public static string FormatIncludes(string text, string documentPath, IEnumerable <string> includeDirectories, FormatterOptionsPage settings) { string documentDir = Path.GetDirectoryName(documentPath); string documentName = Path.GetFileNameWithoutExtension(documentPath); includeDirectories = new string[] { Microsoft.VisualStudio.PlatformUI.PathUtil.Normalize(documentDir) + Path.DirectorySeparatorChar }.Concat(includeDirectories); string newLineChars = Utils.GetDominantNewLineSeparator(text); var lines = IncludeLineInfo.ParseIncludes(text, settings.RemoveEmptyLines ? ParseOptions.RemoveEmptyLines : ParseOptions.None); // Format. IEnumerable <string> formatingDirs = includeDirectories; if (settings.IgnoreFileRelative) { formatingDirs = formatingDirs.Skip(1); } FormatPaths(lines, settings.PathFormat, formatingDirs); FormatDelimiters(lines, settings.DelimiterFormatting); FormatSlashes(lines, settings.SlashFormatting); // Sorting. Ignores non-include lines. SortIncludes(ref lines, settings, documentName); // Combine again. return(string.Join(newLineChars, lines.Select(x => x.RawLine))); }
private static bool SortIncludeBatch(FormatterOptionsPage settings, string[] precedenceRegexes, List <IncludeLineInfo> outSortedList, IEnumerable <IncludeLineInfo> includeBatch) { // Get enumerator and cancel if batch is empty. var originalLineEnumerator = includeBatch.GetEnumerator(); bool hasElements = originalLineEnumerator.MoveNext(); if (!hasElements) { return(false); } // Fetch settings. FormatterOptionsPage.TypeSorting typeSorting = settings.SortByType; bool regexIncludeDelimiter = settings.RegexIncludeDelimiter; bool blankAfterRegexGroupMatch = settings.BlankAfterRegexGroupMatch; // Select only valid include lines and sort them. They'll stay in this relative sorted // order when rearranged by regex precedence groups. var includeLines = includeBatch .Where(x => x.ContainsActiveInclude) .OrderBy(x => x.IncludeContent); // Group the includes by the index of the precedence regex they match, or // precedenceRegexes.Length for no match, and sort the groups by index. var includeGroups = includeLines .GroupBy(x => { var includeContent = regexIncludeDelimiter ? x.GetIncludeContentWithDelimiters() : x.IncludeContent; for (int precedence = 0; precedence < precedenceRegexes.Count(); ++precedence) { if (Regex.Match(includeContent, precedenceRegexes[precedence]).Success) { return(precedence); } } return(precedenceRegexes.Length); }, x => x) .OrderBy(x => x.Key); // Optional newlines between regex match groups var groupStarts = new HashSet <IncludeLineInfo>(); if (blankAfterRegexGroupMatch && precedenceRegexes.Length > 0 && includeLines.Count() > 1) { // Set flag to prepend a newline to each group's first include foreach (var grouping in includeGroups) { groupStarts.Add(grouping.First()); } } // Flatten the groups var sortedIncludes = includeGroups.SelectMany(x => x.Select(y => y)); // Sort by angle or quoted delimiters if either of those options were selected if (typeSorting == FormatterOptionsPage.TypeSorting.AngleBracketsFirst) { sortedIncludes = sortedIncludes.OrderBy(x => x.LineDelimiterType == IncludeLineInfo.DelimiterType.AngleBrackets ? 0 : 1); } else if (typeSorting == FormatterOptionsPage.TypeSorting.QuotedFirst) { sortedIncludes = sortedIncludes.OrderBy(x => x.LineDelimiterType == IncludeLineInfo.DelimiterType.Quotes ? 0 : 1); } // Finally, update the actual lines { bool firstLine = true; foreach (var sortedLine in sortedIncludes) { // Advance until there is a include line to replace. There *must* be one left if sortedIncludes is not empty. while (!originalLineEnumerator.Current.ContainsActiveInclude) { outSortedList.Add(originalLineEnumerator.Current); hasElements = originalLineEnumerator.MoveNext(); System.Diagnostics.Debug.Assert(hasElements, "There must be an element left in the original list if there are still sorted elements to put back in."); } bool isLastLine = !originalLineEnumerator.MoveNext(); // Handle prepending a newline if requested, as long as: // - this include is the begin of a new group // - it's not the first line // - it's not the last line of the batch. // - the previous line isn't already a non-include if (groupStarts.Contains(sortedLine) && !firstLine && !isLastLine && outSortedList[outSortedList.Count - 1].ContainsActiveInclude) { outSortedList.Add(new IncludeLineInfo()); } outSortedList.Add(sortedLine); firstLine = false; } } return(true); }