Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_formatter): IndentIfBreak IR element
Browse files Browse the repository at this point in the history
This PR adds a new `indent_if_break` IR element that has the same semantics as using `if_group_breaks` with `indent` and `if_group_fits_on_line`

```
write!(f, [
  group(&text("Head")).with_group_id(Some(id)),
  if_group_breaks(&indent(&text("indented"))).with_group_id(Some(id)),
  if_group_fits_on_line(&text("indented")).with_group_id(Some(id))
])
```

The advantage of the new IR is that it simplifies the debug output of the IR and removes the need to memoize the content.

## Tests

I added new doc tests and ran `cargo test` to verify that using `indent_if_break` doesn't introduce any regression on existing syntax.
  • Loading branch information
MichaReiser committed Aug 29, 2022
1 parent af4a788 commit ed37d1a
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 7 deletions.
120 changes: 120 additions & 0 deletions crates/rome_formatter/src/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1714,6 +1714,126 @@ impl<Context> std::fmt::Debug for IfGroupBreaks<'_, Context> {
}
}

/// Increases the indent level by one if the group with the specified id breaks.
///
/// This IR has the same semantics as using [if_group_breaks] and [if_group_fits_on_line] together.
///
/// ```
/// # use rome_formatter::prelude::*;
/// # use rome_formatter::write;
/// # let format = format_with(|f: &mut Formatter<SimpleFormatContext>| {
/// let id = f.group_id("head");
///
/// write!(f, [
/// group(&text("Head")).with_group_id(Some(id)),
/// if_group_breaks(&indent(&text("indented"))).with_group_id(Some(id)),
/// if_group_fits_on_line(&text("indented")).with_group_id(Some(id))
/// ])
///
/// # });
/// ```
///
/// If you want to indent some content if the enclosing group breaks, use [`indent`].
///
/// Use [if_group_breaks] or [if_group_fits_on_line] if the fitting and breaking content differs more than just the
/// indention level.
///
/// # Examples
///
/// Indent the body of an arrow function if the group wrapping the signature breaks:
/// ```
/// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write};
/// use rome_formatter::prelude::*;
///
/// let content = format_with(|f| {
/// let group_id = f.group_id("header");
///
/// write!(f, [
/// group(&text("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)),
/// indent_if_break(&format_args![hard_line_break(), text("a => b")], group_id)
/// ])
/// });
///
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
/// line_width: LineWidth::try_from(20).unwrap(),
/// ..SimpleFormatOptions::default()
/// });
///
/// let formatted = format!(context, [content]).unwrap();
///
/// assert_eq!(
/// "(aLongHeaderThatBreaksForSomeReason) =>\n\ta => b",
/// formatted.print().as_code()
/// );
/// ```
///
/// It doesn't add an indent if the group wrapping the signature doesn't break:
/// ```
/// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write};
/// use rome_formatter::prelude::*;
///
/// let content = format_with(|f| {
/// let group_id = f.group_id("header");
///
/// write!(f, [
/// group(&text("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)),
/// indent_if_break(&format_args![hard_line_break(), text("a => b")], group_id)
/// ])
/// });
///
/// let formatted = format!(SimpleFormatContext::default(), [content]).unwrap();
///
/// assert_eq!(
/// "(aLongHeaderThatBreaksForSomeReason) =>\na => b",
/// formatted.print().as_code()
/// );
/// ```
#[inline]
pub fn indent_if_break<Content, Context>(
content: &Content,
group_id: GroupId,
) -> IndentIfBreak<Context>
where
Content: Format<Context>,
{
IndentIfBreak {
group_id,
content: Argument::new(content),
}
}

#[derive(Copy, Clone)]
pub struct IndentIfBreak<'a, Context> {
content: Argument<'a, Context>,
group_id: GroupId,
}

impl<Context> Format<Context> for IndentIfBreak<'_, Context> {
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
let mut buffer = VecBuffer::new(f.state_mut());

buffer.write_fmt(Arguments::from(&self.content))?;

if buffer.is_empty() {
return Ok(());
}

let content = buffer.into_vec();
f.write_element(FormatElement::IndentIfBreaks(
format_element::IndentIfBreak::new(content.into_boxed_slice(), self.group_id),
))
}
}

impl<Context> std::fmt::Debug for IndentIfBreak<'_, Context> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IndentIfBreak")
.field("group_id", &self.group_id)
.field("content", &"{{content}}")
.finish()
}
}

/// Utility for formatting some content with an inline lambda function.
#[derive(Copy, Clone)]
pub struct FormatWith<Context, T> {
Expand Down
43 changes: 43 additions & 0 deletions crates/rome_formatter/src/format_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ pub enum FormatElement {
/// is printed on a single line or multiple lines. See [crate::if_group_breaks] for examples.
ConditionalGroupContent(ConditionalGroupContent),

/// Optimized version of [FormatElement::ConditionalGroupContent] for the case where some content
/// should be indented if the specified group breaks.
IndentIfBreaks(IndentIfBreak),

/// Concatenates multiple elements together. See [crate::Formatter::join_with] for examples.
List(List),

Expand Down Expand Up @@ -177,6 +181,7 @@ impl std::fmt::Debug for FormatElement {
content.fmt(fmt)
}
FormatElement::ConditionalGroupContent(content) => content.fmt(fmt),
FormatElement::IndentIfBreaks(content) => content.fmt(fmt),
FormatElement::List(content) => {
write!(fmt, "List ")?;
content.fmt(fmt)
Expand Down Expand Up @@ -509,6 +514,19 @@ impl std::fmt::Debug for Label {
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct IndentIfBreak {
pub(crate) content: Content,

pub(crate) group_id: GroupId,
}

impl IndentIfBreak {
pub fn new(content: Content, group_id: GroupId) -> Self {
Self { content, group_id }
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ConditionalGroupContent {
pub(crate) content: Content,
Expand Down Expand Up @@ -669,6 +687,7 @@ impl FormatElement {
FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty),
FormatElement::Group(Group { content, .. })
| FormatElement::ConditionalGroupContent(ConditionalGroupContent { content, .. })
| FormatElement::IndentIfBreaks(IndentIfBreak { content, .. })
| FormatElement::Comment(content)
| FormatElement::Fill(Fill { content, .. })
| FormatElement::Verbatim(Verbatim { content, .. })
Expand Down Expand Up @@ -889,6 +908,30 @@ impl Format<IrFormatContext> for FormatElement {

write!(f, [text(")")])
}
FormatElement::IndentIfBreaks(content) => {
write!(
f,
[
text("indent_if_break("),
group(&soft_block_indent(&format_args![
content.content.as_ref(),
text(","),
soft_line_break_or_space(),
text("{"),
group(&format_args![soft_line_indent_or_space(&format_args![
text("group-id:"),
space(),
dynamic_text(
&std::format!("{:?}", content.group_id),
TextSize::default()
),
soft_line_break_or_space()
])]),
text("}")
]))
]
)
}
FormatElement::ConditionalGroupContent(content) => {
match content.mode {
PrintMode::Flat => {
Expand Down
30 changes: 29 additions & 1 deletion crates/rome_formatter/src/printer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ mod printer_options;
pub use printer_options::*;

use crate::format_element::{
Align, ConditionalGroupContent, DedentMode, Group, LineMode, PrintMode, VerbatimKind,
Align, ConditionalGroupContent, DedentMode, Group, IndentIfBreak, LineMode, PrintMode,
VerbatimKind,
};
use crate::intersperse::Intersperse;
use crate::{FormatElement, GroupId, IndentStyle, Printed, SourceMarker, TextRange};
Expand Down Expand Up @@ -212,6 +213,18 @@ impl<'a> Printer<'a> {
}
}

FormatElement::IndentIfBreaks(IndentIfBreak { content, group_id }) => {
let group_mode = self.state.group_modes.unwrap_print_mode(*group_id, element);

match group_mode {
PrintMode::Flat => queue.extend_with_args(content.iter(), args),
PrintMode::Expanded => queue.extend_with_args(
content.iter(),
args.increment_indent_level(self.options.indent_style),
),
}
}

FormatElement::Line(line_mode) => {
if args.mode.is_flat()
&& matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace)
Expand Down Expand Up @@ -922,6 +935,21 @@ fn fits_element_on_line<'a, 'rest>(
}
}

FormatElement::IndentIfBreaks(indent) => {
let group_mode = state
.group_modes
.get_print_mode(indent.group_id)
.unwrap_or(args.mode);

match group_mode {
PrintMode::Flat => queue.extend(indent.content.iter(), args),
PrintMode::Expanded => queue.extend(
indent.content.iter(),
args.increment_indent_level(options.indent_style()),
),
}
}

FormatElement::List(list) => queue.extend(list.iter(), args),

FormatElement::Fill(fill) => queue.queue.0.extend(
Expand Down
5 changes: 2 additions & 3 deletions crates/rome_js_formatter/src/utils/assignment_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ impl Format<JsFormatContext> for JsAnyAssignmentLike {
}
});

let right = format_with(|f| self.write_right(f)).memoized();
let right = format_with(|f| self.write_right(f));

let inner_content = format_with(|f| {
write!(f, [left])?;
Expand All @@ -949,8 +949,7 @@ impl Format<JsFormatContext> for JsAnyAssignmentLike {
group(&indent(&soft_line_break_or_space()),)
.with_group_id(Some(group_id)),
line_suffix_boundary(),
if_group_breaks(&indent(&right)).with_group_id(Some(group_id)),
if_group_fits_on_line(&right).with_group_id(Some(group_id)),
indent_if_break(&right, group_id)
]
]
}
Expand Down
5 changes: 2 additions & 3 deletions crates/rome_js_formatter/src/utils/binary_like_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,12 @@ impl Format<JsFormatContext> for JsAnyBinaryLikeExpression {

if last_is_jsx {
// SAFETY: `last_is_jsx` is only true if parts is not empty
let jsx_element = parts.last().unwrap().memoized();
let jsx_element = parts.last().unwrap();
write!(
f,
[group(&format_args![
format_non_jsx_parts,
if_group_breaks(&block_indent(&jsx_element)).with_group_id(Some(group_id)),
if_group_fits_on_line(&jsx_element).with_group_id(Some(group_id))
indent_if_break(&jsx_element, group_id),
])]
)
} else {
Expand Down

0 comments on commit ed37d1a

Please sign in to comment.