feat(line): implement iterators for Line (#896)

This allows iterating over the `Span`s of a line using `for` loops and
other iterator methods.

- add `iter` and `iter_mut` methods to `Line`
- implement `IntoIterator` for `Line`, `&Line`, and `&mut Line` traits
- update call sites to iterate over `Line` rather than `Line::spans`
This commit is contained in:
Josh McKinney 2024-01-31 08:33:30 -08:00 committed by GitHub
parent 86168aa711
commit 4278b4088d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 130 additions and 4 deletions

View file

@ -243,7 +243,7 @@ impl Buffer {
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<'_>, width: u16) -> (u16, u16) {
let mut remaining_width = width; let mut remaining_width = width;
let mut x = x; let mut x = x;
for span in &line.spans { for span in line {
if remaining_width == 0 { if remaining_width == 0 {
break; break;
} }

View file

@ -368,6 +368,43 @@ impl<'a> Line<'a> {
pub fn reset_style(self) -> Self { pub fn reset_style(self) -> Self {
self.patch_style(Style::reset()) self.patch_style(Style::reset())
} }
/// Returns an iterator over the spans of this line.
pub fn iter(&self) -> std::slice::Iter<Span<'a>> {
self.spans.iter()
}
/// Returns a mutable iterator over the spans of this line.
pub fn iter_mut(&mut self) -> std::slice::IterMut<Span<'a>> {
self.spans.iter_mut()
}
}
impl<'a> IntoIterator for Line<'a> {
type Item = Span<'a>;
type IntoIter = std::vec::IntoIter<Span<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.spans.into_iter()
}
}
impl<'a> IntoIterator for &'a Line<'a> {
type Item = &'a Span<'a>;
type IntoIter = std::slice::Iter<'a, Span<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut Line<'a> {
type Item = &'a mut Span<'a>;
type IntoIter = std::slice::IterMut<'a, Span<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
} }
impl<'a> From<String> for Line<'a> { impl<'a> From<String> for Line<'a> {
@ -399,7 +436,7 @@ impl<'a> From<Span<'a>> for Line<'a> {
impl<'a> From<Line<'a>> for String { impl<'a> From<Line<'a>> for String {
fn from(line: Line<'a>) -> String { fn from(line: Line<'a>) -> String {
line.spans.iter().fold(String::new(), |mut acc, s| { line.iter().fold(String::new(), |mut acc, s| {
acc.push_str(s.content.as_ref()); acc.push_str(s.content.as_ref());
acc acc
}) })
@ -461,6 +498,8 @@ impl std::fmt::Display for Line<'_> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use rstest::{fixture, rstest};
use super::*; use super::*;
#[test] #[test]
@ -752,4 +791,92 @@ mod tests {
let line = Line::from("Hello, world!").right_aligned(); let line = Line::from("Hello, world!").right_aligned();
assert_eq!(line.alignment, Some(Alignment::Right)); assert_eq!(line.alignment, Some(Alignment::Right));
} }
mod iterators {
use super::*;
/// a fixture used in the tests below to avoid repeating the same setup
#[fixture]
fn hello_world() -> Line<'static> {
Line::from(vec![
Span::styled("Hello ", Color::Blue),
Span::styled("world!", Color::Green),
])
}
#[rstest]
fn iter(hello_world: Line<'_>) {
let mut iter = hello_world.iter();
assert_eq!(iter.next(), Some(&Span::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(&Span::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[rstest]
fn iter_mut(mut hello_world: Line<'_>) {
let mut iter = hello_world.iter_mut();
assert_eq!(iter.next(), Some(&mut Span::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(&mut Span::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[rstest]
fn into_iter(hello_world: Line<'_>) {
let mut iter = hello_world.into_iter();
assert_eq!(iter.next(), Some(Span::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(Span::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[rstest]
fn into_iter_ref(hello_world: Line<'_>) {
let mut iter = (&hello_world).into_iter();
assert_eq!(iter.next(), Some(&Span::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(&Span::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[test]
fn into_iter_mut_ref() {
let mut hello_world = Line::from(vec![
Span::styled("Hello ", Color::Blue),
Span::styled("world!", Color::Green),
]);
let mut iter = (&mut hello_world).into_iter();
assert_eq!(iter.next(), Some(&mut Span::styled("Hello ", Color::Blue)));
assert_eq!(iter.next(), Some(&mut Span::styled("world!", Color::Green)));
assert_eq!(iter.next(), None);
}
#[rstest]
fn for_loop_ref(hello_world: Line<'_>) {
let mut result = String::new();
for span in &hello_world {
result.push_str(span.content.as_ref());
}
assert_eq!(result, "Hello world!");
}
#[rstest]
fn for_loop_mut_ref() {
let mut hello_world = Line::from(vec![
Span::styled("Hello ", Color::Blue),
Span::styled("world!", Color::Green),
]);
let mut result = String::new();
for span in &mut hello_world {
result.push_str(span.content.as_ref());
}
assert_eq!(result, "Hello world!");
}
#[rstest]
fn for_loop_into(hello_world: Line<'_>) {
let mut result = String::new();
for span in hello_world {
result.push_str(span.content.as_ref());
}
assert_eq!(result, "Hello world!");
}
}
} }

View file

@ -357,8 +357,7 @@ mod test {
let text = text.into(); let text = text.into();
let styled_lines = text.lines.iter().map(|line| { let styled_lines = text.lines.iter().map(|line| {
( (
line.spans line.iter()
.iter()
.flat_map(|span| span.styled_graphemes(Style::default())), .flat_map(|span| span.styled_graphemes(Style::default())),
line.alignment.unwrap_or(Alignment::Left), line.alignment.unwrap_or(Alignment::Left),
) )