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; } }
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 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); }