| // 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/iterator_adaptors.h" | |
| #include "common/util/logging.h" | |
| #include "common/util/spacer.h" | |
| #include "common/util/top_n.h" | |
| #include "common/util/tree_operations.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}; | |
| ApplyPreOrder(tree, [=](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); | |
| ApplyPreOrder(token_partitions, | |
| [&partitions](const TokenPartitionTree& node) { | |
| if (is_leaf(node)) { // 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, origin_printer); | |
| 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) { | |
| stream << ", (origin: "; | |
| origin_printer(stream, value.Origin()); | |
| stream << ")"; | |
| } | |
| stream << '\n'; | |
| // token range spans all of children nodes | |
| for (const auto& child : children) { | |
| TokenPartitionTreePrinter(child, verbose, origin_printer) | |
| .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; | |
| } | |
| static absl::string_view StringSpanOfPartitionRange( | |
| const TokenPartitionRange& range) { | |
| CHECK(!range.empty()); | |
| const auto front_range = range.front().Value().TokensRange(); | |
| const auto back_range = range.back().Value().TokensRange(); | |
| CHECK(!front_range.empty()); | |
| CHECK(!back_range.empty()); | |
| return make_string_view_range(front_range.front().Text().begin(), | |
| back_range.back().Text().end()); | |
| } | |
| bool AnyPartitionSubRangeIsDisabled(TokenPartitionRange range, | |
| absl::string_view full_text, | |
| const ByteOffsetSet& disabled_byte_ranges) { | |
| if (range.empty()) return false; | |
| const absl::string_view span = StringSpanOfPartitionRange(range); | |
| VLOG(4) << "text spanned: " << AutoTruncate{span, 40}; | |
| const std::pair<int, int> span_offsets = SubstringOffsets(span, full_text); | |
| ByteOffsetSet diff(disabled_byte_ranges); // copy | |
| diff.Complement(span_offsets); // enabled range(s) | |
| const ByteOffsetSet span_set{span_offsets}; | |
| return diff != span_set; | |
| } | |
| void AdjustIndentationRelative(TokenPartitionTree* tree, int amount) { | |
| ApplyPreOrder(*ABSL_DIE_IF_NULL(tree), [&](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); | |
| } | |
| absl::string_view StringSpanOfTokenRange(const FormatTokenRange& range) { | |
| if (range.empty()) return absl::string_view(); | |
| return make_string_view_range(range.front().Text().begin(), | |
| range.back().Text().end()); | |
| } | |
| void IndentButPreserveOtherSpacing(TokenPartitionRange partition_range, | |
| absl::string_view full_text, | |
| std::vector<PreFormatToken>* ftokens) { | |
| for (const auto& partition : partition_range) { | |
| const auto token_range = partition.Value().TokensRange(); | |
| const absl::string_view partition_text = | |
| StringSpanOfTokenRange(token_range); | |
| std::pair<int, int> byte_range = | |
| SubstringOffsets(partition_text, full_text); | |
| // Tweak byte range to allow the first token to still obey indentation. | |
| ++byte_range.first; | |
| PreserveSpacesOnDisabledTokenRanges(ftokens, ByteOffsetSet{byte_range}, | |
| full_text); | |
| } | |
| } | |
| void ApplyAlreadyFormattedPartitionPropertiesToTokens( | |
| TokenPartitionTree* already_formatted_partition_node, | |
| std::vector<PreFormatToken>* ftokens) { | |
| CHECK_NOTNULL(already_formatted_partition_node); | |
| CHECK_NOTNULL(ftokens); | |
| VLOG(4) << __FUNCTION__ << ": partition:\n" | |
| << TokenPartitionTreePrinter(*already_formatted_partition_node, true); | |
| const auto& uwline = already_formatted_partition_node->Value(); | |
| CHECK_EQ(uwline.PartitionPolicy(), PartitionPolicyEnum::kAlreadyFormatted) | |
| << *already_formatted_partition_node; | |
| if (uwline.IsEmpty()) { | |
| CHECK(is_leaf(*already_formatted_partition_node)); | |
| return; | |
| } | |
| auto mutable_tokens_begin = | |
| ConvertToMutableIterator(uwline.TokensRange().begin(), ftokens->begin()); | |
| // Might be replaced with AppendAligned in the loop below. | |
| mutable_tokens_begin->before.break_decision = | |
| verible::SpacingOptions::MustWrap; | |
| for (auto& child : already_formatted_partition_node->Children()) { | |
| auto slice = child.Value(); | |
| if (slice.PartitionPolicy() != PartitionPolicyEnum::kInline) { | |
| VLOG(1) << "Partition policy is not kInline - ignoring. Parent " | |
| "partition:\n" | |
| << *already_formatted_partition_node; | |
| continue; | |
| } | |
| auto token = verible::ConvertToMutableIterator(slice.TokensRange().begin(), | |
| ftokens->begin()); | |
| token->before.spaces_required = slice.IndentationSpaces(); | |
| token->before.break_decision = verible::SpacingOptions::AppendAligned; | |
| } | |
| auto mutable_tokens_end = | |
| ConvertToMutableIterator(uwline.TokensRange().end(), ftokens->begin()); | |
| for (auto& token : make_range(mutable_tokens_begin, mutable_tokens_end)) { | |
| auto& decision = token.before.break_decision; | |
| if (decision == verible::SpacingOptions::Undecided) | |
| decision = verible::SpacingOptions::MustAppend; | |
| } | |
| // Children are no longer needed | |
| already_formatted_partition_node->Children().clear(); | |
| VLOG(4) << __FUNCTION__ << ": partition after:\n" | |
| << TokenPartitionTreePrinter(*already_formatted_partition_node, true); | |
| } | |
| void MergeConsecutiveSiblings(TokenPartitionTree* tree, size_t pos) { | |
| CHECK_NOTNULL(tree); | |
| CHECK_LT(pos + 1, tree->Children().size()); | |
| const auto& current = tree->Children()[pos]; | |
| const auto& next = tree->Children()[pos + 1]; | |
| // Merge of a non-leaf partition and a leaf partition produces a non-leaf | |
| // partition with token range wider than concatenated token ranges of its | |
| // children. | |
| CHECK(is_leaf(current) == is_leaf(next)) << "left:\n" | |
| << current << "\nright:" << next; | |
| // Effectively concatenate unwrapped line ranges of sibling subpartitions. | |
| MergeConsecutiveSiblings( | |
| *tree, 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); | |
| } | |
| } | |
| TokenPartitionTree* GroupLeafWithPreviousLeaf(TokenPartitionTree* leaf) { | |
| CHECK_NOTNULL(leaf); | |
| VLOG(4) << "origin leaf:\n" << *leaf; | |
| auto* previous_leaf = PreviousLeaf(*leaf); | |
| if (previous_leaf == nullptr) return nullptr; | |
| VLOG(4) << "previous leaf:\n" << *previous_leaf; | |
| // If there is no common ancestor, do nothing and return. | |
| auto& common_ancestor = | |
| *ABSL_DIE_IF_NULL(NearestCommonAncestor(*leaf, *previous_leaf)); | |
| VLOG(4) << "common ancestor:\n" << common_ancestor; | |
| // Verify continuity of token ranges between adjacent leaves. | |
| CHECK(previous_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(); | |
| const auto uwline = leaf->Value(); | |
| const auto previous_uwline = previous_leaf->Value(); | |
| UpdateTokenRangeUpperBound(previous_leaf, &common_ancestor, range_end); | |
| previous_leaf->Children().emplace_back(previous_uwline); | |
| previous_leaf->Children().emplace_back(uwline); | |
| 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! | |
| RemoveSelfFromParent(*leaf); | |
| VLOG(4) << "common ancestor (after merging leaf):\n" << common_ancestor; | |
| } | |
| // Sanity check invariants. | |
| VerifyFullTreeFormatTokenRanges( | |
| common_ancestor, | |
| LeftmostDescendant(common_ancestor).Value().TokensRange().begin()); | |
| return previous_leaf; | |
| } | |
| // Note: this destroys leaf | |
| TokenPartitionTree* MergeLeafIntoPreviousLeaf(TokenPartitionTree* leaf) { | |
| CHECK_NOTNULL(leaf); | |
| VLOG(4) << "origin leaf:\n" << *leaf; | |
| auto* target_leaf = PreviousLeaf(*leaf); | |
| 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(NearestCommonAncestor(*leaf, *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! | |
| RemoveSelfFromParent(*leaf); | |
| VLOG(4) << "common ancestor (after merging leaf):\n" << common_ancestor; | |
| } | |
| // Sanity check invariants. | |
| VerifyFullTreeFormatTokenRanges( | |
| common_ancestor, | |
| LeftmostDescendant(common_ancestor).Value().TokensRange().begin()); | |
| return leaf_parent; | |
| } | |
| // Note: this destroys leaf | |
| TokenPartitionTree* MergeLeafIntoNextLeaf(TokenPartitionTree* leaf) { | |
| CHECK_NOTNULL(leaf); | |
| VLOG(4) << "origin leaf:\n" << *leaf; | |
| auto* target_leaf = NextLeaf(*leaf); | |
| 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(NearestCommonAncestor(*leaf, *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! | |
| RemoveSelfFromParent(*leaf); | |
| VLOG(4) << "common ancestor (after destroying leaf):\n" << common_ancestor; | |
| } | |
| // Sanity check invariants. | |
| VerifyFullTreeFormatTokenRanges( | |
| common_ancestor, | |
| LeftmostDescendant(common_ancestor).Value().TokensRange().begin()); | |
| return leaf_parent; | |
| } | |
| // | |
| // TokenPartitionTree class wrapper used by AppendFittingSubpartitions and | |
| // ReshapeFittingSubpartitions for partition reshaping purposes. | |
| // These wrappers take single-argument constructors to implicitly convert | |
| // to this wrapper. | |
| // | |
| class TokenPartitionTreeWrapper { | |
| public: | |
| /* implicit */ TokenPartitionTreeWrapper( // NOLINT | |
| const TokenPartitionTree& node) | |
| : node_(&node) {} | |
| // Grouping node with no corresponding TokenPartitionTree node | |
| /* implicit */ TokenPartitionTreeWrapper( // NOLINT | |
| 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 = TokenPartitionTree::subnodes_type::const_iterator; | |
| using partition_range = verible::container_iterator_range<partition_iterator>; | |
| struct AppendFittingSubpartitionsResult { | |
| // Indicates that wrapped style has been used. | |
| bool wrapped; | |
| // Length of longest line (including indent) in resulting tree. Might be | |
| // inaccurate when passed subpartitions contain forced line breaks. | |
| int longest_line_len; | |
| }; | |
| // Builds new tree from passed partitions in one of two styles described below. | |
| // The tree is built on `fitted_partitions` node. `header` and at least one | |
| // partition in `subpartitions` are required; `trailer` partition is optional. | |
| // `one_per_line` flag forces line break after each subpartition. | |
| // | |
| // Unwrapped style: | |
| // Used when the header and the first subpartition fit on one line and | |
| // `wrap_first_subpartiton` is false. | |
| // | |
| // All but first line use indent equal to `header` width. `trailer` is appended | |
| // to the last subpartition. | |
| // | |
| // <HEADER><SUBPARTITION 0><SUBPARTITION 1> | |
| // <SUBPARTITION 2><SUBPARTITION 3> | |
| // <SUBPARTITION 3><TRAILER> | |
| // | |
| // Wrapped style: | |
| // Line break is forced before first and after last partition. Lines with | |
| // subpartitions use subpartition's existing indent. | |
| // | |
| // <HEADER> | |
| // <SUBPARTITION 0><SUBPARTITION 1><SUBPARTITION 2> | |
| // <SUBPARTITION 3><SUBPARTITION 3> | |
| // <TRAILER> | |
| static AppendFittingSubpartitionsResult AppendFittingSubpartitions( | |
| VectorTree<TokenPartitionTreeWrapper>* fitted_partitions, | |
| const TokenPartitionTree& header, const partition_range& subpartitions, | |
| const TokenPartitionTree* trailer, const BasicFormatStyle& style, | |
| bool one_per_line, bool wrap_first_subpartition) { | |
| // at least one argument | |
| CHECK_GE(subpartitions.size(), 1); | |
| // Create first partition group | |
| // and populate it with function name (e.g. { [function foo (] }) | |
| fitted_partitions->Children().emplace_back(header.Value()); | |
| auto* group = &fitted_partitions->Children().back(); | |
| group->Children().emplace_back(header); | |
| int indent; | |
| // Try appending first argument | |
| const TokenPartitionTree& first_arg = subpartitions.front(); | |
| verible::UnwrappedLine first_line = group->Value().Value(first_arg); | |
| if (trailer && subpartitions.size() == 1) { | |
| first_line.SpanUpToToken(trailer->Value().TokensRange().end()); | |
| } | |
| int longest_line_len = 0; | |
| verible::FitResult fit_result = FitsOnLine(first_line, style); | |
| const bool wrapped_first_subpartition = | |
| wrap_first_subpartition || !fit_result.fits; | |
| if (!wrapped_first_subpartition) { | |
| // Compute new indentation level based on first partition | |
| const UnwrappedLine& uwline = group->Value().Value(); | |
| indent = FitsOnLine(uwline, style).final_column; | |
| // Append first argument to current group | |
| group->Children().emplace_back(subpartitions.front()); | |
| group->Value().Update(&group->Children().back()); | |
| // keep group indentation | |
| longest_line_len = fit_result.final_column; | |
| } else { | |
| // Measure header | |
| fit_result = FitsOnLine(group->Value().Value(), style); | |
| longest_line_len = std::max(longest_line_len, fit_result.final_column); | |
| // Use original indentation of the subpartition | |
| indent = first_arg.Value().IndentationSpaces(); | |
| // wrap line | |
| auto& siblings = group->Parent()->Children(); | |
| siblings.emplace_back(first_arg.Value()); | |
| group = &siblings.back(); | |
| group->Children().emplace_back(first_arg); | |
| group->Value().SetIndentationSpaces(indent); | |
| // Measure first wrapped line | |
| fit_result = FitsOnLine(group->Value().Value(), style); | |
| longest_line_len = std::max(longest_line_len, fit_result.final_column); | |
| } | |
| const auto remaining_args = | |
| make_container_range(subpartitions.begin() + 1, subpartitions.end()); | |
| for (const auto& arg : remaining_args) { | |
| // Every group should have at least one child | |
| CHECK(!group->Children().empty()); | |
| if (!one_per_line) { | |
| // Try appending current argument to current line | |
| UnwrappedLine uwline = group->Value().Value(arg); | |
| if (trailer && !wrapped_first_subpartition && | |
| (&arg == &remaining_args.back())) { | |
| uwline.SpanUpToToken(trailer->Value().TokensRange().end()); | |
| } | |
| fit_result = FitsOnLine(uwline, style); | |
| if (fit_result.fits) { | |
| // Fits, appending child | |
| group->Children().emplace_back(arg); | |
| group->Value().Update(&group->Children().back()); | |
| longest_line_len = std::max(longest_line_len, fit_result.final_column); | |
| continue; | |
| } | |
| } | |
| // Forced one per line or does not fit, start new group with current child | |
| auto& siblings = group->Parent()->Children(); | |
| siblings.emplace_back(arg.Value()); | |
| group = &siblings.back(); | |
| group->Children().emplace_back(arg); | |
| // no need to update because group was created | |
| // with current child value | |
| // Fix group indentation | |
| group->Value().SetIndentationSpaces(indent); | |
| fit_result = FitsOnLine(group->Value().Value(), style); | |
| longest_line_len = std::max(longest_line_len, fit_result.final_column); | |
| } | |
| if (trailer) { | |
| if (wrapped_first_subpartition) { | |
| auto& siblings = group->Parent()->Children(); | |
| siblings.emplace_back(trailer->Value()); | |
| group = &siblings.back(); | |
| group->Children().emplace_back(*trailer); | |
| group->Value().SetIndentationSpaces(first_line.IndentationSpaces()); | |
| } else { | |
| group->Children().emplace_back(*trailer); | |
| group->Value().Update(&group->Children().back()); | |
| } | |
| fit_result = FitsOnLine(group->Value().Value(), style); | |
| longest_line_len = std::max(longest_line_len, fit_result.final_column); | |
| } | |
| return {wrapped_first_subpartition, longest_line_len}; | |
| } | |
| // Reshapes the tree pointed to by `node` using `AppendFittingSubpartitions()` | |
| // function. Function creates VectorTree<> with additional level of grouping for | |
| // each created line. Function expects at least two partitions: first one | |
| // ("header") is used for computing indentation, the second ("subpartitions") | |
| // should contain subpartitions to be appended and aligned. Optional third | |
| // partition ("trailer") is appended to the last subpartition or placed in a new | |
| // line with the same indent as the header. Example input tree: | |
| // | |
| // { (>>[...], policy: append-fitting-sub-partitions) // `node` tree | |
| // { (>>[string seq_names [ ] = {]) } // header | |
| // { (>>>>[...], policy: always-expand) // subpartitions | |
| // { (>>>>["uart_sanity_vseq" ,]) } | |
| // ... | |
| // { (>>>>["uart_loopback_vseq"]) } | |
| // } | |
| // { (>>[} ;]) } // trailer | |
| // } | |
| // | |
| // When "subpartitions" group has kAlwaysExpand policy, line break is forced | |
| // between each subpartition from the group. | |
| void ReshapeFittingSubpartitions(const BasicFormatStyle& style, | |
| TokenPartitionTree* node) { | |
| VLOG(4) << __FUNCTION__ << ", before:\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_partition = children[1]; | |
| const auto& subpartitions = args_partition.Children(); | |
| const auto* trailer = children.size() > 2 ? &children[2] : nullptr; | |
| const bool one_per_line = args_partition.Value().PartitionPolicy() == | |
| PartitionPolicyEnum::kAlwaysExpand; | |
| partition_range args_range; | |
| if (subpartitions.empty()) { | |
| // Partitions with one argument may have been flattened one level. | |
| args_range = | |
| make_container_range(children.begin() + 1, children.begin() + 2); | |
| } else { | |
| // Arguments exist in a nested subpartition. | |
| args_range = | |
| make_container_range(subpartitions.begin(), subpartitions.end()); | |
| } | |
| VectorTree<TokenPartitionTreeWrapper> unwrapped_tree(node->Value()); | |
| VectorTree<TokenPartitionTreeWrapper> wrapped_tree(node->Value()); | |
| // Format unwrapped_lines. At first without forced wrap after first line | |
| const auto result = AppendFittingSubpartitions( | |
| &unwrapped_tree, header, args_range, trailer, style, one_per_line, false); | |
| if (result.wrapped && result.longest_line_len < style.column_limit) { | |
| // 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. | |
| const auto wrapped_result = AppendFittingSubpartitions( | |
| &wrapped_tree, header, args_range, trailer, style, one_per_line, true); | |
| // Avoid exceeding column limit if possible | |
| if (result.longest_line_len > style.column_limit && | |
| wrapped_result.longest_line_len <= style.column_limit) { | |
| fitted_tree = &wrapped_tree; | |
| } else { | |
| // 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 | |
| temporary_tree.Children().emplace_back(uwline); | |
| auto* group = &temporary_tree.Children().back(); | |
| // 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->Children().push_back(*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 | |
| AdoptSubtreesFrom(*node, &temporary_tree); | |
| VLOG(4) << __FUNCTION__ << ", after:\n" << *node; | |
| } | |
| } // namespace verible |