Skip to content

Commit d8506f6

Browse files
feat: support configurable chain indent with block-like parent
1 parent bf37e12 commit d8506f6

12 files changed

+430
-110
lines changed

Configurations.md

Lines changed: 88 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,6 @@ To enable unstable options, set `unstable_features = true` in `rustfmt.toml` or
1717

1818
Below you find a detailed visual guide on all the supported configuration options of rustfmt:
1919

20-
## `allow_chain_call_overflow`
21-
22-
Wrap elements of a call chain even if one or more lines exceed the `max_width`
23-
24-
- **Default value**: `false`
25-
- **Possible values**: `true`, `false`
26-
- **Stable**: No (tracking issue: ...)
27-
28-
#### `false` (default):
29-
30-
```rust
31-
fn example() {
32-
foo("This text is under the max_width limit, and shouldn't cause any problems on its own.").long("But this line is extra long, and doesn't fit within 100 max_width. 1234567890123456789").baz().collect().unwrap();
33-
}
34-
```
35-
36-
#### `true`:
37-
38-
```rust
39-
fn example() {
40-
foo("This text is under the max_width limit, and shouldn't cause any problems on its own.")
41-
.long("But this line is extra long, and doesn't fit within 100 max_width. 1234567890123456789")
42-
.baz()
43-
.collect()
44-
.unwrap();
45-
}
46-
```
47-
48-
4920
## `binop_separator`
5021

5122
Where to put a binary operator when a binary expression goes multiline.
@@ -417,6 +388,94 @@ fn example() {
417388
}
418389
```
419390

391+
## `chains_block_parent_indent_children`
392+
Determines whether to indent the child chain items of a chain that beings with a block-like parent element.
393+
394+
- **Default value**: `false`
395+
- **Possible values**: `true`, `false`
396+
- **Stable**: No (tracking issue: ...)
397+
398+
#### `false` (default):
399+
400+
```rust
401+
fn example() {
402+
StructA {
403+
test_test: some_value,
404+
}
405+
.foo()
406+
.bar()
407+
.baz()
408+
.qux();
409+
}
410+
```
411+
412+
#### `true`:
413+
414+
```rust
415+
fn example() {
416+
StructA {
417+
test_test: some_value,
418+
}
419+
.foo()
420+
.bar()
421+
.baz()
422+
.qux();
423+
}
424+
```
425+
426+
## `chains_block_parent_indent_parent_item`
427+
Determines whether block-like chain parents are indented
428+
429+
- **Default value**: `"Never"`
430+
- **Possible values**: `"Always"`, `"Never"`, `"OnlySimpleCalls"`
431+
- **Stable**: No (tracking issue: ...)
432+
433+
#### `"Never"` (default):
434+
435+
```rust
436+
#![rustfmt::skip]
437+
438+
fn example() {
439+
StructA {
440+
test_test: some_value,
441+
}
442+
.foo()
443+
.bar()
444+
.baz()
445+
.qux();
446+
447+
let very_very_very_very_very_very_very_very_very_long_var_name = 13;
448+
let all = very_very_very_very_very_long_fun_name(
449+
very_very_very_very_very_very_very_very_very_long_var_name,
450+
)
451+
.iter()
452+
.map(|x| x + very_very_very_very_very_very_long_var_name);
453+
454+
foo(|x| {
455+
// ....
456+
})
457+
.bar()
458+
.baz()
459+
.unwrap();
460+
}
461+
```
462+
463+
#### `"Always"`:
464+
465+
```rust
466+
#![rustfmt::skip]
467+
468+
fn example() {
469+
StructA {
470+
test_test: some_value,
471+
}
472+
.foo()
473+
.bar()
474+
.baz()
475+
.qux();
476+
}
477+
```
478+
420479
## `comment_width`
421480

422481
Maximum length of comments. No effect unless`wrap_comments = true`.

src/chains.rs

Lines changed: 104 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ use std::cmp::min;
6161
use syntax::source_map::{BytePos, Span};
6262
use syntax::{ast, ptr};
6363

64+
use crate::closures::rewrite_closure;
6465
use crate::comment::{rewrite_comment, CharClasses, FullCodeCharKind, RichChar};
65-
use crate::config::IndentStyle;
66+
use crate::config::{ChainsBlockParentElementIndent, IndentStyle};
6667
use crate::expr::rewrite_call;
6768
use crate::lists::extract_pre_comment;
6869
use crate::macros::convert_try_mac;
@@ -444,29 +445,23 @@ impl Rewrite for Chain {
444445
debug!("rewrite chain {:?} {:?}", self, shape);
445446

446447
let result = self.format(context, shape)?;
447-
if context.config.allow_chain_call_overflow() {
448-
match context.config.indent_style() {
449-
IndentStyle::Block => Some(result),
450-
IndentStyle::Visual => match wrap_str(result, context.config.max_width(), shape) {
451-
Some(r) => Some(r),
452-
None => {
453-
let new_shape = Shape::indented(
454-
shape.indent.block_indent(context.config),
455-
context.config,
456-
);
457-
if let Some(result) = self.format(context, new_shape) {
458-
let prefix = shape
459-
.indent
460-
.block_indent(context.config)
461-
.to_string_with_newline(context.config);
462-
return Some(format!("{}{}", prefix, result));
463-
}
464-
None
448+
match context.config.indent_style() {
449+
IndentStyle::Block => Some(result),
450+
IndentStyle::Visual => match wrap_str(result, context.config.max_width(), shape) {
451+
Some(r) => Some(r),
452+
None => {
453+
let new_shape =
454+
Shape::indented(shape.indent.block_indent(context.config), context.config);
455+
if let Some(result) = self.format(context, new_shape) {
456+
let prefix = shape
457+
.indent
458+
.block_indent(context.config)
459+
.to_string_with_newline(context.config);
460+
return Some(format!("{}{}", prefix, result));
465461
}
466-
},
467-
}
468-
} else {
469-
wrap_str(result, context.config.max_width(), shape)
462+
None
463+
}
464+
},
470465
}
471466
}
472467
}
@@ -713,58 +708,93 @@ impl<'a> ChainFormatterBlock<'a> {
713708
}
714709
}
715710

716-
fn format_chain_item(
717-
item: &ChainItem,
718-
context: &RewriteContext<'_>,
719-
shape: Shape,
720-
) -> (String, bool) {
711+
fn format_chain_item(item: &ChainItem, context: &RewriteContext<'_>, shape: Shape) -> String {
712+
fn contains_multiline_args(
713+
exprs: &Vec<ptr::P<ast::Expr>>,
714+
context: &RewriteContext<'_>,
715+
shape: Shape,
716+
) -> bool {
717+
exprs.iter().any(|e| match &e.kind {
718+
ast::ExprKind::Closure(
719+
ref capture,
720+
ref is_async,
721+
ref mobility,
722+
ref fn_decl,
723+
ref body,
724+
ref span,
725+
) => match rewrite_closure(
726+
*capture, is_async, *mobility, fn_decl, body, *span, &context, shape,
727+
) {
728+
Some(rewrite) => rewrite.contains('\n'),
729+
None => true,
730+
},
731+
ast::ExprKind::Lit(ref lit) => match lit.kind {
732+
ast::LitKind::Str(_, str_style) => match str_style {
733+
ast::StrStyle::Raw(..) => true,
734+
ast::StrStyle::Cooked => false,
735+
},
736+
_ => false,
737+
},
738+
ast::ExprKind::Struct(..) => match e.rewrite(&context, shape) {
739+
None => true,
740+
Some(rewrite) => !rewrite.contains('\n'),
741+
},
742+
ast::ExprKind::AddrOf(_, ref expr) => match expr.kind {
743+
ast::ExprKind::Struct(..) => match e.rewrite(&context, shape) {
744+
None => true,
745+
Some(rewrite) => !rewrite.contains('\n'),
746+
},
747+
_ => false,
748+
},
749+
_ => false,
750+
})
751+
}
752+
753+
fn is_simple_call_like(
754+
parent_expr_kind: &ast::ExprKind,
755+
context: &RewriteContext<'_>,
756+
shape: Shape,
757+
) -> bool {
758+
match parent_expr_kind {
759+
ast::ExprKind::MethodCall(_, ref exprs) | ast::ExprKind::Call(_, ref exprs) => {
760+
!contains_multiline_args(exprs, &context, shape)
761+
}
762+
_ => false,
763+
}
764+
}
765+
721766
let orig_result = item.rewrite(context, shape);
722767
let new_shape = match Shape::indented(shape.indent.block_indent(context.config), context.config)
723768
.sub_width(shape.rhs_overhead(context.config))
724769
{
725770
Some(shape) => shape,
726-
None => return (context.snippet(item.span).to_owned(), false),
771+
None => return context.snippet(item.span).to_owned(),
727772
};
728773
let next_line_result = item.rewrite(context, new_shape);
774+
let parent_indent_style = context.config.chains_block_parent_indent_parent_item();
729775
match (orig_result, next_line_result) {
730776
(Some(orig_result), _)
731777
if !orig_result.contains('\n')
732778
&& utils::unicode_str_width(&orig_result) <= shape.width =>
733779
{
734-
(orig_result, false)
780+
orig_result
735781
}
736782
(Some(orig_result), Some(next_line_result)) => match item.kind {
737-
ChainItemKind::Parent(ref expr) if next_line_result.contains('\n') => match expr.kind {
738-
ast::ExprKind::MethodCall(_, ref exprs) | ast::ExprKind::Call(_, ref exprs) => {
739-
let contains_non_indentable_args = exprs.iter().any(|e| match e.kind {
740-
ast::ExprKind::Closure(..)
741-
| ast::ExprKind::Lit(..)
742-
| ast::ExprKind::Struct(..) => true,
743-
ast::ExprKind::AddrOf(_, ref expr) => match expr.kind {
744-
ast::ExprKind::Struct(..) => true,
745-
_ => false,
746-
},
747-
_ => false,
748-
});
749-
if context.config.allow_chain_call_overflow() {
750-
if !contains_non_indentable_args {
751-
(next_line_result, true)
752-
} else {
753-
(orig_result, true)
754-
}
755-
} else {
756-
(orig_result, false)
783+
ChainItemKind::Parent(ref expr) => match parent_indent_style {
784+
ChainsBlockParentElementIndent::Never => orig_result,
785+
ChainsBlockParentElementIndent::Always => next_line_result,
786+
ChainsBlockParentElementIndent::OnlySimpleCalls => {
787+
match is_simple_call_like(&expr.kind, &context, shape) {
788+
true => next_line_result,
789+
false => orig_result,
757790
}
758791
}
759-
_ => (orig_result, context.config.allow_chain_call_overflow()),
760792
},
761-
_ => (orig_result, false),
793+
_ => orig_result,
762794
},
763-
(None, Some(next_line_result)) => (next_line_result, true),
764-
(Some(orig_result), None) => (orig_result, false),
765-
// This only occurs when the chain item exceeds the configured max_width.
766-
// Grab the original snippet so that the chain can still be wrapped.
767-
(None, None) => (context.snippet(item.span).to_owned(), false),
795+
(None, None) => context.snippet(item.span).to_owned(),
796+
(Some(orig_result), _) => orig_result,
797+
(None, Some(next_line_result)) => next_line_result,
768798
}
769799
}
770800

@@ -775,7 +805,8 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
775805
context: &RewriteContext<'_>,
776806
shape: Shape,
777807
) -> Option<()> {
778-
let (mut root_rewrite, forced_root_block) = format_chain_item(parent, context, shape);
808+
// let (mut root_rewrite, forced_root_block) = format_chain_item(parent, context, shape);
809+
let mut root_rewrite = format_chain_item(parent, context, shape);
779810

780811
let mut root_ends_with_block = parent.kind.is_block_like(context, &root_rewrite);
781812
let tab_width = context.config.tab_spaces().saturating_sub(shape.offset);
@@ -798,32 +829,34 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
798829
break;
799830
}
800831
}
832+
// println!("forced_root_block?: {}", &forced_root_block);
833+
// println!("root_ends_with_block?: {}", &root_ends_with_block);
801834
self.shared.rewrites.push(root_rewrite);
802-
self.root_ends_with_block = root_ends_with_block && !forced_root_block;
835+
// self.root_ends_with_block = root_ends_with_block && !forced_root_block;
836+
self.root_ends_with_block = root_ends_with_block;
803837

804838
Some(())
805839
}
806840

807841
fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> {
842+
let always_indent_children = context.config.chains_block_parent_indent_children();
843+
// let child_shape = if !self.root_ends_with_block || always_indent_children {
844+
845+
// } else {}
808846
Some(
809-
if self.root_ends_with_block {
810-
shape.block_indent(0)
811-
} else {
847+
if !self.root_ends_with_block || always_indent_children {
812848
shape.block_indent(context.config.tab_spaces())
849+
} else {
850+
shape.block_indent(0)
813851
}
814852
.with_max_width(context.config),
815853
)
816854
}
817855

818856
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
819-
let allow_chain_call_overflow = context.config.allow_chain_call_overflow();
820857
for item in &self.shared.children[..self.shared.children.len() - 1] {
821-
let rewrite = if allow_chain_call_overflow {
822-
item.rewrite(context, child_shape)
823-
.unwrap_or_else(|| context.snippet(item.span).to_owned())
824-
} else {
825-
item.rewrite(context, child_shape)?
826-
};
858+
// let (rewrite, _) = format_chain_item(&item, &context, child_shape);
859+
let rewrite = format_chain_item(&item, &context, child_shape);
827860
self.shared.rewrites.push(rewrite);
828861
}
829862
Some(())
@@ -916,14 +949,9 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
916949
}
917950

918951
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
919-
let allow_chain_call_overflow = context.config.allow_chain_call_overflow();
920952
for item in &self.shared.children[..self.shared.children.len() - 1] {
921-
let rewrite = if allow_chain_call_overflow {
922-
item.rewrite(context, child_shape)
923-
.unwrap_or_else(|| context.snippet(item.span).to_owned())
924-
} else {
925-
item.rewrite(context, child_shape)?
926-
};
953+
// let (rewrite, _) = format_chain_item(&item, &context, child_shape);
954+
let rewrite = format_chain_item(&item, &context, child_shape);
927955
self.shared.rewrites.push(rewrite);
928956
}
929957
Some(())

0 commit comments

Comments
 (0)