mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-22 04:25:11 +00:00
Extend formatter IR to support Black's expression formatting (#5596)
This commit is contained in:
parent
212fd86bf0
commit
d30e9125eb
5 changed files with 576 additions and 56 deletions
|
@ -1410,7 +1410,7 @@ impl<Context> Format<Context> for Group<'_, Context> {
|
|||
|
||||
impl<Context> std::fmt::Debug for Group<'_, Context> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("GroupElements")
|
||||
f.debug_struct("Group")
|
||||
.field("group_id", &self.group_id)
|
||||
.field("should_expand", &self.should_expand)
|
||||
.field("content", &"{{content}}")
|
||||
|
@ -1418,6 +1418,135 @@ impl<Context> std::fmt::Debug for Group<'_, Context> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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(&text("(")),
|
||||
/// indent_if_group_breaks(&format_args![
|
||||
/// soft_line_break(),
|
||||
/// conditional_group(&format_args![
|
||||
/// text("'aaaaaaa'"),
|
||||
/// soft_line_break_or_space(),
|
||||
/// text("+"),
|
||||
/// space(),
|
||||
/// fits_expanded(&conditional_group(&format_args![
|
||||
/// text("["),
|
||||
/// soft_block_indent(&format_args![
|
||||
/// text("'Good morning!',"),
|
||||
/// soft_line_break_or_space(),
|
||||
/// text("'How are you?'"),
|
||||
/// ]),
|
||||
/// text("]"),
|
||||
/// ], tag::Condition::if_group_fits_on_line(parentheses_id))),
|
||||
/// soft_line_break_or_space(),
|
||||
/// text("+"),
|
||||
/// space(),
|
||||
/// conditional_group(&format_args![
|
||||
/// text("'bbbb'"),
|
||||
/// soft_line_break_or_space(),
|
||||
/// text("and"),
|
||||
/// space(),
|
||||
/// text("'c'")
|
||||
/// ], tag::Condition::if_group_fits_on_line(parentheses_id))
|
||||
/// ], tag::Condition::if_breaks()),
|
||||
/// ], parentheses_id),
|
||||
/// soft_line_break(),
|
||||
/// if_group_breaks(&text(")"))
|
||||
/// ])
|
||||
/// .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))
|
||||
}
|
||||
}
|
||||
|
||||
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).
|
||||
|
@ -1841,6 +1970,86 @@ impl<Context> std::fmt::Debug for IndentIfGroupBreaks<'_, Context> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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![
|
||||
/// text("a"),
|
||||
/// soft_line_break_or_space(),
|
||||
/// text("+"),
|
||||
/// space(),
|
||||
/// fits_expanded(&group(&format_args![
|
||||
/// text("["),
|
||||
/// soft_block_indent(&format_args![
|
||||
/// text("a,"), space(), text("# comment"), expand_parent(), soft_line_break_or_space(),
|
||||
/// text("b")
|
||||
/// ]),
|
||||
/// text("]")
|
||||
/// ]))
|
||||
/// ]),
|
||||
/// ])
|
||||
/// });
|
||||
///
|
||||
/// 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.
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility for formatting some content with an inline lambda function.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct FormatWith<Context, T> {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::tag::Tag;
|
||||
use crate::format_element::tag::DedentMode;
|
||||
use crate::format_element::tag::{Condition, DedentMode};
|
||||
use crate::prelude::tag::GroupMode;
|
||||
use crate::prelude::*;
|
||||
use crate::printer::LineEnding;
|
||||
|
@ -33,12 +33,17 @@ impl Document {
|
|||
#[derive(Debug)]
|
||||
enum Enclosing<'a> {
|
||||
Group(&'a tag::Group),
|
||||
ConditionalGroup(&'a tag::ConditionalGroup),
|
||||
FitsExpanded(&'a tag::FitsExpanded),
|
||||
BestFitting,
|
||||
}
|
||||
|
||||
fn expand_parent(enclosing: &[Enclosing]) {
|
||||
if let Some(Enclosing::Group(group)) = enclosing.last() {
|
||||
group.propagate_expand();
|
||||
match enclosing.last() {
|
||||
Some(Enclosing::Group(group)) => group.propagate_expand(),
|
||||
Some(Enclosing::ConditionalGroup(group)) => group.propagate_expand(),
|
||||
Some(Enclosing::FitsExpanded(fits_expanded)) => fits_expanded.propagate_expand(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +63,14 @@ impl Document {
|
|||
Some(Enclosing::Group(group)) => !group.mode().is_flat(),
|
||||
_ => false,
|
||||
},
|
||||
FormatElement::Tag(Tag::StartConditionalGroup(group)) => {
|
||||
enclosing.push(Enclosing::ConditionalGroup(group));
|
||||
false
|
||||
}
|
||||
FormatElement::Tag(Tag::EndConditionalGroup) => match enclosing.pop() {
|
||||
Some(Enclosing::ConditionalGroup(group)) => !group.mode().is_flat(),
|
||||
_ => false,
|
||||
},
|
||||
FormatElement::Interned(interned) => match checked_interned.get(interned) {
|
||||
Some(interned_expands) => *interned_expands,
|
||||
None => {
|
||||
|
@ -79,6 +92,16 @@ impl Document {
|
|||
enclosing.pop();
|
||||
continue;
|
||||
}
|
||||
FormatElement::Tag(Tag::StartFitsExpanded(fits_expanded)) => {
|
||||
enclosing.push(Enclosing::FitsExpanded(fits_expanded));
|
||||
false
|
||||
}
|
||||
FormatElement::Tag(Tag::EndFitsExpanded) => {
|
||||
enclosing.pop();
|
||||
// Fits expanded acts as a boundary
|
||||
expands = false;
|
||||
continue;
|
||||
}
|
||||
FormatElement::StaticText { text } => text.contains('\n'),
|
||||
FormatElement::DynamicText { text, .. } => text.contains('\n'),
|
||||
FormatElement::SourceCodeSlice {
|
||||
|
@ -441,6 +464,29 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||
}
|
||||
}
|
||||
|
||||
StartConditionalGroup(group) => {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("conditional_group(condition:"),
|
||||
space(),
|
||||
group.condition(),
|
||||
text(","),
|
||||
space()
|
||||
]
|
||||
)?;
|
||||
|
||||
match group.mode() {
|
||||
GroupMode::Flat => {}
|
||||
GroupMode::Expand => {
|
||||
write!(f, [text("expand: true,"), space()])?;
|
||||
}
|
||||
GroupMode::Propagated => {
|
||||
write!(f, [text("expand: propagated,"), space()])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StartIndentIfGroupBreaks(id) => {
|
||||
write!(
|
||||
f,
|
||||
|
@ -491,6 +537,28 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||
write!(f, [text("fill(")])?;
|
||||
}
|
||||
|
||||
StartFitsExpanded(tag::FitsExpanded {
|
||||
condition,
|
||||
propagate_expand,
|
||||
}) => {
|
||||
write!(f, [text("fits_expanded(propagate_expand:"), space()])?;
|
||||
|
||||
if propagate_expand.get() {
|
||||
write!(f, [text("true")])?;
|
||||
} else {
|
||||
write!(f, [text("false")])?;
|
||||
}
|
||||
|
||||
write!(f, [text(","), space()])?;
|
||||
|
||||
if let Some(condition) = condition {
|
||||
write!(
|
||||
f,
|
||||
[text("condition:"), space(), condition, text(","), space()]
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
StartEntry => {
|
||||
// handled after the match for all start tags
|
||||
}
|
||||
|
@ -503,8 +571,10 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
|||
| EndAlign
|
||||
| EndIndent
|
||||
| EndGroup
|
||||
| EndConditionalGroup
|
||||
| EndLineSuffix
|
||||
| EndDedent
|
||||
| EndFitsExpanded
|
||||
| EndVerbatim => {
|
||||
write!(f, [ContentArrayEnd, text(")")])?;
|
||||
}
|
||||
|
@ -658,6 +728,31 @@ impl FormatElements for [FormatElement] {
|
|||
}
|
||||
}
|
||||
|
||||
impl Format<IrFormatContext<'_>> for Condition {
|
||||
fn fmt(&self, f: &mut Formatter<IrFormatContext>) -> FormatResult<()> {
|
||||
match (self.mode, self.group_id) {
|
||||
(PrintMode::Flat, None) => write!(f, [text("if_fits_on_line")]),
|
||||
(PrintMode::Flat, Some(id)) => write!(
|
||||
f,
|
||||
[
|
||||
text("if_group_fits_on_line("),
|
||||
dynamic_text(&std::format!("\"{id:?}\""), None),
|
||||
text(")")
|
||||
]
|
||||
),
|
||||
(PrintMode::Expanded, None) => write!(f, [text("if_breaks")]),
|
||||
(PrintMode::Expanded, Some(id)) => write!(
|
||||
f,
|
||||
[
|
||||
text("if_group_breaks("),
|
||||
dynamic_text(&std::format!("\"{id:?}\""), None),
|
||||
text(")")
|
||||
]
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
|
|
|
@ -33,6 +33,16 @@ pub enum Tag {
|
|||
StartGroup(Group),
|
||||
EndGroup,
|
||||
|
||||
/// Creates a logical group similar to [`Tag::StartGroup`] but only if the condition is met.
|
||||
/// This is an optimized representation for (assuming the content should only be grouped if another group fits):
|
||||
///
|
||||
/// ```text
|
||||
/// if_group_breaks(content, other_group_id),
|
||||
/// if_group_fits_on_line(group(&content), other_group_id)
|
||||
/// ```
|
||||
StartConditionalGroup(ConditionalGroup),
|
||||
EndConditionalGroup,
|
||||
|
||||
/// Allows to specify content that gets printed depending on whatever the enclosing group
|
||||
/// is printed on a single line or multiple lines. See [crate::builders::if_group_breaks] for examples.
|
||||
StartConditionalContent(Condition),
|
||||
|
@ -67,6 +77,9 @@ pub enum Tag {
|
|||
/// See [crate::builders::labelled] for documentation.
|
||||
StartLabelled(LabelId),
|
||||
EndLabelled,
|
||||
|
||||
StartFitsExpanded(FitsExpanded),
|
||||
EndFitsExpanded,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
|
@ -77,7 +90,8 @@ impl Tag {
|
|||
Tag::StartIndent
|
||||
| Tag::StartAlign(_)
|
||||
| Tag::StartDedent(_)
|
||||
| Tag::StartGroup { .. }
|
||||
| Tag::StartGroup(_)
|
||||
| Tag::StartConditionalGroup(_)
|
||||
| Tag::StartConditionalContent(_)
|
||||
| Tag::StartIndentIfGroupBreaks(_)
|
||||
| Tag::StartFill
|
||||
|
@ -85,6 +99,7 @@ impl Tag {
|
|||
| Tag::StartLineSuffix
|
||||
| Tag::StartVerbatim(_)
|
||||
| Tag::StartLabelled(_)
|
||||
| Tag::StartFitsExpanded(_)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -101,6 +116,7 @@ impl Tag {
|
|||
StartAlign(_) | EndAlign => TagKind::Align,
|
||||
StartDedent(_) | EndDedent => TagKind::Dedent,
|
||||
StartGroup(_) | EndGroup => TagKind::Group,
|
||||
StartConditionalGroup(_) | EndConditionalGroup => TagKind::ConditionalGroup,
|
||||
StartConditionalContent(_) | EndConditionalContent => TagKind::ConditionalContent,
|
||||
StartIndentIfGroupBreaks(_) | EndIndentIfGroupBreaks => TagKind::IndentIfGroupBreaks,
|
||||
StartFill | EndFill => TagKind::Fill,
|
||||
|
@ -108,6 +124,7 @@ impl Tag {
|
|||
StartLineSuffix | EndLineSuffix => TagKind::LineSuffix,
|
||||
StartVerbatim(_) | EndVerbatim => TagKind::Verbatim,
|
||||
StartLabelled(_) | EndLabelled => TagKind::Labelled,
|
||||
StartFitsExpanded { .. } | EndFitsExpanded => TagKind::FitsExpanded,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +139,7 @@ pub enum TagKind {
|
|||
Align,
|
||||
Dedent,
|
||||
Group,
|
||||
ConditionalGroup,
|
||||
ConditionalContent,
|
||||
IndentIfGroupBreaks,
|
||||
Fill,
|
||||
|
@ -129,6 +147,7 @@ pub enum TagKind {
|
|||
LineSuffix,
|
||||
Verbatim,
|
||||
Labelled,
|
||||
FitsExpanded,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Default, Clone, Eq, PartialEq)]
|
||||
|
@ -150,6 +169,27 @@ impl GroupMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
pub struct FitsExpanded {
|
||||
pub(crate) condition: Option<Condition>,
|
||||
pub(crate) propagate_expand: Cell<bool>,
|
||||
}
|
||||
|
||||
impl FitsExpanded {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_condition(mut self, condition: Option<Condition>) -> Self {
|
||||
self.condition = condition;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn propagate_expand(&self) {
|
||||
self.propagate_expand.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
pub struct Group {
|
||||
id: Option<GroupId>,
|
||||
|
@ -189,6 +229,33 @@ impl Group {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ConditionalGroup {
|
||||
mode: Cell<GroupMode>,
|
||||
condition: Condition,
|
||||
}
|
||||
|
||||
impl ConditionalGroup {
|
||||
pub fn new(condition: Condition) -> Self {
|
||||
Self {
|
||||
mode: Cell::new(GroupMode::Flat),
|
||||
condition,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn condition(&self) -> Condition {
|
||||
self.condition
|
||||
}
|
||||
|
||||
pub fn propagate_expand(&self) {
|
||||
self.mode.set(GroupMode::Propagated)
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> GroupMode {
|
||||
self.mode.get()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum DedentMode {
|
||||
/// Reduces the indent by a level (if the current indent is > 0)
|
||||
|
@ -198,7 +265,7 @@ pub enum DedentMode {
|
|||
Root,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Condition {
|
||||
/// - `Flat` -> Omitted if the enclosing group is a multiline group, printed for groups fitting on a single line
|
||||
/// - `Expanded` -> Omitted if the enclosing group fits on a single line, printed if the group breaks over multiple lines.
|
||||
|
@ -217,6 +284,34 @@ impl Condition {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn if_fits_on_line() -> Self {
|
||||
Self {
|
||||
mode: PrintMode::Flat,
|
||||
group_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn if_group_fits_on_line(group_id: GroupId) -> Self {
|
||||
Self {
|
||||
mode: PrintMode::Flat,
|
||||
group_id: Some(group_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn if_breaks() -> Self {
|
||||
Self {
|
||||
mode: PrintMode::Expanded,
|
||||
group_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn if_group_breaks(group_id: GroupId) -> Self {
|
||||
Self {
|
||||
mode: PrintMode::Expanded,
|
||||
group_id: Some(group_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_group_id(mut self, id: Option<GroupId>) -> Self {
|
||||
self.group_id = id;
|
||||
self
|
||||
|
|
|
@ -7,6 +7,7 @@ mod stack;
|
|||
use crate::format_element::document::Document;
|
||||
use crate::format_element::tag::{Condition, GroupMode};
|
||||
use crate::format_element::{BestFittingMode, BestFittingVariants, LineMode, PrintMode};
|
||||
use crate::prelude::tag;
|
||||
use crate::prelude::tag::{DedentMode, Tag, TagKind, VerbatimKind};
|
||||
use crate::printer::call_stack::{
|
||||
CallStack, FitsCallStack, PrintCallStack, PrintElementArgs, StackFrame,
|
||||
|
@ -143,40 +144,26 @@ impl<'a> Printer<'a> {
|
|||
}
|
||||
|
||||
FormatElement::Tag(StartGroup(group)) => {
|
||||
let group_mode = match group.mode() {
|
||||
GroupMode::Expand | GroupMode::Propagated => PrintMode::Expanded,
|
||||
GroupMode::Flat => {
|
||||
match args.mode() {
|
||||
PrintMode::Flat if self.state.measured_group_fits => {
|
||||
// A parent group has already verified that this group fits on a single line
|
||||
// Thus, just continue in flat mode
|
||||
PrintMode::Flat
|
||||
}
|
||||
// The printer is either in expanded mode or it's necessary to re-measure if the group fits
|
||||
// because the printer printed a line break
|
||||
_ => {
|
||||
self.state.measured_group_fits = true;
|
||||
|
||||
// Measure to see if the group fits up on a single line. If that's the case,
|
||||
// print the group in "flat" mode, otherwise continue in expanded mode
|
||||
stack.push(TagKind::Group, args.with_print_mode(PrintMode::Flat));
|
||||
let fits = self.fits(queue, stack)?;
|
||||
stack.pop(TagKind::Group)?;
|
||||
|
||||
if fits {
|
||||
PrintMode::Flat
|
||||
} else {
|
||||
PrintMode::Expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
stack.push(TagKind::Group, args.with_print_mode(group_mode));
|
||||
let print_mode =
|
||||
self.print_group(TagKind::Group, group.mode(), args, queue, stack)?;
|
||||
|
||||
if let Some(id) = group.id() {
|
||||
self.state.group_modes.insert_print_mode(id, group_mode);
|
||||
self.state.group_modes.insert_print_mode(id, print_mode);
|
||||
}
|
||||
}
|
||||
|
||||
FormatElement::Tag(StartConditionalGroup(group)) => {
|
||||
let condition = group.condition();
|
||||
let expected_mode = match condition.group_id {
|
||||
None => args.mode(),
|
||||
Some(id) => self.state.group_modes.unwrap_print_mode(id, element),
|
||||
};
|
||||
|
||||
if expected_mode == condition.mode {
|
||||
self.print_group(TagKind::ConditionalGroup, group.mode(), args, queue, stack)?;
|
||||
} else {
|
||||
// Condition isn't met, render as normal content
|
||||
stack.push(TagKind::ConditionalGroup, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,6 +231,29 @@ impl<'a> Printer<'a> {
|
|||
stack.push(TagKind::Verbatim, args);
|
||||
}
|
||||
|
||||
FormatElement::Tag(StartFitsExpanded(tag::FitsExpanded { condition, .. })) => {
|
||||
let condition_met = match condition {
|
||||
Some(condition) => {
|
||||
let group_mode = match condition.group_id {
|
||||
Some(group_id) => {
|
||||
self.state.group_modes.unwrap_print_mode(group_id, element)
|
||||
}
|
||||
None => args.mode(),
|
||||
};
|
||||
|
||||
condition.mode == group_mode
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
if condition_met {
|
||||
// We measured the inner groups all in expanded. It now is necessary to measure if the inner groups fit as well.
|
||||
self.state.measured_group_fits = false;
|
||||
}
|
||||
|
||||
stack.push(TagKind::FitsExpanded, args);
|
||||
}
|
||||
|
||||
FormatElement::Tag(tag @ (StartLabelled(_) | StartEntry)) => {
|
||||
stack.push(tag.kind(), args);
|
||||
}
|
||||
|
@ -252,11 +262,13 @@ impl<'a> Printer<'a> {
|
|||
tag @ (EndLabelled
|
||||
| EndEntry
|
||||
| EndGroup
|
||||
| EndConditionalGroup
|
||||
| EndIndent
|
||||
| EndDedent
|
||||
| EndAlign
|
||||
| EndConditionalContent
|
||||
| EndIndentIfGroupBreaks
|
||||
| EndFitsExpanded
|
||||
| EndVerbatim
|
||||
| EndLineSuffix
|
||||
| EndFill),
|
||||
|
@ -275,6 +287,49 @@ impl<'a> Printer<'a> {
|
|||
result
|
||||
}
|
||||
|
||||
fn print_group(
|
||||
&mut self,
|
||||
kind: TagKind,
|
||||
mode: GroupMode,
|
||||
args: PrintElementArgs,
|
||||
queue: &mut PrintQueue<'a>,
|
||||
stack: &mut PrintCallStack,
|
||||
) -> PrintResult<PrintMode> {
|
||||
let group_mode = match mode {
|
||||
GroupMode::Expand | GroupMode::Propagated => PrintMode::Expanded,
|
||||
GroupMode::Flat => {
|
||||
match args.mode() {
|
||||
PrintMode::Flat if self.state.measured_group_fits => {
|
||||
// A parent group has already verified that this group fits on a single line
|
||||
// Thus, just continue in flat mode
|
||||
PrintMode::Flat
|
||||
}
|
||||
// The printer is either in expanded mode or it's necessary to re-measure if the group fits
|
||||
// because the printer printed a line break
|
||||
_ => {
|
||||
self.state.measured_group_fits = true;
|
||||
|
||||
// Measure to see if the group fits up on a single line. If that's the case,
|
||||
// print the group in "flat" mode, otherwise continue in expanded mode
|
||||
stack.push(kind, args.with_print_mode(PrintMode::Flat));
|
||||
let fits = self.fits(queue, stack)?;
|
||||
stack.pop(kind)?;
|
||||
|
||||
if fits {
|
||||
PrintMode::Flat
|
||||
} else {
|
||||
PrintMode::Expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
stack.push(kind, args.with_print_mode(group_mode));
|
||||
|
||||
Ok(group_mode)
|
||||
}
|
||||
|
||||
fn print_text(&mut self, text: &str, source_range: Option<TextRange>) {
|
||||
if !self.state.pending_indent.is_empty() {
|
||||
let (indent_char, repeat_count) = match self.options.indent_style() {
|
||||
|
@ -1050,22 +1105,24 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
|||
}
|
||||
|
||||
FormatElement::Tag(StartGroup(group)) => {
|
||||
if self.must_be_flat && !group.mode().is_flat() {
|
||||
return Ok(Fits::No);
|
||||
return self.fits_group(TagKind::Group, group.mode(), group.id(), args);
|
||||
}
|
||||
|
||||
// Continue printing groups in expanded mode if measuring a `fits_expanded` element
|
||||
let print_mode = if !group.mode().is_flat() {
|
||||
PrintMode::Expanded
|
||||
} else {
|
||||
args.mode()
|
||||
FormatElement::Tag(StartConditionalGroup(group)) => {
|
||||
let condition = group.condition();
|
||||
|
||||
let print_mode = match condition.group_id {
|
||||
None => args.mode(),
|
||||
Some(group_id) => self
|
||||
.group_modes()
|
||||
.get_print_mode(group_id)
|
||||
.unwrap_or_else(|| args.mode()),
|
||||
};
|
||||
|
||||
self.stack
|
||||
.push(TagKind::Group, args.with_print_mode(print_mode));
|
||||
|
||||
if let Some(id) = group.id() {
|
||||
self.group_modes_mut().insert_print_mode(id, print_mode);
|
||||
if condition.mode == print_mode {
|
||||
return self.fits_group(TagKind::ConditionalGroup, group.mode(), None, args);
|
||||
} else {
|
||||
self.stack.push(TagKind::ConditionalGroup, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1113,6 +1170,42 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
|||
return invalid_end_tag(TagKind::LineSuffix, self.stack.top_kind());
|
||||
}
|
||||
|
||||
FormatElement::Tag(StartFitsExpanded(tag::FitsExpanded {
|
||||
condition,
|
||||
propagate_expand,
|
||||
})) => {
|
||||
let condition_met = match condition {
|
||||
Some(condition) => {
|
||||
let group_mode = match condition.group_id {
|
||||
Some(group_id) => self
|
||||
.group_modes()
|
||||
.get_print_mode(group_id)
|
||||
.unwrap_or_else(|| args.mode()),
|
||||
None => args.mode(),
|
||||
};
|
||||
|
||||
condition.mode == group_mode
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
if condition_met {
|
||||
// Measure in fully expanded mode.
|
||||
self.stack.push(
|
||||
TagKind::FitsExpanded,
|
||||
args.with_print_mode(PrintMode::Expanded)
|
||||
.with_measure_mode(MeasureMode::AllLines),
|
||||
)
|
||||
} else {
|
||||
if propagate_expand.get() && args.mode().is_flat() {
|
||||
return Ok(Fits::No);
|
||||
}
|
||||
|
||||
// As usual
|
||||
self.stack.push(TagKind::FitsExpanded, args)
|
||||
}
|
||||
}
|
||||
|
||||
FormatElement::Tag(
|
||||
tag @ (StartFill | StartVerbatim(_) | StartLabelled(_) | StartEntry),
|
||||
) => {
|
||||
|
@ -1125,11 +1218,13 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
|||
| EndLabelled
|
||||
| EndEntry
|
||||
| EndGroup
|
||||
| EndConditionalGroup
|
||||
| EndIndentIfGroupBreaks
|
||||
| EndConditionalContent
|
||||
| EndAlign
|
||||
| EndDedent
|
||||
| EndIndent),
|
||||
| EndIndent
|
||||
| EndFitsExpanded),
|
||||
) => {
|
||||
self.stack.pop(tag.kind())?;
|
||||
}
|
||||
|
@ -1138,6 +1233,34 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
|||
Ok(Fits::Maybe)
|
||||
}
|
||||
|
||||
fn fits_group(
|
||||
&mut self,
|
||||
kind: TagKind,
|
||||
mode: GroupMode,
|
||||
id: Option<GroupId>,
|
||||
args: PrintElementArgs,
|
||||
) -> PrintResult<Fits> {
|
||||
if self.must_be_flat && !mode.is_flat() {
|
||||
return Ok(Fits::No);
|
||||
}
|
||||
|
||||
// Continue printing groups in expanded mode if measuring a `best_fitting` element where
|
||||
// a group expands.
|
||||
let print_mode = if !mode.is_flat() {
|
||||
PrintMode::Expanded
|
||||
} else {
|
||||
args.mode()
|
||||
};
|
||||
|
||||
self.stack.push(kind, args.with_print_mode(print_mode));
|
||||
|
||||
if let Some(id) = id {
|
||||
self.group_modes_mut().insert_print_mode(id, print_mode);
|
||||
}
|
||||
|
||||
Ok(Fits::Maybe)
|
||||
}
|
||||
|
||||
fn fits_text(&mut self, text: &str) -> Fits {
|
||||
let indent = std::mem::take(&mut self.state.pending_indent);
|
||||
self.state.line_width += indent.level() as usize * self.options().indent_width() as usize
|
||||
|
|
|
@ -29,10 +29,8 @@ impl Debug for DebugComment<'_> {
|
|||
|
||||
strut
|
||||
.field("text", &self.comment.slice.text(self.source_code))
|
||||
.field("position", &self.comment.line_position);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
strut.field("formatted", &self.comment.formatted.get());
|
||||
.field("position", &self.comment.line_position)
|
||||
.field("formatted", &self.comment.formatted.get());
|
||||
|
||||
strut.finish()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue