feat(rome_formatter): `Dedent` IR
MODIFY
crates/rome_formatter/src/builders.rs
crates/rome_formatter/src/builders.rs
@@ -665,6 +665,79 @@ impl<Context> std::fmt::Debug for Indent<'_, Context> {
}
}
+/// It reduces the indention for the given content depending on the closest [indent] or [align] parent element.
+/// * [align] Undoes the spaces added by [align]
+/// * [indent] Reduces the indention level by one
+///
+/// This is a No-op if the indention level is zero.
+///
+/// # Examples
+///
+/// ```
+/// use rome_formatter::{format, format_args};
+/// use rome_formatter::prelude::*;
+///
+/// let block = format!(SimpleFormatContext::default(), [
+/// text("root"),
+/// align(2, &format_args![
+/// hard_line_break(),
+/// text("aligned"),
+/// dedent(&format_args![
+/// hard_line_break(),
+/// text("not aligned"),
+/// ]),
+/// dedent(&indent(&format_args![
+/// hard_line_break(),
+/// text("Indented, not aligned")
+/// ]))
+/// ]),
+/// dedent(&format_args![
+/// hard_line_break(),
+/// text("Dedent on root level is a no-op.")
+/// ])
+/// ]).unwrap();
+///
+/// assert_eq!(
+/// "root\n aligned\nnot aligned\n\tIndented, not aligned\nDedent on root level is a no-op.",
+/// block.print().as_code()
+/// );
+/// ```
+#[inline]
+pub fn dedent<Content, Context>(content: &Content) -> Dedent<Context>
+where
+ Content: Format<Context>,
+{
+ Dedent {
+ content: Argument::new(content),
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct Dedent<'a, Context> {
+ content: Argument<'a, Context>,
+}
+
+impl<Context> Format<Context> for Dedent<'_, 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::Dedent(content.into_boxed_slice()))
+ }
+}
+
+impl<Context> std::fmt::Debug for Dedent<'_, Context> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_tuple("Dedent").field(&"{{content}}").finish()
+ }
+}
+
/// Aligns its content by indenting the content by `count` spaces.
///
/// [align] is a variant of `[indent]` that indents its content by a specified number of spaces rather than

MODIFY
crates/rome_formatter/src/format_element.rs
crates/rome_formatter/src/format_element.rs
@@ -40,6 +40,10 @@ pub enum FormatElement {
/// Nesting (Aligns)[FormatElement::Align] has the effect that all except the most inner align are handled as (Indent)[FormatElement::Indent].
Align(Align),
+ /// Reduces the indention of the specified content by one. Reverse operation of `Indent` and can be used to
+ /// *undo` an `Align` for nested content.
+ Dedent(Content),
+
/// Creates a logical group where its content is either consistently printed:
/// * on a single line: Omitting `LineMode::Soft` line breaks and printing spaces for `LineMode::SoftOrSpace`
/// * on multiple lines: Printing all line breaks
@@ -157,6 +161,7 @@ impl std::fmt::Debug for FormatElement {
FormatElement::Space => write!(fmt, "Space"),
FormatElement::Line(content) => fmt.debug_tuple("Line").field(content).finish(),
FormatElement::Indent(content) => fmt.debug_tuple("Indent").field(content).finish(),
+ FormatElement::Dedent(content) => fmt.debug_tuple("Dedent").field(content).finish(),
FormatElement::Align(Align { count, content }) => fmt
.debug_struct("Align")
.field("count", count)
@@ -651,6 +656,7 @@ impl FormatElement {
| FormatElement::Verbatim(Verbatim { content, .. })
| FormatElement::Label(Label { content, .. })
| FormatElement::Indent(content)
+ | FormatElement::Dedent(content)
| FormatElement::Align(Align { content, .. }) => {
content.iter().any(FormatElement::will_break)
}
@@ -801,6 +807,9 @@ impl Format<IrFormatContext> for FormatElement {
FormatElement::Indent(content) => {
write!(f, [text("indent("), content.as_ref(), text(")")])
}
+ FormatElement::Dedent(content) => {
+ write!(f, [text("dedent("), content.as_ref(), text(")")])
+ }
FormatElement::Align(Align { content, count }) => {
write!(
f,

MODIFY
crates/rome_formatter/src/printer/mod.rs
crates/rome_formatter/src/printer/mod.rs
@@ -170,11 +170,15 @@ impl<'a> Printer<'a> {
}
FormatElement::Indent(content) => {
- queue.extend_with_args(content.iter(), args.indented());
+ queue.extend_with_args(content.iter(), args.increment_indent_level());
+ }
+
+ FormatElement::Dedent(content) => {
+ queue.extend_with_args(content.iter(), args.decrement_indent());
}
FormatElement::Align(Align { content, count }) => {
- queue.extend_with_args(content.iter(), args.aligned(*count))
+ queue.extend_with_args(content.iter(), args.set_indent_align(*count))
}
FormatElement::ConditionalGroupContent(ConditionalGroupContent {
@@ -551,13 +555,18 @@ impl PrintElementArgs {
}
}
- pub fn indented(mut self) -> Self {
+ pub fn increment_indent_level(mut self) -> Self {
self.indent = self.indent.increment_level();
self
}
- pub fn aligned(mut self, count: NonZeroU8) -> Self {
- self.indent = self.indent.add_align(count);
+ pub fn decrement_indent(mut self) -> Self {
+ self.indent = self.indent.decrement();
+ self
+ }
+
+ pub fn set_indent_align(mut self, count: NonZeroU8) -> Self {
+ self.indent = self.indent.set_align(count);
self
}
@@ -622,10 +631,22 @@ impl Indention {
}
}
+ /// Decrements the indent by one by:
+ /// * Reducing the level by one if this is [Indent::Level]
+ /// * Removing the `align` if this is [Indent::Align]
+ ///
+ /// No-op if the level is already zero.
+ fn decrement(self) -> Self {
+ match self {
+ Indention::Level(level) => Indention::Level(level.saturating_sub(1)),
+ Indention::Align { level, .. } => Indention::Level(level),
+ }
+ }
+
/// Adds an `align` of `count` spaces to the current indention.
///
/// It increments the `level` value if the current value is [Indent::IndentAlign].
- fn add_align(self, count: NonZeroU8) -> Self {
+ fn set_align(self, count: NonZeroU8) -> Self {
match self {
Indention::Level(indent_count) => Indention::Align {
level: indent_count,
@@ -857,10 +878,14 @@ fn fits_element_on_line<'a, 'rest>(
}
}
- FormatElement::Indent(content) => queue.extend(content.iter(), args.indented()),
+ FormatElement::Indent(content) => {
+ queue.extend(content.iter(), args.increment_indent_level())
+ }
+
+ FormatElement::Dedent(content) => queue.extend(content.iter(), args.decrement_indent()),
FormatElement::Align(Align { content, count }) => {
- queue.extend(content.iter(), args.aligned(*count))
+ queue.extend(content.iter(), args.set_indent_align(*count))
}
FormatElement::Group(group) => {

feat(rome_formatter): `Dedent` IR (#3007)