From 98ea94fdb7546852b433b47697f10b69497363c1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 14 Feb 2023 22:27:52 -0500 Subject: [PATCH] Add `StaticTextSlice` kind to `FormatElement` enum (#2873) Given our current parser abstractions, we need the ability to tell `ruff_formatter` to print a pre-defined slice from a fixed string of source code, which we've introduced here as `FormatElement::StaticTextSlice`. --- crates/ruff_formatter/src/builders.rs | 29 +++++++++++++++++++ crates/ruff_formatter/src/format_element.rs | 10 ++++++- .../src/format_element/document.rs | 2 ++ crates/ruff_formatter/src/printer/mod.rs | 4 +++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index cee634b52e..084b9d9ab1 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -8,6 +8,7 @@ use std::borrow::Cow; use std::cell::Cell; use std::marker::PhantomData; use std::num::NonZeroU8; +use std::rc::Rc; use Tag::*; /// A line break that only gets printed if the enclosing `Group` doesn't fit on a single line. @@ -303,6 +304,34 @@ impl std::fmt::Debug for DynamicText<'_> { } } +/// Creates a text from a dynamic string and a range of the input source +pub fn static_text_slice(text: Rc, range: TextRange) -> StaticTextSlice { + debug_assert_no_newlines(&text[range]); + + StaticTextSlice { text, range } +} + +#[derive(Eq, PartialEq)] +pub struct StaticTextSlice { + text: Rc, + range: TextRange, +} + +impl Format for StaticTextSlice { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::StaticTextSlice { + text: self.text.clone(), + range: self.range, + }) + } +} + +impl std::fmt::Debug for StaticTextSlice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::write!(f, "StaticTextSlice({})", &self.text[self.range]) + } +} + /// String that is the same as in the input source text if `text` is [`Cow::Borrowed`] or /// some replaced content if `text` is [`Cow::Owned`]. pub fn syntax_token_cow_slice<'a, L: Language>( diff --git a/crates/ruff_formatter/src/format_element.rs b/crates/ruff_formatter/src/format_element.rs index 293709dfb9..f7e89818b5 100644 --- a/crates/ruff_formatter/src/format_element.rs +++ b/crates/ruff_formatter/src/format_element.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use crate::{TagKind, TextSize}; #[cfg(target_pointer_width = "64")] use ruff_rowan::static_assert; -use ruff_rowan::SyntaxTokenText; +use ruff_rowan::{SyntaxTokenText, TextRange}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::rc::Rc; @@ -38,6 +38,9 @@ pub enum FormatElement { source_position: TextSize, }, + /// Token constructed by slicing a defined range from a static string. + StaticTextSlice { text: Rc, range: TextRange }, + /// A token for a text that is taken as is from the source code (input text and formatted representation are identical). /// Implementing by taking a slice from a `SyntaxToken` to avoid allocating a new string. SyntaxTokenTextSlice { @@ -75,6 +78,9 @@ impl std::fmt::Debug for FormatElement { FormatElement::DynamicText { text, .. } => { fmt.debug_tuple("DynamicText").field(text).finish() } + FormatElement::StaticTextSlice { text, .. } => { + fmt.debug_tuple("Text").field(text).finish() + } FormatElement::SyntaxTokenTextSlice { slice, .. } => fmt .debug_tuple("SyntaxTokenTextSlice") .field(slice) @@ -225,6 +231,7 @@ impl FormatElement { matches!( self, FormatElement::SyntaxTokenTextSlice { .. } + | FormatElement::StaticTextSlice { .. } | FormatElement::DynamicText { .. } | FormatElement::StaticText { .. } ) @@ -243,6 +250,7 @@ impl FormatElements for FormatElement { FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty), FormatElement::StaticText { text } => text.contains('\n'), FormatElement::DynamicText { text, .. } => text.contains('\n'), + FormatElement::StaticTextSlice { text, range } => text[*range].contains('\n'), FormatElement::SyntaxTokenTextSlice { slice, .. } => slice.contains('\n'), FormatElement::Interned(interned) => interned.will_break(), // Traverse into the most flat version because the content is guaranteed to expand when even diff --git a/crates/ruff_formatter/src/format_element/document.rs b/crates/ruff_formatter/src/format_element/document.rs index b306444cee..bfd9624228 100644 --- a/crates/ruff_formatter/src/format_element/document.rs +++ b/crates/ruff_formatter/src/format_element/document.rs @@ -81,6 +81,7 @@ impl Document { } FormatElement::StaticText { text } => text.contains('\n'), FormatElement::DynamicText { text, .. } => text.contains('\n'), + FormatElement::StaticTextSlice { text, range } => text[*range].contains('\n'), FormatElement::SyntaxTokenTextSlice { slice, .. } => slice.contains('\n'), FormatElement::ExpandParent | FormatElement::Line(LineMode::Hard | LineMode::Empty) => true, @@ -194,6 +195,7 @@ impl Format for &[FormatElement] { element @ FormatElement::Space | element @ FormatElement::StaticText { .. } | element @ FormatElement::DynamicText { .. } + | element @ FormatElement::StaticTextSlice { .. } | element @ FormatElement::SyntaxTokenTextSlice { .. } => { if !in_text { write!(f, [text("\"")])?; diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index 5ca4d9cbd0..e2d5cf1723 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -99,6 +99,7 @@ impl<'a> Printer<'a> { text, source_position, } => self.print_text(text, Some(*source_position)), + FormatElement::StaticTextSlice { text, range } => self.print_text(&text[*range], None), FormatElement::SyntaxTokenTextSlice { slice, source_position, @@ -1001,6 +1002,9 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { FormatElement::StaticText { text } => return Ok(self.fits_text(text)), FormatElement::DynamicText { text, .. } => return Ok(self.fits_text(text)), + FormatElement::StaticTextSlice { text, range } => { + return Ok(self.fits_text(&text[*range])) + } FormatElement::SyntaxTokenTextSlice { slice, .. } => return Ok(self.fits_text(slice)), FormatElement::LineSuffixBoundary => {