mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 10:22:24 +00:00
2599 lines
78 KiB
Rust
2599 lines
78 KiB
Rust
use std::cell::Cell;
|
|
use std::marker::PhantomData;
|
|
use std::num::NonZeroU8;
|
|
|
|
use ruff_text_size::TextRange;
|
|
#[allow(clippy::enum_glob_use)]
|
|
use Tag::*;
|
|
|
|
use crate::format_element::tag::{Condition, Tag};
|
|
use crate::prelude::tag::{DedentMode, GroupMode, LabelId};
|
|
use crate::prelude::*;
|
|
use crate::{format_element, write, Argument, Arguments, FormatContext, GroupId, TextSize};
|
|
use crate::{Buffer, VecBuffer};
|
|
|
|
/// A line break that only gets printed if the enclosing `Group` doesn't fit on a single line.
|
|
/// It's omitted if the enclosing `Group` fits on a single line.
|
|
/// A soft line break is identical to a hard line break when not enclosed inside of a `Group`.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// Soft line breaks are omitted if the enclosing `Group` fits on a single line
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![token("a,"), soft_line_break(), token("b")])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a,b",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
/// See [`soft_line_break_or_space`] if you want to insert a space between the elements if the enclosing
|
|
/// `Group` fits on a single line.
|
|
///
|
|
/// Soft line breaks are emitted if the enclosing `Group` doesn't fit on a single line
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, LineWidth, 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, [
|
|
/// group(&format_args![
|
|
/// token("a long word,"),
|
|
/// soft_line_break(),
|
|
/// token("so that the group doesn't fit on a single line"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a long word,\nso that the group doesn't fit on a single line",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub const fn soft_line_break() -> Line {
|
|
Line::new(LineMode::Soft)
|
|
}
|
|
|
|
/// A forced line break that are always printed. A hard line break forces any enclosing `Group`
|
|
/// to be printed over multiple lines.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// It forces a line break, even if the enclosing `Group` would otherwise fit on a single line.
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("a,"),
|
|
/// hard_line_break(),
|
|
/// token("b"),
|
|
/// hard_line_break()
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a,\nb\n",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub const fn hard_line_break() -> Line {
|
|
Line::new(LineMode::Hard)
|
|
}
|
|
|
|
/// A forced empty line. An empty line inserts enough line breaks in the output for
|
|
/// the previous and next element to be separated by an empty line.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// fn main() -> FormatResult<()> {
|
|
/// let elements = format!(
|
|
/// SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("a,"),
|
|
/// empty_line(),
|
|
/// token("b"),
|
|
/// empty_line()
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a,\n\nb\n\n",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub const fn empty_line() -> Line {
|
|
Line::new(LineMode::Empty)
|
|
}
|
|
|
|
/// A line break if the enclosing `Group` doesn't fit on a single line, a space otherwise.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// The line breaks are emitted as spaces if the enclosing `Group` fits on a a single line:
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("a,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("b"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a, b",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// The printer breaks the lines if the enclosing `Group` doesn't fit on a single line:
|
|
/// ```
|
|
/// use ruff_formatter::{format_args, format, LineWidth, 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, [
|
|
/// group(&format_args![
|
|
/// token("a long word,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("so that the group doesn't fit on a single line"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a long word,\nso that the group doesn't fit on a single line",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub const fn soft_line_break_or_space() -> Line {
|
|
Line::new(LineMode::SoftOrSpace)
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
|
pub struct Line {
|
|
mode: LineMode,
|
|
}
|
|
|
|
impl Line {
|
|
const fn new(mode: LineMode) -> Self {
|
|
Self { mode }
|
|
}
|
|
}
|
|
|
|
impl<Context> Format<Context> for Line {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Line(self.mode));
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Line {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_tuple("Line").field(&self.mode).finish()
|
|
}
|
|
}
|
|
|
|
/// Creates a token that gets written as is to the output. A token must be ASCII only and is not allowed
|
|
/// to contain any line breaks or tab characters.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::format;
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [token("Hello World")])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "Hello World",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Printing a string literal as a literal requires that the string literal is properly escaped and
|
|
/// enclosed in quotes (depending on the target language).
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::format;
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// // the tab must be encoded as \\t to not literally print a tab character ("Hello{tab}World" vs "Hello\tWorld")
|
|
/// let elements = format!(SimpleFormatContext::default(), [token("\"Hello\\tWorld\"")])?;
|
|
///
|
|
/// assert_eq!(r#""Hello\tWorld""#, elements.print()?.as_code());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn token(text: &'static str) -> Token {
|
|
debug_assert!(text.is_ascii(), "Token must be ASCII text only");
|
|
debug_assert!(
|
|
!text.contains(['\n', '\r', '\t']),
|
|
"A token should not contain any newlines or tab characters"
|
|
);
|
|
|
|
Token { text }
|
|
}
|
|
|
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
pub struct Token {
|
|
text: &'static str,
|
|
}
|
|
|
|
impl<Context> Format<Context> for Token {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Token { text: self.text });
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Token {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
std::write!(f, "Token({})", self.text)
|
|
}
|
|
}
|
|
|
|
/// Creates a source map entry from the passed source `position` to the position in the formatted output.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::format;
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// // the tab must be encoded as \\t to not literally print a tab character ("Hello{tab}World" vs "Hello\tWorld")
|
|
/// use ruff_text_size::TextSize;
|
|
/// use ruff_formatter::SourceMarker;
|
|
///
|
|
///
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// source_position(TextSize::new(0)),
|
|
/// token("\"Hello "),
|
|
/// source_position(TextSize::new(8)),
|
|
/// token("'Ruff'"),
|
|
/// source_position(TextSize::new(14)),
|
|
/// token("\""),
|
|
/// source_position(TextSize::new(20))
|
|
/// ])?;
|
|
///
|
|
/// let printed = elements.print()?;
|
|
///
|
|
/// assert_eq!(printed.as_code(), r#""Hello 'Ruff'""#);
|
|
/// assert_eq!(printed.sourcemap(), [
|
|
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(0) },
|
|
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(7) },
|
|
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(7) },
|
|
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(13) },
|
|
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(13) },
|
|
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(14) },
|
|
/// SourceMarker { source: TextSize::new(20), dest: TextSize::new(14) },
|
|
/// ]);
|
|
///
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub const fn source_position(position: TextSize) -> SourcePosition {
|
|
SourcePosition(position)
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
|
pub struct SourcePosition(TextSize);
|
|
|
|
impl<Context> Format<Context> for SourcePosition {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::SourcePosition(self.0));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Creates a text from a dynamic string with its optional start-position in the source document.
|
|
/// This is done by allocating a new string internally.
|
|
pub fn text(text: &str, position: Option<TextSize>) -> Text {
|
|
debug_assert_no_newlines(text);
|
|
|
|
Text { text, position }
|
|
}
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
pub struct Text<'a> {
|
|
text: &'a str,
|
|
position: Option<TextSize>,
|
|
}
|
|
|
|
impl<Context> Format<Context> for Text<'_> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
if let Some(source_position) = self.position {
|
|
f.write_element(FormatElement::SourcePosition(source_position));
|
|
}
|
|
|
|
f.write_element(FormatElement::Text {
|
|
text: self.text.to_string().into_boxed_str(),
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Text<'_> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
std::write!(f, "Text({})", self.text)
|
|
}
|
|
}
|
|
|
|
/// Emits a text as it is written in the source document. Optimized to avoid allocations.
|
|
pub const fn source_text_slice(
|
|
range: TextRange,
|
|
newlines: ContainsNewlines,
|
|
) -> SourceTextSliceBuilder {
|
|
SourceTextSliceBuilder {
|
|
range,
|
|
new_lines: newlines,
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
|
pub enum ContainsNewlines {
|
|
/// The string contains newline characters
|
|
Yes,
|
|
/// The string contains no newline characters
|
|
No,
|
|
|
|
/// The string may contain newline characters, search the string to determine if there are any newlines.
|
|
Detect,
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Debug)]
|
|
pub struct SourceTextSliceBuilder {
|
|
range: TextRange,
|
|
new_lines: ContainsNewlines,
|
|
}
|
|
|
|
impl<Context> Format<Context> for SourceTextSliceBuilder
|
|
where
|
|
Context: FormatContext,
|
|
{
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
let source_code = f.context().source_code();
|
|
let slice = source_code.slice(self.range);
|
|
debug_assert_no_newlines(slice.text(source_code));
|
|
|
|
let contains_newlines = match self.new_lines {
|
|
ContainsNewlines::Yes => {
|
|
debug_assert!(
|
|
slice.text(source_code).contains('\n'),
|
|
"Text contains no new line characters but the caller specified that it does."
|
|
);
|
|
true
|
|
}
|
|
ContainsNewlines::No => {
|
|
debug_assert!(
|
|
!slice.text(source_code).contains('\n'),
|
|
"Text contains new line characters but the caller specified that it does not."
|
|
);
|
|
false
|
|
}
|
|
ContainsNewlines::Detect => slice.text(source_code).contains('\n'),
|
|
};
|
|
|
|
f.write_element(FormatElement::SourceCodeSlice {
|
|
slice,
|
|
contains_newlines,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
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.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```rust
|
|
/// use ruff_formatter::format;
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// token("a"),
|
|
/// line_suffix(&token("c"), 0),
|
|
/// token("b")
|
|
/// ])?;
|
|
///
|
|
/// 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(&token("(")),
|
|
/// soft_block_indent(&format_args![token("a"), line_suffix(&token(" // a comment"), 13)]),
|
|
/// if_group_breaks(&token(")"))
|
|
/// ]),
|
|
///
|
|
/// // Fits
|
|
/// group(&format_args![
|
|
/// if_group_breaks(&token("(")),
|
|
/// soft_block_indent(&format_args![token("a"), line_suffix(&token(" // a comment"), 0)]),
|
|
/// if_group_breaks(&token(")"))
|
|
/// ]),
|
|
/// ])?;
|
|
/// # assert_eq!("(\n\ta // a comment\n)a // a comment", elements.print()?.as_code());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn line_suffix<Content, Context>(inner: &Content, reserved_width: u32) -> LineSuffix<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
LineSuffix {
|
|
content: Argument::new(inner),
|
|
reserved_width,
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct LineSuffix<'a, Context> {
|
|
content: Argument<'a, Context>,
|
|
reserved_width: u32,
|
|
}
|
|
|
|
impl<Context> Format<Context> for LineSuffix<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartLineSuffix {
|
|
reserved_width: self.reserved_width,
|
|
}));
|
|
Arguments::from(&self.content).fmt(f)?;
|
|
f.write_element(FormatElement::Tag(EndLineSuffix));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for LineSuffix<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_tuple("LineSuffix").field(&"{{content}}").finish()
|
|
}
|
|
}
|
|
|
|
/// Inserts a boundary for line suffixes that forces the printer to print all pending line suffixes.
|
|
/// Helpful if a line suffix shouldn't pass a certain point.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// Forces the line suffix "c" to be printed before the token `d`.
|
|
/// ```
|
|
/// use ruff_formatter::format;
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// token("a"),
|
|
/// line_suffix(&token("c"), 0),
|
|
/// token("b"),
|
|
/// line_suffix_boundary(),
|
|
/// token("d")
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "abc\nd",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub const fn line_suffix_boundary() -> LineSuffixBoundary {
|
|
LineSuffixBoundary
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub struct LineSuffixBoundary;
|
|
|
|
impl<Context> Format<Context> for LineSuffixBoundary {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::LineSuffixBoundary);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Marks some content with a label.
|
|
///
|
|
/// This does not directly influence how this content will be printed, but some
|
|
/// parts of the formatter may inspect the [labelled element](Tag::StartLabelled)
|
|
/// using [`FormatElements::has_label`].
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```rust
|
|
/// use ruff_formatter::prelude::*;
|
|
/// use ruff_formatter::{format, write, LineWidth};
|
|
///
|
|
/// #[derive(Debug, Copy, Clone)]
|
|
/// enum MyLabels {
|
|
/// Main
|
|
/// }
|
|
///
|
|
/// impl tag::LabelDefinition for MyLabels {
|
|
/// fn value(&self) -> u64 {
|
|
/// *self as u64
|
|
/// }
|
|
///
|
|
/// fn name(&self) -> &'static str {
|
|
/// match self {
|
|
/// Self::Main => "Main"
|
|
/// }
|
|
/// }
|
|
/// }
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let formatted = format!(
|
|
/// SimpleFormatContext::default(),
|
|
/// [format_with(|f| {
|
|
/// let mut recording = f.start_recording();
|
|
/// write!(recording, [
|
|
/// labelled(
|
|
/// LabelId::of(MyLabels::Main),
|
|
/// &token("'I have a label'")
|
|
/// )
|
|
/// ])?;
|
|
///
|
|
/// let recorded = recording.stop();
|
|
///
|
|
/// let is_labelled = recorded.first().is_some_and( |element| element.has_label(LabelId::of(MyLabels::Main)));
|
|
///
|
|
/// if is_labelled {
|
|
/// write!(f, [token(" has label `Main`")])
|
|
/// } else {
|
|
/// write!(f, [token(" doesn't have label `Main`")])
|
|
/// }
|
|
/// })]
|
|
/// )?;
|
|
///
|
|
/// assert_eq!("'I have a label' has label `Main`", formatted.print()?.as_code());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// ## Alternatives
|
|
///
|
|
/// Use `Memoized.inspect(f)?.has_label(LabelId::of::<SomeLabelId>()` if you need to know if some content breaks that should
|
|
/// only be written later.
|
|
#[inline]
|
|
pub fn labelled<Content, Context>(label_id: LabelId, content: &Content) -> FormatLabelled<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
FormatLabelled {
|
|
label_id,
|
|
content: Argument::new(content),
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct FormatLabelled<'a, Context> {
|
|
label_id: LabelId,
|
|
content: Argument<'a, Context>,
|
|
}
|
|
|
|
impl<Context> Format<Context> for FormatLabelled<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartLabelled(self.label_id)));
|
|
Arguments::from(&self.content).fmt(f)?;
|
|
f.write_element(FormatElement::Tag(EndLabelled));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for FormatLabelled<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_tuple("Label")
|
|
.field(&self.label_id)
|
|
.field(&"{{content}}")
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// Inserts a single space. Allows to separate different tokens.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::format;
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// // the tab must be encoded as \\t to not literally print a tab character ("Hello{tab}World" vs "Hello\tWorld")
|
|
/// let elements = format!(SimpleFormatContext::default(), [token("a"), space(), token("b")])?;
|
|
///
|
|
/// assert_eq!("a b", elements.print()?.as_code());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub const fn space() -> Space {
|
|
Space
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub struct Space;
|
|
|
|
impl<Context> Format<Context> for Space {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Space);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// It adds a level of indentation to the given content
|
|
///
|
|
/// It doesn't add any line breaks at the edges of the content, meaning that
|
|
/// the line breaks have to be manually added.
|
|
///
|
|
/// This helper should be used only in rare cases, instead you should rely more on
|
|
/// [`block_indent`] and [`soft_block_indent`]
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let block = format!(SimpleFormatContext::default(), [
|
|
/// token("switch {"),
|
|
/// block_indent(&format_args![
|
|
/// token("default:"),
|
|
/// indent(&format_args![
|
|
/// // this is where we want to use a
|
|
/// hard_line_break(),
|
|
/// token("break;"),
|
|
/// ])
|
|
/// ]),
|
|
/// token("}"),
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "switch {\n\tdefault:\n\t\tbreak;\n}",
|
|
/// block.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn indent<Content, Context>(content: &Content) -> Indent<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
Indent {
|
|
content: Argument::new(content),
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct Indent<'a, Context> {
|
|
content: Argument<'a, Context>,
|
|
}
|
|
|
|
impl<Context> Format<Context> for Indent<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartIndent));
|
|
Arguments::from(&self.content).fmt(f)?;
|
|
f.write_element(FormatElement::Tag(EndIndent));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for Indent<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_tuple("Indent").field(&"{{content}}").finish()
|
|
}
|
|
}
|
|
|
|
/// 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 ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let block = format!(SimpleFormatContext::default(), [
|
|
/// token("root"),
|
|
/// align(2, &format_args![
|
|
/// hard_line_break(),
|
|
/// token("aligned"),
|
|
/// dedent(&format_args![
|
|
/// hard_line_break(),
|
|
/// token("not aligned"),
|
|
/// ]),
|
|
/// dedent(&indent(&format_args![
|
|
/// hard_line_break(),
|
|
/// token("Indented, not aligned")
|
|
/// ]))
|
|
/// ]),
|
|
/// dedent(&format_args![
|
|
/// hard_line_break(),
|
|
/// token("Dedent on root level is a no-op.")
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "root\n aligned\nnot aligned\n\tIndented, not aligned\nDedent on root level is a no-op.",
|
|
/// block.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn dedent<Content, Context>(content: &Content) -> Dedent<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
Dedent {
|
|
content: Argument::new(content),
|
|
mode: DedentMode::Level,
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct Dedent<'a, Context> {
|
|
content: Argument<'a, Context>,
|
|
mode: DedentMode,
|
|
}
|
|
|
|
impl<Context> Format<Context> for Dedent<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartDedent(self.mode)));
|
|
Arguments::from(&self.content).fmt(f)?;
|
|
f.write_element(FormatElement::Tag(EndDedent));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
/// It resets the indent document so that the content will be printed at the start of the line.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let block = format!(SimpleFormatContext::default(), [
|
|
/// token("root"),
|
|
/// indent(&format_args![
|
|
/// hard_line_break(),
|
|
/// token("indent level 1"),
|
|
/// indent(&format_args![
|
|
/// hard_line_break(),
|
|
/// token("indent level 2"),
|
|
/// align(2, &format_args![
|
|
/// hard_line_break(),
|
|
/// token("two space align"),
|
|
/// dedent_to_root(&format_args![
|
|
/// hard_line_break(),
|
|
/// token("starts at the beginning of the line")
|
|
/// ]),
|
|
/// ]),
|
|
/// hard_line_break(),
|
|
/// token("end indent level 2"),
|
|
/// ])
|
|
/// ]),
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "root\n\tindent level 1\n\t\tindent level 2\n\t\t two space align\nstarts at the beginning of the line\n\t\tend indent level 2",
|
|
/// block.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// ## Prettier
|
|
///
|
|
/// This resembles the behaviour of Prettier's `align(Number.NEGATIVE_INFINITY, content)` IR element.
|
|
#[inline]
|
|
pub fn dedent_to_root<Content, Context>(content: &Content) -> Dedent<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
Dedent {
|
|
content: Argument::new(content),
|
|
mode: DedentMode::Root,
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
/// using the configured indent character (tab or a specified number of spaces).
|
|
///
|
|
/// You should use [align] when you want to indent a content by a specific number of spaces.
|
|
/// Using [indent] is preferred in all other situations as it respects the users preferred indent character.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ## Tab indention
|
|
///
|
|
/// ```
|
|
/// use std::num::NonZeroU8;
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let block = format!(SimpleFormatContext::default(), [
|
|
/// token("a"),
|
|
/// hard_line_break(),
|
|
/// token("?"),
|
|
/// space(),
|
|
/// align(2, &format_args![
|
|
/// token("function () {"),
|
|
/// hard_line_break(),
|
|
/// token("}"),
|
|
/// ]),
|
|
/// hard_line_break(),
|
|
/// token(":"),
|
|
/// space(),
|
|
/// align(2, &format_args![
|
|
/// token("function () {"),
|
|
/// block_indent(&token("console.log('test');")),
|
|
/// token("}"),
|
|
/// ]),
|
|
/// token(";")
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a\n? function () {\n }\n: function () {\n\t\tconsole.log('test');\n };",
|
|
/// block.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// You can see that:
|
|
///
|
|
/// - the printer indents the function's `}` by two spaces because it is inside of an `align`.
|
|
/// - the block `console.log` gets indented by two tabs.
|
|
/// This is because `align` increases the indention level by one (same as `indent`)
|
|
/// if you nest an `indent` inside an `align`.
|
|
/// Meaning that, `align > ... > indent` results in the same indention as `indent > ... > indent`.
|
|
///
|
|
/// ## Spaces indention
|
|
///
|
|
/// ```
|
|
/// use std::num::NonZeroU8;
|
|
/// use ruff_formatter::{format, format_args, IndentStyle, SimpleFormatOptions};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// indent_style: IndentStyle::Space(4),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// });
|
|
///
|
|
/// let block = format!(context, [
|
|
/// token("a"),
|
|
/// hard_line_break(),
|
|
/// token("?"),
|
|
/// space(),
|
|
/// align(2, &format_args![
|
|
/// token("function () {"),
|
|
/// hard_line_break(),
|
|
/// token("}"),
|
|
/// ]),
|
|
/// hard_line_break(),
|
|
/// token(":"),
|
|
/// space(),
|
|
/// align(2, &format_args![
|
|
/// token("function () {"),
|
|
/// block_indent(&token("console.log('test');")),
|
|
/// token("}"),
|
|
/// ]),
|
|
/// token(";")
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a\n? function () {\n }\n: function () {\n console.log('test');\n };",
|
|
/// block.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// The printing of `align` differs if using spaces as indention sequence *and* it contains an `indent`.
|
|
/// You can see the difference when comparing the indention of the `console.log(...)` expression to the previous example:
|
|
///
|
|
/// - tab indention: Printer indents the expression with two tabs because the `align` increases the indention level.
|
|
/// - space indention: Printer indents the expression by 4 spaces (one indention level) **and** 2 spaces for the align.
|
|
pub fn align<Content, Context>(count: u8, content: &Content) -> Align<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
Align {
|
|
count: NonZeroU8::new(count).expect("Alignment count must be a non-zero number."),
|
|
content: Argument::new(content),
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct Align<'a, Context> {
|
|
count: NonZeroU8,
|
|
content: Argument<'a, Context>,
|
|
}
|
|
|
|
impl<Context> Format<Context> for Align<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartAlign(tag::Align(self.count))));
|
|
Arguments::from(&self.content).fmt(f)?;
|
|
f.write_element(FormatElement::Tag(EndAlign));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for Align<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Align")
|
|
.field("count", &self.count)
|
|
.field("content", &"{{content}}")
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// Inserts a hard line break before and after the content and increases the indention level for the content by one.
|
|
///
|
|
/// Block indents indent a block of code, such as in a function body, and therefore insert a line
|
|
/// break before and after the content.
|
|
///
|
|
/// Doesn't create an indention if the passed in content is [`FormatElement.is_empty`].
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let block = format![
|
|
/// SimpleFormatContext::default(),
|
|
/// [
|
|
/// token("{"),
|
|
/// block_indent(&format_args![
|
|
/// token("let a = 10;"),
|
|
/// hard_line_break(),
|
|
/// token("let c = a + 5;"),
|
|
/// ]),
|
|
/// token("}"),
|
|
/// ]
|
|
/// ]?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "{\n\tlet a = 10;\n\tlet c = a + 5;\n}",
|
|
/// block.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<Context> {
|
|
BlockIndent {
|
|
content: Argument::new(content),
|
|
mode: IndentMode::Block,
|
|
}
|
|
}
|
|
|
|
/// Indents the content by inserting a line break before and after the content and increasing
|
|
/// the indention level for the content by one if the enclosing group doesn't fit on a single line.
|
|
/// Doesn't change the formatting if the enclosing group fits on a single line.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// Indents the content by one level and puts in new lines if the enclosing `Group` doesn't fit on a single line
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, LineWidth, 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, [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("'First string',"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("'second string',"),
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[\n\t'First string',\n\t'second string',\n]",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Doesn't change the formatting if the enclosing `Group` fits on a single line
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("5,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("10"),
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[5, 10]",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn soft_block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<Context> {
|
|
BlockIndent {
|
|
content: Argument::new(content),
|
|
mode: IndentMode::Soft,
|
|
}
|
|
}
|
|
|
|
/// If the enclosing `Group` doesn't fit on a single line, inserts a line break and indent.
|
|
/// Otherwise, just inserts a space.
|
|
///
|
|
/// Line indents are used to break a single line of code, and therefore only insert a line
|
|
/// break before the content and not after the content.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// Indents the content by one level and puts in new lines if the enclosing `Group` doesn't
|
|
/// fit on a single line. Otherwise, just inserts a space.
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, LineWidth, 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, [
|
|
/// group(&format_args![
|
|
/// token("name"),
|
|
/// space(),
|
|
/// token("="),
|
|
/// soft_line_indent_or_space(&format_args![
|
|
/// token("firstName"),
|
|
/// space(),
|
|
/// token("+"),
|
|
/// space(),
|
|
/// token("lastName"),
|
|
/// ]),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "name =\n\tfirstName + lastName",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Only adds a space if the enclosing `Group` fits on a single line
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("a"),
|
|
/// space(),
|
|
/// token("="),
|
|
/// soft_line_indent_or_space(&token("10")),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a = 10",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn soft_line_indent_or_space<Context>(content: &impl Format<Context>) -> BlockIndent<Context> {
|
|
BlockIndent {
|
|
content: Argument::new(content),
|
|
mode: IndentMode::SoftLineOrSpace,
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct BlockIndent<'a, Context> {
|
|
content: Argument<'a, Context>,
|
|
mode: IndentMode,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
|
enum IndentMode {
|
|
Soft,
|
|
Block,
|
|
SoftSpace,
|
|
SoftLineOrSpace,
|
|
}
|
|
|
|
impl<Context> Format<Context> for BlockIndent<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
let snapshot = f.snapshot();
|
|
|
|
f.write_element(FormatElement::Tag(StartIndent));
|
|
|
|
match self.mode {
|
|
IndentMode::Soft => write!(f, [soft_line_break()])?,
|
|
IndentMode::Block => write!(f, [hard_line_break()])?,
|
|
IndentMode::SoftLineOrSpace | IndentMode::SoftSpace => {
|
|
write!(f, [soft_line_break_or_space()])?;
|
|
}
|
|
}
|
|
|
|
let is_empty = {
|
|
let mut recording = f.start_recording();
|
|
recording.write_fmt(Arguments::from(&self.content))?;
|
|
recording.stop().is_empty()
|
|
};
|
|
|
|
if is_empty {
|
|
f.restore_snapshot(snapshot);
|
|
return Ok(());
|
|
}
|
|
|
|
f.write_element(FormatElement::Tag(EndIndent));
|
|
|
|
match self.mode {
|
|
IndentMode::Soft => write!(f, [soft_line_break()]),
|
|
IndentMode::Block => write!(f, [hard_line_break()]),
|
|
IndentMode::SoftSpace => write!(f, [soft_line_break_or_space()]),
|
|
IndentMode::SoftLineOrSpace => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for BlockIndent<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let name = match self.mode {
|
|
IndentMode::Soft => "SoftBlockIndent",
|
|
IndentMode::Block => "HardBlockIndent",
|
|
IndentMode::SoftLineOrSpace => "SoftLineIndentOrSpace",
|
|
IndentMode::SoftSpace => "SoftSpaceBlockIndent",
|
|
};
|
|
|
|
f.debug_tuple(name).field(&"{{content}}").finish()
|
|
}
|
|
}
|
|
|
|
/// Adds spaces around the content if its enclosing group fits on a line, otherwise indents the content and separates it by line breaks.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// Adds line breaks and indents the content if the enclosing group doesn't fit on the line.
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, LineWidth, 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, [
|
|
/// group(&format_args![
|
|
/// token("{"),
|
|
/// soft_space_or_block_indent(&format_args![
|
|
/// token("aPropertyThatExceeds"),
|
|
/// token(":"),
|
|
/// space(),
|
|
/// token("'line width'"),
|
|
/// ]),
|
|
/// token("}")
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "{\n\taPropertyThatExceeds: 'line width'\n}",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Adds spaces around the content if the group fits on the line
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("{"),
|
|
/// soft_space_or_block_indent(&format_args![
|
|
/// token("a"),
|
|
/// token(":"),
|
|
/// space(),
|
|
/// token("5"),
|
|
/// ]),
|
|
/// token("}")
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "{ a: 5 }",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn soft_space_or_block_indent<Context>(content: &impl Format<Context>) -> BlockIndent<Context> {
|
|
BlockIndent {
|
|
content: Argument::new(content),
|
|
mode: IndentMode::SoftSpace,
|
|
}
|
|
}
|
|
|
|
/// Creates a logical `Group` around the content that should either consistently be printed on a single line
|
|
/// or broken across multiple lines.
|
|
///
|
|
/// The printer will try to print the content of the `Group` on a single line, ignoring all soft line breaks and
|
|
/// emitting spaces for soft line breaks or spaces. The printer tracks back if it isn't successful either
|
|
/// because it encountered a hard line break, or because printing the `Group` on a single line exceeds
|
|
/// the configured line width, and thus it must print all its content on multiple lines,
|
|
/// emitting line breaks for all line break kinds.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// `Group` that fits on a single line
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("1,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("2,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("3"),
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[1, 2, 3]",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// The printer breaks the `Group` over multiple lines if its content doesn't fit on a single line
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// line_width: LineWidth::try_from(20).unwrap(),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// });
|
|
///
|
|
/// let elements = format!(context, [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("'Good morning! How are you today?',"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("2,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("3"),
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[\n\t'Good morning! How are you today?',\n\t2,\n\t3\n]",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn group<Context>(content: &impl Format<Context>) -> Group<Context> {
|
|
Group {
|
|
content: Argument::new(content),
|
|
group_id: None,
|
|
should_expand: false,
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct Group<'a, Context> {
|
|
content: Argument<'a, Context>,
|
|
group_id: Option<GroupId>,
|
|
should_expand: bool,
|
|
}
|
|
|
|
impl<Context> Group<'_, Context> {
|
|
#[must_use]
|
|
pub fn with_group_id(mut self, group_id: Option<GroupId>) -> Self {
|
|
self.group_id = group_id;
|
|
self
|
|
}
|
|
|
|
/// Changes the [`PrintMode`] of the group from [`Flat`](PrintMode::Flat) to [`Expanded`](PrintMode::Expanded).
|
|
/// The result is that any soft-line break gets printed as a regular line break.
|
|
///
|
|
/// This is useful for content rendered inside of a [`FormatElement::BestFitting`] that prints each variant
|
|
/// in [`PrintMode::Flat`] to change some content to be printed in [`Expanded`](PrintMode::Expanded) regardless.
|
|
/// See the documentation of the [`best_fitting`] macro for an example.
|
|
#[must_use]
|
|
pub fn should_expand(mut self, should_expand: bool) -> Self {
|
|
self.should_expand = should_expand;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<Context> Format<Context> for Group<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
let mode = if self.should_expand {
|
|
GroupMode::Expand
|
|
} else {
|
|
GroupMode::Flat
|
|
};
|
|
|
|
f.write_element(FormatElement::Tag(StartGroup(
|
|
tag::Group::new().with_id(self.group_id).with_mode(mode),
|
|
)));
|
|
|
|
Arguments::from(&self.content).fmt(f)?;
|
|
|
|
f.write_element(FormatElement::Tag(EndGroup));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for Group<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Group")
|
|
.field("group_id", &self.group_id)
|
|
.field("should_expand", &self.should_expand)
|
|
.field("content", &"{{content}}")
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// Sets the `condition` for the group. The element will behave as a regular group if `condition` is met,
|
|
/// and as *ungrouped* content if the condition is not met.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// Only expand before operators if the parentheses are necessary.
|
|
///
|
|
/// ```
|
|
/// # use ruff_formatter::prelude::*;
|
|
/// # use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions};
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// use ruff_formatter::Formatted;
|
|
/// let content = format_with(|f| {
|
|
/// let parentheses_id = f.group_id("parentheses");
|
|
/// group(&format_args![
|
|
/// if_group_breaks(&token("(")),
|
|
/// indent_if_group_breaks(&format_args![
|
|
/// soft_line_break(),
|
|
/// conditional_group(&format_args![
|
|
/// token("'aaaaaaa'"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("+"),
|
|
/// space(),
|
|
/// fits_expanded(&conditional_group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("'Good morning!',"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("'How are you?'"),
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ], tag::Condition::if_group_fits_on_line(parentheses_id))),
|
|
/// soft_line_break_or_space(),
|
|
/// token("+"),
|
|
/// space(),
|
|
/// conditional_group(&format_args![
|
|
/// token("'bbbb'"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("and"),
|
|
/// space(),
|
|
/// token("'c'")
|
|
/// ], tag::Condition::if_group_fits_on_line(parentheses_id))
|
|
/// ], tag::Condition::if_breaks()),
|
|
/// ], parentheses_id),
|
|
/// soft_line_break(),
|
|
/// if_group_breaks(&token(")"))
|
|
/// ])
|
|
/// .with_group_id(Some(parentheses_id))
|
|
/// .fmt(f)
|
|
/// });
|
|
///
|
|
/// let formatted = format!(SimpleFormatContext::default(), [content])?;
|
|
/// let document = formatted.into_document();
|
|
///
|
|
/// // All content fits
|
|
/// let all_fits = Formatted::new(document.clone(), SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// line_width: LineWidth::try_from(65).unwrap(),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// }));
|
|
///
|
|
/// assert_eq!(
|
|
/// "'aaaaaaa' + ['Good morning!', 'How are you?'] + 'bbbb' and 'c'",
|
|
/// all_fits.print()?.as_code()
|
|
/// );
|
|
///
|
|
/// // The parentheses group fits, because it can expand the list,
|
|
/// let list_expanded = Formatted::new(document.clone(), SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// line_width: LineWidth::try_from(21).unwrap(),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// }));
|
|
///
|
|
/// assert_eq!(
|
|
/// "'aaaaaaa' + [\n\t'Good morning!',\n\t'How are you?'\n] + 'bbbb' and 'c'",
|
|
/// list_expanded.print()?.as_code()
|
|
/// );
|
|
///
|
|
/// // It is necessary to split all groups to fit the content
|
|
/// let all_expanded = Formatted::new(document, SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// line_width: LineWidth::try_from(11).unwrap(),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// }));
|
|
///
|
|
/// assert_eq!(
|
|
/// "(\n\t'aaaaaaa'\n\t+ [\n\t\t'Good morning!',\n\t\t'How are you?'\n\t]\n\t+ 'bbbb'\n\tand 'c'\n)",
|
|
/// all_expanded.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn conditional_group<Content, Context>(
|
|
content: &Content,
|
|
condition: Condition,
|
|
) -> ConditionalGroup<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
ConditionalGroup {
|
|
content: Argument::new(content),
|
|
condition,
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct ConditionalGroup<'content, Context> {
|
|
content: Argument<'content, Context>,
|
|
condition: Condition,
|
|
}
|
|
|
|
impl<Context> Format<Context> for ConditionalGroup<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartConditionalGroup(
|
|
tag::ConditionalGroup::new(self.condition),
|
|
)));
|
|
f.write_fmt(Arguments::from(&self.content))?;
|
|
f.write_element(FormatElement::Tag(EndConditionalGroup));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for ConditionalGroup<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("ConditionalGroup")
|
|
.field("condition", &self.condition)
|
|
.field("content", &"{{content}}")
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// IR element that forces the parent group to print in expanded mode.
|
|
///
|
|
/// Has no effect if used outside of a group or element that introduce implicit groups (fill element).
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, LineWidth};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("'Good morning! How are you today?',"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("2,"),
|
|
/// expand_parent(), // Forces the parent to expand
|
|
/// soft_line_break_or_space(),
|
|
/// token("3"),
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[\n\t'Good morning! How are you today?',\n\t2,\n\t3\n]",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// # Prettier
|
|
/// Equivalent to Prettier's `break_parent` IR element
|
|
pub const fn expand_parent() -> ExpandParent {
|
|
ExpandParent
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub struct ExpandParent;
|
|
|
|
impl<Context> Format<Context> for ExpandParent {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::ExpandParent);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Adds a conditional content that is emitted only if it isn't inside an enclosing `Group` that
|
|
/// is printed on a single line. The element allows, for example, to insert a trailing comma after the last
|
|
/// array element only if the array doesn't fit on a single line.
|
|
///
|
|
/// The element has no special meaning if used outside of a `Group`. In that case, the content is always emitted.
|
|
///
|
|
/// If you're looking for a way to only print something if the `Group` fits on a single line see [`self::if_group_fits_on_line`].
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// Omits the trailing comma for the last array element if the `Group` fits on a single line
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let elements = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("1,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("2,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("3"),
|
|
/// if_group_breaks(&token(","))
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[1, 2, 3]",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Prints the trailing comma for the last array element if the `Group` doesn't fit on a single line
|
|
/// ```
|
|
/// use ruff_formatter::{format_args, format, LineWidth, SimpleFormatOptions};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// fn main() -> FormatResult<()> {
|
|
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// line_width: LineWidth::try_from(20).unwrap(),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// });
|
|
///
|
|
/// let elements = format!(context, [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("'A somewhat longer string to force a line break',"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("2,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("3"),
|
|
/// if_group_breaks(&token(","))
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[\n\t'A somewhat longer string to force a line break',\n\t2,\n\t3,\n]",
|
|
/// elements.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn if_group_breaks<Content, Context>(content: &Content) -> IfGroupBreaks<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
IfGroupBreaks {
|
|
content: Argument::new(content),
|
|
group_id: None,
|
|
mode: PrintMode::Expanded,
|
|
}
|
|
}
|
|
|
|
/// Adds a conditional content specific for `Group`s that fit on a single line. The content isn't
|
|
/// emitted for `Group`s spanning multiple lines.
|
|
///
|
|
/// See [`if_group_breaks`] if you're looking for a way to print content only for groups spanning multiple lines.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// Adds the trailing comma for the last array element if the `Group` fits on a single line
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let formatted = format!(SimpleFormatContext::default(), [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("1,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("2,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("3"),
|
|
/// if_group_fits_on_line(&token(","))
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[1, 2, 3,]",
|
|
/// formatted.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Omits the trailing comma for the last array element if the `Group` doesn't fit on a single line
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// line_width: LineWidth::try_from(20).unwrap(),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// });
|
|
///
|
|
/// let formatted = format!(context, [
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("'A somewhat longer string to force a line break',"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("2,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("3"),
|
|
/// if_group_fits_on_line(&token(","))
|
|
/// ]),
|
|
/// token("]"),
|
|
/// ])
|
|
/// ])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[\n\t'A somewhat longer string to force a line break',\n\t2,\n\t3\n]",
|
|
/// formatted.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn if_group_fits_on_line<Content, Context>(flat_content: &Content) -> IfGroupBreaks<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
IfGroupBreaks {
|
|
mode: PrintMode::Flat,
|
|
group_id: None,
|
|
content: Argument::new(flat_content),
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct IfGroupBreaks<'a, Context> {
|
|
content: Argument<'a, Context>,
|
|
group_id: Option<GroupId>,
|
|
mode: PrintMode,
|
|
}
|
|
|
|
impl<Context> IfGroupBreaks<'_, Context> {
|
|
/// Inserts some content that the printer only prints if the group with the specified `group_id`
|
|
/// is printed in multiline mode. The referred group must appear before this element in the document
|
|
/// but doesn't have to one of its ancestors.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// Prints the trailing comma if the array group doesn't fit. The `group_id` is necessary
|
|
/// because `fill` creates an implicit group around each item and tries to print the item in flat mode.
|
|
/// The item `[4]` in this example fits on a single line but the trailing comma should still be printed
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, write, LineWidth, SimpleFormatOptions};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// line_width: LineWidth::try_from(20).unwrap(),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// });
|
|
///
|
|
/// let formatted = format!(context, [format_with(|f| {
|
|
/// let group_id = f.group_id("array");
|
|
///
|
|
/// write!(f, [
|
|
/// group(
|
|
/// &format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_with(|f| {
|
|
/// f.fill()
|
|
/// .entry(&soft_line_break_or_space(), &token("1,"))
|
|
/// .entry(&soft_line_break_or_space(), &token("234568789,"))
|
|
/// .entry(&soft_line_break_or_space(), &token("3456789,"))
|
|
/// .entry(&soft_line_break_or_space(), &format_args!(
|
|
/// token("["),
|
|
/// soft_block_indent(&token("4")),
|
|
/// token("]"),
|
|
/// if_group_breaks(&token(",")).with_group_id(Some(group_id))
|
|
/// ))
|
|
/// .finish()
|
|
/// })),
|
|
/// token("]")
|
|
/// ],
|
|
/// ).with_group_id(Some(group_id))
|
|
/// ])
|
|
/// })])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "[\n\t1, 234568789,\n\t3456789, [4],\n]",
|
|
/// formatted.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[must_use]
|
|
pub fn with_group_id(mut self, group_id: Option<GroupId>) -> Self {
|
|
self.group_id = group_id;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<Context> Format<Context> for IfGroupBreaks<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartConditionalContent(
|
|
Condition::new(self.mode).with_group_id(self.group_id),
|
|
)));
|
|
Arguments::from(&self.content).fmt(f)?;
|
|
f.write_element(FormatElement::Tag(EndConditionalContent));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for IfGroupBreaks<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let name = match self.mode {
|
|
PrintMode::Flat => "IfGroupFitsOnLine",
|
|
PrintMode::Expanded => "IfGroupBreaks",
|
|
};
|
|
|
|
f.debug_struct(name)
|
|
.field("group_id", &self.group_id)
|
|
.field("content", &"{{content}}")
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// 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 ruff_formatter::prelude::*;
|
|
/// # use ruff_formatter::write;
|
|
/// # let format = format_with(|f: &mut Formatter<SimpleFormatContext>| {
|
|
/// let id = f.group_id("head");
|
|
///
|
|
/// write!(f, [
|
|
/// group(&token("Head")).with_group_id(Some(id)),
|
|
/// if_group_breaks(&indent(&token("indented"))).with_group_id(Some(id)),
|
|
/// if_group_fits_on_line(&token("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 ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let content = format_with(|f| {
|
|
/// let group_id = f.group_id("header");
|
|
///
|
|
/// write!(f, [
|
|
/// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)),
|
|
/// indent_if_group_breaks(&format_args![hard_line_break(), token("a => b")], group_id)
|
|
/// ])
|
|
/// });
|
|
///
|
|
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
|
|
/// line_width: LineWidth::try_from(20).unwrap(),
|
|
/// ..SimpleFormatOptions::default()
|
|
/// });
|
|
///
|
|
/// let formatted = format!(context, [content])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "(aLongHeaderThatBreaksForSomeReason) =>\n\ta => b",
|
|
/// formatted.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// It doesn't add an indent if the group wrapping the signature doesn't break:
|
|
/// ```
|
|
/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let content = format_with(|f| {
|
|
/// let group_id = f.group_id("header");
|
|
///
|
|
/// write!(f, [
|
|
/// group(&token("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)),
|
|
/// indent_if_group_breaks(&format_args![hard_line_break(), token("a => b")], group_id)
|
|
/// ])
|
|
/// });
|
|
///
|
|
/// let formatted = format!(SimpleFormatContext::default(), [content])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "(aLongHeaderThatBreaksForSomeReason) =>\na => b",
|
|
/// formatted.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[inline]
|
|
pub fn indent_if_group_breaks<Content, Context>(
|
|
content: &Content,
|
|
group_id: GroupId,
|
|
) -> IndentIfGroupBreaks<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
IndentIfGroupBreaks {
|
|
group_id,
|
|
content: Argument::new(content),
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct IndentIfGroupBreaks<'a, Context> {
|
|
content: Argument<'a, Context>,
|
|
group_id: GroupId,
|
|
}
|
|
|
|
impl<Context> Format<Context> for IndentIfGroupBreaks<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartIndentIfGroupBreaks(self.group_id)));
|
|
Arguments::from(&self.content).fmt(f)?;
|
|
f.write_element(FormatElement::Tag(EndIndentIfGroupBreaks));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<Context> std::fmt::Debug for IndentIfGroupBreaks<'_, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("IndentIfGroupBreaks")
|
|
.field("group_id", &self.group_id)
|
|
.field("content", &"{{content}}")
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// Changes the definition of *fits* for `content`. Instead of measuring it in *flat*, measure it with
|
|
/// all line breaks expanded and test if no line exceeds the line width. The [`FitsExpanded`] acts
|
|
/// as a expands boundary similar to best fitting, meaning that a [`hard_line_break`] will not cause the parent group to expand.
|
|
///
|
|
/// Useful in conjunction with a group with a condition.
|
|
///
|
|
/// ## Examples
|
|
/// The outer group with the binary expression remains *flat* regardless of the array expression
|
|
/// that spans multiple lines.
|
|
///
|
|
/// ```
|
|
/// # use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write};
|
|
/// # use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let content = format_with(|f| {
|
|
/// let group_id = f.group_id("header");
|
|
///
|
|
/// write!(f, [
|
|
/// group(&format_args![
|
|
/// token("a"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("+"),
|
|
/// space(),
|
|
/// fits_expanded(&group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("a,"), space(), token("# comment"), expand_parent(), soft_line_break_or_space(),
|
|
/// token("b")
|
|
/// ]),
|
|
/// token("]")
|
|
/// ]))
|
|
/// ]),
|
|
/// ])
|
|
/// });
|
|
///
|
|
/// let formatted = format!(SimpleFormatContext::default(), [content])?;
|
|
///
|
|
/// assert_eq!(
|
|
/// "a + [\n\ta, # comment\n\tb\n]",
|
|
/// formatted.print()?.as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn fits_expanded<Content, Context>(content: &Content) -> FitsExpanded<Context>
|
|
where
|
|
Content: Format<Context>,
|
|
{
|
|
FitsExpanded {
|
|
content: Argument::new(content),
|
|
condition: None,
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct FitsExpanded<'a, Context> {
|
|
content: Argument<'a, Context>,
|
|
condition: Option<Condition>,
|
|
}
|
|
|
|
impl<Context> FitsExpanded<'_, Context> {
|
|
/// Sets a `condition` to when the content should fit in expanded mode. The content uses the regular fits
|
|
/// definition if the `condition` is not met.
|
|
#[must_use]
|
|
pub fn with_condition(mut self, condition: Option<Condition>) -> Self {
|
|
self.condition = condition;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<Context> Format<Context> for FitsExpanded<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
f.write_element(FormatElement::Tag(StartFitsExpanded(
|
|
tag::FitsExpanded::new().with_condition(self.condition),
|
|
)));
|
|
f.write_fmt(Arguments::from(&self.content))?;
|
|
f.write_element(FormatElement::Tag(EndFitsExpanded));
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Utility for formatting some content with an inline lambda function.
|
|
#[derive(Copy, Clone)]
|
|
pub struct FormatWith<Context, T> {
|
|
formatter: T,
|
|
context: PhantomData<Context>,
|
|
}
|
|
|
|
impl<Context, T> Format<Context> for FormatWith<Context, T>
|
|
where
|
|
T: Fn(&mut Formatter<Context>) -> FormatResult<()>,
|
|
{
|
|
#[inline]
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
(self.formatter)(f)
|
|
}
|
|
}
|
|
|
|
impl<Context, T> std::fmt::Debug for FormatWith<Context, T> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_tuple("FormatWith").field(&"{{formatter}}").finish()
|
|
}
|
|
}
|
|
|
|
/// Creates an object implementing `Format` that calls the passed closure to perform the formatting.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::prelude::*;
|
|
/// use ruff_formatter::{SimpleFormatContext, format, write};
|
|
/// use ruff_text_size::TextSize;
|
|
///
|
|
/// struct MyFormat {
|
|
/// items: Vec<&'static str>,
|
|
/// }
|
|
///
|
|
/// impl Format<SimpleFormatContext> for MyFormat {
|
|
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
|
|
/// write!(f, [
|
|
/// token("("),
|
|
/// block_indent(&format_with(|f| {
|
|
/// let separator = space();
|
|
/// let mut join = f.join_with(&separator);
|
|
///
|
|
/// for item in &self.items {
|
|
/// join.entry(&format_with(|f| write!(f, [text(item, None)])));
|
|
/// }
|
|
/// join.finish()
|
|
/// })),
|
|
/// token(")")
|
|
/// ])
|
|
/// }
|
|
/// }
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let formatted = format!(SimpleFormatContext::default(), [MyFormat { items: vec!["a", "b", "c"]}])?;
|
|
///
|
|
/// assert_eq!("(\n\ta b c\n)", formatted.print()?.as_code());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub const fn format_with<Context, T>(formatter: T) -> FormatWith<Context, T>
|
|
where
|
|
T: Fn(&mut Formatter<Context>) -> FormatResult<()>,
|
|
{
|
|
FormatWith {
|
|
formatter,
|
|
context: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Creates an inline `Format` object that can only be formatted once.
|
|
///
|
|
/// This can be useful in situation where the borrow checker doesn't allow you to use [`format_with`]
|
|
/// because the code formatting the content consumes the value and cloning the value is too expensive.
|
|
/// An example of this is if you want to nest a `FormatElement` or non-cloneable `Iterator` inside of a
|
|
/// `block_indent` as shown can see in the examples section.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if the object gets formatted more than once.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::prelude::*;
|
|
/// use ruff_formatter::{SimpleFormatContext, format, write, Buffer};
|
|
///
|
|
/// struct MyFormat;
|
|
///
|
|
/// fn generate_values() -> impl Iterator<Item=Token> {
|
|
/// vec![token("1"), token("2"), token("3"), token("4")].into_iter()
|
|
/// }
|
|
///
|
|
/// impl Format<SimpleFormatContext> for MyFormat {
|
|
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
|
|
/// let mut values = generate_values();
|
|
///
|
|
/// let first = values.next();
|
|
///
|
|
/// // Formats the first item outside of the block and all other items inside of the block,
|
|
/// // separated by line breaks
|
|
/// write!(f, [
|
|
/// first,
|
|
/// block_indent(&format_once(|f| {
|
|
/// // Using format_with isn't possible here because the iterator gets consumed here
|
|
/// f.join_with(&hard_line_break()).entries(values).finish()
|
|
/// })),
|
|
/// ])
|
|
/// }
|
|
/// }
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let formatted = format!(SimpleFormatContext::default(), [MyFormat])?;
|
|
///
|
|
/// assert_eq!("1\n\t2\n\t3\n\t4\n", formatted.print()?.as_code());
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// Formatting the same value twice results in a panic.
|
|
///
|
|
/// ```should_panic
|
|
/// use ruff_formatter::prelude::*;
|
|
/// use ruff_formatter::{SimpleFormatContext, format, write, Buffer};
|
|
/// use ruff_text_size::TextSize;
|
|
///
|
|
/// let mut count = 0;
|
|
///
|
|
/// let value = format_once(|f| {
|
|
/// write!(f, [text(&std::format!("Formatted {count}."), None)])
|
|
/// });
|
|
///
|
|
/// format!(SimpleFormatContext::default(), [value]).expect("Formatting once works fine");
|
|
///
|
|
/// // Formatting the value more than once panics
|
|
/// format!(SimpleFormatContext::default(), [value]);
|
|
/// ```
|
|
pub const fn format_once<T, Context>(formatter: T) -> FormatOnce<T, Context>
|
|
where
|
|
T: FnOnce(&mut Formatter<Context>) -> FormatResult<()>,
|
|
{
|
|
FormatOnce {
|
|
formatter: Cell::new(Some(formatter)),
|
|
context: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub struct FormatOnce<T, Context> {
|
|
formatter: Cell<Option<T>>,
|
|
context: PhantomData<Context>,
|
|
}
|
|
|
|
impl<T, Context> Format<Context> for FormatOnce<T, Context>
|
|
where
|
|
T: FnOnce(&mut Formatter<Context>) -> FormatResult<()>,
|
|
{
|
|
#[inline]
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
let formatter = self.formatter.take().expect("Tried to format a `format_once` at least twice. This is not allowed. You may want to use `format_with` or `format.memoized` instead.");
|
|
|
|
(formatter)(f)
|
|
}
|
|
}
|
|
|
|
impl<T, Context> std::fmt::Debug for FormatOnce<T, Context> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_tuple("FormatOnce").field(&"{{formatter}}").finish()
|
|
}
|
|
}
|
|
|
|
/// Builder to join together a sequence of content.
|
|
/// See [`Formatter::join`]
|
|
#[must_use = "must eventually call `finish()` on Format builders"]
|
|
pub struct JoinBuilder<'fmt, 'buf, Separator, Context> {
|
|
result: FormatResult<()>,
|
|
fmt: &'fmt mut Formatter<'buf, Context>,
|
|
with: Option<Separator>,
|
|
has_elements: bool,
|
|
}
|
|
|
|
impl<'fmt, 'buf, Separator, Context> JoinBuilder<'fmt, 'buf, Separator, Context>
|
|
where
|
|
Separator: Format<Context>,
|
|
{
|
|
/// Creates a new instance that joins the elements without a separator
|
|
pub(super) fn new(fmt: &'fmt mut Formatter<'buf, Context>) -> Self {
|
|
Self {
|
|
result: Ok(()),
|
|
fmt,
|
|
has_elements: false,
|
|
with: None,
|
|
}
|
|
}
|
|
|
|
/// Creates a new instance that prints the passed separator between every two entries.
|
|
pub(super) fn with_separator(fmt: &'fmt mut Formatter<'buf, Context>, with: Separator) -> Self {
|
|
Self {
|
|
result: Ok(()),
|
|
fmt,
|
|
has_elements: false,
|
|
with: Some(with),
|
|
}
|
|
}
|
|
|
|
/// Adds a new entry to the join output.
|
|
pub fn entry(&mut self, entry: &dyn Format<Context>) -> &mut Self {
|
|
self.result = self.result.and_then(|_| {
|
|
if let Some(with) = &self.with {
|
|
if self.has_elements {
|
|
with.fmt(self.fmt)?;
|
|
}
|
|
}
|
|
self.has_elements = true;
|
|
|
|
entry.fmt(self.fmt)
|
|
});
|
|
|
|
self
|
|
}
|
|
|
|
/// Adds the contents of an iterator of entries to the join output.
|
|
pub fn entries<F, I>(&mut self, entries: I) -> &mut Self
|
|
where
|
|
F: Format<Context>,
|
|
I: IntoIterator<Item = F>,
|
|
{
|
|
for entry in entries {
|
|
self.entry(&entry);
|
|
}
|
|
|
|
self
|
|
}
|
|
|
|
/// Finishes the output and returns any error encountered.
|
|
pub fn finish(&mut self) -> FormatResult<()> {
|
|
self.result
|
|
}
|
|
}
|
|
|
|
/// Builder to fill as many elements as possible on a single line.
|
|
#[must_use = "must eventually call `finish()` on Format builders"]
|
|
pub struct FillBuilder<'fmt, 'buf, Context> {
|
|
result: FormatResult<()>,
|
|
fmt: &'fmt mut Formatter<'buf, Context>,
|
|
empty: bool,
|
|
}
|
|
|
|
impl<'a, 'buf, Context> FillBuilder<'a, 'buf, Context> {
|
|
pub(crate) fn new(fmt: &'a mut Formatter<'buf, Context>) -> Self {
|
|
fmt.write_element(FormatElement::Tag(StartFill));
|
|
|
|
Self {
|
|
result: Ok(()),
|
|
fmt,
|
|
empty: true,
|
|
}
|
|
}
|
|
|
|
/// Adds an iterator of entries to the fill output. Uses the passed `separator` to separate any two items.
|
|
pub fn entries<F, I>(&mut self, separator: &dyn Format<Context>, entries: I) -> &mut Self
|
|
where
|
|
F: Format<Context>,
|
|
I: IntoIterator<Item = F>,
|
|
{
|
|
for entry in entries {
|
|
self.entry(separator, &entry);
|
|
}
|
|
|
|
self
|
|
}
|
|
|
|
/// Adds a new entry to the fill output. The `separator` isn't written if this is the first element in the list.
|
|
pub fn entry(
|
|
&mut self,
|
|
separator: &dyn Format<Context>,
|
|
entry: &dyn Format<Context>,
|
|
) -> &mut Self {
|
|
self.result = self.result.and_then(|_| {
|
|
if self.empty {
|
|
self.empty = false;
|
|
} else {
|
|
self.fmt.write_element(FormatElement::Tag(StartEntry));
|
|
separator.fmt(self.fmt)?;
|
|
self.fmt.write_element(FormatElement::Tag(EndEntry));
|
|
}
|
|
|
|
self.fmt.write_element(FormatElement::Tag(StartEntry));
|
|
entry.fmt(self.fmt)?;
|
|
self.fmt.write_element(FormatElement::Tag(EndEntry));
|
|
Ok(())
|
|
});
|
|
|
|
self
|
|
}
|
|
|
|
/// Finishes the output and returns any error encountered
|
|
pub fn finish(&mut self) -> FormatResult<()> {
|
|
if self.result.is_ok() {
|
|
self.fmt.write_element(FormatElement::Tag(EndFill));
|
|
}
|
|
self.result
|
|
}
|
|
}
|
|
|
|
/// The first variant is the most flat, and the last is the most expanded variant.
|
|
/// See [`best_fitting!`] macro for a more in-detail documentation
|
|
#[derive(Copy, Clone)]
|
|
pub struct BestFitting<'a, Context> {
|
|
variants: Arguments<'a, Context>,
|
|
mode: BestFittingMode,
|
|
}
|
|
|
|
impl<'a, Context> BestFitting<'a, Context> {
|
|
/// Creates a new best fitting IR with the given variants. The method itself isn't unsafe
|
|
/// but it is to discourage people from using it because the printer will panic if
|
|
/// the slice doesn't contain at least the least and most expanded variants.
|
|
///
|
|
/// You're looking for a way to create a `BestFitting` object, use the `best_fitting![least_expanded, most_expanded]` macro.
|
|
///
|
|
/// ## Safety
|
|
|
|
/// The slice must contain at least two variants.
|
|
#[allow(unsafe_code)]
|
|
pub unsafe fn from_arguments_unchecked(variants: Arguments<'a, Context>) -> Self {
|
|
assert!(
|
|
variants.0.len() >= 2,
|
|
"Requires at least the least expanded and most expanded variants"
|
|
);
|
|
|
|
Self {
|
|
variants,
|
|
mode: BestFittingMode::default(),
|
|
}
|
|
}
|
|
|
|
/// Changes the mode used by this best fitting element to determine whether a variant fits.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ### All Lines
|
|
///
|
|
/// ```
|
|
/// use ruff_formatter::{Formatted, LineWidth, format, format_args, SimpleFormatOptions};
|
|
/// use ruff_formatter::prelude::*;
|
|
///
|
|
/// # fn main() -> FormatResult<()> {
|
|
/// let formatted = format!(
|
|
/// SimpleFormatContext::default(),
|
|
/// [
|
|
/// best_fitting!(
|
|
/// // Everything fits on a single line
|
|
/// format_args!(
|
|
/// group(&format_args![
|
|
/// token("["),
|
|
/// soft_block_indent(&format_args![
|
|
/// token("1,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("2,"),
|
|
/// soft_line_break_or_space(),
|
|
/// token("3"),
|
|
/// ]),
|
|
/// token("]")
|
|
/// ]),
|
|
/// space(),
|
|
/// token("+"),
|
|
/// space(),
|
|
/// token("aVeryLongIdentifier")
|
|
/// ),
|
|
///
|
|
/// // Breaks after `[` and prints each elements on a single line
|
|
/// // The group is necessary because the variant, by default is printed in flat mode and a
|
|
/// // hard line break indicates that the content doesn't fit.
|
|
/// format_args!(
|
|
/// token("["),
|
|
/// group(&block_indent(&format_args![token("1,"), hard_line_break(), token("2,"), hard_line_break(), token("3")])).should_expand(true),
|
|
/// token("]"),
|
|
/// space(),
|
|
/// token("+"),
|
|
/// space(),
|
|
/// token("aVeryLongIdentifier")
|
|
/// ),
|
|
///
|
|
/// // Adds parentheses and indents the body, breaks after the operator
|
|
/// format_args!(
|
|
/// token("("),
|
|
/// block_indent(&format_args![
|
|
/// token("["),
|
|
/// block_indent(&format_args![
|
|
/// token("1,"),
|
|
/// hard_line_break(),
|
|
/// token("2,"),
|
|
/// hard_line_break(),
|
|
/// token("3"),
|
|
/// ]),
|
|
/// token("]"),
|
|
/// hard_line_break(),
|
|
/// token("+"),
|
|
/// space(),
|
|
/// token("aVeryLongIdentifier")
|
|
/// ]),
|
|
/// token(")")
|
|
/// )
|
|
/// ).with_mode(BestFittingMode::AllLines)
|
|
/// ]
|
|
/// )?;
|
|
///
|
|
/// let document = formatted.into_document();
|
|
///
|
|
/// // Takes the first variant if everything fits on a single line
|
|
/// assert_eq!(
|
|
/// "[1, 2, 3] + aVeryLongIdentifier",
|
|
/// Formatted::new(document.clone(), SimpleFormatContext::default())
|
|
/// .print()?
|
|
/// .as_code()
|
|
/// );
|
|
///
|
|
/// // It takes the second if the first variant doesn't fit on a single line. The second variant
|
|
/// // has some additional line breaks to make sure inner groups don't break
|
|
/// assert_eq!(
|
|
/// "[\n\t1,\n\t2,\n\t3\n] + aVeryLongIdentifier",
|
|
/// Formatted::new(document.clone(), SimpleFormatContext::new(SimpleFormatOptions { line_width: 23.try_into().unwrap(), ..SimpleFormatOptions::default() }))
|
|
/// .print()?
|
|
/// .as_code()
|
|
/// );
|
|
///
|
|
/// // Prints the last option as last resort
|
|
/// assert_eq!(
|
|
/// "(\n\t[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n\t+ aVeryLongIdentifier\n)",
|
|
/// Formatted::new(document.clone(), SimpleFormatContext::new(SimpleFormatOptions { line_width: 22.try_into().unwrap(), ..SimpleFormatOptions::default() }))
|
|
/// .print()?
|
|
/// .as_code()
|
|
/// );
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
#[must_use]
|
|
pub fn with_mode(mut self, mode: BestFittingMode) -> Self {
|
|
self.mode = mode;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<Context> Format<Context> for BestFitting<'_, Context> {
|
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
|
let variants = self.variants.items();
|
|
|
|
let mut formatted_variants = Vec::with_capacity(variants.len());
|
|
|
|
for variant in variants {
|
|
let mut buffer = VecBuffer::with_capacity(8, f.state_mut());
|
|
buffer.write_element(FormatElement::Tag(StartEntry));
|
|
buffer.write_fmt(Arguments::from(variant))?;
|
|
buffer.write_element(FormatElement::Tag(EndEntry));
|
|
|
|
formatted_variants.push(buffer.into_vec().into_boxed_slice());
|
|
}
|
|
|
|
// SAFETY: The constructor guarantees that there are always at least two variants. It's, therefore,
|
|
// safe to call into the unsafe `from_vec_unchecked` function
|
|
#[allow(unsafe_code)]
|
|
let element = unsafe {
|
|
FormatElement::BestFitting {
|
|
variants: format_element::BestFittingVariants::from_vec_unchecked(
|
|
formatted_variants,
|
|
),
|
|
mode: self.mode,
|
|
}
|
|
};
|
|
|
|
f.write_element(element);
|
|
|
|
Ok(())
|
|
}
|
|
}
|