use std::error::Error; use std::fmt; use std::hash::Hasher; use std::num::{NonZeroU16, NonZeroU8, ParseIntError}; use std::str::FromStr; use serde::{Deserialize, Serialize}; use unicode_width::UnicodeWidthChar; use ruff_cache::{CacheKey, CacheKeyHasher}; use ruff_macros::CacheKey; use ruff_text_size::TextSize; /// The length of a line of text that is considered too long. /// /// The allowed range of values is 1..=320 #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct LineLength( #[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))] NonZeroU16, ); impl LineLength { /// Maximum allowed value for a valid [`LineLength`] pub const MAX: u16 = 320; /// Return the numeric value for this [`LineLength`] pub fn value(&self) -> u16 { self.0.get() } pub fn text_len(&self) -> TextSize { TextSize::from(u32::from(self.value())) } } impl Default for LineLength { fn default() -> Self { Self(NonZeroU16::new(88).unwrap()) } } impl fmt::Display for LineLength { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl CacheKey for LineLength { fn cache_key(&self, state: &mut CacheKeyHasher) { state.write_u16(self.0.get()); } } /// Error type returned when parsing a [`LineLength`] from a string fails pub enum ParseLineWidthError { /// The string could not be parsed as a valid [u16] ParseError(ParseIntError), /// The [u16] value of the string is not a valid [`LineLength`] TryFromIntError(LineLengthFromIntError), } impl std::fmt::Debug for ParseLineWidthError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self, f) } } impl std::fmt::Display for ParseLineWidthError { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ParseLineWidthError::ParseError(err) => std::fmt::Display::fmt(err, fmt), ParseLineWidthError::TryFromIntError(err) => std::fmt::Display::fmt(err, fmt), } } } impl Error for ParseLineWidthError {} impl FromStr for LineLength { type Err = ParseLineWidthError; fn from_str(s: &str) -> Result { let value = u16::from_str(s).map_err(ParseLineWidthError::ParseError)?; let value = Self::try_from(value).map_err(ParseLineWidthError::TryFromIntError)?; Ok(value) } } /// Error type returned when converting a u16 to a [`LineLength`] fails #[derive(Clone, Copy, Debug)] pub struct LineLengthFromIntError(pub u16); impl TryFrom for LineLength { type Error = LineLengthFromIntError; fn try_from(value: u16) -> Result { match NonZeroU16::try_from(value) { Ok(value) if value.get() <= Self::MAX => Ok(LineLength(value)), Ok(_) | Err(_) => Err(LineLengthFromIntError(value)), } } } impl std::fmt::Display for LineLengthFromIntError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!( f, "The line width must be a value between 1 and {}.", LineLength::MAX ) } } impl From for u16 { fn from(value: LineLength) -> Self { value.0.get() } } impl From for NonZeroU16 { fn from(value: LineLength) -> Self { value.0 } } /// A measure of the width of a line of text. /// /// This is used to determine if a line is too long. /// It should be compared to a [`LineLength`]. #[derive(Clone, Copy, Debug)] pub struct LineWidthBuilder { /// The width of the line. width: usize, /// The column of the line. /// This is used to calculate the width of tabs. column: usize, /// The tab size to use when calculating the width of tabs. tab_size: IndentWidth, } impl Default for LineWidthBuilder { fn default() -> Self { Self::new(IndentWidth::default()) } } impl PartialEq for LineWidthBuilder { fn eq(&self, other: &Self) -> bool { self.width == other.width } } impl Eq for LineWidthBuilder {} impl PartialOrd for LineWidthBuilder { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for LineWidthBuilder { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.width.cmp(&other.width) } } impl LineWidthBuilder { pub fn get(&self) -> usize { self.width } /// Creates a new `LineWidth` with the given tab size. pub fn new(tab_size: IndentWidth) -> Self { LineWidthBuilder { width: 0, column: 0, tab_size, } } fn update(mut self, chars: impl Iterator) -> Self { let tab_size: usize = self.tab_size.as_usize(); for c in chars { match c { '\t' => { let tab_offset = tab_size - (self.column % tab_size); self.width += tab_offset; self.column += tab_offset; } '\n' | '\r' => { self.width = 0; self.column = 0; } _ => { self.width += c.width().unwrap_or(0); self.column += 1; } } } self } /// Adds the given text to the line width. #[must_use] pub fn add_str(self, text: &str) -> Self { self.update(text.chars()) } /// Adds the given character to the line width. #[must_use] pub fn add_char(self, c: char) -> Self { self.update(std::iter::once(c)) } /// Adds the given width to the line width. /// Also adds the given width to the column. /// It is generally better to use [`LineWidthBuilder::add_str`] or [`LineWidthBuilder::add_char`]. /// The width and column should be the same for the corresponding text. /// Currently, this is only used to add spaces. #[must_use] pub fn add_width(mut self, width: usize) -> Self { self.width += width; self.column += width; self } } impl PartialEq for LineWidthBuilder { fn eq(&self, other: &LineLength) -> bool { self.width == (other.value() as usize) } } impl PartialOrd for LineWidthBuilder { fn partial_cmp(&self, other: &LineLength) -> Option { self.width.partial_cmp(&(other.value() as usize)) } } /// The size of a tab. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CacheKey)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct IndentWidth(NonZeroU8); impl IndentWidth { pub(crate) fn as_usize(self) -> usize { self.0.get() as usize } } impl Default for IndentWidth { fn default() -> Self { Self(NonZeroU8::new(4).unwrap()) } } impl fmt::Display for IndentWidth { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl From for IndentWidth { fn from(tab_size: NonZeroU8) -> Self { Self(tab_size) } } impl From for NonZeroU8 { fn from(value: IndentWidth) -> Self { value.0 } }