| //! Rewrite a list some items with overflow. |
| |
| use std::cmp::min; |
| |
| use itertools::Itertools; |
| use syntax::parse::token::DelimToken; |
| use syntax::source_map::Span; |
| use syntax::{ast, ptr}; |
| |
| use crate::closures; |
| use crate::config::lists::*; |
| use crate::config::Version; |
| use crate::expr::{ |
| can_be_overflowed_expr, is_every_expr_simple, is_method_call, is_nested_call, is_simple_expr, |
| rewrite_cond, |
| }; |
| use crate::lists::{ |
| definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator, |
| }; |
| use crate::macros::MacroArg; |
| use crate::patterns::{can_be_overflowed_pat, TuplePatField}; |
| use crate::rewrite::{Rewrite, RewriteContext}; |
| use crate::shape::Shape; |
| use crate::source_map::SpanUtils; |
| use crate::spanned::Spanned; |
| use crate::types::{can_be_overflowed_type, SegmentParam}; |
| use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp}; |
| |
| const SHORT_ITEM_THRESHOLD: usize = 10; |
| |
| /// A list of `format!`-like macros, that take a long format string and a list of arguments to |
| /// format. |
| /// |
| /// Organized as a list of `(&str, usize)` tuples, giving the name of the macro and the number of |
| /// arguments before the format string (none for `format!("format", ...)`, one for `assert!(result, |
| /// "format", ...)`, two for `assert_eq!(left, right, "format", ...)`). |
| const SPECIAL_MACRO_WHITELIST: &[(&str, usize)] = &[ |
| // format! like macros |
| // From the Rust Standard Library. |
| ("eprint!", 0), |
| ("eprintln!", 0), |
| ("format!", 0), |
| ("format_args!", 0), |
| ("print!", 0), |
| ("println!", 0), |
| ("panic!", 0), |
| ("unreachable!", 0), |
| // From the `log` crate. |
| ("debug!", 0), |
| ("error!", 0), |
| ("info!", 0), |
| ("warn!", 0), |
| // write! like macros |
| ("assert!", 1), |
| ("debug_assert!", 1), |
| ("write!", 1), |
| ("writeln!", 1), |
| // assert_eq! like macros |
| ("assert_eq!", 2), |
| ("assert_ne!", 2), |
| ("debug_assert_eq!", 2), |
| ("debug_assert_ne!", 2), |
| ]; |
| |
| const SPECIAL_ATTR_WHITELIST: &[(&str, usize)] = &[ |
| // From the `failure` crate. |
| ("fail", 0), |
| ]; |
| |
| #[derive(Debug)] |
| pub(crate) enum OverflowableItem<'a> { |
| Expr(&'a ast::Expr), |
| GenericParam(&'a ast::GenericParam), |
| MacroArg(&'a MacroArg), |
| NestedMetaItem(&'a ast::NestedMetaItem), |
| SegmentParam(&'a SegmentParam<'a>), |
| StructField(&'a ast::StructField), |
| TuplePatField(&'a TuplePatField<'a>), |
| Ty(&'a ast::Ty), |
| } |
| |
| impl<'a> Rewrite for OverflowableItem<'a> { |
| fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> { |
| self.map(|item| item.rewrite(context, shape)) |
| } |
| } |
| |
| impl<'a> Spanned for OverflowableItem<'a> { |
| fn span(&self) -> Span { |
| self.map(|item| item.span()) |
| } |
| } |
| |
| impl<'a> OverflowableItem<'a> { |
| fn has_attrs(&self) -> bool { |
| match self { |
| OverflowableItem::Expr(ast::Expr { attrs, .. }) |
| | OverflowableItem::GenericParam(ast::GenericParam { attrs, .. }) => !attrs.is_empty(), |
| OverflowableItem::StructField(ast::StructField { attrs, .. }) => !attrs.is_empty(), |
| OverflowableItem::MacroArg(MacroArg::Expr(expr)) => !expr.attrs.is_empty(), |
| OverflowableItem::MacroArg(MacroArg::Item(item)) => !item.attrs.is_empty(), |
| _ => false, |
| } |
| } |
| |
| pub(crate) fn map<F, T>(&self, f: F) -> T |
| where |
| F: Fn(&dyn IntoOverflowableItem<'a>) -> T, |
| { |
| match self { |
| OverflowableItem::Expr(expr) => f(*expr), |
| OverflowableItem::GenericParam(gp) => f(*gp), |
| OverflowableItem::MacroArg(macro_arg) => f(*macro_arg), |
| OverflowableItem::NestedMetaItem(nmi) => f(*nmi), |
| OverflowableItem::SegmentParam(sp) => f(*sp), |
| OverflowableItem::StructField(sf) => f(*sf), |
| OverflowableItem::TuplePatField(pat) => f(*pat), |
| OverflowableItem::Ty(ty) => f(*ty), |
| } |
| } |
| |
| pub(crate) fn is_simple(&self) -> bool { |
| match self { |
| OverflowableItem::Expr(expr) => is_simple_expr(expr), |
| OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_simple_expr(expr), |
| OverflowableItem::NestedMetaItem(nested_meta_item) => match nested_meta_item { |
| ast::NestedMetaItem::Literal(..) => true, |
| ast::NestedMetaItem::MetaItem(ref meta_item) => match meta_item.node { |
| ast::MetaItemKind::Word => true, |
| _ => false, |
| }, |
| }, |
| _ => false, |
| } |
| } |
| |
| pub(crate) fn is_expr(&self) -> bool { |
| match self { |
| OverflowableItem::Expr(..) => true, |
| OverflowableItem::MacroArg(MacroArg::Expr(..)) => true, |
| _ => false, |
| } |
| } |
| |
| pub(crate) fn is_nested_call(&self) -> bool { |
| match self { |
| OverflowableItem::Expr(expr) => is_nested_call(expr), |
| OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_nested_call(expr), |
| _ => false, |
| } |
| } |
| |
| pub(crate) fn to_expr(&self) -> Option<&'a ast::Expr> { |
| match self { |
| OverflowableItem::Expr(expr) => Some(expr), |
| OverflowableItem::MacroArg(macro_arg) => match macro_arg { |
| MacroArg::Expr(ref expr) => Some(expr), |
| _ => None, |
| }, |
| _ => None, |
| } |
| } |
| |
| pub(crate) fn can_be_overflowed(&self, context: &RewriteContext<'_>, len: usize) -> bool { |
| match self { |
| OverflowableItem::Expr(expr) => can_be_overflowed_expr(context, expr, len), |
| OverflowableItem::MacroArg(macro_arg) => match macro_arg { |
| MacroArg::Expr(ref expr) => can_be_overflowed_expr(context, expr, len), |
| MacroArg::Ty(ref ty) => can_be_overflowed_type(context, ty, len), |
| MacroArg::Pat(..) => false, |
| MacroArg::Item(..) => len == 1, |
| MacroArg::Keyword(..) => false, |
| }, |
| OverflowableItem::NestedMetaItem(nested_meta_item) if len == 1 => { |
| match nested_meta_item { |
| ast::NestedMetaItem::Literal(..) => false, |
| ast::NestedMetaItem::MetaItem(..) => true, |
| } |
| } |
| OverflowableItem::SegmentParam(seg) => match seg { |
| SegmentParam::Type(ty) => can_be_overflowed_type(context, ty, len), |
| _ => false, |
| }, |
| OverflowableItem::TuplePatField(pat) => can_be_overflowed_pat(context, pat, len), |
| OverflowableItem::Ty(ty) => can_be_overflowed_type(context, ty, len), |
| _ => false, |
| } |
| } |
| |
| fn whitelist(&self) -> &'static [(&'static str, usize)] { |
| match self { |
| OverflowableItem::MacroArg(..) => SPECIAL_MACRO_WHITELIST, |
| OverflowableItem::NestedMetaItem(..) => SPECIAL_ATTR_WHITELIST, |
| _ => &[], |
| } |
| } |
| } |
| |
| pub(crate) trait IntoOverflowableItem<'a>: Rewrite + Spanned { |
| fn into_overflowable_item(&'a self) -> OverflowableItem<'a>; |
| } |
| |
| impl<'a, T: 'a + IntoOverflowableItem<'a>> IntoOverflowableItem<'a> for ptr::P<T> { |
| fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { |
| (**self).into_overflowable_item() |
| } |
| } |
| |
| macro_rules! impl_into_overflowable_item_for_ast_node { |
| ($($ast_node:ident),*) => { |
| $( |
| impl<'a> IntoOverflowableItem<'a> for ast::$ast_node { |
| fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { |
| OverflowableItem::$ast_node(self) |
| } |
| } |
| )* |
| } |
| } |
| |
| macro_rules! impl_into_overflowable_item_for_rustfmt_types { |
| ([$($ty:ident),*], [$($ty_with_lifetime:ident),*]) => { |
| $( |
| impl<'a> IntoOverflowableItem<'a> for $ty { |
| fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { |
| OverflowableItem::$ty(self) |
| } |
| } |
| )* |
| $( |
| impl<'a> IntoOverflowableItem<'a> for $ty_with_lifetime<'a> { |
| fn into_overflowable_item(&'a self) -> OverflowableItem<'a> { |
| OverflowableItem::$ty_with_lifetime(self) |
| } |
| } |
| )* |
| } |
| } |
| |
| impl_into_overflowable_item_for_ast_node!(Expr, GenericParam, NestedMetaItem, StructField, Ty); |
| impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]); |
| |
| pub(crate) fn into_overflowable_list<'a, T>( |
| iter: impl Iterator<Item = &'a T>, |
| ) -> impl Iterator<Item = OverflowableItem<'a>> |
| where |
| T: 'a + IntoOverflowableItem<'a>, |
| { |
| iter.map(|x| IntoOverflowableItem::into_overflowable_item(x)) |
| } |
| |
| pub(crate) fn rewrite_with_parens<'a, T: 'a + IntoOverflowableItem<'a>>( |
| context: &'a RewriteContext<'_>, |
| ident: &'a str, |
| items: impl Iterator<Item = &'a T>, |
| shape: Shape, |
| span: Span, |
| item_max_width: usize, |
| force_separator_tactic: Option<SeparatorTactic>, |
| ) -> Option<String> { |
| Context::new( |
| context, |
| items, |
| ident, |
| shape, |
| span, |
| "(", |
| ")", |
| item_max_width, |
| force_separator_tactic, |
| None, |
| ) |
| .rewrite(shape) |
| } |
| |
| pub(crate) fn rewrite_with_angle_brackets<'a, T: 'a + IntoOverflowableItem<'a>>( |
| context: &'a RewriteContext<'_>, |
| ident: &'a str, |
| items: impl Iterator<Item = &'a T>, |
| shape: Shape, |
| span: Span, |
| ) -> Option<String> { |
| Context::new( |
| context, |
| items, |
| ident, |
| shape, |
| span, |
| "<", |
| ">", |
| context.config.max_width(), |
| None, |
| None, |
| ) |
| .rewrite(shape) |
| } |
| |
| pub(crate) fn rewrite_with_square_brackets<'a, T: 'a + IntoOverflowableItem<'a>>( |
| context: &'a RewriteContext<'_>, |
| name: &'a str, |
| items: impl Iterator<Item = &'a T>, |
| shape: Shape, |
| span: Span, |
| force_separator_tactic: Option<SeparatorTactic>, |
| delim_token: Option<DelimToken>, |
| ) -> Option<String> { |
| let (lhs, rhs) = match delim_token { |
| Some(DelimToken::Paren) => ("(", ")"), |
| Some(DelimToken::Brace) => ("{", "}"), |
| _ => ("[", "]"), |
| }; |
| Context::new( |
| context, |
| items, |
| name, |
| shape, |
| span, |
| lhs, |
| rhs, |
| context.config.width_heuristics().array_width, |
| force_separator_tactic, |
| Some(("[", "]")), |
| ) |
| .rewrite(shape) |
| } |
| |
| struct Context<'a> { |
| context: &'a RewriteContext<'a>, |
| items: Vec<OverflowableItem<'a>>, |
| ident: &'a str, |
| prefix: &'static str, |
| suffix: &'static str, |
| one_line_shape: Shape, |
| nested_shape: Shape, |
| span: Span, |
| item_max_width: usize, |
| one_line_width: usize, |
| force_separator_tactic: Option<SeparatorTactic>, |
| custom_delims: Option<(&'a str, &'a str)>, |
| } |
| |
| impl<'a> Context<'a> { |
| fn new<T: 'a + IntoOverflowableItem<'a>>( |
| context: &'a RewriteContext<'_>, |
| items: impl Iterator<Item = &'a T>, |
| ident: &'a str, |
| shape: Shape, |
| span: Span, |
| prefix: &'static str, |
| suffix: &'static str, |
| item_max_width: usize, |
| force_separator_tactic: Option<SeparatorTactic>, |
| custom_delims: Option<(&'a str, &'a str)>, |
| ) -> Context<'a> { |
| let used_width = extra_offset(ident, shape); |
| // 1 = `()` |
| let one_line_width = shape.width.saturating_sub(used_width + 2); |
| |
| // 1 = "(" or ")" |
| let one_line_shape = shape |
| .offset_left(last_line_width(ident) + 1) |
| .and_then(|shape| shape.sub_width(1)) |
| .unwrap_or(Shape { width: 0, ..shape }); |
| let nested_shape = shape_from_indent_style(context, shape, used_width + 2, used_width + 1); |
| Context { |
| context, |
| items: into_overflowable_list(items).collect(), |
| ident, |
| one_line_shape, |
| nested_shape, |
| span, |
| prefix, |
| suffix, |
| item_max_width, |
| one_line_width, |
| force_separator_tactic, |
| custom_delims, |
| } |
| } |
| |
| fn last_item(&self) -> Option<&OverflowableItem<'_>> { |
| self.items.last() |
| } |
| |
| fn items_span(&self) -> Span { |
| let span_lo = self |
| .context |
| .snippet_provider |
| .span_after(self.span, self.prefix); |
| mk_sp(span_lo, self.span.hi()) |
| } |
| |
| fn rewrite_last_item_with_overflow( |
| &self, |
| last_list_item: &mut ListItem, |
| shape: Shape, |
| ) -> Option<String> { |
| let last_item = self.last_item()?; |
| let rewrite = match last_item { |
| OverflowableItem::Expr(ref expr) => { |
| match expr.node { |
| // When overflowing the closure which consists of a single control flow |
| // expression, force to use block if its condition uses multi line. |
| ast::ExprKind::Closure(..) => { |
| // If the argument consists of multiple closures, we do not overflow |
| // the last closure. |
| if closures::args_have_many_closure(&self.items) { |
| None |
| } else { |
| closures::rewrite_last_closure(self.context, expr, shape) |
| } |
| } |
| |
| // When overflowing the expressions which consists of a control flow |
| // expression, avoid condition to use multi line. |
| ast::ExprKind::If(..) |
| | ast::ExprKind::IfLet(..) |
| | ast::ExprKind::ForLoop(..) |
| | ast::ExprKind::Loop(..) |
| | ast::ExprKind::While(..) |
| | ast::ExprKind::WhileLet(..) |
| | ast::ExprKind::Match(..) => { |
| let multi_line = rewrite_cond(self.context, expr, shape) |
| .map_or(false, |cond| cond.contains('\n')); |
| |
| if multi_line { |
| None |
| } else { |
| expr.rewrite(self.context, shape) |
| } |
| } |
| |
| _ => expr.rewrite(self.context, shape), |
| } |
| } |
| item => item.rewrite(self.context, shape), |
| }; |
| |
| if let Some(rewrite) = rewrite { |
| // splitn(2, *).next().unwrap() is always safe. |
| let rewrite_first_line = Some(rewrite.splitn(2, '\n').next().unwrap().to_owned()); |
| last_list_item.item = rewrite_first_line; |
| Some(rewrite) |
| } else { |
| None |
| } |
| } |
| |
| fn default_tactic(&self, list_items: &[ListItem]) -> DefinitiveListTactic { |
| definitive_tactic( |
| list_items, |
| ListTactic::LimitedHorizontalVertical(self.item_max_width), |
| Separator::Comma, |
| self.one_line_width, |
| ) |
| } |
| |
| fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveListTactic { |
| // 1 = "(" |
| let combine_arg_with_callee = self.items.len() == 1 |
| && self.items[0].is_expr() |
| && !self.items[0].has_attrs() |
| && self.ident.len() < self.context.config.tab_spaces(); |
| let overflow_last = combine_arg_with_callee || can_be_overflowed(self.context, &self.items); |
| |
| // Replace the last item with its first line to see if it fits with |
| // first arguments. |
| let placeholder = if overflow_last { |
| let old_value = *self.context.force_one_line_chain.borrow(); |
| match self.last_item() { |
| Some(OverflowableItem::Expr(expr)) |
| if !combine_arg_with_callee && is_method_call(expr) => |
| { |
| self.context.force_one_line_chain.replace(true); |
| } |
| Some(OverflowableItem::MacroArg(MacroArg::Expr(expr))) |
| if !combine_arg_with_callee |
| && is_method_call(expr) |
| && self.context.config.version() == Version::Two => |
| { |
| self.context.force_one_line_chain.replace(true); |
| } |
| _ => (), |
| } |
| let result = last_item_shape( |
| &self.items, |
| list_items, |
| self.one_line_shape, |
| self.item_max_width, |
| ) |
| .and_then(|arg_shape| { |
| self.rewrite_last_item_with_overflow( |
| &mut list_items[self.items.len() - 1], |
| arg_shape, |
| ) |
| }); |
| self.context.force_one_line_chain.replace(old_value); |
| result |
| } else { |
| None |
| }; |
| |
| let mut tactic = definitive_tactic( |
| &*list_items, |
| ListTactic::LimitedHorizontalVertical(self.item_max_width), |
| Separator::Comma, |
| self.one_line_width, |
| ); |
| |
| // Replace the stub with the full overflowing last argument if the rewrite |
| // succeeded and its first line fits with the other arguments. |
| match (overflow_last, tactic, placeholder) { |
| (true, DefinitiveListTactic::Horizontal, Some(ref overflowed)) |
| if self.items.len() == 1 => |
| { |
| // When we are rewriting a nested function call, we restrict the |
| // budget for the inner function to avoid them being deeply nested. |
| // However, when the inner function has a prefix or a suffix |
| // (e.g., `foo() as u32`), this budget reduction may produce poorly |
| // formatted code, where a prefix or a suffix being left on its own |
| // line. Here we explicitlly check those cases. |
| if count_newlines(overflowed) == 1 { |
| let rw = self |
| .items |
| .last() |
| .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); |
| let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n')); |
| if no_newline { |
| list_items[self.items.len() - 1].item = rw; |
| } else { |
| list_items[self.items.len() - 1].item = Some(overflowed.to_owned()); |
| } |
| } else { |
| list_items[self.items.len() - 1].item = Some(overflowed.to_owned()); |
| } |
| } |
| (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => { |
| list_items[self.items.len() - 1].item = placeholder; |
| } |
| _ if !self.items.is_empty() => { |
| list_items[self.items.len() - 1].item = self |
| .items |
| .last() |
| .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); |
| |
| // Use horizontal layout for a function with a single argument as long as |
| // everything fits in a single line. |
| // `self.one_line_width == 0` means vertical layout is forced. |
| if self.items.len() == 1 |
| && self.one_line_width != 0 |
| && !list_items[0].has_comment() |
| && !list_items[0].inner_as_ref().contains('\n') |
| && crate::lists::total_item_width(&list_items[0]) <= self.one_line_width |
| { |
| tactic = DefinitiveListTactic::Horizontal; |
| } else { |
| tactic = self.default_tactic(list_items); |
| |
| if tactic == DefinitiveListTactic::Vertical { |
| if let Some((all_simple, num_args_before)) = |
| maybe_get_args_offset(self.ident, &self.items) |
| { |
| let one_line = all_simple |
| && definitive_tactic( |
| &list_items[..num_args_before], |
| ListTactic::HorizontalVertical, |
| Separator::Comma, |
| self.nested_shape.width, |
| ) == DefinitiveListTactic::Horizontal |
| && definitive_tactic( |
| &list_items[num_args_before + 1..], |
| ListTactic::HorizontalVertical, |
| Separator::Comma, |
| self.nested_shape.width, |
| ) == DefinitiveListTactic::Horizontal; |
| |
| if one_line { |
| tactic = DefinitiveListTactic::SpecialMacro(num_args_before); |
| }; |
| } else if is_every_expr_simple(&self.items) && no_long_items(list_items) { |
| tactic = DefinitiveListTactic::Mixed; |
| } |
| } |
| } |
| } |
| _ => (), |
| } |
| |
| tactic |
| } |
| |
| fn rewrite_items(&self) -> Option<(bool, String)> { |
| let span = self.items_span(); |
| let items = itemize_list( |
| self.context.snippet_provider, |
| self.items.iter(), |
| self.suffix, |
| ",", |
| |item| item.span().lo(), |
| |item| item.span().hi(), |
| |item| item.rewrite(self.context, self.nested_shape), |
| span.lo(), |
| span.hi(), |
| true, |
| ); |
| let mut list_items: Vec<_> = items.collect(); |
| |
| // Try letting the last argument overflow to the next line with block |
| // indentation. If its first line fits on one line with the other arguments, |
| // we format the function arguments horizontally. |
| let tactic = self.try_overflow_last_item(&mut list_items); |
| let trailing_separator = if let Some(tactic) = self.force_separator_tactic { |
| tactic |
| } else if !self.context.use_block_indent() { |
| SeparatorTactic::Never |
| } else { |
| self.context.config.trailing_comma() |
| }; |
| let ends_with_newline = match tactic { |
| DefinitiveListTactic::Vertical | DefinitiveListTactic::Mixed => { |
| self.context.use_block_indent() |
| } |
| _ => false, |
| }; |
| |
| let fmt = ListFormatting::new(self.nested_shape, self.context.config) |
| .tactic(tactic) |
| .trailing_separator(trailing_separator) |
| .ends_with_newline(ends_with_newline); |
| |
| write_list(&list_items, &fmt) |
| .map(|items_str| (tactic == DefinitiveListTactic::Horizontal, items_str)) |
| } |
| |
| fn wrap_items(&self, items_str: &str, shape: Shape, is_extendable: bool) -> String { |
| let shape = Shape { |
| width: shape.width.saturating_sub(last_line_width(self.ident)), |
| ..shape |
| }; |
| |
| let (prefix, suffix) = match self.custom_delims { |
| Some((lhs, rhs)) => (lhs, rhs), |
| _ => (self.prefix, self.suffix), |
| }; |
| |
| let extend_width = if items_str.is_empty() { |
| 2 |
| } else { |
| first_line_width(items_str) + 1 |
| }; |
| let nested_indent_str = self |
| .nested_shape |
| .indent |
| .to_string_with_newline(self.context.config); |
| let indent_str = shape |
| .block() |
| .indent |
| .to_string_with_newline(self.context.config); |
| let mut result = String::with_capacity( |
| self.ident.len() + items_str.len() + 2 + indent_str.len() + nested_indent_str.len(), |
| ); |
| result.push_str(self.ident); |
| result.push_str(prefix); |
| let force_single_line = if self.context.config.version() == Version::Two { |
| !self.context.use_block_indent() || (is_extendable && extend_width <= shape.width) |
| } else { |
| // 2 = `()` |
| let fits_one_line = items_str.len() + 2 <= shape.width; |
| !self.context.use_block_indent() |
| || (self.context.inside_macro() && !items_str.contains('\n') && fits_one_line) |
| || (is_extendable && extend_width <= shape.width) |
| }; |
| if force_single_line { |
| result.push_str(items_str); |
| } else { |
| if !items_str.is_empty() { |
| result.push_str(&nested_indent_str); |
| result.push_str(items_str); |
| } |
| result.push_str(&indent_str); |
| } |
| result.push_str(suffix); |
| result |
| } |
| |
| fn rewrite(&self, shape: Shape) -> Option<String> { |
| let (extendable, items_str) = self.rewrite_items()?; |
| |
| // If we are using visual indent style and failed to format, retry with block indent. |
| if !self.context.use_block_indent() |
| && need_block_indent(&items_str, self.nested_shape) |
| && !extendable |
| { |
| self.context.use_block.replace(true); |
| let result = self.rewrite(shape); |
| self.context.use_block.replace(false); |
| return result; |
| } |
| |
| Some(self.wrap_items(&items_str, shape, extendable)) |
| } |
| } |
| |
| fn need_block_indent(s: &str, shape: Shape) -> bool { |
| s.lines().skip(1).any(|s| { |
| s.find(|c| !char::is_whitespace(c)) |
| .map_or(false, |w| w + 1 < shape.indent.width()) |
| }) |
| } |
| |
| fn can_be_overflowed(context: &RewriteContext<'_>, items: &[OverflowableItem<'_>]) -> bool { |
| items |
| .last() |
| .map_or(false, |x| x.can_be_overflowed(context, items.len())) |
| } |
| |
| /// Returns a shape for the last argument which is going to be overflowed. |
| fn last_item_shape( |
| lists: &[OverflowableItem<'_>], |
| items: &[ListItem], |
| shape: Shape, |
| args_max_width: usize, |
| ) -> Option<Shape> { |
| if items.len() == 1 && !lists.get(0)?.is_nested_call() { |
| return Some(shape); |
| } |
| let offset = items |
| .iter() |
| .dropping_back(1) |
| .map(|i| { |
| // 2 = ", " |
| 2 + i.inner_as_ref().len() |
| }) |
| .sum(); |
| Shape { |
| width: min(args_max_width, shape.width), |
| ..shape |
| } |
| .offset_left(offset) |
| } |
| |
| fn shape_from_indent_style( |
| context: &RewriteContext<'_>, |
| shape: Shape, |
| overhead: usize, |
| offset: usize, |
| ) -> Shape { |
| let (shape, overhead) = if context.use_block_indent() { |
| let shape = shape |
| .block() |
| .block_indent(context.config.tab_spaces()) |
| .with_max_width(context.config); |
| (shape, 1) // 1 = "," |
| } else { |
| (shape.visual_indent(offset), overhead) |
| }; |
| Shape { |
| width: shape.width.saturating_sub(overhead), |
| ..shape |
| } |
| } |
| |
| fn no_long_items(list: &[ListItem]) -> bool { |
| list.iter() |
| .all(|item| item.inner_as_ref().len() <= SHORT_ITEM_THRESHOLD) |
| } |
| |
| /// In case special-case style is required, returns an offset from which we start horizontal layout. |
| pub(crate) fn maybe_get_args_offset( |
| callee_str: &str, |
| args: &[OverflowableItem<'_>], |
| ) -> Option<(bool, usize)> { |
| if let Some(&(_, num_args_before)) = args |
| .get(0)? |
| .whitelist() |
| .iter() |
| .find(|&&(s, _)| s == callee_str) |
| { |
| let all_simple = args.len() > num_args_before |
| && is_every_expr_simple(&args[0..num_args_before]) |
| && is_every_expr_simple(&args[num_args_before + 1..]); |
| |
| Some((all_simple, num_args_before)) |
| } else { |
| None |
| } |
| } |