| // Copyright 2017-2020 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "common/formatting/token_partition_tree.h" |
| |
| #include <cstddef> |
| #include <iterator> |
| #include <vector> |
| |
| #include "common/formatting/format_token.h" |
| #include "common/formatting/line_wrap_searcher.h" |
| #include "common/formatting/unwrapped_line.h" |
| #include "common/strings/display_utils.h" |
| #include "common/strings/range.h" |
| #include "common/text/tree_utils.h" |
| #include "common/util/algorithm.h" |
| #include "common/util/container_iterator_range.h" |
| #include "common/util/logging.h" |
| #include "common/util/spacer.h" |
| #include "common/util/top_n.h" |
| #include "common/util/vector_tree.h" |
| |
| namespace verible { |
| |
| using format_token_iterator = std::vector<PreFormatToken>::const_iterator; |
| |
| void VerifyTreeNodeFormatTokenRanges(const TokenPartitionTree& node, |
| format_token_iterator base) { |
| VLOG(4) << __FUNCTION__ << " @ node path: " << NodePath(node); |
| |
| // Converting an iterator to an index is easier for debugging. |
| auto TokenIndex = [=](format_token_iterator iter) { |
| return std::distance(base, iter); |
| }; |
| |
| const auto& children = node.Children(); |
| if (!children.empty()) { |
| const TokenPartitionTreePrinter node_printer(node); |
| { |
| // Hierarchy invariant: parent's range == range spanned by children. |
| // Check against first child's begin, and last child's end. |
| const auto& parent_range = node.Value().TokensRange(); |
| // Translates ranges' iterators into positional indices. |
| const int parent_begin = TokenIndex(parent_range.begin()); |
| const int parent_end = TokenIndex(parent_range.end()); |
| const int children_begin = |
| TokenIndex(children.front().Value().TokensRange().begin()); |
| const int children_end = |
| TokenIndex(children.back().Value().TokensRange().end()); |
| CHECK_EQ(parent_begin, children_begin) << "node:\n" << node_printer; |
| CHECK_EQ(parent_end, children_end) << "node:\n" << node_printer; |
| } |
| { |
| // Sibling continuity invariant: |
| // The end() of one child is the begin() of the next child. |
| auto iter = children.begin(); |
| const auto end = children.end(); |
| auto prev_upper_bound = iter->Value().TokensRange().end(); |
| for (++iter; iter != end; ++iter) { |
| const auto child_range = iter->Value().TokensRange(); |
| const int current_begin = TokenIndex(child_range.begin()); |
| const int previous_end = TokenIndex(prev_upper_bound); |
| CHECK_EQ(current_begin, previous_end) << "node:\n" << node_printer; |
| prev_upper_bound = child_range.end(); |
| } |
| } |
| } |
| VLOG(4) << __FUNCTION__ << " (verified)"; |
| } |
| |
| void VerifyFullTreeFormatTokenRanges(const TokenPartitionTree& tree, |
| format_token_iterator base) { |
| VLOG(4) << __FUNCTION__ << '\n' << TokenPartitionTreePrinter{tree}; |
| tree.ApplyPreOrder([=](const TokenPartitionTree& node) { |
| VerifyTreeNodeFormatTokenRanges(node, base); |
| }); |
| } |
| |
| struct SizeCompare { |
| bool operator()(const UnwrappedLine* left, const UnwrappedLine* right) const { |
| return left->Size() > right->Size(); |
| } |
| }; |
| |
| std::vector<const UnwrappedLine*> FindLargestPartitions( |
| const TokenPartitionTree& token_partitions, size_t num_partitions) { |
| // Sort UnwrappedLines from leaf partitions by size. |
| using partition_set_type = verible::TopN<const UnwrappedLine*, SizeCompare>; |
| partition_set_type partitions(num_partitions); |
| token_partitions.ApplyPreOrder([&partitions](const TokenPartitionTree& node) { |
| if (node.Children().empty()) { // only look at leaf partitions |
| partitions.push(&node.Value()); |
| } |
| }); |
| return partitions.Take(); |
| } |
| |
| std::vector<std::vector<int>> FlushLeftSpacingDifferences( |
| const TokenPartitionRange& partitions) { |
| // Compute per-token differences between original spacings and reference-value |
| // spacings. |
| std::vector<std::vector<int>> flush_left_spacing_deltas; |
| flush_left_spacing_deltas.reserve(partitions.size()); |
| for (const auto& partition : partitions) { |
| flush_left_spacing_deltas.emplace_back(); |
| std::vector<int>& row(flush_left_spacing_deltas.back()); |
| FormatTokenRange ftokens(partition.Value().TokensRange()); |
| if (ftokens.empty()) continue; |
| // Skip the first token, because that represents indentation. |
| ftokens.pop_front(); |
| for (const auto& ftoken : ftokens) { |
| row.push_back(ftoken.ExcessSpaces()); |
| } |
| } |
| return flush_left_spacing_deltas; |
| } |
| |
| std::ostream& TokenPartitionTreePrinter::PrintTree(std::ostream& stream, |
| int indent) const { |
| const auto& value = node.Value(); |
| const auto& children = node.Children(); |
| stream << Spacer(indent) << "{ "; |
| if (children.empty()) { |
| stream << '('; |
| value.AsCode(&stream, verbose); |
| stream << ") }"; |
| } else { |
| stream << '(' |
| // similar to UnwrappedLine::AsCode() |
| << Spacer(value.IndentationSpaces(), |
| UnwrappedLine::kIndentationMarker) |
| // <auto> just means the concatenation of all subpartitions |
| << "[<auto>], policy: " << value.PartitionPolicy() << ") @" |
| << NodePath(node); |
| if (value.Origin() != nullptr) { |
| static constexpr int kContextLimit = 25; |
| stream << ", (origin: \"" |
| << AutoTruncate{StringSpanOfSymbol(*value.Origin()), kContextLimit} |
| << "\")"; |
| } |
| stream << '\n'; |
| // token range spans all of children nodes |
| for (const auto& child : children) { |
| TokenPartitionTreePrinter(child, verbose).PrintTree(stream, indent + 2) |
| << '\n'; |
| } |
| stream << Spacer(indent) << '}'; |
| } |
| return stream; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const TokenPartitionTreePrinter& printer) { |
| return printer.PrintTree(stream); |
| } |
| |
| // Detects when there is a vertical separation of more than one line between |
| // two token partitions. |
| class BlankLineSeparatorDetector { |
| public: |
| // 'bounds' range must not be empty. |
| explicit BlankLineSeparatorDetector(const TokenPartitionRange& bounds) |
| : previous_end_(bounds.front() |
| .Value() |
| .TokensRange() |
| .front() |
| .token->text() |
| .begin()) {} |
| |
| bool operator()(const TokenPartitionTree& node) { |
| const auto range = node.Value().TokensRange(); |
| if (range.empty()) return false; |
| const auto begin = range.front().token->text().begin(); |
| const auto end = range.back().token->text().end(); |
| const auto gap = make_string_view_range(previous_end_, begin); |
| // A blank line between partitions contains 2+ newlines. |
| const bool new_bound = std::count(gap.begin(), gap.end(), '\n') >= 2; |
| previous_end_ = end; |
| return new_bound; |
| } |
| |
| private: |
| // Keeps track of the end of the previous partition, which is the start |
| // of each inter-partition gap (string_view). |
| absl::string_view::const_iterator previous_end_; |
| }; |
| |
| // Subdivides the 'bounds' range into sub-ranges broken up by blank lines. |
| static std::vector<TokenPartitionIterator> |
| PartitionTokenPartitionRangesAtBlankLines(const TokenPartitionRange& bounds) { |
| VLOG(2) << __FUNCTION__; |
| std::vector<TokenPartitionIterator> subpartitions; |
| if (bounds.empty()) return subpartitions; |
| subpartitions.push_back(bounds.begin()); |
| // Bookkeeping for the end of the previous token range, used to evaluate |
| // the inter-token-range text, looking for blank line. |
| verible::find_all(bounds.begin(), bounds.end(), |
| std::back_inserter(subpartitions), |
| BlankLineSeparatorDetector(bounds)); |
| subpartitions.push_back(bounds.end()); |
| VLOG(2) << "end of " << __FUNCTION__ |
| << ", boundaries: " << subpartitions.size(); |
| return subpartitions; |
| } |
| |
| std::vector<TokenPartitionRange> GetSubpartitionsBetweenBlankLines( |
| const TokenPartitionRange& outer_partition_bounds) { |
| VLOG(2) << __FUNCTION__; |
| std::vector<TokenPartitionRange> result; |
| { |
| const std::vector<TokenPartitionIterator> subpartitions_bounds( |
| PartitionTokenPartitionRangesAtBlankLines(outer_partition_bounds)); |
| CHECK_GE(subpartitions_bounds.size(), 2); |
| result.reserve(subpartitions_bounds.size()); |
| |
| auto prev = subpartitions_bounds.begin(); |
| // similar pattern to std::adjacent_difference. |
| for (auto next = std::next(prev); next != subpartitions_bounds.end(); |
| prev = next, ++next) { |
| result.emplace_back(*prev, *next); |
| } |
| } |
| VLOG(2) << "end of " << __FUNCTION__; |
| return result; |
| } |
| |
| void AdjustIndentationRelative(TokenPartitionTree* tree, int amount) { |
| ABSL_DIE_IF_NULL(tree)->ApplyPreOrder([&](UnwrappedLine& line) { |
| const int new_indent = std::max<int>(line.IndentationSpaces() + amount, 0); |
| line.SetIndentationSpaces(new_indent); |
| }); |
| } |
| |
| void AdjustIndentationAbsolute(TokenPartitionTree* tree, int amount) { |
| // Compare the indentation difference at the root node. |
| const int indent_diff = amount - tree->Value().IndentationSpaces(); |
| if (indent_diff == 0) return; |
| AdjustIndentationRelative(tree, indent_diff); |
| } |
| |
| void MergeConsecutiveSiblings(TokenPartitionTree* tree, size_t pos) { |
| // Effectively concatenate unwrapped line ranges of sibling subpartitions. |
| ABSL_DIE_IF_NULL(tree)->MergeConsecutiveSiblings( |
| pos, [](UnwrappedLine* left, const UnwrappedLine& right) { |
| // Verify token range continuity. |
| CHECK(left->TokensRange().end() == right.TokensRange().begin()); |
| left->SpanUpToToken(right.TokensRange().end()); |
| }); |
| } |
| |
| // From leaf node's parent upwards, update the left bound of the UnwrappedLine's |
| // TokenRange. Stop as soon as a node is not a (leftmost) first child. |
| static void UpdateTokenRangeLowerBound(TokenPartitionTree* leaf, |
| TokenPartitionTree* last, |
| format_token_iterator token_iter) { |
| for (auto* node = leaf; node != nullptr && node != last; |
| node = node->Parent()) { |
| node->Value().SpanBackToToken(token_iter); |
| } |
| } |
| |
| // From leaf node upwards, update the right bound of the UnwrappedLine's |
| // TokenRange. Stop as soon as a node is not a (rightmost) last child. |
| static void UpdateTokenRangeUpperBound(TokenPartitionTree* leaf, |
| TokenPartitionTree* last, |
| format_token_iterator token_iter) { |
| for (auto* node = leaf; node != nullptr && node != last; |
| node = node->Parent()) { |
| node->Value().SpanUpToToken(token_iter); |
| } |
| } |
| |
| // Note: this destroys leaf |
| TokenPartitionTree* MergeLeafIntoPreviousLeaf(TokenPartitionTree* leaf) { |
| VLOG(4) << "origin leaf:\n" << *leaf; |
| auto* target_leaf = ABSL_DIE_IF_NULL(leaf)->PreviousLeaf(); |
| if (target_leaf == nullptr) return nullptr; |
| VLOG(4) << "target leaf:\n" << *target_leaf; |
| |
| // If there is no common ancestor, do nothing and return. |
| auto& common_ancestor = |
| *ABSL_DIE_IF_NULL(leaf->NearestCommonAncestor(target_leaf)); |
| VLOG(4) << "common ancestor:\n" << common_ancestor; |
| |
| // Verify continuity of token ranges between adjacent leaves. |
| CHECK(target_leaf->Value().TokensRange().end() == |
| leaf->Value().TokensRange().begin()); |
| |
| auto* leaf_parent = leaf->Parent(); |
| { |
| // Extend the upper-bound of the previous leaf partition to cover the |
| // partition that is about to be removed. |
| const auto range_end = leaf->Value().TokensRange().end(); |
| UpdateTokenRangeUpperBound(target_leaf, &common_ancestor, range_end); |
| if (range_end > common_ancestor.Value().TokensRange().end()) { |
| common_ancestor.Value().SpanUpToToken(range_end); |
| } |
| VLOG(5) << "common ancestor (after updating target):\n" << common_ancestor; |
| // Shrink lower-bounds of the originating subtree. |
| UpdateTokenRangeLowerBound(leaf_parent, &common_ancestor, range_end); |
| VLOG(5) << "common ancestor (after updating origin):\n" << common_ancestor; |
| |
| // Remove the obsolete partition, leaf. |
| // Caution: Existing references to the obsolete partition (and beyond) |
| // will be invalidated! |
| leaf->RemoveSelfFromParent(); |
| VLOG(4) << "common ancestor (after merging leaf):\n" << common_ancestor; |
| } |
| |
| // Sanity check invariants. |
| VerifyFullTreeFormatTokenRanges( |
| common_ancestor, |
| common_ancestor.LeftmostDescendant()->Value().TokensRange().begin()); |
| |
| return leaf_parent; |
| } |
| |
| // Note: this destroys leaf |
| TokenPartitionTree* MergeLeafIntoNextLeaf(TokenPartitionTree* leaf) { |
| VLOG(4) << "origin leaf:\n" << *leaf; |
| auto* target_leaf = ABSL_DIE_IF_NULL(leaf)->NextLeaf(); |
| if (target_leaf == nullptr) return nullptr; |
| VLOG(4) << "target leaf:\n" << *target_leaf; |
| |
| // If there is no common ancestor, do nothing and return. |
| auto& common_ancestor = |
| *ABSL_DIE_IF_NULL(leaf->NearestCommonAncestor(target_leaf)); |
| VLOG(4) << "common ancestor:\n" << common_ancestor; |
| |
| // Verify continuity of token ranges between adjacent leaves. |
| CHECK(target_leaf->Value().TokensRange().begin() == |
| leaf->Value().TokensRange().end()); |
| |
| auto* leaf_parent = leaf->Parent(); |
| { |
| // Extend the lower-bound of the next leaf partition to cover the |
| // partition that is about to be removed. |
| const auto range_begin = leaf->Value().TokensRange().begin(); |
| UpdateTokenRangeLowerBound(target_leaf, &common_ancestor, range_begin); |
| if (range_begin < common_ancestor.Value().TokensRange().begin()) { |
| common_ancestor.Value().SpanBackToToken(range_begin); |
| } |
| VLOG(4) << "common ancestor (after updating target):\n" << common_ancestor; |
| // Shrink upper-bounds of the originating subtree. |
| UpdateTokenRangeUpperBound(leaf_parent, &common_ancestor, range_begin); |
| VLOG(4) << "common ancestor (after updating origin):\n" << common_ancestor; |
| |
| // Remove the obsolete partition, leaf. |
| // Caution: Existing references to the obsolete partition (and beyond) |
| // will be invalidated! |
| leaf->RemoveSelfFromParent(); |
| VLOG(4) << "common ancestor (after destroying leaf):\n" << common_ancestor; |
| } |
| |
| // Sanity check invariants. |
| VerifyFullTreeFormatTokenRanges( |
| common_ancestor, |
| common_ancestor.LeftmostDescendant()->Value().TokensRange().begin()); |
| |
| return leaf_parent; |
| } |
| |
| // |
| // TokenPartitionTree class wrapper used by AppendFittingSubpartitions and |
| // ReshapeFittingSubpartitions for partition reshaping purposes. |
| // |
| class TokenPartitionTreeWrapper { |
| public: |
| TokenPartitionTreeWrapper(const TokenPartitionTree& node) : node_(&node) {} |
| |
| // Grouping node with no corresponding TokenPartitionTree node |
| TokenPartitionTreeWrapper(const UnwrappedLine& unwrapped_line) |
| : node_(nullptr) { |
| unwrapped_line_ = |
| std::unique_ptr<UnwrappedLine>(new UnwrappedLine(unwrapped_line)); |
| } |
| |
| // At least one of node_ or unwrapped_line_ should not be nullptr |
| TokenPartitionTreeWrapper() = delete; |
| |
| TokenPartitionTreeWrapper(const TokenPartitionTreeWrapper& other) { |
| CHECK((other.node_ != nullptr) || (other.unwrapped_line_ != nullptr)); |
| |
| if (other.node_) { |
| node_ = other.node_; |
| } else { |
| node_ = nullptr; |
| unwrapped_line_ = std::unique_ptr<UnwrappedLine>( |
| new UnwrappedLine(*other.unwrapped_line_)); |
| } |
| } |
| |
| // Return wrapped node value or concatenation of subnodes values |
| const UnwrappedLine& Value() const { |
| if (node_) { |
| return node_->Value(); |
| } else { |
| return *unwrapped_line_; |
| } |
| } |
| |
| // Concatenate subnodes value with other node value |
| UnwrappedLine Value(const TokenPartitionTree& other) const { |
| CHECK((node_ == nullptr) && (unwrapped_line_ != nullptr)); |
| UnwrappedLine uw = *unwrapped_line_; |
| uw.SpanUpToToken(other.Value().TokensRange().end()); |
| return uw; |
| } |
| |
| // Update concatenated value of subnodes |
| void Update(const VectorTree<TokenPartitionTreeWrapper>* child) { |
| const auto& token_partition_tree_wrapper = child->Value(); |
| const auto& uwline = token_partition_tree_wrapper.Value(); |
| |
| unwrapped_line_->SpanUpToToken(uwline.TokensRange().end()); |
| } |
| |
| // Fix concatenated value indentation |
| void SetIndentationSpaces(int indent) { |
| CHECK((node_ == nullptr) && (unwrapped_line_ != nullptr)); |
| unwrapped_line_->SetIndentationSpaces(indent); |
| } |
| |
| // Return address to wrapped node |
| const TokenPartitionTree* Node() const { return node_; } |
| |
| private: |
| // Wrapped node |
| const TokenPartitionTree* node_; |
| |
| // Concatenated value of subnodes |
| std::unique_ptr<UnwrappedLine> unwrapped_line_; |
| }; |
| |
| using partition_iterator = std::vector<TokenPartitionTree>::const_iterator; |
| using partition_range = verible::container_iterator_range<partition_iterator>; |
| |
| // Reshapes unfitted_partitions and returns result via fitted_partitions. |
| // Function creates VectorTree<> with additional level of grouping. |
| // Function expects two partitions: first used for computing indentation |
| // second with subpartitions, e.g. |
| // { (>>[<auto>]) @{1,0} // tree root |
| // { (>>[function fffffffffff (]) } // first subpartition 'header' |
| // { (>>>>>>[<auto>]) @{1,0,1}, // root node with subpartitions 'args' |
| // { (>>>>>>[type_a aaaa ,]) } |
| // { (>>>>>>[type_b bbbbb ,]) } |
| // { (>>>>>>[type_c cccccc ,]) } |
| // { (>>>>>>[type_d dddddddd ,]) } |
| // { (>>>>>>[type_e eeeeeeee ,]) } |
| // { (>>>>>>[type_f ffff ) ;]) } |
| // } |
| // } |
| // Function is iterating over subpartitions and tries to append |
| // them if fits in line. Parameter wrap_first_subpartition is used to determine |
| // whether wrap first subpartition or not. |
| // Return value signalise whether first subpartition was wrapped. |
| static bool AppendFittingSubpartitions( |
| VectorTree<TokenPartitionTreeWrapper>* fitted_partitions, |
| const TokenPartitionTree& unfitted_partitions_header, |
| const partition_range& unfitted_partitions_args, |
| const BasicFormatStyle& style, bool wrap_first_subpartition) { |
| bool wrapped_first_subpartition; |
| |
| // first with header |
| const auto& header = unfitted_partitions_header; |
| |
| // arguments |
| const auto& args = unfitted_partitions_args; |
| |
| // at least one argument |
| CHECK_GE(args.size(), 1); |
| |
| // Create first partition group |
| // and populate it with function name (e.g. { [function foo (] }) |
| auto* group = fitted_partitions->NewChild(header.Value()); |
| auto* child = group->NewChild(header); |
| |
| int indent; |
| |
| // Try appending first argument |
| const auto& first_arg = args[0]; |
| auto group_value_arg = group->Value().Value(first_arg); |
| if (wrap_first_subpartition || |
| (FitsOnLine(group_value_arg, style).fits == false)) { |
| // Use wrap indentation |
| indent = style.wrap_spaces + group_value_arg.IndentationSpaces(); |
| |
| // wrap line |
| group = group->NewSibling(first_arg.Value()); // start new group |
| child = group->NewChild(first_arg); // append not fitting 1st argument |
| group->Value().SetIndentationSpaces(indent); |
| |
| // Wrapped first argument |
| wrapped_first_subpartition = true; |
| } else { |
| // Compute new indentation level based on first partition |
| const auto& group_value = group->Value().Value(); |
| const UnwrappedLine& uwline = group_value; |
| indent = FitsOnLine(uwline, style).final_column; |
| |
| // Append first argument to current group |
| child = group->NewChild(args[0]); |
| group->Value().Update(child); |
| // keep group indentation |
| |
| // Appended first argument |
| wrapped_first_subpartition = false; |
| } |
| |
| const auto remaining_args = |
| make_container_range(args.begin() + 1, args.end()); |
| for (const auto& arg : remaining_args) { |
| // Every group should have at least one child |
| CHECK_GT(group->Children().size(), 0); |
| |
| // Try appending current argument to current line |
| UnwrappedLine uwline = group->Value().Value(arg); |
| if (FitsOnLine(uwline, style).fits) { |
| // Fits, appending child |
| child = group->NewChild(arg); |
| group->Value().Update(child); |
| } else { |
| // Does not fit, start new group with current child |
| group = group->NewSibling(arg.Value()); |
| child = group->NewChild(arg); |
| // no need to update because group was created |
| // with current child value |
| |
| // Fix group indentation |
| group->Value().SetIndentationSpaces(indent); |
| } |
| } |
| |
| return wrapped_first_subpartition; |
| } |
| |
| void ReshapeFittingSubpartitions(TokenPartitionTree* node, |
| const BasicFormatStyle& style) { |
| VLOG(4) << __FUNCTION__ << ", partition:\n" << *node; |
| VectorTree<TokenPartitionTreeWrapper>* fitted_tree = nullptr; |
| |
| // Leaf or simple node, e.g. '[function foo ( ) ;]' |
| if (node->Children().size() < 2) { |
| // Nothing to do |
| return; |
| } |
| |
| // Partition with arguments should have at least one argument |
| const auto& children = node->Children(); |
| const auto& header = children[0]; |
| const auto& args = children[1].Children(); |
| partition_range args_range; |
| if (args.empty()) { |
| // Partitions with one argument may have been flattened one level. |
| args_range = make_container_range(children.begin() + 1, children.end()); |
| } else { |
| // Arguments exist in a nested subpartition. |
| args_range = make_container_range(args.begin(), args.end()); |
| } |
| |
| VectorTree<TokenPartitionTreeWrapper> unwrapped_tree(node->Value()); |
| VectorTree<TokenPartitionTreeWrapper> wrapped_tree(node->Value()); |
| |
| // Format unwrapped_lines. At first without forced wrap after first line |
| bool wrapped_first_token = AppendFittingSubpartitions( |
| &unwrapped_tree, header, args_range, style, false); |
| |
| if (wrapped_first_token) { |
| // First token was forced to wrap so there's no need to |
| // generate wrapped version (it has to be wrapped) |
| fitted_tree = &unwrapped_tree; |
| } else { |
| // Generate wrapped version to compare results. |
| // Below function passes-trough lines that doesn't fit |
| // e.g. very looooooooooong arguments with length over column limit |
| // and leaves optimization to line_wrap_searcher. |
| // In this approach generated result may not be |
| // exactly correct beacause of additional line break done later. |
| AppendFittingSubpartitions(&wrapped_tree, header, args_range, style, true); |
| |
| // Compare number of grouping nodes |
| // If number of grouped node is equal then prefer unwrapped result |
| if (unwrapped_tree.Children().size() <= wrapped_tree.Children().size()) { |
| fitted_tree = &unwrapped_tree; |
| } else { |
| fitted_tree = &wrapped_tree; |
| } |
| } |
| |
| // Rebuild TokenPartitionTree |
| TokenPartitionTree temporary_tree(node->Value()); |
| |
| // Iterate over partition groups |
| for (const auto& itr : fitted_tree->Children()) { |
| auto uwline = itr.Value().Value(); |
| // Partitions groups should fit in line but we're |
| // leaving final decision to ExpandableTreeView |
| uwline.SetPartitionPolicy(PartitionPolicyEnum::kFitOnLineElseExpand); |
| |
| // Create new grouping node |
| auto* group = temporary_tree.NewChild(uwline); |
| |
| // Iterate over partitions in group |
| for (const auto& partition : itr.Children()) { |
| // access partition_node_type |
| const auto* node = partition.Value().Node(); |
| |
| // Append child (warning contains original indentation) |
| group->NewChild(*node); |
| } |
| } |
| |
| // Update grouped childrens indentation in case of expanding grouping |
| // partitions |
| for (auto& group : temporary_tree.Children()) { |
| for (auto& subpart : group.Children()) { |
| AdjustIndentationAbsolute(&subpart, group.Value().IndentationSpaces()); |
| } |
| } |
| |
| // Remove moved nodes |
| node->Children().clear(); |
| |
| // Move back from temporary tree |
| node->AdoptSubtreesFrom(&temporary_tree); |
| } |
| |
| } // namespace verible |