mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 12:29:48 +00:00
Memoize text width (#6552)
This commit is contained in:
parent
fa6bff0078
commit
5f59101811
14 changed files with 213 additions and 184 deletions
|
@ -129,7 +129,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nesting() {
|
fn test_nesting() {
|
||||||
let mut context = FormatState::new(());
|
let mut context = FormatState::new(SimpleFormatContext::default());
|
||||||
let mut buffer = VecBuffer::new(&mut context);
|
let mut buffer = VecBuffer::new(&mut context);
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
|
|
|
@ -9,7 +9,9 @@ use Tag::*;
|
||||||
use crate::format_element::tag::{Condition, Tag};
|
use crate::format_element::tag::{Condition, Tag};
|
||||||
use crate::prelude::tag::{DedentMode, GroupMode, LabelId};
|
use crate::prelude::tag::{DedentMode, GroupMode, LabelId};
|
||||||
use crate::prelude::*;
|
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 crate::{Buffer, VecBuffer};
|
||||||
|
|
||||||
/// A line break that only gets printed if the enclosing `Group` doesn't fit on a single line.
|
/// A line break that only gets printed if the enclosing `Group` doesn't fit on a single line.
|
||||||
|
@ -348,7 +350,10 @@ pub struct Text<'a> {
|
||||||
position: Option<TextSize>,
|
position: Option<TextSize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Context> Format<Context> for Text<'_> {
|
impl<Context> Format<Context> for Text<'_>
|
||||||
|
where
|
||||||
|
Context: FormatContext,
|
||||||
|
{
|
||||||
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
||||||
if let Some(source_position) = self.position {
|
if let Some(source_position) = self.position {
|
||||||
f.write_element(FormatElement::SourcePosition(source_position));
|
f.write_element(FormatElement::SourcePosition(source_position));
|
||||||
|
@ -356,6 +361,7 @@ impl<Context> Format<Context> for Text<'_> {
|
||||||
|
|
||||||
f.write_element(FormatElement::Text {
|
f.write_element(FormatElement::Text {
|
||||||
text: self.text.to_string().into_boxed_str(),
|
text: self.text.to_string().into_boxed_str(),
|
||||||
|
text_width: TextWidth::from_text(self.text, f.options().tab_width()),
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -369,31 +375,13 @@ impl std::fmt::Debug for Text<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emits a text as it is written in the source document. Optimized to avoid allocations.
|
/// Emits a text as it is written in the source document. Optimized to avoid allocations.
|
||||||
pub const fn source_text_slice(
|
pub const fn source_text_slice(range: TextRange) -> SourceTextSliceBuilder {
|
||||||
range: TextRange,
|
SourceTextSliceBuilder { range }
|
||||||
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)]
|
#[derive(Eq, PartialEq, Debug)]
|
||||||
pub struct SourceTextSliceBuilder {
|
pub struct SourceTextSliceBuilder {
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
new_lines: ContainsNewlines,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Context> Format<Context> for SourceTextSliceBuilder
|
impl<Context> Format<Context> for SourceTextSliceBuilder
|
||||||
|
@ -405,28 +393,10 @@ where
|
||||||
let slice = source_code.slice(self.range);
|
let slice = source_code.slice(self.range);
|
||||||
debug_assert_no_newlines(slice.text(source_code));
|
debug_assert_no_newlines(slice.text(source_code));
|
||||||
|
|
||||||
let contains_newlines = match self.new_lines {
|
let text_width =
|
||||||
ContainsNewlines::Yes => {
|
TextWidth::from_text(slice.text(source_code), f.context().options().tab_width());
|
||||||
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 {
|
f.write_element(FormatElement::SourceCodeSlice { slice, text_width });
|
||||||
slice,
|
|
||||||
contains_newlines,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@ pub mod tag;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::num::NonZeroU32;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
use crate::format_element::tag::{GroupMode, LabelId, Tag};
|
use crate::format_element::tag::{GroupMode, LabelId, Tag};
|
||||||
use crate::source_code::SourceCodeSlice;
|
use crate::source_code::SourceCodeSlice;
|
||||||
use crate::TagKind;
|
use crate::{TabWidth, TagKind};
|
||||||
use ruff_text_size::TextSize;
|
use ruff_text_size::TextSize;
|
||||||
|
|
||||||
/// Language agnostic IR for formatting source code.
|
/// Language agnostic IR for formatting source code.
|
||||||
|
@ -37,13 +39,13 @@ pub enum FormatElement {
|
||||||
Text {
|
Text {
|
||||||
/// There's no need for the text to be mutable, using `Box<str>` safes 8 bytes over `String`.
|
/// There's no need for the text to be mutable, using `Box<str>` safes 8 bytes over `String`.
|
||||||
text: Box<str>,
|
text: Box<str>,
|
||||||
|
text_width: TextWidth,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Text that gets emitted as it is in the source code. Optimized to avoid any allocations.
|
/// Text that gets emitted as it is in the source code. Optimized to avoid any allocations.
|
||||||
SourceCodeSlice {
|
SourceCodeSlice {
|
||||||
slice: SourceCodeSlice,
|
slice: SourceCodeSlice,
|
||||||
/// Whether the string contains any new line characters
|
text_width: TextWidth,
|
||||||
contains_newlines: bool,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Prevents that line suffixes move past this boundary. Forces the printer to print any pending
|
/// Prevents that line suffixes move past this boundary. Forces the printer to print any pending
|
||||||
|
@ -73,13 +75,10 @@ impl std::fmt::Debug for FormatElement {
|
||||||
FormatElement::ExpandParent => write!(fmt, "ExpandParent"),
|
FormatElement::ExpandParent => write!(fmt, "ExpandParent"),
|
||||||
FormatElement::Token { text } => fmt.debug_tuple("Token").field(text).finish(),
|
FormatElement::Token { text } => fmt.debug_tuple("Token").field(text).finish(),
|
||||||
FormatElement::Text { text, .. } => fmt.debug_tuple("DynamicText").field(text).finish(),
|
FormatElement::Text { text, .. } => fmt.debug_tuple("DynamicText").field(text).finish(),
|
||||||
FormatElement::SourceCodeSlice {
|
FormatElement::SourceCodeSlice { slice, text_width } => fmt
|
||||||
slice,
|
|
||||||
contains_newlines,
|
|
||||||
} => fmt
|
|
||||||
.debug_tuple("Text")
|
.debug_tuple("Text")
|
||||||
.field(slice)
|
.field(slice)
|
||||||
.field(contains_newlines)
|
.field(text_width)
|
||||||
.finish(),
|
.finish(),
|
||||||
FormatElement::LineSuffixBoundary => write!(fmt, "LineSuffixBoundary"),
|
FormatElement::LineSuffixBoundary => write!(fmt, "LineSuffixBoundary"),
|
||||||
FormatElement::BestFitting { variants, mode } => fmt
|
FormatElement::BestFitting { variants, mode } => fmt
|
||||||
|
@ -255,11 +254,8 @@ impl FormatElements for FormatElement {
|
||||||
FormatElement::ExpandParent => true,
|
FormatElement::ExpandParent => true,
|
||||||
FormatElement::Tag(Tag::StartGroup(group)) => !group.mode().is_flat(),
|
FormatElement::Tag(Tag::StartGroup(group)) => !group.mode().is_flat(),
|
||||||
FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty),
|
FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty),
|
||||||
|
FormatElement::Text { text_width, .. } => text_width.is_multiline(),
|
||||||
FormatElement::Text { text, .. } => text.contains('\n'),
|
FormatElement::SourceCodeSlice { text_width, .. } => text_width.is_multiline(),
|
||||||
FormatElement::SourceCodeSlice {
|
|
||||||
contains_newlines, ..
|
|
||||||
} => *contains_newlines,
|
|
||||||
FormatElement::Interned(interned) => interned.will_break(),
|
FormatElement::Interned(interned) => interned.will_break(),
|
||||||
// Traverse into the most flat version because the content is guaranteed to expand when even
|
// 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.
|
// the most flat version contains some content that forces a break.
|
||||||
|
@ -403,6 +399,67 @@ pub trait FormatElements {
|
||||||
fn end_tag(&self, kind: TagKind) -> Option<&Tag>;
|
fn end_tag(&self, kind: TagKind) -> Option<&Tag>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// New-type wrapper for a single-line text unicode width.
|
||||||
|
/// Mainly to prevent access to the inner value.
|
||||||
|
///
|
||||||
|
/// ## Representation
|
||||||
|
///
|
||||||
|
/// Represents the width by adding 1 to the actual width so that the width can be represented by a [`NonZeroU32`],
|
||||||
|
/// allowing [`TextWidth`] or [`Option<Width>`] fit in 4 bytes rather than 8.
|
||||||
|
///
|
||||||
|
/// This means that 2^32 can not be precisely represented and instead has the same value as 2^32-1.
|
||||||
|
/// This imprecision shouldn't matter in practice because either text are longer than any configured line width
|
||||||
|
/// and thus, the text should break.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Width(NonZeroU32);
|
||||||
|
|
||||||
|
impl Width {
|
||||||
|
pub(crate) const fn new(width: u32) -> Self {
|
||||||
|
Width(NonZeroU32::MIN.saturating_add(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn value(self) -> u32 {
|
||||||
|
self.0.get() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pre-computed unicode width of a text if it is a single-line text or a marker
|
||||||
|
/// that it is a multiline text if it contains a line feed.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum TextWidth {
|
||||||
|
Width(Width),
|
||||||
|
Multiline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextWidth {
|
||||||
|
pub 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,
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
c => c.width().unwrap_or(0) as u32,
|
||||||
|
};
|
||||||
|
width += char_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Width(Width::new(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn width(self) -> Option<Width> {
|
||||||
|
match self {
|
||||||
|
TextWidth::Width(width) => Some(width),
|
||||||
|
TextWidth::Multiline => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn is_multiline(self) -> bool {
|
||||||
|
matches!(self, TextWidth::Multiline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -430,19 +487,21 @@ mod sizes {
|
||||||
// be recomputed at a later point in time?
|
// be recomputed at a later point in time?
|
||||||
// You reduced the size of a format element? Excellent work!
|
// You reduced the size of a format element? Excellent work!
|
||||||
|
|
||||||
|
use super::{BestFittingVariants, Interned, TextWidth};
|
||||||
use static_assertions::assert_eq_size;
|
use static_assertions::assert_eq_size;
|
||||||
|
|
||||||
assert_eq_size!(ruff_text_size::TextRange, [u8; 8]);
|
assert_eq_size!(ruff_text_size::TextRange, [u8; 8]);
|
||||||
assert_eq_size!(crate::prelude::tag::VerbatimKind, [u8; 8]);
|
assert_eq_size!(TextWidth, [u8; 4]);
|
||||||
assert_eq_size!(crate::prelude::Interned, [u8; 16]);
|
assert_eq_size!(super::tag::VerbatimKind, [u8; 8]);
|
||||||
assert_eq_size!(crate::format_element::BestFittingVariants, [u8; 16]);
|
assert_eq_size!(Interned, [u8; 16]);
|
||||||
|
assert_eq_size!(BestFittingVariants, [u8; 16]);
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
assert_eq_size!(crate::SourceCodeSlice, [u8; 8]);
|
assert_eq_size!(crate::SourceCodeSlice, [u8; 8]);
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
assert_eq_size!(crate::format_element::Tag, [u8; 16]);
|
assert_eq_size!(super::Tag, [u8; 16]);
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
assert_eq_size!(crate::FormatElement, [u8; 24]);
|
assert_eq_size!(super::FormatElement, [u8; 24]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,10 +104,11 @@ impl Document {
|
||||||
expands = false;
|
expands = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
FormatElement::Text { text, .. } => text.contains('\n'),
|
FormatElement::Text {
|
||||||
FormatElement::SourceCodeSlice {
|
text: _,
|
||||||
contains_newlines, ..
|
text_width,
|
||||||
} => *contains_newlines,
|
} => text_width.is_multiline(),
|
||||||
|
FormatElement::SourceCodeSlice { text_width, .. } => text_width.is_multiline(),
|
||||||
FormatElement::ExpandParent
|
FormatElement::ExpandParent
|
||||||
| FormatElement::Line(LineMode::Hard | LineMode::Empty) => true,
|
| FormatElement::Line(LineMode::Hard | LineMode::Empty) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
|
@ -259,11 +260,16 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
| FormatElement::Text { .. }
|
| FormatElement::Text { .. }
|
||||||
| FormatElement::SourceCodeSlice { .. }) => {
|
| FormatElement::SourceCodeSlice { .. }) => {
|
||||||
fn write_escaped(element: &FormatElement, f: &mut Formatter<IrFormatContext>) {
|
fn write_escaped(element: &FormatElement, f: &mut Formatter<IrFormatContext>) {
|
||||||
let text = match element {
|
let (text, text_width) = match element {
|
||||||
FormatElement::Token { text } => text,
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
FormatElement::Text { text } => text.as_ref(),
|
FormatElement::Token { text } => {
|
||||||
FormatElement::SourceCodeSlice { slice, .. } => {
|
(*text, TextWidth::Width(Width::new(text.len() as u32)))
|
||||||
slice.text(f.context().source_code())
|
}
|
||||||
|
FormatElement::Text { text, text_width } => {
|
||||||
|
(text.as_ref(), *text_width)
|
||||||
|
}
|
||||||
|
FormatElement::SourceCodeSlice { slice, text_width } => {
|
||||||
|
(slice.text(f.context().source_code()), *text_width)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
@ -271,6 +277,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
if text.contains('"') {
|
if text.contains('"') {
|
||||||
f.write_element(FormatElement::Text {
|
f.write_element(FormatElement::Text {
|
||||||
text: text.replace('"', r#"\""#).into(),
|
text: text.replace('"', r#"\""#).into(),
|
||||||
|
text_width,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
f.write_element(element.clone());
|
f.write_element(element.clone());
|
||||||
|
@ -854,15 +861,9 @@ mod tests {
|
||||||
[group(&format_args![
|
[group(&format_args![
|
||||||
token("("),
|
token("("),
|
||||||
soft_block_indent(&format_args![
|
soft_block_indent(&format_args![
|
||||||
source_text_slice(
|
source_text_slice(TextRange::at(TextSize::new(0), TextSize::new(19)),),
|
||||||
TextRange::at(TextSize::new(0), TextSize::new(19)),
|
|
||||||
ContainsNewlines::No
|
|
||||||
),
|
|
||||||
space(),
|
space(),
|
||||||
source_text_slice(
|
source_text_slice(TextRange::at(TextSize::new(20), TextSize::new(28)),),
|
||||||
TextRange::at(TextSize::new(20), TextSize::new(28)),
|
|
||||||
ContainsNewlines::No
|
|
||||||
),
|
|
||||||
])
|
])
|
||||||
])]
|
])]
|
||||||
)
|
)
|
||||||
|
|
|
@ -343,15 +343,15 @@ mod tests {
|
||||||
|
|
||||||
struct TestFormat;
|
struct TestFormat;
|
||||||
|
|
||||||
impl Format<()> for TestFormat {
|
impl Format<SimpleFormatContext> for TestFormat {
|
||||||
fn fmt(&self, f: &mut Formatter<()>) -> FormatResult<()> {
|
fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
|
||||||
write!(f, [token("test")])
|
write!(f, [token("test")])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_element() {
|
fn test_single_element() {
|
||||||
let mut state = FormatState::new(());
|
let mut state = FormatState::new(SimpleFormatContext::default());
|
||||||
let mut buffer = VecBuffer::new(&mut state);
|
let mut buffer = VecBuffer::new(&mut state);
|
||||||
|
|
||||||
write![&mut buffer, [TestFormat]].unwrap();
|
write![&mut buffer, [TestFormat]].unwrap();
|
||||||
|
@ -364,7 +364,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_elements() {
|
fn test_multiple_elements() {
|
||||||
let mut state = FormatState::new(());
|
let mut state = FormatState::new(SimpleFormatContext::default());
|
||||||
let mut buffer = VecBuffer::new(&mut state);
|
let mut buffer = VecBuffer::new(&mut state);
|
||||||
|
|
||||||
write![
|
write![
|
||||||
|
|
|
@ -9,8 +9,8 @@ use ruff_text_size::{Ranged, TextLen, TextSize};
|
||||||
use crate::format_element::document::Document;
|
use crate::format_element::document::Document;
|
||||||
use crate::format_element::tag::{Condition, GroupMode};
|
use crate::format_element::tag::{Condition, GroupMode};
|
||||||
use crate::format_element::{BestFittingMode, BestFittingVariants, LineMode, PrintMode};
|
use crate::format_element::{BestFittingMode, BestFittingVariants, LineMode, PrintMode};
|
||||||
use crate::prelude::tag;
|
|
||||||
use crate::prelude::tag::{DedentMode, Tag, TagKind, VerbatimKind};
|
use crate::prelude::tag::{DedentMode, Tag, TagKind, VerbatimKind};
|
||||||
|
use crate::prelude::{tag, TextWidth};
|
||||||
use crate::printer::call_stack::{
|
use crate::printer::call_stack::{
|
||||||
CallStack, FitsCallStack, PrintCallStack, PrintElementArgs, StackFrame,
|
CallStack, FitsCallStack, PrintCallStack, PrintElementArgs, StackFrame,
|
||||||
};
|
};
|
||||||
|
@ -96,10 +96,22 @@ impl<'a> Printer<'a> {
|
||||||
match element {
|
match element {
|
||||||
FormatElement::Space => self.print_text(Text::Token(" "), None),
|
FormatElement::Space => self.print_text(Text::Token(" "), None),
|
||||||
FormatElement::Token { text } => self.print_text(Text::Token(text), None),
|
FormatElement::Token { text } => self.print_text(Text::Token(text), None),
|
||||||
FormatElement::Text { text } => self.print_text(Text::Text(text), None),
|
FormatElement::Text { text, text_width } => self.print_text(
|
||||||
FormatElement::SourceCodeSlice { slice, .. } => {
|
Text::Text {
|
||||||
|
text,
|
||||||
|
text_width: *text_width,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
FormatElement::SourceCodeSlice { slice, text_width } => {
|
||||||
let text = slice.text(self.source_code);
|
let text = slice.text(self.source_code);
|
||||||
self.print_text(Text::Text(text), Some(slice.range()));
|
self.print_text(
|
||||||
|
Text::Text {
|
||||||
|
text,
|
||||||
|
text_width: *text_width,
|
||||||
|
},
|
||||||
|
Some(slice.range()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
FormatElement::Line(line_mode) => {
|
FormatElement::Line(line_mode) => {
|
||||||
if args.mode().is_flat()
|
if args.mode().is_flat()
|
||||||
|
@ -395,12 +407,20 @@ impl<'a> Printer<'a> {
|
||||||
self.state.buffer.push_str(token);
|
self.state.buffer.push_str(token);
|
||||||
self.state.line_width += token.len() as u32;
|
self.state.line_width += token.len() as u32;
|
||||||
}
|
}
|
||||||
Text::Text(text) => {
|
Text::Text {
|
||||||
|
text,
|
||||||
|
text_width: width,
|
||||||
|
} => {
|
||||||
|
if let Some(width) = width.width() {
|
||||||
|
self.state.buffer.push_str(text);
|
||||||
|
self.state.line_width += width.value();
|
||||||
|
} else {
|
||||||
for char in text.chars() {
|
for char in text.chars() {
|
||||||
self.print_char(char);
|
self.print_char(char);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(range) = source_range {
|
if let Some(range) = source_range {
|
||||||
self.state.source_position = range.end();
|
self.state.source_position = range.end();
|
||||||
|
@ -1086,10 +1106,24 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::Token { text } => return Ok(self.fits_text(Text::Token(text), args)),
|
FormatElement::Token { text } => return Ok(self.fits_text(Text::Token(text), args)),
|
||||||
FormatElement::Text { text, .. } => return Ok(self.fits_text(Text::Text(text), args)),
|
FormatElement::Text { text, text_width } => {
|
||||||
FormatElement::SourceCodeSlice { slice, .. } => {
|
return Ok(self.fits_text(
|
||||||
|
Text::Text {
|
||||||
|
text,
|
||||||
|
text_width: *text_width,
|
||||||
|
},
|
||||||
|
args,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
FormatElement::SourceCodeSlice { slice, text_width } => {
|
||||||
let text = slice.text(self.printer.source_code);
|
let text = slice.text(self.printer.source_code);
|
||||||
return Ok(self.fits_text(Text::Text(text), args));
|
return Ok(self.fits_text(
|
||||||
|
Text::Text {
|
||||||
|
text,
|
||||||
|
text_width: *text_width,
|
||||||
|
},
|
||||||
|
args,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
FormatElement::LineSuffixBoundary => {
|
FormatElement::LineSuffixBoundary => {
|
||||||
if self.state.has_line_suffix {
|
if self.state.has_line_suffix {
|
||||||
|
@ -1307,7 +1341,10 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||||
Text::Token(token) => {
|
Text::Token(token) => {
|
||||||
self.state.line_width += token.len() as u32;
|
self.state.line_width += token.len() as u32;
|
||||||
}
|
}
|
||||||
Text::Text(text) => {
|
Text::Text { text, text_width } => {
|
||||||
|
if let Some(width) = text_width.width() {
|
||||||
|
self.state.line_width += width.value();
|
||||||
|
} else {
|
||||||
for c in text.chars() {
|
for c in text.chars() {
|
||||||
let char_width = match c {
|
let char_width = match c {
|
||||||
'\t' => self.options().tab_width.value(),
|
'\t' => self.options().tab_width.value(),
|
||||||
|
@ -1331,6 +1368,7 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.state.line_width > self.options().line_width.into() {
|
if self.state.line_width > self.options().line_width.into() {
|
||||||
return Fits::No;
|
return Fits::No;
|
||||||
|
@ -1451,7 +1489,10 @@ enum Text<'a> {
|
||||||
/// ASCII only text that contains no line breaks or tab characters.
|
/// ASCII only text that contains no line breaks or tab characters.
|
||||||
Token(&'a str),
|
Token(&'a str),
|
||||||
/// Arbitrary text. May contain `\n` line breaks, tab characters, or unicode characters.
|
/// Arbitrary text. May contain `\n` line breaks, tab characters, or unicode characters.
|
||||||
Text(&'a str),
|
Text {
|
||||||
|
text: &'a str,
|
||||||
|
text_width: TextWidth,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1669,7 +1710,7 @@ two lines`,
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fill_breaks() {
|
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 buffer = VecBuffer::new(&mut state);
|
||||||
let mut formatter = Formatter::new(&mut buffer);
|
let mut formatter = Formatter::new(&mut buffer);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use unicode_width::UnicodeWidthChar;
|
|
||||||
|
|
||||||
use ruff_formatter::{format_args, write, FormatError, FormatOptions, SourceCode};
|
use ruff_formatter::{format_args, write, FormatError, FormatOptions, SourceCode};
|
||||||
use ruff_python_ast::node::{AnyNodeRef, AstNode};
|
use ruff_python_ast::node::{AnyNodeRef, AstNode};
|
||||||
use ruff_python_trivia::{lines_after, lines_after_ignoring_trivia, lines_before};
|
use ruff_python_trivia::{lines_after, lines_after_ignoring_trivia, lines_before};
|
||||||
|
@ -377,16 +375,12 @@ impl Format<PyFormatContext<'_>> for FormatTrailingEndOfLineComment<'_> {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
// Start with 2 because of the two leading spaces.
|
// Start with 2 because of the two leading spaces.
|
||||||
let mut width = 2;
|
let width = 2u32.saturating_add(
|
||||||
|
TextWidth::from_text(&normalized_comment, f.options().tab_width())
|
||||||
// SAFETY: The formatted file is <= 4GB, and each comment should as well.
|
.width()
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
.expect("Expected comment not to contain any newlines")
|
||||||
for c in normalized_comment.chars() {
|
.value(),
|
||||||
width += match c {
|
);
|
||||||
'\t' => f.options().tab_width().value(),
|
|
||||||
c => c.width().unwrap_or(0) as u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
width
|
width
|
||||||
};
|
};
|
||||||
|
@ -430,11 +424,9 @@ pub(crate) struct FormatNormalizedComment<'a> {
|
||||||
impl Format<PyFormatContext<'_>> for FormatNormalizedComment<'_> {
|
impl Format<PyFormatContext<'_>> for FormatNormalizedComment<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
|
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
|
||||||
match self.comment {
|
match self.comment {
|
||||||
Cow::Borrowed(borrowed) => source_text_slice(
|
Cow::Borrowed(borrowed) => {
|
||||||
TextRange::at(self.range.start(), borrowed.text_len()),
|
source_text_slice(TextRange::at(self.range.start(), borrowed.text_len())).fmt(f)
|
||||||
ContainsNewlines::No,
|
}
|
||||||
)
|
|
||||||
.fmt(f),
|
|
||||||
|
|
||||||
Cow::Owned(ref owned) => {
|
Cow::Owned(ref owned) => {
|
||||||
write!(
|
write!(
|
||||||
|
|
|
@ -8,6 +8,6 @@ pub struct FormatExprIpyEscapeCommand;
|
||||||
|
|
||||||
impl FormatNodeRule<ExprIpyEscapeCommand> for FormatExprIpyEscapeCommand {
|
impl FormatNodeRule<ExprIpyEscapeCommand> for FormatExprIpyEscapeCommand {
|
||||||
fn fmt_fields(&self, item: &ExprIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt_fields(&self, item: &ExprIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
source_text_slice(item.range(), ContainsNewlines::No).fmt(f)
|
source_text_slice(item.range()).fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ impl FormatNodeRule<ExprName> for FormatExprName {
|
||||||
.text(f.context().source_code())
|
.text(f.context().source_code())
|
||||||
);
|
);
|
||||||
|
|
||||||
write!(f, [source_text_slice(*range, ContainsNewlines::No)])
|
write!(f, [source_text_slice(*range)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_dangling_comments(
|
fn fmt_dangling_comments(
|
||||||
|
|
|
@ -24,7 +24,7 @@ impl Format<PyFormatContext<'_>> for FormatInt<'_> {
|
||||||
let normalized = normalize_integer(content);
|
let normalized = normalize_integer(content);
|
||||||
|
|
||||||
match normalized {
|
match normalized {
|
||||||
Cow::Borrowed(_) => source_text_slice(range, ContainsNewlines::No).fmt(f),
|
Cow::Borrowed(_) => source_text_slice(range).fmt(f),
|
||||||
Cow::Owned(normalized) => text(&normalized, Some(range.start())).fmt(f),
|
Cow::Owned(normalized) => text(&normalized, Some(range.start())).fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ impl Format<PyFormatContext<'_>> for FormatFloat<'_> {
|
||||||
let normalized = normalize_floating_number(content);
|
let normalized = normalize_floating_number(content);
|
||||||
|
|
||||||
match normalized {
|
match normalized {
|
||||||
Cow::Borrowed(_) => source_text_slice(range, ContainsNewlines::No).fmt(f),
|
Cow::Borrowed(_) => source_text_slice(range).fmt(f),
|
||||||
Cow::Owned(normalized) => text(&normalized, Some(range.start())).fmt(f),
|
Cow::Owned(normalized) => text(&normalized, Some(range.start())).fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ impl Format<PyFormatContext<'_>> for FormatComplex<'_> {
|
||||||
|
|
||||||
match normalized {
|
match normalized {
|
||||||
Cow::Borrowed(_) => {
|
Cow::Borrowed(_) => {
|
||||||
source_text_slice(range.sub_end(TextSize::from(1)), ContainsNewlines::No).fmt(f)?;
|
source_text_slice(range.sub_end(TextSize::from(1))).fmt(f)?;
|
||||||
}
|
}
|
||||||
Cow::Owned(normalized) => {
|
Cow::Owned(normalized) => {
|
||||||
text(&normalized, Some(range.start())).fmt(f)?;
|
text(&normalized, Some(range.start())).fmt(f)?;
|
||||||
|
|
|
@ -314,7 +314,7 @@ impl FormatStringPart {
|
||||||
|
|
||||||
impl Format<PyFormatContext<'_>> for FormatStringPart {
|
impl Format<PyFormatContext<'_>> for FormatStringPart {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
let (normalized, contains_newlines) = normalize_string(
|
let normalized = normalize_string(
|
||||||
f.context().locator().slice(self.range),
|
f.context().locator().slice(self.range),
|
||||||
self.preferred_quotes,
|
self.preferred_quotes,
|
||||||
self.is_raw_string,
|
self.is_raw_string,
|
||||||
|
@ -323,7 +323,7 @@ impl Format<PyFormatContext<'_>> for FormatStringPart {
|
||||||
write!(f, [self.prefix, self.preferred_quotes])?;
|
write!(f, [self.prefix, self.preferred_quotes])?;
|
||||||
match normalized {
|
match normalized {
|
||||||
Cow::Borrowed(_) => {
|
Cow::Borrowed(_) => {
|
||||||
source_text_slice(self.range(), contains_newlines).fmt(f)?;
|
source_text_slice(self.range()).fmt(f)?;
|
||||||
}
|
}
|
||||||
Cow::Owned(normalized) => {
|
Cow::Owned(normalized) => {
|
||||||
text(&normalized, Some(self.start())).fmt(f)?;
|
text(&normalized, Some(self.start())).fmt(f)?;
|
||||||
|
@ -604,11 +604,7 @@ impl Format<PyFormatContext<'_>> for StringQuotes {
|
||||||
/// with the provided `style`.
|
/// with the provided `style`.
|
||||||
///
|
///
|
||||||
/// Returns the normalized string and whether it contains new lines.
|
/// Returns the normalized string and whether it contains new lines.
|
||||||
fn normalize_string(
|
fn normalize_string(input: &str, quotes: StringQuotes, is_raw: bool) -> Cow<str> {
|
||||||
input: &str,
|
|
||||||
quotes: StringQuotes,
|
|
||||||
is_raw: bool,
|
|
||||||
) -> (Cow<str>, ContainsNewlines) {
|
|
||||||
// The normalized string if `input` is not yet normalized.
|
// The normalized string if `input` is not yet normalized.
|
||||||
// `output` must remain empty if `input` is already normalized.
|
// `output` must remain empty if `input` is already normalized.
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
@ -616,8 +612,6 @@ fn normalize_string(
|
||||||
// If `last_index` is `0` at the end, then the input is already normalized and can be returned as is.
|
// If `last_index` is `0` at the end, then the input is already normalized and can be returned as is.
|
||||||
let mut last_index = 0;
|
let mut last_index = 0;
|
||||||
|
|
||||||
let mut newlines = ContainsNewlines::No;
|
|
||||||
|
|
||||||
let style = quotes.style;
|
let style = quotes.style;
|
||||||
let preferred_quote = style.as_char();
|
let preferred_quote = style.as_char();
|
||||||
let opposite_quote = style.invert().as_char();
|
let opposite_quote = style.invert().as_char();
|
||||||
|
@ -638,9 +632,6 @@ fn normalize_string(
|
||||||
}
|
}
|
||||||
|
|
||||||
last_index = index + '\r'.len_utf8();
|
last_index = index + '\r'.len_utf8();
|
||||||
newlines = ContainsNewlines::Yes;
|
|
||||||
} else if c == '\n' {
|
|
||||||
newlines = ContainsNewlines::Yes;
|
|
||||||
} else if !quotes.triple && !is_raw {
|
} else if !quotes.triple && !is_raw {
|
||||||
if c == '\\' {
|
if c == '\\' {
|
||||||
if let Some(next) = input.as_bytes().get(index + 1).copied().map(char::from) {
|
if let Some(next) = input.as_bytes().get(index + 1).copied().map(char::from) {
|
||||||
|
@ -675,7 +666,7 @@ fn normalize_string(
|
||||||
Cow::Owned(output)
|
Cow::Owned(output)
|
||||||
};
|
};
|
||||||
|
|
||||||
(normalized, newlines)
|
normalized
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For docstring indentation, black counts spaces as 1 and tabs by increasing the indentation up
|
/// For docstring indentation, black counts spaces as 1 and tabs by increasing the indentation up
|
||||||
|
@ -792,7 +783,7 @@ fn format_docstring(string_part: &FormatStringPart, f: &mut PyFormatter) -> Form
|
||||||
return string_part.fmt(f);
|
return string_part.fmt(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (normalized, _) = normalize_string(
|
let normalized = normalize_string(
|
||||||
locator.slice(string_part),
|
locator.slice(string_part),
|
||||||
string_part.preferred_quotes,
|
string_part.preferred_quotes,
|
||||||
string_part.is_raw_string,
|
string_part.is_raw_string,
|
||||||
|
@ -837,7 +828,7 @@ fn format_docstring(string_part: &FormatStringPart, f: &mut PyFormatter) -> Form
|
||||||
let trimmed_line_range =
|
let trimmed_line_range =
|
||||||
TextRange::at(offset, trim_end.text_len()).add_start(leading_whitespace);
|
TextRange::at(offset, trim_end.text_len()).add_start(leading_whitespace);
|
||||||
if already_normalized {
|
if already_normalized {
|
||||||
source_text_slice(trimmed_line_range, ContainsNewlines::No).fmt(f)?;
|
source_text_slice(trimmed_line_range).fmt(f)?;
|
||||||
} else {
|
} else {
|
||||||
text(trim_both, Some(trimmed_line_range.start())).fmt(f)?;
|
text(trim_both, Some(trimmed_line_range.start())).fmt(f)?;
|
||||||
}
|
}
|
||||||
|
@ -954,7 +945,7 @@ fn format_docstring_line(
|
||||||
let trimmed_line_range =
|
let trimmed_line_range =
|
||||||
TextRange::at(offset, trim_end.text_len()).add_start(stripped_indentation);
|
TextRange::at(offset, trim_end.text_len()).add_start(stripped_indentation);
|
||||||
if already_normalized {
|
if already_normalized {
|
||||||
source_text_slice(trimmed_line_range, ContainsNewlines::No).fmt(f)?;
|
source_text_slice(trimmed_line_range).fmt(f)?;
|
||||||
} else {
|
} else {
|
||||||
// All indents are ascii spaces, so the slicing is correct
|
// All indents are ascii spaces, so the slicing is correct
|
||||||
text(
|
text(
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub struct FormatIdentifier;
|
||||||
|
|
||||||
impl FormatRule<Identifier, PyFormatContext<'_>> for FormatIdentifier {
|
impl FormatRule<Identifier, PyFormatContext<'_>> for FormatIdentifier {
|
||||||
fn fmt(&self, item: &Identifier, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, item: &Identifier, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
source_text_slice(item.range(), ContainsNewlines::No).fmt(f)
|
source_text_slice(item.range()).fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub struct FormatStmtIpyEscapeCommand;
|
||||||
|
|
||||||
impl FormatNodeRule<StmtIpyEscapeCommand> for FormatStmtIpyEscapeCommand {
|
impl FormatNodeRule<StmtIpyEscapeCommand> for FormatStmtIpyEscapeCommand {
|
||||||
fn fmt_fields(&self, item: &StmtIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt_fields(&self, item: &StmtIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
source_text_slice(item.range(), ContainsNewlines::No).fmt(f)
|
source_text_slice(item.range()).fmt(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_suppressed(
|
fn is_suppressed(
|
||||||
|
|
|
@ -709,7 +709,7 @@ impl Format<PyFormatContext<'_>> for FormatVerbatimStatementRange {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Non empty line, write the text of the line
|
// Non empty line, write the text of the line
|
||||||
verbatim_text(trimmed_line_range, logical_line.contains_newlines).fmt(f)?;
|
verbatim_text(trimmed_line_range).fmt(f)?;
|
||||||
|
|
||||||
// Write the line separator that terminates the line, except if it is the last line (that isn't separated by a hard line break).
|
// Write the line separator that terminates the line, except if it is the last line (that isn't separated by a hard line break).
|
||||||
if logical_line.has_trailing_newline {
|
if logical_line.has_trailing_newline {
|
||||||
|
@ -760,7 +760,6 @@ where
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let mut parens = 0u32;
|
let mut parens = 0u32;
|
||||||
let mut contains_newlines = ContainsNewlines::No;
|
|
||||||
|
|
||||||
let (content_end, full_end) = loop {
|
let (content_end, full_end) = loop {
|
||||||
match self.lexer.next() {
|
match self.lexer.next() {
|
||||||
|
@ -768,18 +767,12 @@ where
|
||||||
Tok::Newline => break (range.start(), range.end()),
|
Tok::Newline => break (range.start(), range.end()),
|
||||||
// Ignore if inside an expression
|
// Ignore if inside an expression
|
||||||
Tok::NonLogicalNewline if parens == 0 => break (range.start(), range.end()),
|
Tok::NonLogicalNewline if parens == 0 => break (range.start(), range.end()),
|
||||||
Tok::NonLogicalNewline => {
|
|
||||||
contains_newlines = ContainsNewlines::Yes;
|
|
||||||
}
|
|
||||||
Tok::Lbrace | Tok::Lpar | Tok::Lsqb => {
|
Tok::Lbrace | Tok::Lpar | Tok::Lsqb => {
|
||||||
parens = parens.saturating_add(1);
|
parens = parens.saturating_add(1);
|
||||||
}
|
}
|
||||||
Tok::Rbrace | Tok::Rpar | Tok::Rsqb => {
|
Tok::Rbrace | Tok::Rpar | Tok::Rsqb => {
|
||||||
parens = parens.saturating_sub(1);
|
parens = parens.saturating_sub(1);
|
||||||
}
|
}
|
||||||
Tok::String { value, .. } if value.contains(['\n', '\r']) => {
|
|
||||||
contains_newlines = ContainsNewlines::Yes;
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
|
@ -790,7 +783,6 @@ where
|
||||||
self.last_line_end = self.content_end;
|
self.last_line_end = self.content_end;
|
||||||
Some(Ok(LogicalLine {
|
Some(Ok(LogicalLine {
|
||||||
content_range: TextRange::new(content_start, self.content_end),
|
content_range: TextRange::new(content_start, self.content_end),
|
||||||
contains_newlines: ContainsNewlines::No,
|
|
||||||
has_trailing_newline: false,
|
has_trailing_newline: false,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
|
@ -810,7 +802,6 @@ where
|
||||||
|
|
||||||
Some(Ok(LogicalLine {
|
Some(Ok(LogicalLine {
|
||||||
content_range: TextRange::new(line_start, content_end),
|
content_range: TextRange::new(line_start, content_end),
|
||||||
contains_newlines,
|
|
||||||
has_trailing_newline: true,
|
has_trailing_newline: true,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -822,8 +813,6 @@ impl<I> FusedIterator for LogicalLinesIter<I> where I: Iterator<Item = LexResult
|
||||||
struct LogicalLine {
|
struct LogicalLine {
|
||||||
/// The range of this lines content (excluding the trailing newline)
|
/// The range of this lines content (excluding the trailing newline)
|
||||||
content_range: TextRange,
|
content_range: TextRange,
|
||||||
/// Whether the content in `content_range` contains any newlines.
|
|
||||||
contains_newlines: ContainsNewlines,
|
|
||||||
/// Does this logical line have a trailing newline or does it just happen to be the last line.
|
/// Does this logical line have a trailing newline or does it just happen to be the last line.
|
||||||
has_trailing_newline: bool,
|
has_trailing_newline: bool,
|
||||||
}
|
}
|
||||||
|
@ -836,16 +825,14 @@ impl Ranged for LogicalLine {
|
||||||
|
|
||||||
struct VerbatimText {
|
struct VerbatimText {
|
||||||
verbatim_range: TextRange,
|
verbatim_range: TextRange,
|
||||||
contains_newlines: ContainsNewlines,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verbatim_text<T>(item: T, contains_newlines: ContainsNewlines) -> VerbatimText
|
fn verbatim_text<T>(item: T) -> VerbatimText
|
||||||
where
|
where
|
||||||
T: Ranged,
|
T: Ranged,
|
||||||
{
|
{
|
||||||
VerbatimText {
|
VerbatimText {
|
||||||
verbatim_range: item.range(),
|
verbatim_range: item.range(),
|
||||||
contains_newlines,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -859,13 +846,7 @@ impl Format<PyFormatContext<'_>> for VerbatimText {
|
||||||
|
|
||||||
match normalize_newlines(f.context().locator().slice(self.verbatim_range), ['\r']) {
|
match normalize_newlines(f.context().locator().slice(self.verbatim_range), ['\r']) {
|
||||||
Cow::Borrowed(_) => {
|
Cow::Borrowed(_) => {
|
||||||
write!(
|
write!(f, [source_text_slice(self.verbatim_range,)])?;
|
||||||
f,
|
|
||||||
[source_text_slice(
|
|
||||||
self.verbatim_range,
|
|
||||||
self.contains_newlines
|
|
||||||
)]
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Cow::Owned(cleaned) => {
|
Cow::Owned(cleaned) => {
|
||||||
write!(
|
write!(
|
||||||
|
@ -924,7 +905,7 @@ impl Format<PyFormatContext<'_>> for FormatSuppressedNode<'_> {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
leading_comments(node_comments.leading),
|
leading_comments(node_comments.leading),
|
||||||
verbatim_text(self.node, ContainsNewlines::Detect),
|
verbatim_text(self.node),
|
||||||
trailing_comments(node_comments.trailing)
|
trailing_comments(node_comments.trailing)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -937,13 +918,7 @@ pub(crate) fn write_suppressed_clause_header(
|
||||||
f: &mut PyFormatter,
|
f: &mut PyFormatter,
|
||||||
) -> FormatResult<()> {
|
) -> FormatResult<()> {
|
||||||
// Write the outer comments and format the node as verbatim
|
// Write the outer comments and format the node as verbatim
|
||||||
write!(
|
write!(f, [verbatim_text(header.range(f.context().source())?)])?;
|
||||||
f,
|
|
||||||
[verbatim_text(
|
|
||||||
header.range(f.context().source())?,
|
|
||||||
ContainsNewlines::Detect
|
|
||||||
)]
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let comments = f.context().comments();
|
let comments = f.context().comments();
|
||||||
header.visit(&mut |child| {
|
header.visit(&mut |child| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue