diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 2605bf87e6c9d..a0060452f661c 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -435,46 +435,78 @@ fn debug_assert_no_newlines(text: &str) { debug_assert!(!text.contains('\r'), "The content '{text}' contains an unsupported '\\r' line terminator character but text must only use line feeds '\\n' as line separator. Use '\\n' instead of '\\r' and '\\r\\n' to insert a line break in strings."); } -/// Pushes some content to the end of the current line +/// Pushes some content to the end of the current line. /// /// ## Examples /// -/// ``` -/// use ruff_formatter::{format}; +/// ```rust +/// use ruff_formatter::format; /// use ruff_formatter::prelude::*; /// -/// fn main() -> FormatResult<()> { +/// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// text("a"), -/// line_suffix(&text("c")), +/// line_suffix(&text("c"), 0), /// text("b") /// ])?; /// -/// assert_eq!( -/// "abc", -/// elements.print()?.as_code() -/// ); +/// assert_eq!("abc", elements.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +/// +/// Provide reserved width for the line suffix to include it during measurement. +/// ```rust +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatContext, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(10).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// // Breaks +/// group(&format_args![ +/// if_group_breaks(&text("(")), +/// soft_block_indent(&format_args![text("a"), line_suffix(&text(" // a comment"), 13)]), +/// if_group_breaks(&text(")")) +/// ]), +/// +/// // Fits +/// group(&format_args![ +/// if_group_breaks(&text("(")), +/// soft_block_indent(&format_args![text("a"), line_suffix(&text(" // a comment"), 0)]), +/// if_group_breaks(&text(")")) +/// ]), +/// ])?; +/// # assert_eq!("(\n\ta // a comment\n)a // a comment", elements.print()?.as_code()); /// # Ok(()) /// # } /// ``` #[inline] -pub fn line_suffix(inner: &Content) -> LineSuffix +pub fn line_suffix(inner: &Content, reserved_width: u32) -> LineSuffix where Content: Format, { LineSuffix { content: Argument::new(inner), + reserved_width, } } #[derive(Copy, Clone)] pub struct LineSuffix<'a, Context> { content: Argument<'a, Context>, + reserved_width: u32, } impl Format for LineSuffix<'_, Context> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - f.write_element(FormatElement::Tag(StartLineSuffix)); + f.write_element(FormatElement::Tag(StartLineSuffix { + reserved_width: self.reserved_width, + })); Arguments::from(&self.content).fmt(f)?; f.write_element(FormatElement::Tag(EndLineSuffix)); @@ -501,7 +533,7 @@ impl std::fmt::Debug for LineSuffix<'_, Context> { /// # fn main() -> FormatResult<()> { /// let elements = format!(SimpleFormatContext::default(), [ /// text("a"), -/// line_suffix(&text("c")), +/// line_suffix(&text("c"), 0), /// text("b"), /// line_suffix_boundary(), /// text("d") diff --git a/crates/ruff_formatter/src/format_element/document.rs b/crates/ruff_formatter/src/format_element/document.rs index 23b64645aa9c9..1d682218746da 100644 --- a/crates/ruff_formatter/src/format_element/document.rs +++ b/crates/ruff_formatter/src/format_element/document.rs @@ -459,8 +459,16 @@ impl Format> for &[FormatElement] { )?; } - StartLineSuffix => { - write!(f, [text("line_suffix(")])?; + StartLineSuffix { reserved_width } => { + write!( + f, + [ + text("line_suffix("), + dynamic_text(&std::format!("{reserved_width:?}"), None), + text(","), + space(), + ] + )?; } StartVerbatim(_) => { @@ -672,7 +680,9 @@ impl FormatElements for [FormatElement] { match element { // Line suffix // Ignore if any of its content breaks - FormatElement::Tag(Tag::StartLineSuffix | Tag::StartFitsExpanded(_)) => { + FormatElement::Tag( + Tag::StartLineSuffix { reserved_width: _ } | Tag::StartFitsExpanded(_), + ) => { ignore_depth += 1; } FormatElement::Tag(Tag::EndLineSuffix | Tag::EndFitsExpanded) => { diff --git a/crates/ruff_formatter/src/format_element/tag.rs b/crates/ruff_formatter/src/format_element/tag.rs index 91609b82c4800..da69013faa271 100644 --- a/crates/ruff_formatter/src/format_element/tag.rs +++ b/crates/ruff_formatter/src/format_element/tag.rs @@ -63,8 +63,11 @@ pub enum Tag { StartEntry, EndEntry, - /// Delay the printing of its content until the next line break - StartLineSuffix, + /// Delay the printing of its content until the next line break. Using reserved width will include + /// the associated line suffix during measurement. + StartLineSuffix { + reserved_width: u32, + }, EndLineSuffix, /// A token that tracks tokens/nodes that are printed as verbatim. @@ -96,7 +99,7 @@ impl Tag { | Tag::StartIndentIfGroupBreaks(_) | Tag::StartFill | Tag::StartEntry - | Tag::StartLineSuffix + | Tag::StartLineSuffix { reserved_width: _ } | Tag::StartVerbatim(_) | Tag::StartLabelled(_) | Tag::StartFitsExpanded(_) @@ -122,7 +125,7 @@ impl Tag { StartIndentIfGroupBreaks(_) | EndIndentIfGroupBreaks => TagKind::IndentIfGroupBreaks, StartFill | EndFill => TagKind::Fill, StartEntry | EndEntry => TagKind::Entry, - StartLineSuffix | EndLineSuffix => TagKind::LineSuffix, + StartLineSuffix { reserved_width: _ } | EndLineSuffix => TagKind::LineSuffix, StartVerbatim(_) | EndVerbatim => TagKind::Verbatim, StartLabelled(_) | EndLabelled => TagKind::Labelled, StartFitsExpanded { .. } | EndFitsExpanded => TagKind::FitsExpanded, diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index eef035faca318..9851a63c4600c 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -236,7 +236,8 @@ impl<'a> Printer<'a> { stack.push(TagKind::IndentIfGroupBreaks, args); } - FormatElement::Tag(StartLineSuffix) => { + FormatElement::Tag(StartLineSuffix { reserved_width }) => { + self.state.line_width += reserved_width; self.state .line_suffixes .extend(args, queue.iter_content(TagKind::LineSuffix)); @@ -1191,7 +1192,11 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { } } - FormatElement::Tag(StartLineSuffix) => { + FormatElement::Tag(StartLineSuffix { reserved_width }) => { + self.state.line_width += reserved_width; + if self.state.line_width > self.options().print_width.into() { + return Ok(Fits::No); + } self.queue.skip_content(TagKind::LineSuffix); self.state.has_line_suffix = true; } @@ -1727,12 +1732,42 @@ two lines`, text("]") ]), text(";"), - line_suffix(&format_args![space(), text("// trailing")]) + line_suffix(&format_args![space(), text("// trailing")], 0) ]); assert_eq!(printed.as_code(), "[1, 2, 3]; // trailing"); } + #[test] + fn line_suffix_with_reserved_width() { + let printed = format(&format_args![ + group(&format_args![ + text("["), + soft_block_indent(&format_with(|f| { + f.fill() + .entry( + &soft_line_break_or_space(), + &format_args!(text("1"), text(",")), + ) + .entry( + &soft_line_break_or_space(), + &format_args!(text("2"), text(",")), + ) + .entry( + &soft_line_break_or_space(), + &format_args!(text("3"), if_group_breaks(&text(","))), + ) + .finish() + })), + text("]") + ]), + text(";"), + line_suffix(&format_args![space(), text("// Using reserved width causes this content to not fit even though it's a line suffix element")], 93) + ]); + + assert_eq!(printed.as_code(), "[\n 1, 2, 3\n]; // Using reserved width causes this content to not fit even though it's a line suffix element"); + } + #[test] fn conditional_with_group_id_in_fits() { let content = format_with(|f| { diff --git a/crates/ruff_python_formatter/src/comments/format.rs b/crates/ruff_python_formatter/src/comments/format.rs index 0f0c65232bd01..4084d76a2873e 100644 --- a/crates/ruff_python_formatter/src/comments/format.rs +++ b/crates/ruff_python_formatter/src/comments/format.rs @@ -150,10 +150,13 @@ impl Format> for FormatTrailingComments<'_> { write!( f, [ - line_suffix(&format_args![ - empty_lines(lines_before_comment), - format_comment(trailing) - ]), + line_suffix( + &format_args![ + empty_lines(lines_before_comment), + format_comment(trailing) + ], + 0 + ), expand_parent() ] )?; @@ -161,7 +164,7 @@ impl Format> for FormatTrailingComments<'_> { write!( f, [ - line_suffix(&format_args![space(), space(), format_comment(trailing)]), + line_suffix(&format_args![space(), space(), format_comment(trailing)], 0), expand_parent() ] )?; @@ -266,7 +269,7 @@ impl Format> for FormatDanglingOpenParenthesisComments<'_> { write!( f, [ - line_suffix(&format_args!(space(), space(), format_comment(comment))), + line_suffix(&format_args!(space(), space(), format_comment(comment)), 0), expand_parent() ] )?;