1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use rewrite::{Rewrite, RewriteContext};
12 use utils::{make_indent, extra_offset};
13 use expr::rewrite_call;
15 use syntax::{ast, ptr};
16 use syntax::codemap::{mk_sp, Span};
17 use syntax::print::pprust;
19 pub fn rewrite_chain(orig_expr: &ast::Expr,
20 context: &RewriteContext,
24 let mut expr = orig_expr;
25 let mut rewrites = Vec::new();
26 let indent = offset + context.config.tab_spaces;
27 let max_width = try_opt!(context.config.max_width.checked_sub(indent));
29 while let Some(pair) = pop_expr_chain(expr, orig_expr.span, context, max_width, indent) {
30 let (rewrite, parent_expr) = pair;
32 rewrites.push(try_opt!(rewrite));
36 let parent_rewrite = try_opt!(expr.rewrite(context, width, offset));
37 let total_width = rewrites.iter().fold(0, |a, b| a + b.len()) + parent_rewrite.len();
38 let fits_single_line = total_width <= width && rewrites.iter().all(|s| !s.contains('\n'));
40 if rewrites.len() == 1 && !fits_single_line &&
41 (is_continuable(expr) || parent_rewrite.len() <= context.config.tab_spaces) {
42 let extra_offset = extra_offset(&parent_rewrite, offset);
43 let offset = offset + extra_offset;
44 let max_width = try_opt!(width.checked_sub(extra_offset));
46 let rerewrite = pop_expr_chain(orig_expr, orig_expr.span, context, max_width, offset)
50 return Some(format!("{}{}", parent_rewrite, try_opt!(rerewrite)));
53 let connector = if fits_single_line {
56 format!("\n{}", make_indent(indent))
59 // FIXME: don't do this. There's a more efficient way. VecDeque?
62 // Put the first link on the same line as parent, if it fits.
63 let first_connector = if parent_rewrite.len() + rewrites[0].len() <= width &&
64 is_continuable(expr) &&
65 !rewrites[0].contains('\n') ||
66 parent_rewrite.len() <= context.config.tab_spaces {
72 Some(format!("{}{}{}", parent_rewrite, first_connector, rewrites.join(&connector)))
75 // Returns None when the expression is not a chainable. Otherwise, rewrites the
76 // outermost chain element and returns the remaining chain.
77 fn pop_expr_chain<'a>(expr: &'a ast::Expr,
79 context: &RewriteContext,
82 -> Option<(Option<String>, &'a ast::Expr)> {
84 ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions) => {
85 Some((rewrite_method_call(method_name.node,
94 ast::Expr_::ExprField(ref subexpr, ref field) => {
95 Some((Some(format!(".{}", field.node)), subexpr))
97 ast::Expr_::ExprTupField(ref subexpr, ref field) => {
98 Some((Some(format!(".{}", field.node)), subexpr))
104 // Determines we can continue formatting a given expression on the same line.
105 fn is_continuable(expr: &ast::Expr) -> bool {
107 ast::Expr_::ExprPath(..) => true,
112 fn rewrite_method_call(method_name: ast::Ident,
113 types: &[ptr::P<ast::Ty>],
114 args: &[ptr::P<ast::Expr>],
116 context: &RewriteContext,
120 let type_str = if types.is_empty() {
123 let type_list = types.iter().map(|ty| pprust::ty_to_string(ty)).collect::<Vec<_>>();
124 format!("::<{}>", type_list.join(", "))
127 let callee_str = format!(".{}{}", method_name, type_str);
128 let inner_context = &RewriteContext {
129 block_indent: offset,
133 rewrite_call(inner_context, &callee_str, args, span, width, offset)