diff --git a/crates/ruff_formatter/src/arguments.rs b/crates/ruff_formatter/src/arguments.rs index dae1a2df3edaa2..ddfda91ca7ba5d 100644 --- a/crates/ruff_formatter/src/arguments.rs +++ b/crates/ruff_formatter/src/arguments.rs @@ -133,7 +133,7 @@ mod tests { #[test] fn test_nesting() { - let mut context = FormatState::new(()); + let mut context = FormatState::new(SimpleFormatContext::default()); let mut buffer = VecBuffer::new(&mut context); write!( @@ -151,14 +151,26 @@ mod tests { assert_eq!( buffer.into_vec(), vec![ - FormatElement::StaticText { text: "function" }, + FormatElement::StaticText { + text: "function", + text_width: TextWidth::new_width(8) + }, FormatElement::Space, - FormatElement::StaticText { text: "a" }, + FormatElement::StaticText { + text: "a", + text_width: TextWidth::new_width(1) + }, FormatElement::Space, // Group FormatElement::Tag(Tag::StartGroup(tag::Group::new())), - FormatElement::StaticText { text: "(" }, - FormatElement::StaticText { text: ")" }, + FormatElement::StaticText { + text: "(", + text_width: TextWidth::new_width(1) + }, + FormatElement::StaticText { + text: ")", + text_width: TextWidth::new_width(1) + }, FormatElement::Tag(Tag::EndGroup) ] ); diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 1cbe01e553853a..37492fa3561bfe 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -1,7 +1,9 @@ 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::{ + format_element, write, Argument, Arguments, FormatContext, FormatOptions, GroupId, TextSize, +}; use crate::{Buffer, VecBuffer}; use ruff_text_size::TextRange; @@ -264,9 +266,15 @@ pub struct StaticText { text: &'static str, } -impl Format for StaticText { +impl Format for StaticText +where + Context: FormatContext, +{ fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - f.write_element(FormatElement::StaticText { text: self.text }) + f.write_element(FormatElement::StaticText { + text: self.text, + text_width: TextWidth::from_text(self.text, f.options().tab_width()), + }) } } @@ -343,7 +351,10 @@ pub struct DynamicText<'a> { position: Option, } -impl Format for DynamicText<'_> { +impl Format for DynamicText<'_> +where + Context: FormatContext, +{ fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { if let Some(source_position) = self.position { f.write_element(FormatElement::SourcePosition(source_position))?; @@ -351,6 +362,7 @@ impl Format for DynamicText<'_> { f.write_element(FormatElement::DynamicText { text: self.text.to_string().into_boxed_str(), + text_width: TextWidth::from_text(self.text, f.options().tab_width()), }) } } @@ -398,28 +410,27 @@ where let slice = source_code.slice(self.range); debug_assert_no_newlines(slice.text(source_code)); - let contains_newlines = match self.new_lines { + let text_width = 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 + TextWidth::Multiline } ContainsNewlines::No => { debug_assert!( !slice.text(source_code).contains('\n'), "Text contains new line characters but the caller specified that it does not." ); - false + TextWidth::from_text(slice.text(source_code), f.context().options().tab_width()) + } + ContainsNewlines::Detect => { + TextWidth::from_text(slice.text(source_code), f.context().options().tab_width()) } - ContainsNewlines::Detect => slice.text(source_code).contains('\n'), }; - f.write_element(FormatElement::SourceCodeSlice { - slice, - contains_newlines, - }) + f.write_element(FormatElement::SourceCodeSlice { slice, text_width }) } } diff --git a/crates/ruff_formatter/src/format_element.rs b/crates/ruff_formatter/src/format_element.rs index 5598f103e1a572..29f1135d2bb7eb 100644 --- a/crates/ruff_formatter/src/format_element.rs +++ b/crates/ruff_formatter/src/format_element.rs @@ -3,12 +3,14 @@ pub mod tag; use std::borrow::Cow; use std::hash::{Hash, Hasher}; +use std::num::NonZeroU32; use std::ops::Deref; use std::rc::Rc; +use unicode_width::UnicodeWidthChar; use crate::format_element::tag::{GroupMode, LabelId, Tag}; use crate::source_code::SourceCodeSlice; -use crate::TagKind; +use crate::{TabWidth, TagKind}; use ruff_text_size::TextSize; /// Language agnostic IR for formatting source code. @@ -31,20 +33,23 @@ pub enum FormatElement { SourcePosition(TextSize), /// Token constructed by the formatter from a static string - StaticText { text: &'static str }, + StaticText { + text: &'static str, + text_width: TextWidth, + }, /// Token constructed from the input source as a dynamic /// string. DynamicText { /// There's no need for the text to be mutable, using `Box` safes 8 bytes over `String`. text: Box, + text_width: TextWidth, }, /// Text that gets emitted as it is in the source code. Optimized to avoid any allocations. SourceCodeSlice { slice: SourceCodeSlice, - /// Whether the string contains any new line characters - contains_newlines: bool, + text_width: TextWidth, }, /// Prevents that line suffixes move past this boundary. Forces the printer to print any pending @@ -69,19 +74,18 @@ impl std::fmt::Debug for FormatElement { FormatElement::Space => write!(fmt, "Space"), FormatElement::Line(mode) => fmt.debug_tuple("Line").field(mode).finish(), FormatElement::ExpandParent => write!(fmt, "ExpandParent"), - FormatElement::StaticText { text } => { - fmt.debug_tuple("StaticText").field(text).finish() - } + FormatElement::StaticText { text, text_width } => fmt + .debug_tuple("StaticText") + .field(text) + .field(text_width) + .finish(), FormatElement::DynamicText { text, .. } => { fmt.debug_tuple("DynamicText").field(text).finish() } - FormatElement::SourceCodeSlice { - slice, - contains_newlines, - } => fmt + FormatElement::SourceCodeSlice { slice, text_width } => fmt .debug_tuple("Text") .field(slice) - .field(contains_newlines) + .field(text_width) .finish(), FormatElement::LineSuffixBoundary => write!(fmt, "LineSuffixBoundary"), FormatElement::BestFitting { variants } => fmt @@ -256,11 +260,12 @@ impl FormatElements for FormatElement { FormatElement::ExpandParent => true, FormatElement::Tag(Tag::StartGroup(group)) => !group.mode().is_flat(), FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty), - FormatElement::StaticText { text } => text.contains('\n'), + FormatElement::StaticText { + text: _, + text_width, + } => text_width.is_multiline(), FormatElement::DynamicText { text, .. } => text.contains('\n'), - FormatElement::SourceCodeSlice { - contains_newlines, .. - } => *contains_newlines, + FormatElement::SourceCodeSlice { text_width, .. } => text_width.is_multiline(), FormatElement::Interned(interned) => interned.will_break(), // Traverse into the most flat version because the content is guaranteed to expand when even // the most flat version contains some content that forces a break. @@ -380,6 +385,46 @@ pub trait FormatElements { fn end_tag(&self, kind: TagKind) -> Option<&Tag>; } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TextWidth { + Width(NonZeroU32), + Multiline, +} + +impl TextWidth { + pub(crate) const fn new_width(width: u32) -> Self { + // SAFETY: 1 + x is guaranteed to be non zero + #[allow(unsafe_code)] + TextWidth::Width(unsafe { NonZeroU32::new_unchecked(width.saturating_add(1)) }) + } + + pub(crate) fn from_text(text: &str, tab_width: TabWidth) -> TextWidth { + let mut width = 0u32; + + for c in text.chars() { + let char_width = match c { + '\t' => tab_width.value(), + '\n' => return TextWidth::Multiline, + c => c.width().unwrap_or(0) as u32, + }; + width += char_width as u32; + } + + Self::new_width(width) + } + + pub(crate) const fn width(self) -> Option { + match self { + TextWidth::Width(width) => Some(width.get() - 1), + TextWidth::Multiline => None, + } + } + + pub(crate) const fn is_multiline(self) -> bool { + matches!(self, TextWidth::Multiline) + } +} + #[cfg(test)] mod tests { diff --git a/crates/ruff_formatter/src/format_element/document.rs b/crates/ruff_formatter/src/format_element/document.rs index 7db6e9de2af5df..f1f311a027a1f7 100644 --- a/crates/ruff_formatter/src/format_element/document.rs +++ b/crates/ruff_formatter/src/format_element/document.rs @@ -4,7 +4,7 @@ use crate::prelude::tag::GroupMode; use crate::prelude::*; use crate::printer::LineEnding; use crate::source_code::SourceCode; -use crate::{format, write}; +use crate::{format, write, TabWidth}; use crate::{ BufferExtensions, Format, FormatContext, FormatElement, FormatOptions, FormatResult, Formatter, IndentStyle, LineWidth, PrinterOptions, @@ -103,11 +103,12 @@ impl Document { expands = false; continue; } - FormatElement::StaticText { text } => text.contains('\n'), + FormatElement::StaticText { + text: _, + text_width, + } => text_width.is_multiline(), FormatElement::DynamicText { text, .. } => text.contains('\n'), - FormatElement::SourceCodeSlice { - contains_newlines, .. - } => *contains_newlines, + FormatElement::SourceCodeSlice { text_width, .. } => text_width.is_multiline(), FormatElement::ExpandParent | FormatElement::Line(LineMode::Hard | LineMode::Empty) => true, _ => false, @@ -211,13 +212,17 @@ impl FormatOptions for IrFormatOptions { IndentStyle::Space(2) } + fn tab_width(&self) -> TabWidth { + TabWidth::default() + } + fn line_width(&self) -> LineWidth { LineWidth(80) } fn as_print_options(&self) -> PrinterOptions { PrinterOptions { - tab_width: 2, + tab_width: self.tab_width(), print_width: self.line_width().into(), line_ending: LineEnding::LineFeed, indent_style: IndentStyle::Space(2), @@ -255,11 +260,13 @@ impl Format> for &[FormatElement] { element: &FormatElement, f: &mut Formatter, ) -> FormatResult<()> { - let text = match element { - FormatElement::StaticText { text } => text, - FormatElement::DynamicText { text } => text.as_ref(), - FormatElement::SourceCodeSlice { slice, .. } => { - slice.text(f.context().source_code()) + let (text, text_width) = match element { + FormatElement::StaticText { text, text_width } => (*text, *text_width), + FormatElement::DynamicText { text, text_width } => { + (text.as_ref(), *text_width) + } + FormatElement::SourceCodeSlice { slice, text_width } => { + (slice.text(f.context().source_code()), *text_width) } _ => unreachable!(), }; @@ -267,6 +274,7 @@ impl Format> for &[FormatElement] { if text.contains('"') { f.write_element(FormatElement::DynamicText { text: text.replace('"', r#"\""#).into(), + text_width, }) } else { f.write_element(element.clone()) @@ -876,16 +884,25 @@ mod tests { use Tag::*; let document = Document::from(vec![ - FormatElement::StaticText { text: "[" }, + FormatElement::StaticText { + text: "[", + text_width: TextWidth::new_width(1), + }, FormatElement::Tag(StartGroup(tag::Group::new())), FormatElement::Tag(StartIndent), FormatElement::Line(LineMode::Soft), - FormatElement::StaticText { text: "a" }, + FormatElement::StaticText { + text: "a", + text_width: TextWidth::new_width(1), + }, // Close group instead of indent FormatElement::Tag(EndGroup), FormatElement::Line(LineMode::Soft), FormatElement::Tag(EndIndent), - FormatElement::StaticText { text: "]" }, + FormatElement::StaticText { + text: "]", + text_width: TextWidth::new_width(1), + }, // End tag without start FormatElement::Tag(EndIndent), // Start tag without an end diff --git a/crates/ruff_formatter/src/lib.rs b/crates/ruff_formatter/src/lib.rs index e98d998ef36877..5f24e383270278 100644 --- a/crates/ruff_formatter/src/lib.rs +++ b/crates/ruff_formatter/src/lib.rs @@ -52,7 +52,7 @@ pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, Pri pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS}; pub use group_id::GroupId; use ruff_text_size::{TextRange, TextSize}; -use std::num::ParseIntError; +use std::num::{NonZeroU8, ParseIntError, TryFromIntError}; use std::str::FromStr; #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] @@ -109,6 +109,35 @@ impl std::fmt::Display for IndentStyle { } } +/// Width of a tab. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct TabWidth(NonZeroU8); + +impl TabWidth { + /// Return the numeric value for this [LineWidth] + pub const fn value(&self) -> u32 { + self.0.get() as u32 + } +} + +impl Default for TabWidth { + fn default() -> Self { + // SAFETY: 2 is guaranteed to not be 0 + #[allow(unsafe_code)] + Self(unsafe { NonZeroU8::new_unchecked(2) }) + } +} + +impl TryFrom for TabWidth { + type Error = TryFromIntError; + + fn try_from(value: u8) -> Result { + NonZeroU8::try_from(value).map(Self) + } +} + /// Validated value for the `line_width` formatter options /// /// The allowed range of values is 1..=320 @@ -214,6 +243,9 @@ pub trait FormatOptions { /// The indent style. fn indent_style(&self) -> IndentStyle; + /// The visual width of a tab character. + fn tab_width(&self) -> TabWidth; + /// What's the max width of a line. Defaults to 80. fn line_width(&self) -> LineWidth; @@ -265,6 +297,10 @@ impl FormatOptions for SimpleFormatOptions { self.indent_style } + fn tab_width(&self) -> TabWidth { + TabWidth::default() + } + fn line_width(&self) -> LineWidth { self.line_width } @@ -272,6 +308,7 @@ impl FormatOptions for SimpleFormatOptions { fn as_print_options(&self) -> PrinterOptions { PrinterOptions::default() .with_indent(self.indent_style) + .with_tab_width(self.tab_width()) .with_print_width(self.line_width.into()) } } diff --git a/crates/ruff_formatter/src/macros.rs b/crates/ruff_formatter/src/macros.rs index 44ee89a43b6df8..f674586f5e1a24 100644 --- a/crates/ruff_formatter/src/macros.rs +++ b/crates/ruff_formatter/src/macros.rs @@ -343,28 +343,31 @@ mod tests { struct TestFormat; - impl Format<()> for TestFormat { - fn fmt(&self, f: &mut Formatter<()>) -> FormatResult<()> { + impl Format for TestFormat { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { write!(f, [text("test")]) } } #[test] fn test_single_element() { - let mut state = FormatState::new(()); + let mut state = FormatState::new(SimpleFormatContext::default()); let mut buffer = VecBuffer::new(&mut state); write![&mut buffer, [TestFormat]].unwrap(); assert_eq!( buffer.into_vec(), - vec![FormatElement::StaticText { text: "test" }] + vec![FormatElement::StaticText { + text: "test", + text_width: TextWidth::new_width(4) + }] ); } #[test] fn test_multiple_elements() { - let mut state = FormatState::new(()); + let mut state = FormatState::new(SimpleFormatContext::default()); let mut buffer = VecBuffer::new(&mut state); write![ @@ -376,11 +379,20 @@ mod tests { assert_eq!( buffer.into_vec(), vec![ - FormatElement::StaticText { text: "a" }, + FormatElement::StaticText { + text: "a", + text_width: TextWidth::new_width(1) + }, FormatElement::Space, - FormatElement::StaticText { text: "simple" }, + FormatElement::StaticText { + text: "simple", + text_width: TextWidth::new_width(6) + }, FormatElement::Space, - FormatElement::StaticText { text: "test" } + FormatElement::StaticText { + text: "test", + text_width: TextWidth::new_width(4) + } ] ); } diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index f65eb6fdf37911..9c010bafac3307 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -7,8 +7,8 @@ mod stack; use crate::format_element::document::Document; use crate::format_element::tag::{Condition, GroupMode}; use crate::format_element::{BestFittingVariants, LineMode, PrintMode}; -use crate::prelude::tag; use crate::prelude::tag::{DedentMode, Tag, TagKind, VerbatimKind}; +use crate::prelude::{tag, TextWidth}; use crate::printer::call_stack::{ CallStack, FitsCallStack, PrintCallStack, PrintElementArgs, StackFrame, }; @@ -90,31 +90,35 @@ impl<'a> Printer<'a> { let args = stack.top(); match element { - FormatElement::Space => self.print_text(" ", None), - FormatElement::StaticText { text } => self.print_text(text, None), - FormatElement::DynamicText { text } => self.print_text(text, None), - FormatElement::SourceCodeSlice { slice, .. } => { + FormatElement::Space => self.print_text(" ", TextWidth::new_width(1), None), + FormatElement::StaticText { text, text_width } => { + self.print_text(text, *text_width, None) + } + FormatElement::DynamicText { text, text_width } => { + self.print_text(text, *text_width, None) + } + FormatElement::SourceCodeSlice { slice, text_width } => { let text = slice.text(self.source_code); - self.print_text(text, Some(slice.range())); + self.print_text(text, *text_width, Some(slice.range())); } FormatElement::Line(line_mode) => { if args.mode().is_flat() && matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace) { if line_mode == &LineMode::SoftOrSpace { - self.print_text(" ", None); + self.print_text(" ", TextWidth::new_width(1), None); } } else if self.state.line_suffixes.has_pending() { self.flush_line_suffixes(queue, stack, Some(element)); } else { // Only print a newline if the current line isn't already empty if self.state.line_width > 0 { - self.print_str("\n"); + self.print_char('\n'); } // Print a second line break if this is an empty line if line_mode == &LineMode::Empty { - self.print_str("\n"); + self.print_char('\n'); } self.state.pending_indent = args.indention(); @@ -332,7 +336,7 @@ impl<'a> Printer<'a> { Ok(group_mode) } - fn print_text(&mut self, text: &str, source_range: Option) { + fn print_text(&mut self, text: &str, text_width: TextWidth, source_range: Option) { if !self.state.pending_indent.is_empty() { let (indent_char, repeat_count) = match self.options.indent_style() { IndentStyle::Tab => ('\t', 1), @@ -370,7 +374,7 @@ impl<'a> Printer<'a> { self.push_marker(); - self.print_str(text); + self.print_str(text, text_width); if let Some(range) = source_range { self.state.source_position = range.end(); @@ -687,9 +691,14 @@ impl<'a> Printer<'a> { invalid_end_tag(TagKind::Entry, stack.top_kind()) } - fn print_str(&mut self, content: &str) { - for char in content.chars() { - self.print_char(char); + fn print_str(&mut self, content: &str, text_width: TextWidth) { + if let Some(width) = text_width.width() { + self.state.buffer.push_str(content); + self.state.line_width += width; + } else { + for char in content.chars() { + self.print_char(char); + } } } @@ -709,7 +718,7 @@ impl<'a> Printer<'a> { #[allow(clippy::cast_possible_truncation)] let char_width = if char == '\t' { - u32::from(self.options.tab_width) + self.options.tab_width.value() } else { // SAFETY: A u32 is sufficient to represent the width of a file <= 4GB char.width().unwrap_or(0) as u32 @@ -1018,12 +1027,14 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { let args = self.stack.top(); match element { - FormatElement::Space => return Ok(self.fits_text(" ", args)), + FormatElement::Space => return Ok(self.fits_text(" ", TextWidth::new_width(1), args)), FormatElement::Line(line_mode) => { match args.mode() { PrintMode::Flat => match line_mode { - LineMode::SoftOrSpace => return Ok(self.fits_text(" ", args)), + LineMode::SoftOrSpace => { + return Ok(self.fits_text(" ", TextWidth::new_width(1), args)) + } LineMode::Soft => {} LineMode::Hard | LineMode::Empty => { return Ok(if self.must_be_flat { @@ -1052,11 +1063,15 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { } } - FormatElement::StaticText { text } => return Ok(self.fits_text(text, args)), - FormatElement::DynamicText { text, .. } => return Ok(self.fits_text(text, args)), - FormatElement::SourceCodeSlice { slice, .. } => { + FormatElement::StaticText { text, text_width } => { + return Ok(self.fits_text(text, *text_width, args)) + } + FormatElement::DynamicText { text, text_width } => { + return Ok(self.fits_text(text, *text_width, args)) + } + FormatElement::SourceCodeSlice { slice, text_width } => { let text = slice.text(self.printer.source_code); - return Ok(self.fits_text(text, args)); + return Ok(self.fits_text(text, *text_width, args)); } FormatElement::LineSuffixBoundary => { if self.state.has_line_suffix { @@ -1269,32 +1284,37 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { Fits::Maybe } - fn fits_text(&mut self, text: &str, args: PrintElementArgs) -> Fits { + fn fits_text(&mut self, text: &str, text_width: TextWidth, args: PrintElementArgs) -> Fits { let indent = std::mem::take(&mut self.state.pending_indent); self.state.line_width += u32::from(indent.level()) * u32::from(self.options().indent_width()) + u32::from(indent.align()); - for c in text.chars() { - let char_width = match c { - '\t' => u32::from(self.options().tab_width), - '\n' => { - if self.must_be_flat { - return Fits::No; - } - match args.measure_mode() { - MeasureMode::FirstLine => return Fits::Yes, - MeasureMode::AllLines => { - self.state.line_width = 0; - continue; - } + if let Some(width) = text_width.width() { + self.state.line_width += width; + } else { + for c in text.chars() { + let char_width = match c { + '\t' => self.options().tab_width.value(), + '\n' => { + if self.must_be_flat { + return Fits::No; + } else { + match args.measure_mode() { + MeasureMode::FirstLine => return Fits::Yes, + MeasureMode::AllLines => { + self.state.line_width = 0; + continue; + } + } + }; } - } - // SAFETY: A u32 is sufficient to format files <= 4GB - #[allow(clippy::cast_possible_truncation)] - c => c.width().unwrap_or(0) as u32, - }; - self.state.line_width += char_width; + // SAFETY: A u32 is sufficient to format files <= 4GB + #[allow(clippy::cast_possible_truncation)] + c => c.width().unwrap_or(0) as u32, + }; + self.state.line_width += char_width; + } } if self.state.line_width > self.options().print_width.into() { @@ -1407,7 +1427,9 @@ mod tests { use crate::prelude::*; use crate::printer::{LineEnding, PrintWidth, Printer, PrinterOptions}; use crate::source_code::SourceCode; - use crate::{format_args, write, Document, FormatState, IndentStyle, Printed, VecBuffer}; + use crate::{ + format_args, write, Document, FormatState, IndentStyle, Printed, TabWidth, VecBuffer, + }; fn format(root: &dyn Format) -> Printed { format_with_options( @@ -1557,7 +1579,7 @@ two lines`, fn it_use_the_indent_character_specified_in_the_options() { let options = PrinterOptions { indent_style: IndentStyle::Tab, - tab_width: 4, + tab_width: TabWidth::try_from(4).unwrap(), print_width: PrintWidth::new(19), ..PrinterOptions::default() }; @@ -1614,7 +1636,7 @@ two lines`, #[test] fn test_fill_breaks() { - let mut state = FormatState::new(()); + let mut state = FormatState::new(SimpleFormatContext::default()); let mut buffer = VecBuffer::new(&mut state); let mut formatter = Formatter::new(&mut buffer); diff --git a/crates/ruff_formatter/src/printer/printer_options/mod.rs b/crates/ruff_formatter/src/printer/printer_options/mod.rs index d68f60ef2ed3f2..a6d80768eb4540 100644 --- a/crates/ruff_formatter/src/printer/printer_options/mod.rs +++ b/crates/ruff_formatter/src/printer/printer_options/mod.rs @@ -1,10 +1,10 @@ -use crate::{FormatOptions, IndentStyle, LineWidth}; +use crate::{FormatOptions, IndentStyle, LineWidth, TabWidth}; /// Options that affect how the [`crate::Printer`] prints the format tokens -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct PrinterOptions { /// Width of a single tab character (does it equal 2, 4, ... spaces?) - pub tab_width: u8, + pub tab_width: TabWidth, /// What's the max width of a line. Defaults to 80 pub print_width: PrintWidth, @@ -74,23 +74,30 @@ impl PrinterOptions { self } + pub fn with_tab_width(mut self, tab_width: TabWidth) -> Self { + self.tab_width = tab_width; + + self + } + pub(crate) fn indent_style(&self) -> IndentStyle { self.indent_style } /// Width of an indent in characters. - pub(super) const fn indent_width(&self) -> u8 { + pub(super) const fn indent_width(&self) -> u32 { match self.indent_style { - IndentStyle::Tab => self.tab_width, - IndentStyle::Space(count) => count, + IndentStyle::Tab => self.tab_width.value(), + IndentStyle::Space(count) => count as u32, } } } #[allow(dead_code)] -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Default)] pub enum LineEnding { /// Line Feed only (\n), common on Linux and macOS as well as inside git repos + #[default] LineFeed, /// Carriage Return + Line Feed characters (\r\n), common on Windows @@ -110,14 +117,3 @@ impl LineEnding { } } } - -impl Default for PrinterOptions { - fn default() -> Self { - PrinterOptions { - tab_width: 2, - print_width: PrintWidth::default(), - indent_style: IndentStyle::default(), - line_ending: LineEnding::LineFeed, - } - } -} diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 5e3213ae95492f..50be16f0226896 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -181,9 +181,7 @@ impl Format> for NotYetImplemented<'_> { }, )))?; - f.write_element(FormatElement::DynamicText { - text: Box::from(text), - })?; + dynamic_text(&text, None).fmt(f)?; f.write_element(FormatElement::Tag(Tag::EndVerbatim))?; diff --git a/crates/ruff_python_formatter/src/options.rs b/crates/ruff_python_formatter/src/options.rs index ef2d84ca793d30..6aa7bfc7e484fa 100644 --- a/crates/ruff_python_formatter/src/options.rs +++ b/crates/ruff_python_formatter/src/options.rs @@ -1,5 +1,5 @@ use ruff_formatter::printer::{LineEnding, PrinterOptions}; -use ruff_formatter::{FormatOptions, IndentStyle, LineWidth}; +use ruff_formatter::{FormatOptions, IndentStyle, LineWidth, TabWidth}; use ruff_python_ast::PySourceType; use std::path::Path; use std::str::FromStr; @@ -102,13 +102,17 @@ impl FormatOptions for PyFormatOptions { self.indent_style } + fn tab_width(&self) -> TabWidth { + TabWidth::try_from(4).unwrap() + } + fn line_width(&self) -> LineWidth { self.line_width } fn as_print_options(&self) -> PrinterOptions { PrinterOptions { - tab_width: 4, + tab_width: self.tab_width(), print_width: self.line_width.into(), line_ending: LineEnding::LineFeed, indent_style: self.indent_style,