diff --git a/library/core/src/bstr/mod.rs b/library/core/src/bstr/mod.rs index 2be7dfc9bfdda..9d623d3af8b5d 100644 --- a/library/core/src/bstr/mod.rs +++ b/library/core/src/bstr/mod.rs @@ -6,7 +6,7 @@ mod traits; pub use traits::{impl_partial_eq, impl_partial_eq_n, impl_partial_eq_ord}; use crate::borrow::{Borrow, BorrowMut}; -use crate::fmt; +use crate::fmt::{self, Alignment}; use crate::ops::{Deref, DerefMut, DerefPure}; /// A wrapper for `&[u8]` representing a human-readable string that's conventionally, but not @@ -174,43 +174,85 @@ impl fmt::Debug for ByteStr { #[unstable(feature = "bstr", issue = "134915")] impl fmt::Display for ByteStr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let nchars: usize = self - .utf8_chunks() - .map(|chunk| { - chunk.valid().chars().count() + if chunk.invalid().is_empty() { 0 } else { 1 } - }) - .sum(); - - let padding = f.width().unwrap_or(0).saturating_sub(nchars); - let fill = f.fill(); - - let (lpad, rpad) = match f.align() { - Some(fmt::Alignment::Right) => (padding, 0), - Some(fmt::Alignment::Center) => { - let half = padding / 2; - (half, half + padding % 2) + fn emit(byte_str: &ByteStr, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for chunk in byte_str.utf8_chunks() { + f.write_str(chunk.valid())?; + if !chunk.invalid().is_empty() { + f.write_str("\u{FFFD}")?; + } } - // Either alignment is not specified or it's left aligned - // which behaves the same with padding - _ => (0, padding), - }; - for _ in 0..lpad { - write!(f, "{fill}")?; + Ok(()) } - for chunk in self.utf8_chunks() { - f.write_str(chunk.valid())?; - if !chunk.invalid().is_empty() { - f.write_str("\u{FFFD}")?; - } + let requested_width = f.width().unwrap_or(0); + if requested_width == 0 && f.precision().is_none() { + // Avoid counting the characters if no truncation or padding was + // requested. + return emit(self, f); } - for _ in 0..rpad { - write!(f, "{fill}")?; - } + let (truncated, actual_width) = match f.precision() { + // The entire string is truncated away. Weird, but ok. + Some(0) => (ByteStr::new(&[]), 0), + // Advance through string until we run out of space. + Some(precision) => { + let mut remaining_width = precision; + let mut chunks = self.utf8_chunks(); + let mut current_width = 0; + let mut offset = 0; + loop { + let Some(chunk) = chunks.next() else { + // We reached the end of the string without running out + // of space, so print the entire string. + break (self, current_width); + }; + + let mut chars = chunk.valid().char_indices(); + let Err(remaining) = chars.advance_by(remaining_width) else { + // We've counted off `precision` characters, so truncate + // the string at the current offset. + break (&self[..offset + chars.offset()], precision); + }; + + offset += chunk.valid().len(); + current_width += remaining_width - remaining.get(); + remaining_width = remaining.get(); + + // `remaining_width` cannot be zero, there is still space + // remaining. So next, count the � character emitted for + // the invalid chunk (if it exists). + if !chunk.invalid().is_empty() { + offset += chunk.invalid().len(); + current_width += 1; + remaining_width -= 1; + + if remaining_width == 0 { + break (&self[..offset], precision); + } + } + } + } + // The string shouldn't be truncated at all, so just count the number + // of characters to calculate the padding. + None => { + let actual_width = self + .utf8_chunks() + .map(|chunk| { + chunk.valid().chars().count() + + if chunk.invalid().is_empty() { 0 } else { 1 } + }) + .sum(); + (self, actual_width) + } + }; - Ok(()) + // The width is originally stored as a 16-bit number, so this cannot fail. + let padding = u16::try_from(requested_width.saturating_sub(actual_width)).unwrap(); + + let post_padding = f.padding(padding, Alignment::Left)?; + emit(truncated, f)?; + post_padding.write(f) } } diff --git a/library/coretests/tests/bstr.rs b/library/coretests/tests/bstr.rs index cd4d69d6b337d..dffc7e3f03494 100644 --- a/library/coretests/tests/bstr.rs +++ b/library/coretests/tests/bstr.rs @@ -49,4 +49,19 @@ fn test_display() { assert_eq!(&format!("{b2:->3}!"), "�(��!"); assert_eq!(&format!("{b2:-^3}!"), "�(��!"); assert_eq!(&format!("{b2:-^2}!"), "�(��!"); + + assert_eq!(&format!("{b1:.1}!"), &format!("{:.1}!", "abc")); + assert_eq!(&format!("{b1:.2}!"), &format!("{:.2}!", "abc")); + assert_eq!(&format!("{b1:.3}!"), &format!("{:.3}!", "abc")); + assert_eq!(&format!("{b1:-<5.2}!"), &format!("{:-<5.2}!", "abc")); + assert_eq!(&format!("{b1:-^5.2}!"), &format!("{:-^5.2}!", "abc")); + assert_eq!(&format!("{b1:->5.2}!"), &format!("{:->5.2}!", "abc")); + + assert_eq!(&format!("{b2:.1}!"), "�!"); + assert_eq!(&format!("{b2:.2}!"), "�(!"); + assert_eq!(&format!("{b2:.3}!"), "�(�!"); + assert_eq!(&format!("{b2:.4}!"), "�(��!"); + assert_eq!(&format!("{b2:-<6.3}!"), "�(�---!"); + assert_eq!(&format!("{b2:-^6.3}!"), "-�(�--!"); + assert_eq!(&format!("{b2:->6.3}!"), "---�(�!"); }