refactor(buffer): simplify set_stringn logic (#1083)

This commit is contained in:
EdJoPaTo 2024-05-03 01:32:52 +02:00 committed by GitHub
parent 366c2a0e6d
commit baedc39494
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,8 +1,3 @@
use std::{
cmp::min,
fmt::{Debug, Formatter, Result},
};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -186,65 +181,56 @@ impl Buffer {
} }
/// Print a string, starting at the position (x, y) /// Print a string, starting at the position (x, y)
///
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
/// your own type that implements [`Into<Style>`]).
pub fn set_string<T, S>(&mut self, x: u16, y: u16, string: T, style: S) pub fn set_string<T, S>(&mut self, x: u16, y: u16, string: T, style: S)
where where
T: AsRef<str>, T: AsRef<str>,
S: Into<Style>, S: Into<Style>,
{ {
self.set_stringn(x, y, string, usize::MAX, style.into()); self.set_stringn(x, y, string, usize::MAX, style);
} }
/// Print at most the first n characters of a string if enough space is available /// Print at most the first n characters of a string if enough space is available
/// until the end of the line /// until the end of the line.
/// ///
/// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or /// Use [`Buffer::set_string`] when the maximum amount of characters can be printed.
/// your own type that implements [`Into<Style>`]).
pub fn set_stringn<T, S>( pub fn set_stringn<T, S>(
&mut self, &mut self,
x: u16, mut x: u16,
y: u16, y: u16,
string: T, string: T,
width: usize, max_width: usize,
style: S, style: S,
) -> (u16, u16) ) -> (u16, u16)
where where
T: AsRef<str>, T: AsRef<str>,
S: Into<Style>, S: Into<Style>,
{ {
let max_width = max_width.try_into().unwrap_or(u16::MAX);
let mut remaining_width = self.area.right().saturating_sub(x).min(max_width);
let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true)
.map(|symbol| (symbol, symbol.width() as u16))
.filter(|(_symbol, width)| *width > 0)
.map_while(|(symbol, width)| {
remaining_width = remaining_width.checked_sub(width)?;
Some((symbol, width))
});
let style = style.into(); let style = style.into();
let mut index = self.index_of(x, y); for (symbol, width) in graphemes {
let mut x_offset = x as usize; self.get_mut(x, y).set_symbol(symbol).set_style(style);
let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true); let next_symbol = x + width;
let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize)); x += 1;
for s in graphemes {
let width = s.width();
if width == 0 {
continue;
}
// `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
// change dimensions to usize or u32 and someone resizes the terminal to 1x2^32.
if width > max_offset.saturating_sub(x_offset) {
break;
}
self.content[index].set_symbol(s);
self.content[index].set_style(style);
// Reset following cells if multi-width (they would be hidden by the grapheme), // Reset following cells if multi-width (they would be hidden by the grapheme),
for i in index + 1..index + width { while x < next_symbol {
self.content[i].reset(); self.get_mut(x, y).reset();
x += 1;
} }
index += width;
x_offset += width;
} }
(x_offset as u16, y) (x, y)
} }
/// Print a line, starting at the position (x, y) /// Print a line, starting at the position (x, y)
pub fn set_line(&mut self, x: u16, y: u16, line: &Line<'_>, width: u16) -> (u16, u16) { pub fn set_line(&mut self, x: u16, y: u16, line: &Line<'_>, max_width: u16) -> (u16, u16) {
let mut remaining_width = width; let mut remaining_width = max_width;
let mut x = x; let mut x = x;
for span in line { for span in line {
if remaining_width == 0 { if remaining_width == 0 {
@ -265,8 +251,8 @@ impl Buffer {
} }
/// Print a span, starting at the position (x, y) /// Print a span, starting at the position (x, y)
pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, width: u16) -> (u16, u16) { pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, max_width: u16) -> (u16, u16) {
self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style) self.set_stringn(x, y, &span.content, max_width as usize, span.style)
} }
/// Set the style of all cells in the given area. /// Set the style of all cells in the given area.
@ -305,8 +291,7 @@ impl Buffer {
/// Merge an other buffer into this one /// Merge an other buffer into this one
pub fn merge(&mut self, other: &Self) { pub fn merge(&mut self, other: &Self) {
let area = self.area.union(other.area); let area = self.area.union(other.area);
let cell = Cell::default(); self.content.resize(area.area() as usize, Cell::default());
self.content.resize(area.area() as usize, cell.clone());
// Move original content to the appropriate space // Move original content to the appropriate space
let size = self.area.area() as usize; let size = self.area.area() as usize;
@ -316,7 +301,7 @@ impl Buffer {
let k = ((y - area.y) * area.width + x - area.x) as usize; let k = ((y - area.y) * area.width + x - area.x) as usize;
if i != k { if i != k {
self.content[k] = self.content[i].clone(); self.content[k] = self.content[i].clone();
self.content[i] = cell.clone(); self.content[i] = Cell::default();
} }
} }
@ -385,7 +370,7 @@ impl Buffer {
} }
} }
impl Debug for Buffer { impl std::fmt::Debug for Buffer {
/// Writes a debug representation of the buffer to the given formatter. /// Writes a debug representation of the buffer to the given formatter.
/// ///
/// The format is like a pretty printed struct, with the following fields: /// The format is like a pretty printed struct, with the following fields:
@ -393,7 +378,7 @@ impl Debug for Buffer {
/// * `content`: displayed as a list of strings representing the content of the buffer /// * `content`: displayed as a list of strings representing the content of the buffer
/// * `styles`: displayed as a list of: `{ x: 1, y: 2, fg: Color::Red, bg: Color::Blue, /// * `styles`: displayed as a list of: `{ x: 1, y: 2, fg: Color::Red, bg: Color::Blue,
/// modifier: Modifier::BOLD }` only showing a value when there is a change in style. /// modifier: Modifier::BOLD }` only showing a value when there is a change in style.
fn fmt(&self, f: &mut Formatter<'_>) -> Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!( f.write_fmt(format_args!(
"Buffer {{\n area: {:?},\n content: [\n", "Buffer {{\n area: {:?},\n content: [\n",
&self.area &self.area