Support flexible pipe operator formatting
Rules are now as follows: - If a pipeline contains a newline, then the entire pipeline is formatted over multiple lines. - If it doesn't, then it's formatted as a single-line UNLESS it cannot fit; in which case, we fallback to multiline again.
This commit is contained in:
parent
ae981403c6
commit
1311d9bd27
|
@ -456,6 +456,7 @@ pub enum UntypedExpr {
|
||||||
|
|
||||||
PipeLine {
|
PipeLine {
|
||||||
expressions: Vec1<Self>,
|
expressions: Vec1<Self>,
|
||||||
|
one_liner: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
Assignment {
|
Assignment {
|
||||||
|
|
|
@ -15,7 +15,9 @@ use crate::{
|
||||||
docvec,
|
docvec,
|
||||||
expr::{UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
expr::{UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
|
||||||
parser::extra::{Comment, ModuleExtra},
|
parser::extra::{Comment, ModuleExtra},
|
||||||
pretty::{break_, concat, flex_break, join, line, lines, nil, Document, Documentable},
|
pretty::{
|
||||||
|
break_, concat, flex_break, join, line, lines, nil, prebreak, Document, Documentable,
|
||||||
|
},
|
||||||
tipo::{self, Type},
|
tipo::{self, Type},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -683,7 +685,10 @@ impl<'comments> Formatter<'comments> {
|
||||||
..
|
..
|
||||||
} => self.if_expr(branches, final_else),
|
} => self.if_expr(branches, final_else),
|
||||||
|
|
||||||
UntypedExpr::PipeLine { expressions, .. } => self.pipeline(expressions),
|
UntypedExpr::PipeLine {
|
||||||
|
expressions,
|
||||||
|
one_liner,
|
||||||
|
} => self.pipeline(expressions, *one_liner),
|
||||||
|
|
||||||
UntypedExpr::Int { value, .. } => value.to_doc(),
|
UntypedExpr::Int { value, .. } => value.to_doc(),
|
||||||
|
|
||||||
|
@ -1013,7 +1018,11 @@ impl<'comments> Formatter<'comments> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pipeline<'a>(&mut self, expressions: &'a Vec1<UntypedExpr>) -> Document<'a> {
|
fn pipeline<'a>(
|
||||||
|
&mut self,
|
||||||
|
expressions: &'a Vec1<UntypedExpr>,
|
||||||
|
one_liner: bool,
|
||||||
|
) -> Document<'a> {
|
||||||
let mut docs = Vec::with_capacity(expressions.len() * 3);
|
let mut docs = Vec::with_capacity(expressions.len() * 3);
|
||||||
let first = expressions.first();
|
let first = expressions.first();
|
||||||
let first_precedence = first.binop_precedence();
|
let first_precedence = first.binop_precedence();
|
||||||
|
@ -1031,13 +1040,30 @@ impl<'comments> Formatter<'comments> {
|
||||||
|
|
||||||
_ => self.wrap_expr(expr),
|
_ => self.wrap_expr(expr),
|
||||||
};
|
};
|
||||||
docs.push(line());
|
|
||||||
docs.push(commented("|> ".to_doc(), comments));
|
let expr = self.operator_side(doc, 4, expr.binop_precedence());
|
||||||
docs.push(self.operator_side(doc, 4, expr.binop_precedence()));
|
|
||||||
|
match printed_comments(comments, true) {
|
||||||
|
None => {
|
||||||
|
let pipe = prebreak("|> ", " |> ").nest(2);
|
||||||
|
docs.push(pipe.append(expr));
|
||||||
|
}
|
||||||
|
Some(comments) => {
|
||||||
|
let pipe = prebreak("|> ", "|> ");
|
||||||
|
docs.push(
|
||||||
|
" ".to_doc()
|
||||||
|
.append(comments.nest(2).append(pipe.append(expr).group())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if one_liner {
|
||||||
|
docs.to_doc().group()
|
||||||
|
} else {
|
||||||
docs.to_doc().force_break()
|
docs.to_doc().force_break()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn pipe_capture_right_hand_side<'a>(&mut self, fun: &'a UntypedExpr) -> Document<'a> {
|
fn pipe_capture_right_hand_side<'a>(&mut self, fun: &'a UntypedExpr) -> Document<'a> {
|
||||||
let (fun, args) = match fun {
|
let (fun, args) = match fun {
|
||||||
|
|
|
@ -54,7 +54,6 @@ pub fn module(
|
||||||
extra.empty_lines.push(span.start);
|
extra.empty_lines.push(span.start);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Token::NewLine => None,
|
|
||||||
Token::LeftParen => {
|
Token::LeftParen => {
|
||||||
if previous_is_newline {
|
if previous_is_newline {
|
||||||
Some((Token::NewLineLeftParen, *span))
|
Some((Token::NewLineLeftParen, *span))
|
||||||
|
@ -62,6 +61,14 @@ pub fn module(
|
||||||
Some((Token::LeftParen, *span))
|
Some((Token::LeftParen, *span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Token::Pipe => {
|
||||||
|
if previous_is_newline {
|
||||||
|
Some((Token::NewLinePipe, *span))
|
||||||
|
} else {
|
||||||
|
Some((Token::Pipe, *span))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Token::NewLine => None,
|
||||||
_ => Some((token, *span)),
|
_ => Some((token, *span)),
|
||||||
};
|
};
|
||||||
previous_is_newline = current_is_newline;
|
previous_is_newline = current_is_newline;
|
||||||
|
@ -1309,17 +1316,30 @@ pub fn expr_parser(
|
||||||
// Pipeline
|
// Pipeline
|
||||||
disjunction
|
disjunction
|
||||||
.clone()
|
.clone()
|
||||||
.then(just(Token::Pipe).ignore_then(disjunction).repeated())
|
.then(
|
||||||
.foldl(|l, r| {
|
choice((just(Token::Pipe), just(Token::NewLinePipe)))
|
||||||
let expressions = if let expr::UntypedExpr::PipeLine { mut expressions } = l {
|
.then(disjunction)
|
||||||
|
.repeated(),
|
||||||
|
)
|
||||||
|
.foldl(|l, (pipe, r)| {
|
||||||
|
if let expr::UntypedExpr::PipeLine {
|
||||||
|
mut expressions,
|
||||||
|
one_liner,
|
||||||
|
} = l
|
||||||
|
{
|
||||||
expressions.push(r);
|
expressions.push(r);
|
||||||
expressions
|
return expr::UntypedExpr::PipeLine {
|
||||||
} else {
|
expressions,
|
||||||
|
one_liner,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let mut expressions = Vec1::new(l);
|
let mut expressions = Vec1::new(l);
|
||||||
expressions.push(r);
|
expressions.push(r);
|
||||||
expressions
|
expr::UntypedExpr::PipeLine {
|
||||||
};
|
expressions,
|
||||||
expr::UntypedExpr::PipeLine { expressions }
|
one_liner: pipe != Token::NewLinePipe,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ pub enum Token {
|
||||||
Vbar, // '|'
|
Vbar, // '|'
|
||||||
VbarVbar, // '||'
|
VbarVbar, // '||'
|
||||||
AmperAmper, // '&&'
|
AmperAmper, // '&&'
|
||||||
|
NewLinePipe, // '↳|>'
|
||||||
Pipe, // '|>'
|
Pipe, // '|>'
|
||||||
Dot, // '.'
|
Dot, // '.'
|
||||||
RArrow, // '->'
|
RArrow, // '->'
|
||||||
|
@ -134,6 +135,7 @@ impl fmt::Display for Token {
|
||||||
Token::Vbar => "|",
|
Token::Vbar => "|",
|
||||||
Token::VbarVbar => "||",
|
Token::VbarVbar => "||",
|
||||||
Token::AmperAmper => "&&",
|
Token::AmperAmper => "&&",
|
||||||
|
Token::NewLinePipe => "↳|>",
|
||||||
Token::Pipe => "|>",
|
Token::Pipe => "|>",
|
||||||
Token::Dot => ".",
|
Token::Dot => ".",
|
||||||
Token::RArrow => "->",
|
Token::RArrow => "->",
|
||||||
|
|
|
@ -139,6 +139,7 @@ pub enum Document<'a> {
|
||||||
Break {
|
Break {
|
||||||
broken: &'a str,
|
broken: &'a str,
|
||||||
unbroken: &'a str,
|
unbroken: &'a str,
|
||||||
|
break_first: bool,
|
||||||
kind: BreakKind,
|
kind: BreakKind,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -207,6 +208,7 @@ fn fits(
|
||||||
Document::Group(doc) => docs.push_front((indent, Mode::Unbroken, doc)),
|
Document::Group(doc) => docs.push_front((indent, Mode::Unbroken, doc)),
|
||||||
|
|
||||||
Document::Str(s) => limit -= s.len() as isize,
|
Document::Str(s) => limit -= s.len() as isize,
|
||||||
|
|
||||||
Document::String(s) => limit -= s.len() as isize,
|
Document::String(s) => limit -= s.len() as isize,
|
||||||
|
|
||||||
Document::Break { unbroken, .. } => match mode {
|
Document::Break { unbroken, .. } => match mode {
|
||||||
|
@ -255,31 +257,39 @@ fn format(
|
||||||
Document::Break {
|
Document::Break {
|
||||||
broken,
|
broken,
|
||||||
unbroken,
|
unbroken,
|
||||||
|
break_first,
|
||||||
kind: BreakKind::Flex,
|
kind: BreakKind::Flex,
|
||||||
} => {
|
} => {
|
||||||
let unbroken_width = width + unbroken.len() as isize;
|
let unbroken_width = width + unbroken.len() as isize;
|
||||||
|
|
||||||
if fits(limit, unbroken_width, docs.clone()) {
|
if fits(limit, unbroken_width, docs.clone()) {
|
||||||
writer.push_str(unbroken);
|
writer.push_str(unbroken);
|
||||||
|
|
||||||
width = unbroken_width;
|
width = unbroken_width;
|
||||||
} else {
|
continue;
|
||||||
writer.push_str(broken);
|
}
|
||||||
|
|
||||||
|
if *break_first {
|
||||||
writer.push('\n');
|
writer.push('\n');
|
||||||
|
|
||||||
for _ in 0..indent {
|
for _ in 0..indent {
|
||||||
writer.push(' ');
|
writer.push(' ');
|
||||||
}
|
}
|
||||||
|
writer.push_str(broken);
|
||||||
|
} else {
|
||||||
|
writer.push_str(broken);
|
||||||
|
writer.push('\n');
|
||||||
|
for _ in 0..indent {
|
||||||
|
writer.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
width = indent;
|
width = indent;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Strict breaks are conditional to the mode
|
// Strict breaks are conditional to the mode
|
||||||
Document::Break {
|
Document::Break {
|
||||||
broken,
|
broken,
|
||||||
unbroken,
|
unbroken,
|
||||||
|
break_first,
|
||||||
kind: BreakKind::Strict,
|
kind: BreakKind::Strict,
|
||||||
} => {
|
} => {
|
||||||
width = match mode {
|
width = match mode {
|
||||||
|
@ -289,6 +299,18 @@ fn format(
|
||||||
width + unbroken.len() as isize
|
width + unbroken.len() as isize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mode::Broken | Mode::ForcedBroken if *break_first => {
|
||||||
|
writer.push('\n');
|
||||||
|
|
||||||
|
for _ in 0..indent {
|
||||||
|
writer.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.push_str(broken);
|
||||||
|
|
||||||
|
indent
|
||||||
|
}
|
||||||
|
|
||||||
Mode::Broken | Mode::ForcedBroken => {
|
Mode::Broken | Mode::ForcedBroken => {
|
||||||
writer.push_str(broken);
|
writer.push_str(broken);
|
||||||
|
|
||||||
|
@ -361,6 +383,16 @@ pub fn break_<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
|
||||||
broken,
|
broken,
|
||||||
unbroken,
|
unbroken,
|
||||||
kind: BreakKind::Strict,
|
kind: BreakKind::Strict,
|
||||||
|
break_first: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prebreak<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
|
||||||
|
Document::Break {
|
||||||
|
broken,
|
||||||
|
unbroken,
|
||||||
|
kind: BreakKind::Strict,
|
||||||
|
break_first: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,6 +401,16 @@ pub fn flex_break<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
|
||||||
broken,
|
broken,
|
||||||
unbroken,
|
unbroken,
|
||||||
kind: BreakKind::Flex,
|
kind: BreakKind::Flex,
|
||||||
|
break_first: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flex_prebreak<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
|
||||||
|
Document::Break {
|
||||||
|
broken,
|
||||||
|
unbroken,
|
||||||
|
kind: BreakKind::Flex,
|
||||||
|
break_first: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -560,3 +560,50 @@ fn test_unicode() {
|
||||||
|
|
||||||
assert_fmt(src, src);
|
assert_fmt(src, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preserve_pipe() {
|
||||||
|
let src = indoc! { r#"
|
||||||
|
fn foo() {
|
||||||
|
a |> b |> c |> d
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
a
|
||||||
|
// Foo
|
||||||
|
|> b// Some comments
|
||||||
|
|> c
|
||||||
|
|> d
|
||||||
|
}
|
||||||
|
|
||||||
|
fn baz() {
|
||||||
|
// Commented
|
||||||
|
however |> it_automatically_breaks |> into_multiple_lines |> anytime_when |> it_is_too_long // What?
|
||||||
|
}
|
||||||
|
"#};
|
||||||
|
|
||||||
|
let expected = indoc! { r#"
|
||||||
|
fn foo() {
|
||||||
|
a |> b |> c |> d
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
a // Foo
|
||||||
|
|> b // Some comments
|
||||||
|
|> c
|
||||||
|
|> d
|
||||||
|
}
|
||||||
|
|
||||||
|
fn baz() {
|
||||||
|
// Commented
|
||||||
|
however
|
||||||
|
|> it_automatically_breaks
|
||||||
|
|> into_multiple_lines
|
||||||
|
|> anytime_when
|
||||||
|
|> it_is_too_long
|
||||||
|
// What?
|
||||||
|
}
|
||||||
|
"#};
|
||||||
|
|
||||||
|
assert_fmt(src, expected);
|
||||||
|
}
|
||||||
|
|
|
@ -545,6 +545,7 @@ fn pipeline() {
|
||||||
tipo: (),
|
tipo: (),
|
||||||
}],
|
}],
|
||||||
body: expr::UntypedExpr::PipeLine {
|
body: expr::UntypedExpr::PipeLine {
|
||||||
|
one_liner: false,
|
||||||
expressions: vec1::vec1![
|
expressions: vec1::vec1![
|
||||||
expr::UntypedExpr::BinOp {
|
expr::UntypedExpr::BinOp {
|
||||||
location: Span::new((), 31..36),
|
location: Span::new((), 31..36),
|
||||||
|
@ -717,6 +718,7 @@ fn let_bindings() {
|
||||||
expr::UntypedExpr::Assignment {
|
expr::UntypedExpr::Assignment {
|
||||||
location: Span::new((), 23..70),
|
location: Span::new((), 23..70),
|
||||||
value: Box::new(expr::UntypedExpr::PipeLine {
|
value: Box::new(expr::UntypedExpr::PipeLine {
|
||||||
|
one_liner: false,
|
||||||
expressions: vec1::vec1![
|
expressions: vec1::vec1![
|
||||||
expr::UntypedExpr::BinOp {
|
expr::UntypedExpr::BinOp {
|
||||||
location: Span::new((), 35..40),
|
location: Span::new((), 35..40),
|
||||||
|
@ -1105,6 +1107,7 @@ fn anonymous_function() {
|
||||||
annotation: None,
|
annotation: None,
|
||||||
},
|
},
|
||||||
expr::UntypedExpr::PipeLine {
|
expr::UntypedExpr::PipeLine {
|
||||||
|
one_liner: true,
|
||||||
expressions: vec1::vec1![
|
expressions: vec1::vec1![
|
||||||
expr::UntypedExpr::Int {
|
expr::UntypedExpr::Int {
|
||||||
location: Span::new((), 71..72),
|
location: Span::new((), 71..72),
|
||||||
|
|
|
@ -274,7 +274,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
location, value, ..
|
location, value, ..
|
||||||
} => Ok(self.infer_string(value, location)),
|
} => Ok(self.infer_string(value, location)),
|
||||||
|
|
||||||
UntypedExpr::PipeLine { expressions } => self.infer_pipeline(expressions),
|
UntypedExpr::PipeLine { expressions, .. } => self.infer_pipeline(expressions),
|
||||||
|
|
||||||
UntypedExpr::Fn {
|
UntypedExpr::Fn {
|
||||||
location,
|
location,
|
||||||
|
|
Loading…
Reference in New Issue