static void prettify_node_recursive(const wcstring &source, const parse_node_tree_t &tree, node_offset_t node_idx, indent_t node_indent, parse_token_type_t parent_type, bool *has_new_line, wcstring *out_result, bool do_indent) { const parse_node_t &node = tree.at(node_idx); const parse_token_type_t node_type = node.type; /* Increment the indent if we are either a root job_list, or root case_item_list, or in an if or while header (#1665) */ const bool is_root_job_list = (node_type == symbol_job_list && parent_type != symbol_job_list); const bool is_root_case_item_list = (node_type == symbol_case_item_list && parent_type != symbol_case_item_list); const bool is_if_while_header = ((node_type == symbol_job || node_type == symbol_andor_job_list) && (parent_type == symbol_if_clause || parent_type == symbol_while_header)); if (is_root_job_list || is_root_case_item_list || is_if_while_header) { node_indent += 1; } /* Handle comments, which come before the text */ if (node.has_comments()) { const parse_node_tree_t::parse_node_list_t comment_nodes = tree.comment_nodes_for_node(node); for (size_t i=0; i < comment_nodes.size(); i++) { const parse_node_t &comment_node = *comment_nodes.at(i); append_whitespace(node_indent, do_indent, *has_new_line, out_result); out_result->append(source, comment_node.source_start, comment_node.source_length); } } if (node_type == parse_token_type_end) { /* Newline */ out_result->push_back(L'\n'); *has_new_line = true; } else if ((node_type >= FIRST_PARSE_TOKEN_TYPE && node_type <= LAST_PARSE_TOKEN_TYPE) || node_type == parse_special_type_parse_error) { if (node.has_source()) { /* Some type representing a particular token */ append_whitespace(node_indent, do_indent, *has_new_line, out_result); out_result->append(source, node.source_start, node.source_length); *has_new_line = false; } } /* Recurse to all our children */ for (node_offset_t idx = 0; idx < node.child_count; idx++) { /* Note we pass our type to our child, which becomes its parent node type */ prettify_node_recursive(source, tree, node.child_start + idx, node_indent, node_type, has_new_line, out_result, do_indent); } }
/* We are given a parse tree, the index of a node within the tree, its indent, and a vector of indents the same size as the original source string. Set the indent correspdonding to the node's source range, if appropriate. trailing_indent is the indent for nodes with unrealized source, i.e. if I type 'if false <ret>' then we have an if node with an empty job list (without source) but we want the last line to be indented anyways. switch statements also indent. max_visited_node_idx is the largest index we visited. */ static void compute_indents_recursive(const parse_node_tree_t &tree, node_offset_t node_idx, int node_indent, parse_token_type_t parent_type, std::vector<int> *indents, int *trailing_indent, node_offset_t *max_visited_node_idx) { /* Guard against incomplete trees */ if (node_idx > tree.size()) return; /* Update max_visited_node_idx */ if (node_idx > *max_visited_node_idx) *max_visited_node_idx = node_idx; /* We could implement this by utilizing the fish grammar. But there's an easy trick instead: almost everything that wraps a job list should be indented by 1. So just find all of the job lists. One exception is switch, which wraps a case_item_list instead of a job_list. The other exception is job_list itself: a job_list is a job and a job_list, and we want that child list to be indented the same as the parent. So just find all job_lists whose parent is not a job_list, and increment their indent by 1. */ const parse_node_t &node = tree.at(node_idx); const parse_token_type_t node_type = node.type; /* Increment the indent if we are either a root job_list, or root case_item_list */ const bool is_root_job_list = (node_type == symbol_job_list && parent_type != symbol_job_list); const bool is_root_case_item_list = (node_type == symbol_case_item_list && parent_type != symbol_case_item_list); if (is_root_job_list || is_root_case_item_list) { node_indent += 1; } /* If we have source, store the trailing indent unconditionally. If we do not have source, store the trailing indent only if ours is bigger; this prevents the trailing "run" of terminal job lists from affecting the trailing indent. For example, code like this: if foo will be parsed as this: job_list job if_statement job [if] job_list [empty] job_list [empty] There's two "terminal" job lists, and we want the innermost one. Note we are relying on the fact that nodes are in the same order as the source, i.e. an in-order traversal of the node tree also traverses the source from beginning to end. */ if (node.has_source() || node_indent > *trailing_indent) { *trailing_indent = node_indent; } /* Store the indent into the indent array */ if (node.source_start != SOURCE_OFFSET_INVALID && node.source_start < indents->size()) { if (node.has_source()) { /* A normal non-empty node. Store the indent unconditionally. */ indents->at(node.source_start) = node_indent; } else { /* An empty node. We have a source offset but no source length. This can come about when a node legitimately empty: while true; end The job_list inside the while loop is empty. It still has a source offset (at the end of the while statement) but no source extent. We still need to capture that indent, because there may be comments inside: while true # loop forever end The 'loop forever' comment must be indented, by virtue of storing the indent. Now consider what happens if we remove the end: while true # loop forever Now both the job_list and end_command are unmaterialized. However, we want the indent to be of the job_list and not the end_command. Therefore, we only store the indent if it's bigger. */ if (node_indent > indents->at(node.source_start)) { indents->at(node.source_start) = node_indent; } } } /* Recursive to all our children */ for (node_offset_t idx = 0; idx < node.child_count; idx++) { /* Note we pass our type to our child, which becomes its parent node type */ compute_indents_recursive(tree, node.child_start + idx, node_indent, node_type, indents, trailing_indent, max_visited_node_idx); } }
static void prettify_node_recursive(const wcstring &source, const parse_node_tree_t &tree, node_offset_t node_idx, indent_t node_indent, parse_token_type_t parent_type, bool *has_new_line, wcstring *out_result, bool do_indent) { const parse_node_t &node = tree.at(node_idx); const parse_token_type_t node_type = node.type; const parse_token_type_t prev_node_type = node_idx > 0 ? tree.at(node_idx - 1).type : token_type_invalid; // Increment the indent if we are either a root job_list, or root case_item_list, or in an if or // while header (#1665). const bool is_root_job_list = node_type == symbol_job_list && parent_type != symbol_job_list; const bool is_root_case_list = node_type == symbol_case_item_list && parent_type != symbol_case_item_list; const bool is_if_while_header = (node_type == symbol_job_conjunction || node_type == symbol_andor_job_list) && (parent_type == symbol_if_clause || parent_type == symbol_while_header); if (is_root_job_list || is_root_case_list || is_if_while_header) { node_indent += 1; } if (dump_parse_tree) dump_node(node_indent, node, source); if (node.has_comments()) // handle comments, which come before the text { auto comment_nodes = tree.comment_nodes_for_node(node); for (const auto &comment : comment_nodes) { append_whitespace(node_indent, do_indent, *has_new_line, out_result); auto source_range = comment.source_range(); out_result->append(source, source_range->start, source_range->length); } } if (node_type == parse_token_type_end) { out_result->push_back(L'\n'); *has_new_line = true; } else if ((node_type >= FIRST_PARSE_TOKEN_TYPE && node_type <= LAST_PARSE_TOKEN_TYPE) || node_type == parse_special_type_parse_error) { if (node.keyword != parse_keyword_none) { append_whitespace(node_indent, do_indent, *has_new_line, out_result); out_result->append(keyword_description(node.keyword)); *has_new_line = false; } else if (node.has_source()) { // Some type representing a particular token. if (prev_node_type != parse_token_type_redirection) { append_whitespace(node_indent, do_indent, *has_new_line, out_result); } out_result->append(source, node.source_start, node.source_length); *has_new_line = false; } } // Recurse to all our children. for (node_offset_t idx = 0; idx < node.child_count; idx++) { // Note: We pass our type to our child, which becomes its parent node type. // Note: While node.child_start could be -1 (NODE_OFFSET_INVALID) the addition is safe // because we won't execute this call in that case since node.child_count should be zero. prettify_node_recursive(source, tree, node.child_start + idx, node_indent, node_type, has_new_line, out_result, do_indent); } }