--- a/crates/jrsonnet-formatter/src/children.rs +++ b/crates/jrsonnet-formatter/src/children.rs @@ -80,14 +80,19 @@ ) } -pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool { - count_newlines_before(tt) - + prev_inline - .map(count_newlines_after) - .unwrap_or_default() +// Should start, triggers multi-line render +pub fn should_start_with_newline( + prev_inline: Option<&ChildTrivia>, + tt: &ChildTrivia, +) -> (bool, bool) { + let count = + count_newlines_before(tt) + prev_inline.map(count_newlines_after).unwrap_or_default(); + ( // First for previous item end, second for current item - >= 2 + count >= 2, + count >= 1, + ) } fn count_newlines_before(tt: &ChildTrivia) -> usize { @@ -147,13 +152,14 @@ } else { mem::take(&mut next) }; + let (should_start_with_newline, triggers_multiline) = should_start_with_newline( + current_child.as_ref().map(|c| &c.inline_trivia), + &before_trivia, + ); let last_child = current_child.replace(Child { // First item should not start with newline - should_start_with_newline: had_some - && should_start_with_newline( - current_child.as_ref().map(|c| &c.inline_trivia), - &before_trivia, - ), + should_start_with_newline: had_some && should_start_with_newline, + triggers_multiline, before_trivia, value, inline_trivia: Vec::new(), @@ -203,7 +209,8 @@ should_start_with_newline: should_start_with_newline( current_child.as_ref().map(|c| &c.inline_trivia), &next, - ), + ) + .0, trivia: next, }; @@ -234,6 +241,9 @@ /// item2, /// ``` pub inline_trivia: ChildTrivia, + /// Is this child has whitespace that is considered significant, meaning + /// user has inserted it to split the value into multiple lines. + pub triggers_multiline: bool, } pub struct EndingComments { --- a/crates/jrsonnet-formatter/src/comments.rs +++ b/crates/jrsonnet-formatter/src/comments.rs @@ -72,7 +72,10 @@ if matches!(loc, CommentLocation::ItemInline) { p!(out, str(" ")); } - p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */") nl); + p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */")); + if matches!(loc, CommentLocation::AboveItem | CommentLocation::EndOfItems) { + p!(out, nl); + } } else if !lines.is_empty() { fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str { let offset = a --- a/crates/jrsonnet-formatter/src/lib.rs +++ b/crates/jrsonnet-formatter/src/lib.rs @@ -3,8 +3,10 @@ use children::{children_between, trivia_before}; use dprint_core::formatting::{ condition_helpers::is_multiple_lines, + condition_resolvers::true_resolver, ir_helpers::{new_line_group, with_indent}, - ConditionResolver, ConditionResolverContext, LineNumber, PrintItems, PrintOptions, + ConditionResolver, ConditionResolverContext, LineNumber, PrintItemPath, PrintItems, + PrintOptions, Signal, }; use hi_doc::{Formatting, SnippetBuilder}; use jrsonnet_rowan_parser::{ @@ -12,13 +14,13 @@ Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart, DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text, - UnaryOperator, Visibility, + Trivia, TriviaKind, UnaryOperator, Visibility, }, AstNode, AstToken as _, SyntaxToken, }; use crate::{ - children::{trivia_after, Child}, + children::{trivia_after, Child, EndingComments}, comments::{format_comments, CommentLocation}, }; @@ -27,6 +29,23 @@ #[cfg(test)] mod tests; +fn with_indent_eoi(cond: ConditionResolver, o: PrintItems, e: EndingComments) -> PrintItems { + let end_comments_items = { + let mut items = PrintItems::new(); + if e.should_start_with_newline { + p!(&mut items, nl); + } + format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items); + items.into_rc_path() + }; + let items = + new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path(); + + let indented = with_indent(pi!(@i; nl items(items.into()))); + + pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into()))) +} + pub trait Printable { fn print(&self, out: &mut PrintItems); } @@ -147,6 +166,10 @@ ($o:ident, $($t:tt)*) => { pi!(@s; $o: $($t)*) }; + (&mut $o:ident, $($t:tt)*) => { + let om = &mut $o; + pi!(@s; om: $($t)*) + }; } pub(crate) use p; pub(crate) use pi; @@ -461,22 +484,53 @@ p!(out, str("{ }")); return; } - p!(out, str("{") >i nl); - for (i, mem) in children.into_iter().enumerate() { - if mem.should_start_with_newline && i != 0 { - p!(out, nl); + + let source_is_multiline = children.iter().any(|c| c.triggers_multiline) + || end_comments.should_start_with_newline; + + let start = LineNumber::new("obj start line"); + let end = LineNumber::new("obj end line"); + let multi_line: ConditionResolver = if source_is_multiline { + true_resolver() + } else { + Rc::new(move |ctx: &mut ConditionResolverContext| { + is_multiple_lines(ctx, start, end) + }) + }; + + fn gen_members( + children: Vec>, + multi_line: ConditionResolver, + ) -> PrintItems { + let mut _out = PrintItems::new(); + let out = &mut _out; + let mut members = children.into_iter().peekable(); + while let Some(mem) = members.next() { + if mem.should_start_with_newline { + p!(out, nl); + } + format_comments(&mem.before_trivia, CommentLocation::AboveItem, out); + p!(out, { mem.value }); + let has_more = members.peek().is_some(); + if has_more { + p!(out, str(",")); + } else { + p!(out, if("trailing comma", multi_line, str(","))); + } + format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out); + p!(out, if_else("member separator", multi_line, nl)(sonl)); } - format_comments(&mem.before_trivia, CommentLocation::AboveItem, out); - p!(out, {mem.value} str(",")); - format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out); - p!(out, nl); + _out } - if end_comments.should_start_with_newline { - p!(out, nl); - } - format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out); - p!(out,