Skip to content

Commit

Permalink
feat(line): implement iterators for Line (#896)
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
joshka authored Jan 31, 2024
1 parent 86168aa commit 4278b40
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/buffer/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ impl Buffer {
pub fn set_line(&mut self, x: u16, y: u16, line: &Line<'_>, width: u16) -> (u16, u16) {
let mut remaining_width = width;
let mut x = x;
for span in &line.spans {
for span in line {
if remaining_width == 0 {
break;
}
Expand Down
129 changes: 128 additions & 1 deletion src/text/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,43 @@ impl<'a> Line<'a> {
pub fn reset_style(self) -> Self {
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> {
Expand Down Expand Up @@ -399,7 +436,7 @@ impl<'a> From<Span<'a>> for Line<'a> {

impl<'a> From<Line<'a>> for 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
})
Expand Down Expand Up @@ -461,6 +498,8 @@ impl std::fmt::Display for Line<'_> {

#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};

use super::*;

#[test]
Expand Down Expand Up @@ -752,4 +791,92 @@ mod tests {
let line = Line::from("Hello, world!").right_aligned();
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!");
}
}
}
3 changes: 1 addition & 2 deletions src/widgets/reflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,7 @@ mod test {
let text = text.into();
let styled_lines = text.lines.iter().map(|line| {
(
line.spans
.iter()
line.iter()
.flat_map(|span| span.styled_graphemes(Style::default())),
line.alignment.unwrap_or(Alignment::Left),
)
Expand Down

0 comments on commit 4278b40

Please sign in to comment.