| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/console/format_node_console.h" |
| |
| #include "src/developer/debug/zxdb/common/ref_ptr_to.h" |
| #include "src/developer/debug/zxdb/common/string_util.h" |
| #include "src/developer/debug/zxdb/console/command_utils.h" |
| #include "src/developer/debug/zxdb/console/format_name.h" |
| #include "src/developer/debug/zxdb/console/string_util.h" |
| #include "src/developer/debug/zxdb/expr/format.h" |
| #include "src/developer/debug/zxdb/expr/format_node.h" |
| #include "src/lib/fxl/memory/ref_counted.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // Provides a way to execute a single callback when a set of deferred actions is complete. |
| class AggregateDeferredCallback : public fxl::RefCountedThreadSafe<AggregateDeferredCallback> { |
| public: |
| // Returns a subordinate callback, the completion of which will contribute to the outer callback. |
| fit::deferred_callback MakeSubordinate() { |
| // This callback exists only to hold the bound reference to the outer callback. |
| return fit::defer_callback([ref = RefPtrTo(this)]() {}); |
| } |
| |
| private: |
| FRIEND_REF_COUNTED_THREAD_SAFE(AggregateDeferredCallback); |
| FRIEND_MAKE_REF_COUNTED(AggregateDeferredCallback); |
| |
| explicit AggregateDeferredCallback(fit::deferred_callback cb) : outer_(std::move(cb)) {} |
| |
| fit::deferred_callback outer_; |
| }; |
| |
| // Some state needs to be tracked recursively as we iterate through the tree. |
| struct RecursiveState { |
| explicit RecursiveState(const ConsoleFormatOptions& opts) : options(opts) {} |
| |
| // Returns a modified version of the state for one additional level of recursion, advancing |
| // both indent and tree depth. |
| RecursiveState Advance() const; |
| |
| // Advances the tree depth but not the visual nesting depth. |
| RecursiveState AdvanceNoIndent() const; |
| |
| // Returns true if the combination of options means the type should be shown. |
| bool TypeForcedOn() const { |
| return !inhibit_one_type && options.verbosity == ConsoleFormatOptions::Verbosity::kAllTypes; |
| } |
| |
| // Returns true if the current item should be formatted in expanded tree mode or in one line. |
| bool ShouldExpand() const { |
| return options.wrapping == ConsoleFormatOptions::Wrapping::kExpanded || |
| (options.wrapping == ConsoleFormatOptions::Wrapping::kSmart && smart_indent_is_expanded); |
| } |
| |
| int GetIndentAmount() const { |
| if (!ShouldExpand()) |
| return 0; |
| return options.indent_amount * indent_depth; |
| } |
| |
| // Returns the string to indent a node at the given level. |
| std::string GetIndentString() const { return std::string(GetIndentAmount(), ' '); } |
| |
| // Returns true if items at this level should be elided because the printing depth is too deep. |
| bool DepthTooDeep() const { return tree_depth >= options.max_depth; } |
| |
| const ConsoleFormatOptions options; |
| |
| // Forces various information off for exactly one level of printing. Use for array printing, for |
| // example, to avoid duplicating the type information for every entry. |
| bool inhibit_one_name = false; |
| bool inhibit_one_type = false; |
| |
| // How deep in the node tree we are, the top level is 0. |
| int tree_depth = 0; |
| |
| // How many levels of visual indent we are, the top level is 0. This is often the same as the |
| // tree depth but some things like pointer resolution are different levels of the tree but |
| // are presented as the same visual level. |
| int indent_depth = 0; |
| |
| // Number of pointers we've expanded so far in this level of recursion. |
| int pointer_depth = 0; |
| |
| // Controls what mode of smart indenting (single-line or expanded) the current node should be |
| // formatted in. |
| bool smart_indent_is_expanded = true; |
| }; |
| |
| RecursiveState RecursiveState::Advance() const { |
| RecursiveState result = *this; |
| result.inhibit_one_name = false; |
| result.inhibit_one_type = false; |
| |
| result.tree_depth++; |
| result.indent_depth++; |
| |
| return result; |
| } |
| |
| RecursiveState RecursiveState::AdvanceNoIndent() const { |
| RecursiveState result = Advance(); |
| result.indent_depth--; // Undo visual indent from Advance(). |
| return result; |
| } |
| |
| void RecursiveDescribeFormatNode(FormatNode* node, fxl::RefPtr<EvalContext> context, |
| const RecursiveState& state, fit::deferred_callback cb) { |
| if (state.tree_depth == state.options.max_depth) |
| return; // Reached max depth, give up. |
| |
| auto on_complete = fit::defer_callback([weak_node = node->GetWeakPtr(), context, |
| cb = std::move(cb), state = state.Advance()]() mutable { |
| // Description is complete. |
| if (!weak_node || weak_node->children().empty()) |
| return; |
| |
| // Check for pointer expansion to avoid recursing into too many. |
| if (weak_node->description_kind() == FormatNode::kPointer) { |
| if (state.pointer_depth == state.options.pointer_expand_depth) |
| return; // Don't recurse into another level of pointer. |
| state.pointer_depth++; |
| } |
| |
| // Recurse into children. |
| auto aggregator = fxl::MakeRefCounted<AggregateDeferredCallback>(std::move(cb)); |
| for (const auto& child : weak_node->children()) |
| RecursiveDescribeFormatNode(child.get(), context, state, aggregator->MakeSubordinate()); |
| }); |
| |
| if (node->state() != FormatNode::kDescribed) |
| FillFormatNodeDescription(node, state.options, context, std::move(on_complete)); |
| } |
| |
| bool IsRust(const FormatNode* node) { |
| if (!node->value().type()) |
| return false; |
| return node->value().type()->GetLanguage() == DwarfLang::kRust; |
| } |
| |
| // Forward decls, see the implementation for comments. |
| OutputBuffer DoFormatNode(const FormatNode* node, const RecursiveState& state, int line_start_char); |
| OutputBuffer DoFormatNodeOneWay(const FormatNode* node, const RecursiveState& state, |
| int indent_amount); |
| |
| // Get a possibly-elided version of the type name for a medium verbosity level. |
| std::string GetElidedTypeName(const RecursiveState& state, const std::string& name) { |
| if (state.options.verbosity != ConsoleFormatOptions::Verbosity::kMinimal) |
| return name; // No eliding except in minimal mode. |
| |
| // Pick different thresholds depending on if we're doing multiline or not. The threshold and |
| // the eliding length are different so we don't elide one or two characters. |
| size_t always_allow_below = 64; |
| size_t elide_to = 50; |
| if (!state.ShouldExpand()) { |
| always_allow_below /= 2; |
| elide_to /= 2; |
| } |
| |
| // Names shorter than this are always displayed in full. |
| if (name.size() <= always_allow_below) |
| return name; |
| |
| return name.substr(0, elide_to) + "…"; |
| } |
| |
| // Rust collections (structs) normally have the type name before the bracket like "MyStruct{...}". |
| // This function appends this name. |
| // |
| // For some struct types including common standard containers the names can get very long. As a |
| // result in "minimal" verbosity mode we never show the namespaces and also elide template |
| // parameters when the template is long. |
| void AppendRustCollectionName(const FormatNode* node, const RecursiveState& state, |
| OutputBuffer* out) { |
| if (!IsRust(node) || !node->value().type()) |
| return; |
| |
| if (state.options.verbosity != ConsoleFormatOptions::Verbosity::kMinimal) { |
| // Use the full identifier name in the more verbose modes. |
| FormatIdentifierOptions opts; |
| opts.show_global_qual = false; |
| opts.elide_templates = false; |
| opts.bold_last = false; |
| out->Append(FormatIdentifier(node->value().type()->GetIdentifier(), opts)); |
| return; |
| } |
| |
| // Special-case "Vec" with the macro that's normally used and omit the type. |
| if (StringStartsWith(node->type(), "alloc::vec::Vec<")) { |
| out->Append("vec!"); |
| return; |
| } |
| |
| // In minimal mode, extract just the last component of the identifier. |
| const Identifier& ident = node->value().type()->GetIdentifier(); |
| if (ident.empty()) |
| return; |
| ParsedIdentifier parsed = |
| ToParsedIdentifier(Identifier(IdentifierQualification::kRelative, ident.components().back())); |
| |
| if (parsed.components()[0].has_template()) { |
| // Some containers have very long template names. Since Rust structs always have the name, the |
| // lengths can get a little bit nuts. Elide the template contents when too long by converting |
| // all template parameters to one string and eliding. |
| const auto& template_contents = parsed.components()[0].template_contents(); |
| std::string template_string; |
| for (size_t i = 0; i < template_contents.size(); i++) { |
| if (i > 0) |
| template_string += ", "; |
| template_string += template_contents[i]; |
| } |
| |
| // Replace the template parameters with our single string, elided if necessary. |
| parsed.components()[0] = ParsedIdentifierComponent(parsed.components()[0].name(), |
| {GetElidedTypeName(state, template_string)}); |
| } |
| |
| FormatIdentifierOptions ident_opts; |
| ident_opts.show_global_qual = false; |
| ident_opts.elide_templates = false; |
| ident_opts.bold_last = false; |
| out->Append(FormatIdentifier(parsed, ident_opts)); |
| } |
| |
| // Writes the suffix for when there are multiple items in a collection or an array. |
| void AppendItemSuffix(const RecursiveState& state, bool last_item, OutputBuffer* out) { |
| if (state.ShouldExpand()) |
| out->Append("\n"); // Expanded children always end with a newline. |
| else if (!last_item) |
| out->Append(Syntax::kOperatorNormal, ", "); // Get commas for everything but the last. |
| } |
| |
| // Returns true if type information should be displayed before the variable name, as in |
| // <TYPE> name = value |
| bool ShouldPrependTypeNameBeforeName(const FormatNode* node, const RecursiveState& state) { |
| if (!state.TypeForcedOn()) |
| return false; // Never show types unless requested. |
| |
| if (node->type().empty()) |
| return false; // Don't show empty types. |
| |
| if (!IsRust(node)) |
| return true; // Non-Rust code always gets the type. |
| |
| if (node->description_kind() == FormatNode::kCollection || |
| node->description_kind() == FormatNode::kArray || |
| node->description_kind() == FormatNode::kRustTuple || |
| node->description_kind() == FormatNode::kRustTupleStruct) { |
| // Rust structs and tuple structs are special. They encode the type after the variable, so: |
| // "foo = StructName{...}" and we don't want to prepend it before "foo". But this doesn't happen |
| // for tuples. |
| // |
| // Rust tuples don't have the name shown ever. The individual members will have their types |
| // displayed which will encode the type of the tuple. |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Appends the formatted name and "=" as needed by this node. |
| void AppendNodeNameAndType(const FormatNode* node, const RecursiveState& state, OutputBuffer* out) { |
| if (ShouldPrependTypeNameBeforeName(node, state)) { |
| out->Append(Syntax::kOperatorDim, "("); |
| out->Append(Syntax::kComment, node->type()); |
| out->Append(Syntax::kOperatorDim, ") "); |
| } |
| |
| if (!state.inhibit_one_name && !node->name().empty()) { |
| // Node name. Base class names are less important so get dimmed. Especially STL base classes |
| // can get very long so we also elide them unless verbose mode is turned on. |
| if (node->child_kind() == FormatNode::kBaseClass) { |
| out->Append(Syntax::kComment, GetElidedTypeName(state, node->name())); |
| } else { |
| // Normal variable-style node name. |
| out->Append(Syntax::kVariable, node->name()); |
| } |
| |
| // Rust uses colons for most things, while C uses equals. The exception for Rust is local |
| // variables which "=" to better match how the variables are declared. |
| if (IsRust(node) && node->child_kind() != FormatNode::kVariable) |
| out->Append(Syntax::kOperatorNormal, ": "); |
| else |
| out->Append(Syntax::kOperatorNormal, " = "); |
| } |
| } |
| |
| // Writes the node's children. The caller sets up how it wants the children to be formatted by |
| // passing both the recursive state for the node, and the state it wants to use for the children. |
| // |
| // The opening and closing strings are used to wrap the contents, e.g. "{" and "}". If these are |
| // empty it's assumed that the first line doesn't need a newline after it in multiline mode |
| // (normally the opening character would be followed by a newline and an indent in multiline mode). |
| void AppendNodeChildren(const FormatNode* node, const RecursiveState& node_state, |
| const std::string& opening, const std::string& closing, |
| const RecursiveState& child_state, OutputBuffer* out) { |
| out->Append(Syntax::kOperatorNormal, opening); |
| |
| if (child_state.DepthTooDeep()) { |
| // Don't print the child names if those children will be elided. Otherwise this prints a |
| // whole struct out with no data: "{foo=…, bar=…, baz=…}" which is wasteful of space and not |
| // helpful. |
| out->Append(Syntax::kComment, "…"); |
| out->Append(Syntax::kOperatorNormal, closing); |
| return; |
| } |
| |
| // Special-case empty ones because we never want wrapping. |
| if (node->children().empty()) { |
| out->Append(Syntax::kOperatorNormal, closing); |
| return; |
| } |
| |
| if (!opening.empty() && node_state.ShouldExpand()) |
| out->Append("\n"); |
| |
| int child_indent = child_state.GetIndentAmount(); |
| for (size_t i = 0; i < node->children().size(); i++) { |
| const FormatNode* child = node->children()[i].get(); |
| |
| if (child->state() == FormatNode::kEmpty) { |
| // Arrays can have "empty" nodes which use the name to indicate clipping ("..."). |
| if (!child->name().empty()) |
| out->Append(Syntax::kComment, child->name()); |
| } else { |
| out->Append(DoFormatNode(child, child_state, child_indent)); |
| } |
| |
| // Separator (comma or newline). |
| AppendItemSuffix(node_state, i + 1 == node->children().size(), out); |
| } |
| |
| out->Append(Syntax::kOperatorNormal, node_state.GetIndentString() + closing); |
| } |
| |
| OutputBuffer DoFormatArrayOrTupleNode(const FormatNode* node, const RecursiveState& state) { |
| OutputBuffer out; |
| |
| RecursiveState child_state = state.Advance(); |
| // In multiline mode, show the names of the array indices ("[0] = ..."). |
| child_state.inhibit_one_name = !state.ShouldExpand(); |
| |
| if (node->description_kind() == FormatNode::kArray) { |
| // Arrays all have the same type so don't show the type for every child. |
| child_state.inhibit_one_type = true; |
| |
| if (IsRust(node)) { |
| // Rust sequences use "[...]". |
| AppendRustCollectionName(node, state, &out); |
| AppendNodeChildren(node, state, "[", "]", child_state, &out); |
| } else { |
| AppendNodeChildren(node, state, "{", "}", child_state, &out); |
| } |
| } else { |
| // Rust tuple or tuple struct. These should not be empty. |
| if (node->description_kind() == FormatNode::kRustTupleStruct) { |
| // Tuple structs get the name, regular tuples have none. |
| AppendRustCollectionName(node, state, &out); |
| } |
| |
| // Tuples of length 1 never show the child names, even in expanded mode because it's not |
| // helpful and looks weird.o |
| // |
| // We show the indices of tuple members in expanded mode for larger tuples even though |
| // Rust wouldn't do that because highly nested data structures can get very difficult to |
| // follow. Something like a closure or a generator can go on for >100 lines and have many |
| // tuple members (>10). Without these indices the structure is not interpretable. This doesn't |
| // come up when using println in the language very often because normally these types of |
| // tuples are not printed. |
| if (node->children().size() == 1) |
| child_state.inhibit_one_name = true; |
| |
| AppendNodeChildren(node, state, "(", ")", child_state, &out); |
| } |
| |
| return out; |
| } |
| |
| OutputBuffer DoFormatCollectionNode(const FormatNode* node, const RecursiveState& state) { |
| OutputBuffer out; |
| |
| // Rust structs are always prefixed with the struct name. This does nothing for non-Rust. |
| AppendRustCollectionName(node, state, &out); |
| |
| // Special-case empty collections because we never want wrapping. |
| if (node->children().empty()) { |
| // Rust empty structs have no brackets. |
| if (!IsRust(node)) |
| out.Append(Syntax::kOperatorNormal, "{}"); |
| return out; |
| } |
| |
| AppendNodeChildren(node, state, "{", "}", state.Advance(), &out); |
| return out; |
| } |
| |
| OutputBuffer DoFormatPointerNode(const FormatNode* node, const RecursiveState& state) { |
| OutputBuffer out; |
| |
| // When type information is forced on, the type will have already been printed. Otherwise we print |
| // a "(*)" to indicate the value is a pointer. |
| if (!state.TypeForcedOn()) |
| out.Append(Syntax::kOperatorDim, "(*)"); |
| |
| // Pointers will have a child node that expands to the pointed-to value. If this is in a |
| // "described" state with no error, then show the data. |
| // |
| // Don't show errors dereferencing pointers here since for long structure listings with |
| // potentially multiple bad pointers, these get very verbose. The user can print the specific |
| // value and see the error if desired. |
| if (node->children().empty() || node->children()[0]->state() != FormatNode::kDescribed || |
| node->children()[0]->err().has_error()) { |
| // Just have the pointer address. |
| out.Append(node->description()); |
| } else { |
| // Have the pointed-to data, dim the address and show the data. Omit types because we already |
| // printed the pointer type. We do not want to indent another level. |
| RecursiveState child_state = state.AdvanceNoIndent(); |
| child_state.inhibit_one_name = true; |
| child_state.inhibit_one_type = true; |
| |
| out.Append(Syntax::kComment, node->description() + " " + GetRightArrow() + " "); |
| // Use the "one way" version to propagate our formatting mode. This node cant't get different |
| // formatting than its child. |
| out.Append(DoFormatNodeOneWay(node->children()[0].get(), child_state, 0)); |
| } |
| return out; |
| } |
| |
| OutputBuffer DoFormatReferenceNode(const FormatNode* node, const RecursiveState& state) { |
| // References will have a child node that expands to the referenced value. If this is in a |
| // "described" state, then we have the data and should show it. Otherwise omit this. |
| if (node->children().empty() || node->children()[0]->state() != FormatNode::kDescribed) { |
| // The caller should have expanded the references if it wants them shown. If not, display |
| // a placeholder with the address. Show the whole thing dimmed to help indicate this isn't the |
| // actual value. |
| return OutputBuffer(Syntax::kComment, "(&)" + node->description()); |
| } |
| |
| // Have the pointed-to data, just show the value. We do not want to indent another level. |
| RecursiveState child_state = state.AdvanceNoIndent(); |
| child_state.inhibit_one_name = true; |
| child_state.inhibit_one_type = true; |
| |
| // Use the "one way" version to propagate our formatting mode. This node cant't get different |
| // formatting than its child. |
| return DoFormatNodeOneWay(node->children()[0].get(), child_state, 0); |
| } |
| |
| // A Rust enum is currently like a reference in that is has a child that's the resolved type. |
| // A resolved enum type will be a TupleStruct (unnamed members) or a Struct (named members). Enums |
| // with no members are encoded as empty structs which the struct printing code will handle. |
| OutputBuffer DoFormatRustEnum(const FormatNode* node, const RecursiveState& state) { |
| if (node->children().empty()) { |
| // The caller should have expanded the enum if it wants it to be shown. If not, display the |
| // description which will generally be the short name of the enum. |
| return OutputBuffer(Syntax::kComment, node->description()); |
| } |
| |
| // Have the pointed-to data, just show the value. We do not want to indent another level. The |
| // type is also hidden because it will be a generated type. If requested, the type of the overall |
| // enum will have already been printed. |
| RecursiveState child_state = state.AdvanceNoIndent(); |
| child_state.inhibit_one_name = true; |
| child_state.inhibit_one_type = true; |
| |
| // Use the "one way" version to propagate our formatting mode. This node can't get different |
| // formatting than its child. |
| return DoFormatNodeOneWay(node->children()[0].get(), child_state, 0); |
| } |
| |
| // A generic node with one child for things like std::optional or std::atomic. |
| OutputBuffer DoFormatWrapper(const FormatNode* node, const RecursiveState& state) { |
| if (node->children().empty()) |
| return OutputBuffer(node->description() + node->wrapper_prefix() + node->wrapper_suffix()); |
| |
| RecursiveState child_state = state.AdvanceNoIndent(); |
| |
| OutputBuffer out; |
| out.Append(node->description()); |
| out.Append(Syntax::kOperatorNormal, node->wrapper_prefix()); |
| out.Append(DoFormatNodeOneWay(node->children()[0].get(), child_state, 0)); |
| out.Append(Syntax::kOperatorNormal, node->wrapper_suffix()); |
| return out; |
| } |
| |
| // Appends the description for a normal node (number or whatever). |
| OutputBuffer DoFormatStandardNode(const FormatNode* node, const RecursiveState& state, |
| Syntax syntax = Syntax::kNormal) { |
| return OutputBuffer(syntax, node->description()); |
| } |
| |
| // Groups have no heading, they're always just a list of children. |
| OutputBuffer DoFormatGroupNode(const FormatNode* node, const RecursiveState& state) { |
| OutputBuffer out; |
| AppendNodeChildren(node, state, std::string(), std::string(), state, &out); |
| return out; |
| } |
| |
| // Inner implementation for DoFormatNode() (see that for more). This does the actual formatting one |
| // time according to the current formatting options and state. |
| // |
| // Some callers may want to call this directly rather than DoFormatNode() if they want to bypass the |
| // two-pass formatting. This is useful for pointers which are two nodes (when auto dereferenced) but |
| // appear as one and the expansion mode of the outer should be passed transparently to the nested |
| // node. |
| OutputBuffer DoFormatNodeOneWay(const FormatNode* node, const RecursiveState& state, |
| int indent_amount) { |
| OutputBuffer out; |
| |
| if (indent_amount) |
| out.Append(std::string(indent_amount, ' ')); |
| |
| AppendNodeNameAndType(node, state, &out); |
| if (state.DepthTooDeep() || node->state() != FormatNode::kDescribed) { |
| // Hit max depth, give up. |
| out.Append(Syntax::kComment, "…"); |
| } else if (node->err().has_error()) { |
| // Write the error. |
| out.Append(Syntax::kComment, "<" + node->err().msg() + ">"); |
| } else { |
| // Normal formatting. |
| switch (node->description_kind()) { |
| case FormatNode::kNone: |
| case FormatNode::kBaseType: |
| case FormatNode::kFunctionPointer: |
| case FormatNode::kOther: |
| // All these things just print the description only. |
| out.Append(DoFormatStandardNode(node, state)); |
| break; |
| case FormatNode::kGroup: |
| out.Append(DoFormatGroupNode(node, state)); |
| break; |
| case FormatNode::kArray: |
| case FormatNode::kRustTuple: |
| case FormatNode::kRustTupleStruct: |
| out.Append(DoFormatArrayOrTupleNode(node, state)); |
| break; |
| case FormatNode::kCollection: |
| out.Append(DoFormatCollectionNode(node, state)); |
| break; |
| case FormatNode::kPointer: |
| out.Append(DoFormatPointerNode(node, state)); |
| break; |
| case FormatNode::kReference: |
| out.Append(DoFormatReferenceNode(node, state)); |
| break; |
| case FormatNode::kRustEnum: |
| out.Append(DoFormatRustEnum(node, state)); |
| break; |
| case FormatNode::kWrapper: |
| out.Append(DoFormatWrapper(node, state)); |
| break; |
| case FormatNode::kString: |
| // For strings, use the number format to determine if the output should be displayed |
| // as the string data or a numeric array. |
| if (state.options.num_format == FormatOptions::NumFormat::kDefault) { |
| out.Append(DoFormatStandardNode(node, state, Syntax::kStringNormal)); |
| } else { |
| out.Append(DoFormatArrayOrTupleNode(node, state)); |
| } |
| break; |
| } |
| } |
| |
| return out; |
| } |
| |
| // Formats one node. This layer provides the smart wrapping logic where we may try to format an |
| // item twice, first to see if it fits on one line, and possibly again ig it doesn't. |
| // |
| // The caller of this function is in charge of computing (but not inserting) the appropriate level |
| // of indent and also appending trailing newlines if necessary. |
| // |
| // The caller must compute the indenting amount because whether or not there's indenting for this |
| // node depends the expansion level of the caller, not this node. (This item could be in single-line |
| // mode and still be indented if the caller is expanded.) |
| OutputBuffer DoFormatNode(const FormatNode* node, const RecursiveState& state, int indent_amount) { |
| if (state.options.wrapping != ConsoleFormatOptions::Wrapping::kSmart) |
| return DoFormatNodeOneWay(node, state, indent_amount); // Nothing fancy to do. |
| |
| // Nodes with few children we try to fit on one line if possible in smart mode. Generally |
| // structures with more than 4 children will always spill over to multiline mode, so this check is |
| // more of an optimization to avoid trying and failing to format the whole thing as a single line |
| // first. Even if it fits, many members on a line are not readable. |
| if (state.smart_indent_is_expanded && node->children().size() <= 4) { |
| // The previous node was in an expanded state. First try to format this one as non-expanded. |
| // to see if it fits. |
| RecursiveState one_line_state = state; |
| one_line_state.smart_indent_is_expanded = false; |
| |
| OutputBuffer one_line = DoFormatNodeOneWay(node, one_line_state, indent_amount); |
| if (static_cast<int>(one_line.UnicodeCharWidth()) <= state.options.smart_indent_cols) |
| return one_line; // Fits. |
| |
| // Too big, format as expanded. |
| return DoFormatNodeOneWay(node, state, indent_amount); |
| } |
| |
| // The previous node was already in one-line mode, stay in that mode. |
| return DoFormatNodeOneWay(node, state, indent_amount); |
| } |
| |
| } // namespace |
| |
| void DescribeFormatNodeForConsole(FormatNode* node, const ConsoleFormatOptions& options, |
| fxl::RefPtr<EvalContext> context, fit::deferred_callback cb) { |
| RecursiveDescribeFormatNode(node, context, RecursiveState(options), std::move(cb)); |
| } |
| |
| OutputBuffer FormatNodeForConsole(const FormatNode& node, const ConsoleFormatOptions& options) { |
| return DoFormatNode(&node, RecursiveState(options), 0); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatValueForConsole(ExprValue value, |
| const ConsoleFormatOptions& options, |
| fxl::RefPtr<EvalContext> context, |
| const std::string& value_name) { |
| auto node = std::make_unique<FormatNode>(value_name, std::move(value)); |
| node->set_child_kind(FormatNode::kVariable); |
| |
| FormatNode* node_ptr = node.get(); |
| |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| DescribeFormatNodeForConsole(node_ptr, options, context, |
| fit::defer_callback([node = std::move(node), options, out]() { |
| // Asynchronous expansion is complete, now format the completed |
| // output. |
| out->Complete(FormatNodeForConsole(*node, options)); |
| })); |
| return out; |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatVariableForConsole(const Variable* var, |
| const ConsoleFormatOptions& options, |
| fxl::RefPtr<EvalContext> context) { |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| context->GetVariableValue( |
| RefPtrTo(var), [name = var->GetAssignedName(), context, options, out](ErrOrValue value) { |
| if (value.has_error()) { |
| // In the error case, construct a node with the error set so the formatting with other |
| // types of errors is consistent. |
| FormatNode err_node(name); |
| err_node.SetDescribedError(value.err()); |
| out->Complete(FormatNodeForConsole(err_node, options)); |
| } else { |
| out->Complete(FormatValueForConsole(value.take_value(), options, context, name)); |
| } |
| }); |
| return out; |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatExpressionsForConsole( |
| const std::vector<std::string>& expressions, const ConsoleFormatOptions& options, |
| fxl::RefPtr<EvalContext> context) { |
| // Put all of the expressions in a "group" node and then evaluate and format this node. This will |
| // allow single- and multi-line formatting for the values and also handles all of the asynchronous |
| // state across all of the expressions automatically. |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| auto list_node = std::make_unique<FormatNode>(FormatNode::GroupTag()); |
| FormatNode* list_node_ptr = list_node.get(); |
| |
| for (const std::string& expr : expressions) { |
| // Use the expression as the description. |
| auto child_node = std::make_unique<FormatNode>(expr, expr); |
| list_node->children().push_back(std::move(child_node)); |
| } |
| |
| DescribeFormatNodeForConsole( |
| list_node_ptr, options, context, |
| fit::defer_callback([list_node = std::move(list_node), options, out]() { |
| RecursiveState state(options); |
| out->Complete(FormatNodeForConsole(*list_node, options)); |
| })); |
| return out; |
| } |
| |
| } // namespace zxdb |