blob: b3d78ab6b1db2b65356bb399e5c9ec63d1940607 [file] [log] [blame]
// 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