about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMarcus Klaas de Vries <mail@marcusklaas.nl>2015-09-11 00:56:14 +0200
committerMarcus Klaas de Vries <mail@marcusklaas.nl>2015-09-11 00:56:14 +0200
commit0b7b3c8725f2adbdcd0c05f81f06b2b938d99552 (patch)
tree3ed01a4ac586c7c4cb3528d7532fc9a4dd22701f
parent1af301c33d1886a4e037f6ddb1ce495aa8d0573b (diff)
parent7f576b06022357672eb24d6103673f982a6ba817 (diff)
Merge pull request #216 from marcusklaas/format-fields
WIP: Format expression chains
-rw-r--r--src/bin/rustfmt.rs5
-rw-r--r--src/chains.rs195
-rw-r--r--src/comment.rs107
-rw-r--r--src/config.rs15
-rw-r--r--src/expr.rs155
-rw-r--r--src/imports.rs16
-rw-r--r--src/items.rs37
-rw-r--r--src/lib.rs2
-rw-r--r--src/lists.rs23
-rw-r--r--src/missed_spans.rs25
-rw-r--r--src/string.rs4
-rw-r--r--src/types.rs148
-rw-r--r--src/utils.rs35
-rw-r--r--src/visitor.rs17
-rw-r--r--tests/config/small_tabs.toml4
-rw-r--r--tests/source/chains-no-overflow.rs37
-rw-r--r--tests/source/chains.rs43
-rw-r--r--tests/source/expr-no-hints.rs8
-rw-r--r--tests/source/expr-visual-indent.rs9
-rw-r--r--tests/source/string-lit.rs2
-rw-r--r--tests/system.rs16
-rw-r--r--tests/target/chains-no-overflow.rs45
-rw-r--r--tests/target/chains-no-overlow-2.rs16
-rw-r--r--tests/target/chains.rs50
-rw-r--r--tests/target/closure.rs55
-rw-r--r--tests/target/expr-no-hints.rs6
-rw-r--r--tests/target/expr-visual-indent.rs9
-rw-r--r--tests/target/expr.rs12
-rw-r--r--tests/target/match.rs2
-rw-r--r--tests/target/string-lit.rs2
30 files changed, 740 insertions, 360 deletions
diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs
index 9f2387f5c2c..9913c68cdfa 100644
--- a/src/bin/rustfmt.rs
+++ b/src/bin/rustfmt.rs
@@ -82,7 +82,9 @@ fn main() {
 }
 
 fn print_usage<S: Into<String>>(reason: S) {
-    println!("{}\n\r usage: rustfmt [-h Help] [--write-mode=[replace|overwrite|display|diff]] <file_name>", reason.into());
+    println!("{}\n\r usage: rustfmt [-h Help] [--write-mode=[replace|overwrite|display|diff]] \
+              <file_name>",
+             reason.into());
 }
 
 fn determine_params<I>(args: I) -> Option<(Vec<String>, WriteMode)>
@@ -123,6 +125,5 @@ fn determine_params<I>(args: I) -> Option<(Vec<String>, WriteMode)>
         return None;
     }
 
-
     Some((rustc_args, write_mode))
 }
diff --git a/src/chains.rs b/src/chains.rs
new file mode 100644
index 00000000000..bd9f302824b
--- /dev/null
+++ b/src/chains.rs
@@ -0,0 +1,195 @@
+// Copyright 2015 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.
+
+// Formatting of chained expressions, i.e. expressions which are chained by
+// dots: struct and enum field access and method calls.
+//
+// Instead of walking these subexpressions one-by-one, as is our usual strategy
+// for expression formatting, we collect maximal sequences of these expressions
+// and handle them simultaneously.
+//
+// Whenever possible, the entire chain is put on a single line. If that fails,
+// we put each subexpression on a separate, much like the (default) function
+// argument function argument strategy.
+
+use rewrite::{Rewrite, RewriteContext};
+use utils::{first_line_width, make_indent};
+use expr::rewrite_call;
+
+use syntax::{ast, ptr};
+use syntax::codemap::{mk_sp, Span};
+use syntax::print::pprust;
+
+pub fn rewrite_chain(mut expr: &ast::Expr,
+                     context: &RewriteContext,
+                     width: usize,
+                     offset: usize)
+                     -> Option<String> {
+    let total_span = expr.span;
+    let mut subexpr_list = vec![expr];
+
+    while let Some(subexpr) = pop_expr_chain(expr) {
+        subexpr_list.push(subexpr);
+        expr = subexpr;
+    }
+
+    let parent = subexpr_list.pop().unwrap();
+    let parent_rewrite = try_opt!(expr.rewrite(context, width, offset));
+    let (extra_indent, extend) = if !parent_rewrite.contains('\n') && is_continuable(parent) ||
+                                    parent_rewrite.len() <= context.config.tab_spaces {
+        (parent_rewrite.len(), true)
+    } else {
+        (context.config.tab_spaces, false)
+    };
+    let indent = offset + extra_indent;
+
+    let max_width = try_opt!(width.checked_sub(extra_indent));
+    let mut rewrites = try_opt!(subexpr_list.iter()
+                                            .rev()
+                                            .map(|e| {
+                                                rewrite_chain_expr(e,
+                                                                   total_span,
+                                                                   context,
+                                                                   max_width,
+                                                                   indent)
+                                            })
+                                            .collect::<Option<Vec<_>>>());
+
+    // Total of all items excluding the last.
+    let almost_total = rewrites.split_last()
+                               .unwrap()
+                               .1
+                               .iter()
+                               .fold(0, |a, b| a + first_line_width(b)) +
+                       parent_rewrite.len();
+    let total_width = almost_total + first_line_width(rewrites.last().unwrap());
+    let veto_single_line = if context.config.take_source_hints && subexpr_list.len() > 1 {
+        // Look at the source code. Unless all chain elements start on the same
+        // line, we won't consider putting them on a single line either.
+        let first_line_no = context.codemap.lookup_char_pos(subexpr_list[0].span.lo).line;
+
+        subexpr_list[1..]
+            .iter()
+            .any(|ex| context.codemap.lookup_char_pos(ex.span.hi).line != first_line_no)
+    } else {
+        false
+    };
+
+    let fits_single_line = !veto_single_line &&
+                           match subexpr_list[0].node {
+        ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions)
+            if context.config.chains_overflow_last => {
+            let (last, init) = rewrites.split_last_mut().unwrap();
+
+            if init.iter().all(|s| !s.contains('\n')) && total_width <= width {
+                let last_rewrite = width.checked_sub(almost_total)
+                                        .and_then(|inner_width| {
+                                            rewrite_method_call(method_name.node,
+                                                                types,
+                                                                expressions,
+                                                                total_span,
+                                                                context,
+                                                                inner_width,
+                                                                offset + almost_total)
+                                        });
+                match last_rewrite {
+                    Some(mut string) => {
+                        ::std::mem::swap(&mut string, last);
+                        true
+                    }
+                    None => false,
+                }
+            } else {
+                false
+            }
+        }
+        _ => total_width <= width && rewrites.iter().all(|s| !s.contains('\n')),
+    };
+
+    let connector = if fits_single_line {
+        String::new()
+    } else {
+        format!("\n{}", make_indent(indent))
+    };
+
+    let first_connector = if extend {
+        ""
+    } else {
+        &connector[..]
+    };
+
+    Some(format!("{}{}{}", parent_rewrite, first_connector, rewrites.join(&connector)))
+}
+
+fn pop_expr_chain<'a>(expr: &'a ast::Expr) -> Option<&'a ast::Expr> {
+    match expr.node {
+        ast::Expr_::ExprMethodCall(_, _, ref expressions) => {
+            Some(&expressions[0])
+        }
+        ast::Expr_::ExprTupField(ref subexpr, _) |
+        ast::Expr_::ExprField(ref subexpr, _) => {
+            Some(subexpr)
+        }
+        _ => None,
+    }
+}
+
+fn rewrite_chain_expr(expr: &ast::Expr,
+                      span: Span,
+                      context: &RewriteContext,
+                      width: usize,
+                      offset: usize)
+                      -> Option<String> {
+    match expr.node {
+        ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions) => {
+            let inner = &RewriteContext {
+                block_indent: offset,
+                ..*context
+            };
+            rewrite_method_call(method_name.node, types, expressions, span, inner, width, offset)
+        }
+        ast::Expr_::ExprField(_, ref field) => {
+            Some(format!(".{}", field.node))
+        }
+        ast::Expr_::ExprTupField(_, ref field) => {
+            Some(format!(".{}", field.node))
+        }
+        _ => unreachable!(),
+    }
+}
+
+// Determines we can continue formatting a given expression on the same line.
+fn is_continuable(expr: &ast::Expr) -> bool {
+    match expr.node {
+        ast::Expr_::ExprPath(..) => true,
+        _ => false,
+    }
+}
+
+fn rewrite_method_call(method_name: ast::Ident,
+                       types: &[ptr::P<ast::Ty>],
+                       args: &[ptr::P<ast::Expr>],
+                       span: Span,
+                       context: &RewriteContext,
+                       width: usize,
+                       offset: usize)
+                       -> Option<String> {
+    let type_str = if types.is_empty() {
+        String::new()
+    } else {
+        let type_list = types.iter().map(|ty| pprust::ty_to_string(ty)).collect::<Vec<_>>();
+        format!("::<{}>", type_list.join(", "))
+    };
+
+    let callee_str = format!(".{}{}", method_name, type_str);
+    let span = mk_sp(args[0].span.hi, span.hi);
+
+    rewrite_call(context, &callee_str, &args[1..], span, width, offset)
+}
diff --git a/src/comment.rs b/src/comment.rs
index f1cecd7318b..ff7558131d2 100644
--- a/src/comment.rs
+++ b/src/comment.rs
@@ -25,8 +25,7 @@ pub fn rewrite_comment(orig: &str, block_style: bool, width: usize, offset: usiz
         ("// ", "", "// ")
     };
 
-    let max_chars = width.checked_sub(closer.len()).unwrap_or(1)
-                         .checked_sub(opener.len()).unwrap_or(1);
+    let max_chars = width.checked_sub(closer.len() + opener.len()).unwrap_or(1);
 
     let fmt = StringFormat {
         opener: "",
@@ -41,44 +40,45 @@ pub fn rewrite_comment(orig: &str, block_style: bool, width: usize, offset: usiz
     let indent_str = make_indent(offset);
     let line_breaks = s.chars().filter(|&c| c == '\n').count();
 
-    let (_, mut s) = s.lines().enumerate()
-        .map(|(i, mut line)| {
-            line = line.trim();
-
-            // Drop old closer.
-            if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
-                line = &line[..(line.len() - 2)];
-            }
-
-            line.trim_right()
-        })
-        .map(left_trim_comment_line)
-        .map(|line| {
-            if line_breaks == 0 {
-                line.trim_left()
-            } else {
-                line
-            }
-        })
-        .fold((true, opener.to_owned()), |(first, mut acc), line| {
-            if !first {
-                acc.push('\n');
-                acc.push_str(&indent_str);
-                acc.push_str(line_start);
-            }
-
-            if line.len() > max_chars {
-                acc.push_str(&rewrite_string(line, &fmt));
-            } else {
-                if line.len() == 0 {
-                    acc.pop(); // Remove space if this is an empty comment.
-                } else {
-                    acc.push_str(line);
-                }
-            }
-
-            (false, acc)
-        });
+    let (_, mut s) = s.lines()
+                      .enumerate()
+                      .map(|(i, mut line)| {
+                          line = line.trim();
+                          // Drop old closer.
+                          if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") {
+                              line = &line[..(line.len() - 2)];
+                          }
+
+                          line.trim_right()
+                      })
+                      .map(left_trim_comment_line)
+                      .map(|line| {
+                          if line_breaks == 0 {
+                              line.trim_left()
+                          } else {
+                              line
+                          }
+                      })
+                      .fold((true, opener.to_owned()),
+                            |(first, mut acc), line| {
+                                if !first {
+                                    acc.push('\n');
+                                    acc.push_str(&indent_str);
+                                    acc.push_str(line_start);
+                                }
+
+                                if line.len() > max_chars {
+                                    acc.push_str(&rewrite_string(line, &fmt));
+                                } else {
+                                    if line.len() == 0 {
+                                        acc.pop(); // Remove space if this is an empty comment.
+                                    } else {
+                                        acc.push_str(line);
+                                    }
+                                }
+
+                                (false, acc)
+                            });
 
     s.push_str(closer);
 
@@ -146,24 +146,11 @@ pub fn find_comment_end(s: &str) -> Option<usize> {
     }
 }
 
-#[test]
-fn comment_end() {
-    assert_eq!(Some(6), find_comment_end("// hi\n"));
-    assert_eq!(Some(9), find_comment_end("/* sup */ "));
-    assert_eq!(Some(9), find_comment_end("/*/**/ */ "));
-    assert_eq!(Some(6), find_comment_end("/*/ */ weird!"));
-    assert_eq!(None, find_comment_end("/* hi /* test */"));
-    assert_eq!(None, find_comment_end("// hi /* test */"));
-    assert_eq!(Some(9), find_comment_end("// hi /*\n."));
-}
-
-
 /// Returns true if text contains any comment.
 pub fn contains_comment(text: &str) -> bool {
-    CharClasses::new(text.chars()).any(|(kind, _)| kind == CodeCharKind::Comment )
+    CharClasses::new(text.chars()).any(|(kind, _)| kind == CodeCharKind::Comment)
 }
 
-
 struct CharClasses<T>
     where T: Iterator,
           T::Item: RichChar
@@ -324,10 +311,14 @@ mod test {
     // This is probably intended to be a non-test fn, but it is not used. I'm
     // keeping it around unless it helps us test stuff.
     fn uncommented(text: &str) -> String {
-        CharClasses::new(text.chars()).filter_map(|(s, c)| match s {
-            CodeCharKind::Normal => Some(c),
-            CodeCharKind::Comment => None
-        }).collect()
+        CharClasses::new(text.chars())
+            .filter_map(|(s, c)| {
+                match s {
+                    CodeCharKind::Normal => Some(c),
+                    CodeCharKind::Comment => None,
+                }
+            })
+            .collect()
     }
 
     #[test]
diff --git a/src/config.rs b/src/config.rs
index 95994531950..d45f184051f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -133,8 +133,8 @@ create_config! {
     fn_args_density: Density,
     fn_args_layout: StructLitStyle,
     fn_arg_indent: BlockIndentStyle,
-    where_density: Density, // Should we at least try to put the where clause on the same line as
-                            // the rest of the function decl?
+    where_density: Density, // Should we at least try to put the where clause on
+                            // the same line as the rest of the function decl?
     where_indent: BlockIndentStyle, // Visual will be treated like Tabbed
     where_layout: ListTactic,
     where_pred_indent: BlockIndentStyle,
@@ -147,14 +147,14 @@ create_config! {
     report_todo: ReportTactic,
     report_fixme: ReportTactic,
     reorder_imports: bool, // Alphabetically, case sensitive.
-    expr_indent_style: BlockIndentStyle,
-    closure_indent_style: BlockIndentStyle,
     single_line_if_else: bool,
     format_strings: bool,
+    chains_overflow_last: bool,
+    take_source_hints: bool, // Retain some formatting characteristics from
+                             // the source code.
 }
 
 impl Default for Config {
-
     fn default() -> Config {
         Config {
             max_width: 100,
@@ -181,11 +181,10 @@ impl Default for Config {
             report_todo: ReportTactic::Always,
             report_fixme: ReportTactic::Never,
             reorder_imports: false,
-            expr_indent_style: BlockIndentStyle::Tabbed,
-            closure_indent_style: BlockIndentStyle::Visual,
             single_line_if_else: false,
             format_strings: true,
+            chains_overflow_last: true,
+            take_source_hints: true,
         }
     }
-
 }
diff --git a/src/expr.rs b/src/expr.rs
index e641d7c6762..c7dad3d2ccf 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -9,6 +9,7 @@
 // except according to those terms.
 
 use std::cmp::Ordering;
+use std::borrow::Borrow;
 
 use rewrite::{Rewrite, RewriteContext};
 use lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTactic};
@@ -17,10 +18,11 @@ use StructLitStyle;
 use utils::{span_after, make_indent, extra_offset, first_line_width, last_line_width, wrap_str,
             binary_search};
 use visitor::FmtVisitor;
-use config::{BlockIndentStyle, MultilineStyle};
+use config::MultilineStyle;
 use comment::{FindUncommented, rewrite_comment, contains_comment};
 use types::rewrite_path;
 use items::{span_lo_for_arg, span_hi_for_arg, rewrite_fn_input};
+use chains::rewrite_chain;
 
 use syntax::{ast, ptr};
 use syntax::codemap::{CodeMap, Span, BytePos, mk_sp};
@@ -38,7 +40,7 @@ impl Rewrite for ast::Expr {
                 }
             }
             ast::Expr_::ExprCall(ref callee, ref args) => {
-                rewrite_call(context, callee, args, self.span, width, offset)
+                rewrite_call(context, &**callee, args, self.span, width, offset)
             }
             ast::Expr_::ExprParen(ref subexpr) => {
                 rewrite_paren(context, subexpr, width, offset)
@@ -137,6 +139,11 @@ impl Rewrite for ast::Expr {
             ast::Expr_::ExprClosure(capture, ref fn_decl, ref body) => {
                 rewrite_closure(capture, fn_decl, body, self.span, context, width, offset)
             }
+            ast::Expr_::ExprField(..) |
+            ast::Expr_::ExprTupField(..) |
+            ast::Expr_::ExprMethodCall(..) => {
+                rewrite_chain(self, context, width, offset)
+            }
             // We do not format these expressions yet, but they should still
             // satisfy our width restrictions.
             _ => wrap_str(context.snippet(self.span), context.config.max_width, width, offset),
@@ -201,8 +208,6 @@ fn rewrite_closure(capture: ast::CaptureClause,
         prefix.push_str(&ret_str);
     }
 
-    let closure_indent = closure_indent(context, offset);
-
     // Try to format closure body as a single line expression without braces.
     if is_simple_block(body, context.codemap) && !prefix.contains('\n') {
         let (spacer, closer) = if ret_str.is_empty() {
@@ -233,17 +238,16 @@ fn rewrite_closure(capture: ast::CaptureClause,
 
     // We couldn't format the closure body as a single line expression; fall
     // back to block formatting.
-    let inner_context = context.overflow_context(closure_indent - context.block_indent);
     let body_rewrite = body.expr
                            .as_ref()
                            .and_then(|body_expr| {
                                if let ast::Expr_::ExprBlock(ref inner) = body_expr.node {
-                                   Some(inner.rewrite(&inner_context, 2, 0))
+                                   Some(inner.rewrite(&context, 2, 0))
                                } else {
                                    None
                                }
                            })
-                           .unwrap_or_else(|| body.rewrite(&inner_context, 2, 0));
+                           .unwrap_or_else(|| body.rewrite(&context, 2, 0));
 
     Some(format!("{} {}", prefix, try_opt!(body_rewrite)))
 }
@@ -277,8 +281,10 @@ impl Rewrite for ast::Block {
                 };
 
                 if is_simple_block(self, context.codemap) && prefix.len() < width {
-                    let body =
-                        self.expr.as_ref().unwrap().rewrite(context, width - prefix.len(), offset);
+                    let body = self.expr
+                                   .as_ref()
+                                   .unwrap()
+                                   .rewrite(context, width - prefix.len(), offset);
                     if let Some(ref expr_str) = body {
                         let result = format!("{}{{ {} }}", prefix, expr_str);
                         if result.len() <= width && !result.contains('\n') {
@@ -393,9 +399,9 @@ impl<'a> Rewrite for Loop<'a> {
         };
 
         // FIXME: this drops any comment between "loop" and the block.
-        self.block.rewrite(context, width, offset).map(|result| {
-            format!("{}{}{} {}", label_string, self.keyword, pat_expr_string, result)
-        })
+        self.block
+            .rewrite(context, width, offset)
+            .map(|result| format!("{}{}{} {}", label_string, self.keyword, pat_expr_string, result))
     }
 }
 
@@ -670,15 +676,13 @@ impl Rewrite for ast::Arm {
         total_width += (pat_strs.len() - 1) * 3;
 
         let mut vertical = total_width > pat_budget || pat_strs.iter().any(|p| p.contains('\n'));
-        if !vertical {
+        if !vertical && context.config.take_source_hints {
             // If the patterns were previously stacked, keep them stacked.
-            // FIXME should be an option.
             let pat_span = mk_sp(pats[0].span.lo, pats[pats.len() - 1].span.hi);
             let pat_str = context.snippet(pat_span);
             vertical = pat_str.find('\n').is_some();
         }
 
-
         let pats_width = if vertical {
             pat_strs[pat_strs.len() - 1].len()
         } else {
@@ -717,9 +721,7 @@ impl Rewrite for ast::Arm {
         // 4 = ` => `.len()
         if context.config.max_width > line_start + comma.len() + 4 {
             let budget = context.config.max_width - line_start - comma.len() - 4;
-            if let Some(ref body_str) = body.rewrite(context,
-                                                     budget,
-                                                     line_start + 4) {
+            if let Some(ref body_str) = body.rewrite(context, budget, line_start + 4) {
                 if first_line_width(body_str) <= budget {
                     return Some(format!("{}{} => {}{}",
                                         attr_str.trim_left(),
@@ -762,9 +764,7 @@ fn rewrite_guard(context: &RewriteContext,
         // 4 = ` if `, 5 = ` => {`
         let overhead = pattern_width + 4 + 5;
         if overhead < width {
-            let cond_str = guard.rewrite(context,
-                                         width - overhead,
-                                         offset + pattern_width + 4);
+            let cond_str = guard.rewrite(context, width - overhead, offset + pattern_width + 4);
             if let Some(cond_str) = cond_str {
                 return Some(format!(" if {}", cond_str));
             }
@@ -866,36 +866,35 @@ fn rewrite_string_lit(context: &RewriteContext,
     Some(rewrite_string(str_lit, &fmt))
 }
 
-fn rewrite_call(context: &RewriteContext,
-                callee: &ast::Expr,
-                args: &[ptr::P<ast::Expr>],
-                span: Span,
-                width: usize,
-                offset: usize)
-                -> Option<String> {
-    let callback = |callee_max_width| {
-                       rewrite_call_inner(context,
-                                          callee,
-                                          callee_max_width,
-                                          args,
-                                          span,
-                                          width,
-                                          offset)
-                   };
+pub fn rewrite_call<R>(context: &RewriteContext,
+                       callee: &R,
+                       args: &[ptr::P<ast::Expr>],
+                       span: Span,
+                       width: usize,
+                       offset: usize)
+                       -> Option<String>
+    where R: Rewrite
+{
+    let closure = |callee_max_width| {
+        rewrite_call_inner(context, callee, callee_max_width, args, span, width, offset)
+    };
 
     // 2 is for parens
     let max_width = try_opt!(width.checked_sub(2));
-    binary_search(1, max_width, callback)
+    binary_search(1, max_width, closure)
 }
 
-fn rewrite_call_inner(context: &RewriteContext,
-                      callee: &ast::Expr,
-                      max_callee_width: usize,
-                      args: &[ptr::P<ast::Expr>],
-                      span: Span,
-                      width: usize,
-                      offset: usize)
-                      -> Result<String, Ordering> {
+fn rewrite_call_inner<R>(context: &RewriteContext,
+                         callee: &R,
+                         max_callee_width: usize,
+                         args: &[ptr::P<ast::Expr>],
+                         span: Span,
+                         width: usize,
+                         offset: usize)
+                         -> Result<String, Ordering>
+    where R: Rewrite
+{
+    let callee = callee.borrow();
     // FIXME using byte lens instead of char lens (and probably all over the
     // place too)
     let callee_str = match callee.rewrite(context, max_callee_width, offset) {
@@ -909,6 +908,9 @@ fn rewrite_call_inner(context: &RewriteContext,
         None => return Err(Ordering::Greater),
     };
 
+    let span_lo = span_after(span, "(", context.codemap);
+    let span = mk_sp(span_lo, span.hi);
+
     let extra_offset = extra_offset(&callee_str, offset);
     // 2 is for parens.
     let remaining_width = match width.checked_sub(extra_offset + 2) {
@@ -916,8 +918,12 @@ fn rewrite_call_inner(context: &RewriteContext,
         None => return Err(Ordering::Greater),
     };
     let offset = offset + extra_offset + 1;
-    let inner_indent = expr_indent(context, offset);
-    let inner_context = context.overflow_context(inner_indent - context.block_indent);
+    let block_indent = if args.len() == 1 {
+        context.block_indent
+    } else {
+        offset
+    };
+    let inner_context = &RewriteContext { block_indent: block_indent, ..*context };
 
     let items = itemize_list(context.codemap,
                              args.iter(),
@@ -929,7 +935,7 @@ fn rewrite_call_inner(context: &RewriteContext,
                                  item.rewrite(&inner_context, remaining_width, offset)
                                      .unwrap_or(context.snippet(item.span))
                              },
-                             callee.span.hi + BytePos(1),
+                             span.lo,
                              span.hi);
 
     let fmt = ListFormatting::for_fn(remaining_width, offset);
@@ -941,21 +947,6 @@ fn rewrite_call_inner(context: &RewriteContext,
     Ok(format!("{}({})", callee_str, list_str))
 }
 
-macro_rules! block_indent_helper {
-    ($name:ident, $option:ident) => (
-        fn $name(context: &RewriteContext, offset: usize) -> usize {
-            match context.config.$option {
-                BlockIndentStyle::Inherit => context.block_indent,
-                BlockIndentStyle::Tabbed => context.block_indent + context.config.tab_spaces,
-                BlockIndentStyle::Visual => offset,
-            }
-        }
-    );
-}
-
-block_indent_helper!(expr_indent, expr_indent_style);
-block_indent_helper!(closure_indent, closure_indent_style);
-
 fn rewrite_paren(context: &RewriteContext,
                  subexpr: &ast::Expr,
                  width: usize,
@@ -1004,7 +995,8 @@ fn rewrite_struct_lit<'a>(context: &RewriteContext,
         }
     };
 
-    let field_iter = fields.into_iter().map(StructLitField::Regular)
+    let field_iter = fields.into_iter()
+                           .map(StructLitField::Regular)
                            .chain(base.into_iter().map(StructLitField::Base));
 
     let inner_context = &RewriteContext { block_indent: indent, ..*context };
@@ -1016,10 +1008,11 @@ fn rewrite_struct_lit<'a>(context: &RewriteContext,
                                  match *item {
                                      StructLitField::Regular(ref field) => field.span.lo,
                                      StructLitField::Base(ref expr) => {
-                                         let last_field_hi =
-                                             fields.last().map_or(span.lo, |field| field.span.hi);
-                                         let snippet =
-                                             context.snippet(mk_sp(last_field_hi, expr.span.lo));
+                                         let last_field_hi = fields.last()
+                                                                   .map_or(span.lo,
+                                                                           |field| field.span.hi);
+                                         let snippet = context.snippet(mk_sp(last_field_hi,
+                                                                             expr.span.lo));
                                          let pos = snippet.find_uncommented("..").unwrap();
                                          last_field_hi + BytePos(pos as u32)
                                      }
@@ -1035,7 +1028,7 @@ fn rewrite_struct_lit<'a>(context: &RewriteContext,
                                  match *item {
                                      StructLitField::Regular(ref field) => {
                                          rewrite_field(inner_context, &field, h_budget, indent)
-                                            .unwrap_or(context.snippet(field.span))
+                                             .unwrap_or(context.snippet(field.span))
                                      }
                                      StructLitField::Base(ref expr) => {
                                          // 2 = ..
@@ -1067,11 +1060,10 @@ fn rewrite_struct_lit<'a>(context: &RewriteContext,
     let fields_str = try_opt!(write_list(&items.collect::<Vec<_>>(), &fmt));
 
     let format_on_newline = || {
-                                let inner_indent = make_indent(context.block_indent +
-                                                               context.config.tab_spaces);
-                                let outer_indent = make_indent(context.block_indent);
-                                Some(format!("{} {{\n{}{}\n{}}}", path_str, inner_indent, fields_str, outer_indent))
-                            };
+        let inner_indent = make_indent(context.block_indent + context.config.tab_spaces);
+        let outer_indent = make_indent(context.block_indent);
+        Some(format!("{} {{\n{}{}\n{}}}", path_str, inner_indent, fields_str, outer_indent))
+    };
 
     match (context.config.struct_lit_style, context.config.struct_lit_multiline_style) {
         (StructLitStyle::Block, _) if fields_str.contains('\n') => format_on_newline(),
@@ -1090,8 +1082,8 @@ fn rewrite_field(context: &RewriteContext,
                  -> Option<String> {
     let name = &field.ident.node.to_string();
     let overhead = name.len() + 2;
-    let expr =
-        field.expr.rewrite(context, try_opt!(width.checked_sub(overhead)), offset + overhead);
+    let expr = field.expr
+                    .rewrite(context, try_opt!(width.checked_sub(overhead)), offset + overhead);
     expr.map(|s| format!("{}: {}", name, s))
 }
 
@@ -1152,10 +1144,7 @@ fn rewrite_binary_op(context: &RewriteContext,
     // worth trying to put everything on one line.
     if rhs_result.len() + 2 + operator_str.len() < width && !rhs_result.contains('\n') {
         // 1 = space between lhs expr and operator
-        if let Some(mut result) = lhs.rewrite(context,
-                                              width - 1 - operator_str.len(),
-                                              offset) {
-
+        if let Some(mut result) = lhs.rewrite(context, width - 1 - operator_str.len(), offset) {
             result.push(' ');
             result.push_str(&operator_str);
             result.push(' ');
@@ -1167,9 +1156,7 @@ fn rewrite_binary_op(context: &RewriteContext,
                 return Some(result);
             }
 
-            if let Some(rhs_result) = rhs.rewrite(context,
-                                                  remaining_width,
-                                                  offset + result.len()) {
+            if let Some(rhs_result) = rhs.rewrite(context, remaining_width, offset + result.len()) {
                 if rhs_result.len() <= remaining_width {
                     result.push_str(&rhs_result);
                     return Some(result);
diff --git a/src/imports.rs b/src/imports.rs
index 8411b3208df..1d215d022d1 100644
--- a/src/imports.rs
+++ b/src/imports.rs
@@ -38,10 +38,10 @@ impl Rewrite for ast::ViewPath {
                 let path_str = try_opt!(path.rewrite(context, width - ident_str.len() - 4, offset));
 
                 Some(if path.segments.last().unwrap().identifier == ident {
-                        path_str
-                    } else {
-                        format!("{} as {}", path_str, ident_str)
-                    })
+                    path_str
+                } else {
+                    format!("{} as {}", path_str, ident_str)
+                })
             }
         }
     }
@@ -150,10 +150,10 @@ pub fn rewrite_use_list(width: usize,
     let list_str = try_opt!(write_list(&items[first_index..], &fmt));
 
     Some(if path_str.is_empty() {
-            format!("{{{}}}", list_str)
-        } else {
-            format!("{}::{{{}}}", path_str, list_str)
-        })
+        format!("{{{}}}", list_str)
+    } else {
+        format!("{}::{{{}}}", path_str, list_str)
+    })
 }
 
 // Returns true when self item was found.
diff --git a/src/items.rs b/src/items.rs
index 7555ea01199..958dbb20df0 100644
--- a/src/items.rs
+++ b/src/items.rs
@@ -334,11 +334,13 @@ impl<'a> FmtVisitor<'a> {
         // FIXME: the comment for the self argument is dropped. This is blocked
         // on rust issue #27522.
         let min_args = explicit_self.and_then(|explicit_self| {
-                           rewrite_explicit_self(explicit_self, args)
-                       }).map(|self_str| {
-                           arg_item_strs[0] = self_str;
-                           2
-                       }).unwrap_or(1);
+                                        rewrite_explicit_self(explicit_self, args)
+                                    })
+                                    .map(|self_str| {
+                                        arg_item_strs[0] = self_str;
+                                        2
+                                    })
+                                    .unwrap_or(1);
 
         // Comments between args
         let mut arg_items = Vec::new();
@@ -511,8 +513,9 @@ impl<'a> FmtVisitor<'a> {
                                              |arg| arg.ty.span.hi,
                                              |arg| {
                                                  // FIXME silly width, indent
-                                                 arg.ty.rewrite(&self.get_context(), 1000, 0)
-                                                       .unwrap()
+                                                 arg.ty
+                                                    .rewrite(&self.get_context(), 1000, 0)
+                                                    .unwrap()
                                              },
                                              span_after(field.span, "(", self.codemap),
                                              next_span_start);
@@ -760,11 +763,12 @@ impl<'a> FmtVisitor<'a> {
         let typ = field.node.ty.rewrite(&self.get_context(), 1000, 0).unwrap();
 
         let indent = self.block_indent + self.config.tab_spaces;
-        let mut attr_str = field.node.attrs
-                                     .rewrite(&self.get_context(),
-                                              self.config.max_width - indent,
-                                              indent)
-                                     .unwrap();
+        let mut attr_str = field.node
+                                .attrs
+                                .rewrite(&self.get_context(),
+                                         self.config.max_width - indent,
+                                         indent)
+                                .unwrap();
         if !attr_str.is_empty() {
             attr_str.push('\n');
             attr_str.push_str(&make_indent(indent));
@@ -803,12 +807,9 @@ impl<'a> FmtVisitor<'a> {
         // Strings for the generics.
         let context = self.get_context();
         // FIXME: don't unwrap
-        let lt_strs = lifetimes.iter().map(|lt| {
-            lt.rewrite(&context, h_budget, offset).unwrap()
-        });
-        let ty_strs = tys.iter().map(|ty_param| {
-            ty_param.rewrite(&context, h_budget, offset).unwrap()
-        });
+        let lt_strs = lifetimes.iter().map(|lt| lt.rewrite(&context, h_budget, offset).unwrap());
+        let ty_strs = tys.iter()
+                         .map(|ty_param| ty_param.rewrite(&context, h_budget, offset).unwrap());
 
         // Extract comments between generics.
         let lt_spans = lifetimes.iter().map(|l| {
diff --git a/src/lib.rs b/src/lib.rs
index 7975c77e06d..3f8900d6713 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -10,6 +10,7 @@
 
 #![feature(rustc_private)]
 #![feature(custom_attribute)]
+#![feature(slice_splits)]
 #![allow(unused_attributes)]
 
 // TODO we're going to allocate a whole bunch of temp Strings, is it worth
@@ -70,6 +71,7 @@ mod string;
 mod comment;
 mod modules;
 pub mod rustfmt_diff;
+mod chains;
 
 const MIN_STRING: usize = 10;
 // When we get scoped annotations, we should have rustfmt::skip.
diff --git a/src/lists.rs b/src/lists.rs
index cf68d86dd2b..678c84c199a 100644
--- a/src/lists.rs
+++ b/src/lists.rs
@@ -272,9 +272,10 @@ impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
         self.inner.next().map(|item| {
             let mut new_lines = false;
             // Pre-comment
-            let pre_snippet = self.codemap.span_to_snippet(codemap::mk_sp(self.prev_span_end,
-                                                                          (self.get_lo)(&item)))
-                                          .unwrap();
+            let pre_snippet = self.codemap
+                                  .span_to_snippet(codemap::mk_sp(self.prev_span_end,
+                                                                  (self.get_lo)(&item)))
+                                  .unwrap();
             let trimmed_pre_snippet = pre_snippet.trim();
             let pre_comment = if !trimmed_pre_snippet.is_empty() {
                 Some(trimmed_pre_snippet.to_owned())
@@ -285,11 +286,12 @@ impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
             // Post-comment
             let next_start = match self.inner.peek() {
                 Some(ref next_item) => (self.get_lo)(next_item),
-                None => self.next_span_start
+                None => self.next_span_start,
             };
-            let post_snippet = self.codemap.span_to_snippet(codemap::mk_sp((self.get_hi)(&item),
-                                                                           next_start))
-                                           .unwrap();
+            let post_snippet = self.codemap
+                                   .span_to_snippet(codemap::mk_sp((self.get_hi)(&item),
+                                                                   next_start))
+                                   .unwrap();
 
             let comment_end = match self.inner.peek() {
                 Some(..) => {
@@ -315,12 +317,11 @@ impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
                         }
                         // Potential *single* line comment.
                         (_, Some(j)) => j + 1,
-                        _ => post_snippet.len()
+                        _ => post_snippet.len(),
                     }
-                },
+                }
                 None => {
-                    post_snippet.find_uncommented(self.terminator)
-                                .unwrap_or(post_snippet.len())
+                    post_snippet.find_uncommented(self.terminator).unwrap_or(post_snippet.len())
                 }
             };
 
diff --git a/src/missed_spans.rs b/src/missed_spans.rs
index 33d096c3ba0..535fc4c8ad9 100644
--- a/src/missed_spans.rs
+++ b/src/missed_spans.rs
@@ -17,21 +17,20 @@ impl<'a> FmtVisitor<'a> {
     // TODO these format_missing methods are ugly. Refactor and add unit tests
     // for the central whitespace stripping loop.
     pub fn format_missing(&mut self, end: BytePos) {
-        self.format_missing_inner(end, |this, last_snippet, _| {
-            this.buffer.push_str(last_snippet)
-        })
+        self.format_missing_inner(end, |this, last_snippet, _| this.buffer.push_str(last_snippet))
     }
 
     pub fn format_missing_with_indent(&mut self, end: BytePos) {
-        self.format_missing_inner(end, |this, last_snippet, snippet| {
-            this.buffer.push_str(last_snippet.trim_right());
-            if last_snippet == snippet {
+        self.format_missing_inner(end,
+                                  |this, last_snippet, snippet| {
+                                      this.buffer.push_str(last_snippet.trim_right());
+                                      if last_snippet == snippet {
                 // No new lines in the snippet.
-                this.buffer.push_str("\n");
-            }
-            let indent = make_indent(this.block_indent);
-            this.buffer.push_str(&indent);
-        })
+                                          this.buffer.push_str("\n");
+                                      }
+                                      let indent = make_indent(this.block_indent);
+                                      this.buffer.push_str(&indent);
+                                  })
     }
 
     fn format_missing_inner<F: Fn(&mut FmtVisitor, &str, &str)>(&mut self,
@@ -60,9 +59,7 @@ impl<'a> FmtVisitor<'a> {
         let span = codemap::mk_sp(start, end);
         let snippet = self.snippet(span);
 
-        self.write_snippet(&snippet,
-                           true,
-                           &process_last_snippet);
+        self.write_snippet(&snippet, true, &process_last_snippet);
     }
 
     fn write_snippet<F: Fn(&mut FmtVisitor, &str, &str)>(&mut self,
diff --git a/src/string.rs b/src/string.rs
index aba6a7f48da..5e5af61faf5 100644
--- a/src/string.rs
+++ b/src/string.rs
@@ -44,8 +44,8 @@ pub fn rewrite_string<'a>(s: &str, fmt: &StringFormat<'a>) -> String {
     result.push_str(fmt.opener);
 
     let ender_length = fmt.line_end.len();
-    let max_chars = fmt.width.checked_sub(fmt.opener.len()).unwrap_or(0)
-                             .checked_sub(ender_length).unwrap_or(1);
+    let max_chars = fmt.width.checked_sub(fmt.opener.len() + ender_length).unwrap_or(1);
+
     loop {
         let mut cur_end = cur_start + max_chars;
 
diff --git a/src/types.rs b/src/types.rs
index dace1d84bca..e0f3140d4d9 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -130,16 +130,16 @@ impl<'a> Rewrite for SegmentParam<'a> {
     // FIXME doesn't always use width, offset
     fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
         Some(match *self {
-                SegmentParam::LifeTime(ref lt) => {
-                    pprust::lifetime_to_string(lt)
-                }
-                SegmentParam::Type(ref ty) => {
-                    try_opt!(ty.rewrite(context, width, offset))
-                }
-                SegmentParam::Binding(ref binding) => {
-                    format!("{} = {}", binding.ident, try_opt!(binding.ty.rewrite(context, width, offset)))
-                }
-            })
+            SegmentParam::LifeTime(ref lt) => {
+                pprust::lifetime_to_string(lt)
+            }
+            SegmentParam::Type(ref ty) => {
+                try_opt!(ty.rewrite(context, width, offset))
+            }
+            SegmentParam::Binding(ref binding) => {
+                format!("{} = {}", binding.ident, try_opt!(binding.ty.rewrite(context, width, offset)))
+            }
+        })
     }
 }
 
@@ -196,13 +196,12 @@ fn rewrite_segment(segment: &ast::PathSegment,
         ast::PathParameters::AngleBracketedParameters(ref data) if !data.lifetimes.is_empty() ||
                                                                    !data.types.is_empty() ||
                                                                    !data.bindings.is_empty() => {
-            let param_list = data.lifetimes.iter()
-                                           .map(SegmentParam::LifeTime)
-                                           .chain(data.types.iter()
-                                                      .map(|x| SegmentParam::Type(&*x)))
-                                           .chain(data.bindings.iter()
-                                                      .map(|x| SegmentParam::Binding(&*x)))
-                                           .collect::<Vec<_>>();
+            let param_list = data.lifetimes
+                                 .iter()
+                                 .map(SegmentParam::LifeTime)
+                                 .chain(data.types.iter().map(|x| SegmentParam::Type(&*x)))
+                                 .chain(data.bindings.iter().map(|x| SegmentParam::Binding(&*x)))
+                                 .collect::<Vec<_>>();
 
             let next_span_lo = param_list.last().unwrap().get_span().hi + BytePos(1);
             let list_lo = span_after(codemap::mk_sp(*span_lo, span_hi), "<", context.codemap);
@@ -274,57 +273,67 @@ impl Rewrite for ast::WherePredicate {
         // TODO dead spans?
         // TODO assumes we'll always fit on one line...
         Some(match *self {
-                ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate{ref bound_lifetimes,
-                                                                          ref bounded_ty,
-                                                                          ref bounds,
-                                                                          ..}) => {
-                    if !bound_lifetimes.is_empty() {
-                        let lifetime_str = bound_lifetimes.iter().map(|lt| {
-                                           lt.rewrite(context, width, offset).unwrap()
-                                       }).collect::<Vec<_>>().join(", ");
-                        let type_str = pprust::ty_to_string(bounded_ty);
+            ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate { ref bound_lifetimes,
+                                                                           ref bounded_ty,
+                                                                           ref bounds,
+                                                                           .. }) => {
+                if !bound_lifetimes.is_empty() {
+                    let lifetime_str = bound_lifetimes.iter()
+                                                      .map(|lt| {
+                                                          lt.rewrite(context, width, offset)
+                                                            .unwrap()
+                                                      })
+                                                      .collect::<Vec<_>>()
+                                                      .join(", ");
+                    let type_str = pprust::ty_to_string(bounded_ty);
                     // 8 = "for<> : ".len()
-                        let used_width = lifetime_str.len() + type_str.len() + 8;
-                        let bounds_str = bounds.iter().map(|ty_bound| {
-                                         ty_bound.rewrite(context,
-                                                          width - used_width,
-                                                          offset + used_width)
-                                                 .unwrap()
-                                     }).collect::<Vec<_>>().join(" + ");
-
-                        format!("for<{}> {}: {}", lifetime_str, type_str, bounds_str)
-                    } else {
-                        let type_str = pprust::ty_to_string(bounded_ty);
+                    let used_width = lifetime_str.len() + type_str.len() + 8;
+                    let bounds_str = bounds.iter()
+                                           .map(|ty_bound| {
+                                               ty_bound.rewrite(context,
+                                                                width - used_width,
+                                                                offset + used_width)
+                                                       .unwrap()
+                                           })
+                                           .collect::<Vec<_>>()
+                                           .join(" + ");
+
+                    format!("for<{}> {}: {}", lifetime_str, type_str, bounds_str)
+                } else {
+                    let type_str = pprust::ty_to_string(bounded_ty);
                     // 2 = ": ".len()
-                        let used_width = type_str.len() + 2;
-                        let bounds_str = bounds.iter().map(|ty_bound| {
-                                         ty_bound.rewrite(context,
-                                                          width - used_width,
-                                                          offset + used_width)
-                                                 .unwrap()
-                                     }).collect::<Vec<_>>().join(" + ");
-
-                        format!("{}: {}", type_str, bounds_str)
-                    }
+                    let used_width = type_str.len() + 2;
+                    let bounds_str = bounds.iter()
+                                           .map(|ty_bound| {
+                                               ty_bound.rewrite(context,
+                                                                width - used_width,
+                                                                offset + used_width)
+                                                       .unwrap()
+                                           })
+                                           .collect::<Vec<_>>()
+                                           .join(" + ");
+
+                    format!("{}: {}", type_str, bounds_str)
                 }
-                ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate{ref lifetime,
-                                                                            ref bounds,
-                                                                            ..}) => {
-                    format!("{}: {}",
+            }
+            ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate { ref lifetime,
+                                                                             ref bounds,
+                                                                             .. }) => {
+                format!("{}: {}",
                         pprust::lifetime_to_string(lifetime),
                         bounds.iter().map(pprust::lifetime_to_string)
                               .collect::<Vec<_>>().join(" + "))
-                }
-                ast::WherePredicate::EqPredicate(ast::WhereEqPredicate{ref path, ref ty, ..}) => {
-                    let ty_str = pprust::ty_to_string(ty);
+            }
+            ast::WherePredicate::EqPredicate(ast::WhereEqPredicate { ref path, ref ty, .. }) => {
+                let ty_str = pprust::ty_to_string(ty);
                 // 3 = " = ".len()
-                    let used_width = 3 + ty_str.len();
-                    let path_str = try_opt!(path.rewrite(context,
+                let used_width = 3 + ty_str.len();
+                let path_str = try_opt!(path.rewrite(context,
                                                      width - used_width,
                                                      offset + used_width));
-                    format!("{} = {}", path_str, ty_str)
-                }
-            })
+                format!("{} = {}", path_str, ty_str)
+            }
+        })
     }
 }
 
@@ -359,8 +368,9 @@ impl Rewrite for ast::TyParamBound {
 
 impl Rewrite for ast::TyParamBounds {
     fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
-        let strs: Vec<_> =
-            self.iter().map(|b| b.rewrite(context, width, offset).unwrap()).collect();
+        let strs: Vec<_> = self.iter()
+                               .map(|b| b.rewrite(context, width, offset).unwrap())
+                               .collect();
         Some(strs.join(" + "))
     }
 }
@@ -373,9 +383,11 @@ impl Rewrite for ast::TyParam {
         if !self.bounds.is_empty() {
             result.push_str(": ");
 
-            let bounds = self.bounds.iter().map(|ty_bound| {
-                ty_bound.rewrite(context, width, offset).unwrap()
-            }).collect::<Vec<_>>().join(" + ");
+            let bounds = self.bounds
+                             .iter()
+                             .map(|ty_bound| ty_bound.rewrite(context, width, offset).unwrap())
+                             .collect::<Vec<_>>()
+                             .join(" + ");
 
             result.push_str(&bounds);
         }
@@ -392,9 +404,11 @@ impl Rewrite for ast::TyParam {
 impl Rewrite for ast::PolyTraitRef {
     fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
         if !self.bound_lifetimes.is_empty() {
-            let lifetime_str = self.bound_lifetimes.iter().map(|lt| {
-                lt.rewrite(context, width, offset).unwrap()
-            }).collect::<Vec<_>>().join(", ");
+            let lifetime_str = self.bound_lifetimes
+                                   .iter()
+                                   .map(|lt| lt.rewrite(context, width, offset).unwrap())
+                                   .collect::<Vec<_>>()
+                                   .join(", ");
             // 6 is "for<> ".len()
             let extra_offset = lifetime_str.len() + 6;
             let max_path_width = try_opt!(width.checked_sub(extra_offset));
diff --git a/src/utils.rs b/src/utils.rs
index 94f8fec2b0d..973f0fa56ab 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -14,6 +14,7 @@ use syntax::ast::{self, Visibility, Attribute, MetaItem, MetaItem_};
 use syntax::codemap::{CodeMap, Span, BytePos};
 
 use comment::FindUncommented;
+use rewrite::{Rewrite, RewriteContext};
 
 use SKIP_ANNOTATION;
 
@@ -93,10 +94,16 @@ pub fn contains_skip(attrs: &[Attribute]) -> bool {
 // Find the end of a TyParam
 #[inline]
 pub fn end_typaram(typaram: &ast::TyParam) -> BytePos {
-    typaram.bounds.last().map(|bound| match *bound {
-        ast::RegionTyParamBound(ref lt) => lt.span,
-        ast::TraitTyParamBound(ref prt, _) => prt.span,
-    }).unwrap_or(typaram.span).hi
+    typaram.bounds
+           .last()
+           .map(|bound| {
+               match *bound {
+                   ast::RegionTyParamBound(ref lt) => lt.span,
+                   ast::TraitTyParamBound(ref prt, _) => prt.span,
+               }
+           })
+           .unwrap_or(typaram.span)
+           .hi
 }
 
 #[inline]
@@ -202,6 +209,12 @@ pub fn wrap_str<S: AsRef<str>>(s: S, max_width: usize, width: usize, offset: usi
     Some(s)
 }
 
+impl Rewrite for String {
+    fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
+        wrap_str(self, context.config.max_width, width, offset).map(ToOwned::to_owned)
+    }
+}
+
 // Binary search in integer range. Returns the first Ok value returned by the
 // callback.
 // The callback takes an integer and returns either an Ok, or an Err indicating
@@ -231,13 +244,13 @@ pub fn binary_search<C, T>(mut lo: usize, mut hi: usize, callback: C) -> Option<
 #[test]
 fn bin_search_test() {
     let closure = |i| {
-                      match i {
-                          4 => Ok(()),
-                          j if j > 4 => Err(Ordering::Less),
-                          j if j < 4 => Err(Ordering::Greater),
-                          _ => unreachable!(),
-                      }
-                  };
+        match i {
+            4 => Ok(()),
+            j if j > 4 => Err(Ordering::Less),
+            j if j < 4 => Err(Ordering::Greater),
+            _ => unreachable!(),
+        }
+    };
 
     assert_eq!(Some(()), binary_search(1, 10, &closure));
     assert_eq!(None, binary_search(1, 3, &closure));
diff --git a/src/visitor.rs b/src/visitor.rs
index c1c1f2fcbbd..54ada1b7455 100644
--- a/src/visitor.rs
+++ b/src/visitor.rs
@@ -202,19 +202,11 @@ impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
             }
             ast::Item_::ItemStruct(ref def, ref generics) => {
                 self.format_missing_with_indent(item.span.lo);
-                self.visit_struct(item.ident,
-                                  item.vis,
-                                  def,
-                                  generics,
-                                  item.span);
+                self.visit_struct(item.ident, item.vis, def, generics, item.span);
             }
             ast::Item_::ItemEnum(ref def, ref generics) => {
                 self.format_missing_with_indent(item.span.lo);
-                self.visit_enum(item.ident,
-                                item.vis,
-                                def,
-                                generics,
-                                item.span);
+                self.visit_enum(item.ident, item.vis, def, generics, item.span);
                 self.last_pos = item.span.hi;
             }
             ast::Item_::ItemMod(ref module) => {
@@ -236,10 +228,7 @@ impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> {
             self.format_missing_with_indent(ti.span.lo);
 
             let indent = self.block_indent;
-            let new_fn = self.rewrite_required_fn(indent,
-                                                  ti.ident,
-                                                  sig,
-                                                  ti.span);
+            let new_fn = self.rewrite_required_fn(indent, ti.ident, sig, ti.span);
 
 
             if let Some(fn_str) = new_fn {
diff --git a/tests/config/small_tabs.toml b/tests/config/small_tabs.toml
index c5122b760a0..754cde57cd9 100644
--- a/tests/config/small_tabs.toml
+++ b/tests/config/small_tabs.toml
@@ -21,7 +21,7 @@ enum_trailing_comma = true
 report_todo = "Always"
 report_fixme = "Never"
 reorder_imports = false
-expr_indent_style = "Tabbed"
-closure_indent_style = "Visual"
 single_line_if_else = false
 format_strings = true
+chains_overflow_last = true
+take_source_hints = true
diff --git a/tests/source/chains-no-overflow.rs b/tests/source/chains-no-overflow.rs
new file mode 100644
index 00000000000..b94ca21b258
--- /dev/null
+++ b/tests/source/chains-no-overflow.rs
@@ -0,0 +1,37 @@
+// rustfmt-chains_overflow_last: false
+// Test chain formatting without overflowing the last item.
+
+fn main() {
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc
+                       .ddddddddddddddddddddddddddd();
+
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd.eeeeeeee();
+
+    x()
+        .y(|| match cond() { true => (), false => () });
+
+    loong_func()
+        .quux(move || if true {
+            1
+        } else {
+            2
+        });
+
+    fffffffffffffffffffffffffffffffffff(a,
+                                        {
+                                            SCRIPT_TASK_ROOT
+                                            .with(|root| {
+                                                        *root.borrow_mut()  =   Some(&script_task);
+                                                    });
+                                        });                                        
+
+    let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx
+        .map(|x| x + 5)
+        .map(|x| x / 2)
+        .fold(0, |acc, x| acc + x);
+
+    aaaaaaaaaaaaaaaa.map(|x| {
+                         x += 1;
+                         x
+                     }).filter(some_mod::some_filter)
+}
diff --git a/tests/source/chains.rs b/tests/source/chains.rs
new file mode 100644
index 00000000000..f53afaa0c28
--- /dev/null
+++ b/tests/source/chains.rs
@@ -0,0 +1,43 @@
+// Test chain formatting.
+
+fn main() {
+    // Don't put chains on a single line if it wasn't so in source.
+    let a = b .c
+    .d.1
+                .foo(|x| x + 1);
+
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc
+                       .ddddddddddddddddddddddddddd();
+
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd.eeeeeeee();
+
+    // Test case where first chain element isn't a path, but is shorter than
+    // the size of a tab.
+    x()
+        .y(|| match cond() { true => (), false => () });
+
+    loong_func()
+        .quux(move || if true {
+            1
+        } else {
+            2
+        });
+
+    fffffffffffffffffffffffffffffffffff(a,
+                                        {
+                                            SCRIPT_TASK_ROOT
+                                            .with(|root| {
+                                                *root.borrow_mut()  =   Some(&script_task);
+                                            });
+                                        });                                        
+
+    let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx
+        .map(|x| x + 5)
+        .map(|x| x / 2)
+        .fold(0, |acc, x| acc + x);
+
+    aaaaaaaaaaaaaaaa.map(|x| {
+                         x += 1;
+                         x
+                     }).filter(some_mod::some_filter)
+}
diff --git a/tests/source/expr-no-hints.rs b/tests/source/expr-no-hints.rs
new file mode 100644
index 00000000000..7329a742bac
--- /dev/null
+++ b/tests/source/expr-no-hints.rs
@@ -0,0 +1,8 @@
+// rustfmt-take_source_hints: false
+// We know best!
+
+fn main() {
+    a.b
+     .c
+     .d();
+}
diff --git a/tests/source/expr-visual-indent.rs b/tests/source/expr-visual-indent.rs
deleted file mode 100644
index 3d7c1b92be8..00000000000
--- a/tests/source/expr-visual-indent.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-// rustfmt-expr_indent_style: Visual
-
-// Visual level block indentation.
-
-fn matcher() {
-    Some(while true {
-        test();
-    })
-}
\ No newline at end of file
diff --git a/tests/source/string-lit.rs b/tests/source/string-lit.rs
index e95aaae75e0..35d8ec072c2 100644
--- a/tests/source/string-lit.rs
+++ b/tests/source/string-lit.rs
@@ -30,5 +30,5 @@ formatting"#;
     let unicode3 = "中华Việt Nam";
     let unicode4 = "☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃";
 
-    "stuff"
+    "stuffin'"
 }
diff --git a/tests/system.rs b/tests/system.rs
index b0d96468eca..9086697978e 100644
--- a/tests/system.rs
+++ b/tests/system.rs
@@ -121,9 +121,8 @@ pub fn idempotent_check(filename: String) -> Result<(), HashMap<String, Vec<Mism
     // multithreaded rustfmt
     thread::catch_panic(move || {
         run(args, WriteMode::Return(HANDLE_RESULT), config);
-    }).map_err(|any|
-        *any.downcast().ok().expect("Downcast failed.")
-    )
+    })
+        .map_err(|any| *any.downcast().ok().expect("Downcast failed."))
 }
 
 
@@ -138,7 +137,9 @@ fn get_config(config_file: Option<&str>) -> Box<Config> {
         }
     };
 
-    let mut def_config_file = fs::File::open(config_file_name).ok().expect("Couldn't open config.");
+    let mut def_config_file = fs::File::open(config_file_name)
+                                  .ok()
+                                  .expect("Couldn't open config.");
     let mut def_config = String::new();
     def_config_file.read_to_string(&mut def_config).ok().expect("Couldn't read config.");
 
@@ -148,14 +149,17 @@ fn get_config(config_file: Option<&str>) -> Box<Config> {
 // Reads significant comments of the form: // rustfmt-key: value
 // into a hash map.
 fn read_significant_comments(file_name: &str) -> HashMap<String, String> {
-    let file = fs::File::open(file_name).ok().expect(&format!("Couldn't read file {}.", file_name));
+    let file = fs::File::open(file_name)
+                   .ok()
+                   .expect(&format!("Couldn't read file {}.", file_name));
     let reader = BufReader::new(file);
     let pattern = r"^\s*//\s*rustfmt-([^:]+):\s*(\S+)";
     let regex = regex::Regex::new(&pattern).ok().expect("Failed creating pattern 1.");
 
     // Matches lines containing significant comments or whitespace.
     let line_regex = regex::Regex::new(r"(^\s*$)|(^\s*//\s*rustfmt-[^:]+:\s*\S+)")
-        .ok().expect("Failed creating pattern 2.");
+                         .ok()
+                         .expect("Failed creating pattern 2.");
 
     reader.lines()
           .map(|line| line.ok().expect("Failed getting line."))
diff --git a/tests/target/chains-no-overflow.rs b/tests/target/chains-no-overflow.rs
new file mode 100644
index 00000000000..9b1f244637d
--- /dev/null
+++ b/tests/target/chains-no-overflow.rs
@@ -0,0 +1,45 @@
+// rustfmt-chains_overflow_last: false
+// Test chain formatting without overflowing the last item.
+
+fn main() {
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd();
+
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc
+                       .ddddddddddddddddddddddddddd
+                       .eeeeeeee();
+
+    x().y(|| {
+           match cond() {
+               true => (),
+               false => (),
+           }
+       });
+
+    loong_func()
+        .quux(move || {
+            if true {
+                1
+            } else {
+                2
+            }
+        });
+
+    fffffffffffffffffffffffffffffffffff(a,
+                                        {
+                                            SCRIPT_TASK_ROOT.with(|root| {
+                                                                *root.borrow_mut() =
+                                                                    Some(&script_task);
+                                                            });
+                                        });
+
+    let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx.map(|x| x + 5)
+                                                                          .map(|x| x / 2)
+                                                                          .fold(0,
+                                                                                |acc, x| acc + x);
+
+    aaaaaaaaaaaaaaaa.map(|x| {
+                        x += 1;
+                        x
+                    })
+                    .filter(some_mod::some_filter)
+}
diff --git a/tests/target/chains-no-overlow-2.rs b/tests/target/chains-no-overlow-2.rs
new file mode 100644
index 00000000000..d795fa2f961
--- /dev/null
+++ b/tests/target/chains-no-overlow-2.rs
@@ -0,0 +1,16 @@
+// rustfmt-chains_overflow_last: false
+
+fn main() {
+    reader.lines()
+          .map(|line| line.ok().expect("Failed getting line."))
+          .take_while(|line| line_regex.is_match(&line))
+          .filter_map(|line| {
+              regex.captures_iter(&line)
+                   .next()
+                   .map(|capture| {
+                       (capture.at(1).expect("Couldn\'t unwrap capture.").to_owned(),
+                        capture.at(2).expect("Couldn\'t unwrap capture.").to_owned())
+                   })
+          })
+          .collect();
+}
diff --git a/tests/target/chains.rs b/tests/target/chains.rs
new file mode 100644
index 00000000000..590f0a5b156
--- /dev/null
+++ b/tests/target/chains.rs
@@ -0,0 +1,50 @@
+// Test chain formatting.
+
+fn main() {
+    // Don't put chains on a single line if it wasn't so in source.
+    let a = b.c
+             .d
+             .1
+             .foo(|x| x + 1);
+
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd();
+
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc
+                       .ddddddddddddddddddddddddddd
+                       .eeeeeeee();
+
+    // Test case where first chain element isn't a path, but is shorter than
+    // the size of a tab.
+    x().y(|| {
+        match cond() {
+            true => (),
+            false => (),
+        }
+    });
+
+    loong_func().quux(move || {
+        if true {
+            1
+        } else {
+            2
+        }
+    });
+
+    fffffffffffffffffffffffffffffffffff(a,
+                                        {
+                                            SCRIPT_TASK_ROOT.with(|root| {
+                                                *root.borrow_mut() = Some(&script_task);
+                                            });
+                                        });
+
+    let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx.map(|x| x + 5)
+                                                                          .map(|x| x / 2)
+                                                                          .fold(0,
+                                                                                |acc, x| acc + x);
+
+    aaaaaaaaaaaaaaaa.map(|x| {
+                        x += 1;
+                        x
+                    })
+                    .filter(some_mod::some_filter)
+}
diff --git a/tests/target/closure.rs b/tests/target/closure.rs
index a0565167093..b42296487be 100644
--- a/tests/target/closure.rs
+++ b/tests/target/closure.rs
@@ -9,52 +9,51 @@ fn main() {
                      b: WithType, // argument
                      // ignored
                      _| {
-                        (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
-                         bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)
-                    };
+        (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)
+    };
 
     let block_body = move |xxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
                            ref yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy| {
-                              xxxxxxxxxxxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
-                          };
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+    };
 
     let loooooooooooooong_name = |field| {
              // TODO(#27): format comments.
-                                     if field.node.attrs.len() > 0 {
-                                         field.node.attrs[0].span.lo
-                                     } else {
-                                         field.span.lo
-                                     }
-                                 };
+        if field.node.attrs.len() > 0 {
+            field.node.attrs[0].span.lo
+        } else {
+            field.span.lo
+        }
+    };
 
     let block_me = |field| {
-                       if true_story() {
-                           1
-                       } else {
-                           2
-                       }
-                   };
+        if true_story() {
+            1
+        } else {
+            2
+        }
+    };
 
     let unblock_me = |trivial| closure();
 
     let empty = |arg| {};
 
     let simple = |arg| { /* TODO(#27): comment formatting */
-                     foo(arg)
-                 };
+        foo(arg)
+    };
 
     let test = || {
-                   do_something();
-                   do_something_else();
-               };
+        do_something();
+        do_something_else();
+    };
 
     let arg_test = |big_argument_name, test123| {
-                       looooooooooooooooooong_function_naaaaaaaaaaaaaaaaame()
-                   };
+        looooooooooooooooooong_function_naaaaaaaaaaaaaaaaame()
+    };
 
     let arg_test = |big_argument_name, test123| {
-                       looooooooooooooooooong_function_naaaaaaaaaaaaaaaaame()
-                   };
+        looooooooooooooooooong_function_naaaaaaaaaaaaaaaaame()
+    };
 
     let simple_closure = move || -> () {};
 
@@ -63,8 +62,8 @@ fn main() {
     let closure_with_return_type = |aaaaaaaaaaaaaaaaaaaaaaarg1,
                                     aaaaaaaaaaaaaaaaaaaaaaarg2|
                                     -> Strong {
-                                       "sup".to_owned()
-                                   };
+        "sup".to_owned()
+    };
 
     |arg1, arg2, _, _, arg3, arg4| {
         let temp = arg4 + arg3;
diff --git a/tests/target/expr-no-hints.rs b/tests/target/expr-no-hints.rs
new file mode 100644
index 00000000000..62dd64c28a6
--- /dev/null
+++ b/tests/target/expr-no-hints.rs
@@ -0,0 +1,6 @@
+// rustfmt-take_source_hints: false
+// We know best!
+
+fn main() {
+    a.b.c.d();
+}
diff --git a/tests/target/expr-visual-indent.rs b/tests/target/expr-visual-indent.rs
deleted file mode 100644
index d74b5aae93c..00000000000
--- a/tests/target/expr-visual-indent.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-// rustfmt-expr_indent_style: Visual
-
-// Visual level block indentation.
-
-fn matcher() {
-    Some(while true {
-             test();
-         })
-}
diff --git a/tests/target/expr.rs b/tests/target/expr.rs
index 9bfe51e0b26..d24d463c2c2 100644
--- a/tests/target/expr.rs
+++ b/tests/target/expr.rs
@@ -89,13 +89,13 @@ fn bar() {
     }
 
     syntactically_correct(loop {
-            sup('?');
-        },
+                              sup('?');
+                          },
                           if cond {
-            0
-        } else {
-            1
-        });
+                              0
+                          } else {
+                              1
+                          });
 
     let third = ..10;
     let infi_range = ..;
diff --git a/tests/target/match.rs b/tests/target/match.rs
index eab8c604084..5cfbb5a7072 100644
--- a/tests/target/match.rs
+++ b/tests/target/match.rs
@@ -57,7 +57,7 @@ fn foo() {
 // Test that a match on an overflow line is laid out properly.
 fn main() {
     let sub_span =
-        match self.span.sub_span_after_keywooooooooooooooooooooord(use_item.span, keywords::As) {
+        match xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx {
             Some(sub_span) => Some(sub_span),
             None => sub_span,
         };
diff --git a/tests/target/string-lit.rs b/tests/target/string-lit.rs
index 21cdc199d6e..36f8f6a48f9 100644
--- a/tests/target/string-lit.rs
+++ b/tests/target/string-lit.rs
@@ -36,5 +36,5 @@ formatting"#;
     let unicode4 = "☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃\
                     ☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃";
 
-    "stuff"
+    "stuffin'"
 }