| // Copyright 2018 The Rust Project Developers. See the COPYRIGHT |
| // file at the top-level directory of this distribution and at |
| // http://rust-lang.org/COPYRIGHT. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| //! Rewrite a list some items with overflow. |
| // FIXME: Replace `ToExpr` with some enum. |
| |
| use config::lists::*; |
| use syntax::ast; |
| use syntax::codemap::Span; |
| |
| use closures; |
| use codemap::SpanUtils; |
| use expr::{is_nested_call, maybe_get_args_offset, ToExpr}; |
| use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator}; |
| use rewrite::{Rewrite, RewriteContext}; |
| use shape::Shape; |
| use spanned::Spanned; |
| use utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp, paren_overhead}; |
| |
| use std::cmp::min; |
| |
| pub fn rewrite_with_parens<T>( |
| context: &RewriteContext, |
| ident: &str, |
| items: &[&T], |
| shape: Shape, |
| span: Span, |
| item_max_width: usize, |
| force_separator_tactic: Option<SeparatorTactic>, |
| ) -> Option<String> |
| where |
| T: Rewrite + ToExpr + Spanned, |
| { |
| Context::new( |
| context, |
| items, |
| ident, |
| shape, |
| span, |
| "(", |
| ")", |
| item_max_width, |
| force_separator_tactic, |
| ).rewrite(shape) |
| } |
| |
| pub fn rewrite_with_angle_brackets<T>( |
| context: &RewriteContext, |
| ident: &str, |
| items: &[&T], |
| shape: Shape, |
| span: Span, |
| ) -> Option<String> |
| where |
| T: Rewrite + ToExpr + Spanned, |
| { |
| Context::new( |
| context, |
| items, |
| ident, |
| shape, |
| span, |
| "<", |
| ">", |
| context.config.max_width(), |
| None, |
| ).rewrite(shape) |
| } |
| |
| struct Context<'a, T: 'a> { |
| context: &'a RewriteContext<'a>, |
| items: &'a [&'a T], |
| 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>, |
| } |
| |
| impl<'a, T: 'a + Rewrite + ToExpr + Spanned> Context<'a, T> { |
| pub fn new( |
| context: &'a RewriteContext, |
| items: &'a [&'a T], |
| ident: &'a str, |
| shape: Shape, |
| span: Span, |
| prefix: &'static str, |
| suffix: &'static str, |
| item_max_width: usize, |
| force_separator_tactic: Option<SeparatorTactic>, |
| ) -> Context<'a, T> { |
| // 2 = `( `, 1 = `(` |
| let paren_overhead = if context.config.spaces_within_parens_and_brackets() { |
| 2 |
| } else { |
| 1 |
| }; |
| let used_width = extra_offset(ident, shape); |
| let one_line_width = shape |
| .width |
| .checked_sub(used_width + 2 * paren_overhead) |
| .unwrap_or(0); |
| |
| // 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 * paren_overhead, |
| used_width + paren_overhead, |
| ); |
| Context { |
| context, |
| items, |
| ident, |
| one_line_shape, |
| nested_shape, |
| span, |
| prefix, |
| suffix, |
| item_max_width, |
| one_line_width, |
| force_separator_tactic, |
| } |
| } |
| |
| fn last_item(&self) -> Option<&&T> { |
| 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 = if let Some(expr) = last_item.to_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) |
| } |
| } |
| _ => expr.rewrite(self.context, shape), |
| } |
| } else { |
| last_item.rewrite(self.context, shape) |
| }; |
| |
| if let Some(rewrite) = rewrite { |
| let rewrite_first_line = Some(rewrite[..first_line_width(&rewrite)].to_owned()); |
| last_list_item.item = rewrite_first_line; |
| Some(rewrite) |
| } else { |
| None |
| } |
| } |
| |
| 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].to_expr().is_some() |
| && self.ident.len() + 1 <= 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(); |
| if !combine_arg_with_callee { |
| if let Some(expr) = self.last_item().and_then(|item| item.to_expr()) { |
| if let ast::ExprKind::MethodCall(..) = expr.node { |
| 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 |
| // bugdet 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.len() >= 1 => { |
| list_items[self.items.len() - 1].item = self.items |
| .last() |
| .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); |
| |
| let default_tactic = || { |
| definitive_tactic( |
| &*list_items, |
| ListTactic::LimitedHorizontalVertical(self.item_max_width), |
| Separator::Comma, |
| self.one_line_width, |
| ) |
| }; |
| |
| // Use horizontal layout for a function with a single argument as long as |
| // everything fits in a single line. |
| if self.items.len() == 1 |
| && self.one_line_width != 0 // Vertical layout is forced. |
| && !list_items[0].has_comment() |
| && !list_items[0].inner_as_ref().contains('\n') |
| && ::lists::total_item_width(&list_items[0]) <= self.one_line_width |
| { |
| tactic = DefinitiveListTactic::Horizontal; |
| } else { |
| tactic = default_tactic(); |
| |
| 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); |
| }; |
| } |
| } |
| } |
| } |
| _ => (), |
| } |
| |
| 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 fmt = ListFormatting { |
| tactic, |
| separator: ",", |
| 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() |
| }, |
| separator_place: SeparatorPlace::Back, |
| shape: self.nested_shape, |
| ends_with_newline: self.context.use_block_indent() |
| && tactic == DefinitiveListTactic::Vertical, |
| preserve_newline: false, |
| config: self.context.config, |
| }; |
| |
| 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 |
| .checked_sub(last_line_width(self.ident)) |
| .unwrap_or(0), |
| ..shape |
| }; |
| |
| let paren_overhead = paren_overhead(self.context); |
| let fits_one_line = items_str.len() + paren_overhead <= shape.width; |
| let extend_width = if items_str.is_empty() { |
| paren_overhead |
| } else { |
| paren_overhead / 2 |
| }; |
| 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(self.prefix); |
| if !self.context.use_block_indent() |
| || (self.context.inside_macro() && !items_str.contains('\n') && fits_one_line) |
| || (is_extendable && extend_width <= shape.width) |
| { |
| if self.context.config.spaces_within_parens_and_brackets() && !items_str.is_empty() { |
| result.push(' '); |
| result.push_str(items_str); |
| result.push(' '); |
| } else { |
| 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(self.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<'a, T>(context: &RewriteContext, items: &[&T]) -> bool |
| where |
| T: Rewrite + Spanned + ToExpr + 'a, |
| { |
| 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<T>( |
| lists: &[&T], |
| items: &[ListItem], |
| shape: Shape, |
| args_max_width: usize, |
| ) -> Option<Shape> |
| where |
| T: Rewrite + Spanned + ToExpr, |
| { |
| let is_nested_call = lists |
| .iter() |
| .next() |
| .and_then(|item| item.to_expr()) |
| .map_or(false, is_nested_call); |
| if items.len() == 1 && !is_nested_call { |
| return Some(shape); |
| } |
| let offset = items.iter().rev().skip(1).fold(0, |acc, i| { |
| // 2 = ", " |
| acc + 2 + i.inner_as_ref().len() |
| }); |
| 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 { |
| if context.use_block_indent() { |
| // 1 = "," |
| shape |
| .block() |
| .block_indent(context.config.tab_spaces()) |
| .with_max_width(context.config) |
| .sub_width(1) |
| .unwrap() |
| } else { |
| let shape = shape.visual_indent(offset); |
| if let Some(shape) = shape.sub_width(overhead) { |
| shape |
| } else { |
| Shape { width: 0, ..shape } |
| } |
| } |
| } |