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