zte 0.1.5

A buffer/window-driven text editor that takes inspiration from Howl
use vek::*;
use super::Line;

#[derive(Clone)]
pub struct Content {
    lines: Vec<Vec<char>>,
}

impl Content {
    pub fn len(&self) -> usize {
        self
            .lines()
            .map(|line| line.len())
            .sum::<usize>().saturating_sub(1)
    }

    pub fn lines(&self) -> impl ExactSizeIterator<Item=Line> {
        self.lines
            .iter()
            .map(|line| Line::from(line.as_slice()))
    }

    pub fn line(&self, line: usize) -> Option<Line> {
        self
            .lines()
            .skip(line)
            .next()
    }

    pub fn chars(&self) -> impl Iterator<Item=char> + '_ {
        self.lines
            .iter()
            .map(|line| line
                .iter()
                .copied()
                .chain(Some('\n')))
            .flatten()
    }

    pub fn char_at(&self, pos: usize) -> Option<char> {
        let (rank, line) = self.pos_to_rank_line(pos).into_tuple();
        self.line(line)?.chars().nth(rank)
    }

    pub fn pos_to_rank_line(&self, mut pos: usize) -> Vec2<usize> {
        let mut row = 0;
        for line in self.lines() {
            if pos >= line.len() {
                row += 1;
                pos -= line.len();
            } else {
                break;
            }
        }
        Vec2::new(pos, row)
    }

    pub fn insert(&mut self, pos: usize, c: char) {
        let (rank, line) = self.pos_to_rank_line(pos).into_tuple();

        if self.lines.len() <= line {
            self.lines.push(Vec::new());
        }

        match c {
            '\n' => {
                let tail = self.lines[line].split_off(rank);
                self.lines.insert(line + 1, tail);
            },
            c => self.lines[line].insert(rank, c),
        }
    }

    pub fn insert_line(&mut self, line: usize, s: &str) {
        self.lines.insert(line, s.chars().collect());
    }

    pub fn remove(&mut self, pos: usize) {
        let (rank, line) = self.pos_to_rank_line(pos).into_tuple();

        if self.lines.len() <= line {
            self.lines.push(Vec::new());
        }

        if self.lines
            .get(line)
            .map(|l| rank == l.len())
            .unwrap_or(false)
        {
            if line < self.lines.len() - 1 {
                let mut old_line = self.lines.remove(line + 1);
                self.lines[line].append(&mut old_line);
            }
        } else if self.lines.get(line).is_some() {
            self.lines[line].remove(rank);
        }
    }
}

impl ToString for Content {
    fn to_string(&self) -> String {
        self.chars().collect()
    }
}

impl<T: AsRef<str>> From<T> for Content {
    fn from(s: T) -> Self {
        Self {
            lines: s
                .as_ref()
                .lines()
                .map(|l| l.chars().collect())
                .collect(),
        }
    }
}

impl Default for Content {
    fn default() -> Self {
        Self::from("\n")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pos_line() {
        let content = Content::from("hello");

        assert_eq!(content.lines().len(), 1);
        assert_eq!(content.lines().next().unwrap().len(), 6);
        assert_eq!(content.pos_loc(0), Vec2::new(0, 0));
        assert_eq!(content.pos_loc(3), Vec2::new(3, 0));
        assert_eq!(content.pos_loc(4), Vec2::new(4, 0));
        assert_eq!(content.pos_loc(5), Vec2::new(5, 0));

        let content = Content::from("hello\nworld\ntest");

        assert_eq!(content.lines().len(), 3);
        assert_eq!(content.lines().skip(2).next().unwrap().len(), 5);
        assert_eq!(content.pos_loc(5), Vec2::new(5, 0));
        assert_eq!(content.pos_loc(6), Vec2::new(0, 1));
        assert_eq!(content.pos_loc(11), Vec2::new(5, 1));
    }
}