pepper 0.31.0

A simple and opinionated modal code editor for your terminal
Documentation
use std::{
    cmp::{Ord, Ordering, PartialOrd},
    fmt,
};

pub type BufferPositionIndex = u32;

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct BufferPosition {
    pub line_index: BufferPositionIndex,
    pub column_byte_index: BufferPositionIndex,
}

impl BufferPosition {
    pub const fn zero() -> Self {
        Self {
            line_index: 0,
            column_byte_index: 0,
        }
    }

    pub const fn line_col(
        line_index: BufferPositionIndex,
        column_byte_index: BufferPositionIndex,
    ) -> Self {
        Self {
            line_index,
            column_byte_index,
        }
    }

    pub fn insert(self, range: BufferRange) -> Self {
        if self.line_index < range.from.line_index {
            self
        } else if self.line_index > range.from.line_index {
            Self {
                column_byte_index: self.column_byte_index,
                line_index: self.line_index + range.to.line_index - range.from.line_index,
            }
        } else if self.column_byte_index < range.from.column_byte_index {
            self
        } else {
            Self {
                column_byte_index: self.column_byte_index + range.to.column_byte_index
                    - range.from.column_byte_index,
                line_index: self.line_index + range.to.line_index - range.from.line_index,
            }
        }
    }

    pub fn delete(self, range: BufferRange) -> Self {
        if self.line_index < range.from.line_index {
            self
        } else if self.line_index > range.to.line_index {
            Self {
                column_byte_index: self.column_byte_index,
                line_index: self.line_index - (range.to.line_index - range.from.line_index),
            }
        } else if self.line_index == range.from.line_index
            && self.column_byte_index < range.from.column_byte_index
        {
            self
        } else if self.line_index == range.to.line_index
            && self.column_byte_index > range.to.column_byte_index
        {
            Self {
                column_byte_index: range.from.column_byte_index + self.column_byte_index
                    - range.to.column_byte_index,
                line_index: range.from.line_index,
            }
        } else {
            range.from
        }
    }

    pub fn parse(s: &str) -> Option<(Self, &str)> {
        fn is_non_ascii_digit(c: char) -> bool {
            !c.is_ascii_digit()
        }

        let i = s.find(is_non_ascii_digit).unwrap_or(s.len());
        let (line_number, s) = s.split_at(i);
        let line_index = match line_number.parse::<BufferPositionIndex>() {
            Ok(n) => n.saturating_sub(1),
            Err(_) => return None,
        };

        let mut chars = s.chars();
        if !matches!(chars.next(), Some(':')) {
            return Some((Self::line_col(line_index, 0), s));
        }
        let rest_after_line_index = s;
        let s = chars.as_str();

        let i = s.find(is_non_ascii_digit).unwrap_or(s.len());
        let column_index = match s[..i].parse::<BufferPositionIndex>() {
            Ok(n) => n.saturating_sub(1),
            Err(_) => return Some((Self::line_col(line_index, 0), rest_after_line_index)),
        };
        let s = &s[i..];
        Some((Self::line_col(line_index, column_index), s))
    }
}

impl fmt::Debug for BufferPosition {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "BufferPosition(line: {}, col: {})",
            self.line_index, self.column_byte_index,
        )
    }
}

impl fmt::Display for BufferPosition {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}:{}", self.line_index + 1, self.column_byte_index + 1)
    }
}

impl Ord for BufferPosition {
    fn cmp(&self, other: &Self) -> Ordering {
        if self.line_index < other.line_index {
            Ordering::Less
        } else if self.line_index > other.line_index {
            Ordering::Greater
        } else if self.column_byte_index < other.column_byte_index {
            Ordering::Less
        } else if self.column_byte_index > other.column_byte_index {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    }
}

impl PartialOrd for BufferPosition {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct BufferRange {
    pub from: BufferPosition,
    pub to: BufferPosition,
    __: (),
}

impl BufferRange {
    pub const fn zero() -> Self {
        Self {
            from: BufferPosition::zero(),
            to: BufferPosition::zero(),
            __: (),
        }
    }

    pub fn between(from: BufferPosition, to: BufferPosition) -> Self {
        let (from, to) = if from <= to { (from, to) } else { (to, from) };
        Self { from, to, __: () }
    }

    pub fn between_with_direction(from: BufferPosition, to: BufferPosition) -> (Self, bool) {
        let forward = from <= to;
        let (from, to) = if forward { (from, to) } else { (to, from) };
        (Self { from, to, __: () }, forward)
    }
}

impl fmt::Debug for BufferRange {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "BufferRange(line: {}, col: {} => line: {}, col: {})",
            self.from.line_index,
            self.from.column_byte_index,
            self.to.line_index,
            self.to.column_byte_index
        )
    }
}

pub struct BufferRangesParser<'a>(pub &'a str);
impl<'a> Iterator for BufferRangesParser<'a> {
    type Item = (BufferPosition, BufferPosition);

    fn next(&mut self) -> Option<Self::Item> {
        if self.0.is_empty() {
            return None;
        }

        let (from, rest) = BufferPosition::parse(self.0)?;
        let mut rest_chars = rest.chars();
        let (to, rest) = match rest_chars.next() {
            Some('-') => match BufferPosition::parse(rest_chars.as_str()) {
                Some(result) => result,
                None => (from, rest),
            },
            _ => (from, rest),
        };

        let mut rest_chars = rest.chars();
        match rest_chars.next() {
            Some(',') => self.0 = rest_chars.as_str(),
            _ => self.0 = "",
        }

        Some((from, to))
    }
}

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

    fn pos(
        line_index: BufferPositionIndex,
        column_byte_index: BufferPositionIndex,
    ) -> BufferPosition {
        BufferPosition::line_col(line_index, column_byte_index)
    }

    #[test]
    fn buffer_position_comparison() {
        assert!(pos(0, 0) < pos(0, 9));
        assert!(pos(0, 0) < pos(0, 14));
    }

    #[test]
    fn buffer_position_insert() {
        let pos12 = pos(1, 2);
        let pos31 = pos(3, 1);
        let pos32 = pos(3, 2);
        let pos33 = pos(3, 3);
        let pos36 = pos(3, 6);
        let pos42 = pos(4, 2);
        let pos53 = pos(5, 3);
        let pos66 = pos(6, 6);
        let range31_33 = BufferRange::between(pos(3, 1), pos(3, 3));
        let range33_51 = BufferRange::between(pos(3, 3), pos(5, 1));

        assert_eq!(pos12, pos12.insert(range31_33));
        assert_eq!(pos(3, 3), pos31.insert(range31_33));
        assert_eq!(pos(3, 4), pos32.insert(range31_33));
        assert_eq!(pos(3, 5), pos33.insert(range31_33));
        assert_eq!(pos42, pos42.insert(range31_33));

        assert_eq!(pos12, pos12.insert(range33_51));
        assert_eq!(pos(5, 1), pos33.insert(range33_51));
        assert_eq!(pos(5, 4), pos36.insert(range33_51));
        assert_eq!(pos(6, 2), pos42.insert(range33_51));
        assert_eq!(pos(7, 3), pos53.insert(range33_51));
        assert_eq!(pos(8, 6), pos66.insert(range33_51));
    }

    #[test]
    fn buffer_position_delete() {
        let pos12 = pos(1, 2);
        let pos31 = pos(3, 1);
        let pos32 = pos(3, 2);
        let pos33 = pos(3, 3);
        let pos36 = pos(3, 6);
        let pos42 = pos(4, 2);
        let pos53 = pos(5, 3);
        let pos66 = pos(6, 6);
        let range31_33 = BufferRange::between(pos(3, 1), pos(3, 3));
        let range33_51 = BufferRange::between(pos(3, 3), pos(5, 1));

        assert_eq!(pos12, pos12.delete(range31_33));
        assert_eq!(pos(3, 1), pos31.delete(range31_33));
        assert_eq!(pos(3, 1), pos32.delete(range31_33));
        assert_eq!(pos(3, 1), pos33.delete(range31_33));
        assert_eq!(pos42, pos42.delete(range31_33));
        assert_eq!(pos53, pos53.delete(range31_33));

        assert_eq!(pos12, pos12.delete(range33_51));
        assert_eq!(pos(3, 3), pos33.delete(range33_51));
        assert_eq!(pos(3, 3), pos36.delete(range33_51));
        assert_eq!(pos(3, 3), pos42.delete(range33_51));
        assert_eq!(pos(3, 5), pos53.delete(range33_51));
        assert_eq!(pos(4, 6), pos66.delete(range33_51));
    }

    #[test]
    fn buffer_position_parsing() {
        assert_eq!(None, BufferPosition::parse(""));
        assert_eq!(None, BufferPosition::parse(":"));
        assert_eq!(None, BufferPosition::parse("a:"));
        assert_eq!(None, BufferPosition::parse(":b"));
        assert_eq!(None, BufferPosition::parse("a:b"));

        assert_eq!(Some((pos(0, 0), "")), BufferPosition::parse("0"));
        assert_eq!(Some((pos(0, 0), "")), BufferPosition::parse("1"));
        assert_eq!(Some((pos(1, 0), "")), BufferPosition::parse("2"));
        assert_eq!(Some((pos(98, 0), "")), BufferPosition::parse("99"));

        assert_eq!(Some((pos(0, 0), "")), BufferPosition::parse("0:0"));
        assert_eq!(Some((pos(0, 0), "")), BufferPosition::parse("1:1"));
        assert_eq!(Some((pos(3, 1), "")), BufferPosition::parse("4:2"));
        assert_eq!(Some((pos(98, 98), "")), BufferPosition::parse("99:99"));

        assert_eq!(Some((pos(3, 0), ":")), BufferPosition::parse("4:"));
        assert_eq!(Some((pos(3, 0), ":x")), BufferPosition::parse("4:x"));
        assert_eq!(Some((pos(3, 8), "xx")), BufferPosition::parse("4:9xx"));
        assert_eq!(Some((pos(3, 8), ":xx")), BufferPosition::parse("4:9:xx"));
    }

    #[test]
    fn buffer_ranges_parsing() {
        fn range(
            from: (BufferPositionIndex, BufferPositionIndex),
            to: (BufferPositionIndex, BufferPositionIndex),
        ) -> (BufferPosition, BufferPosition) {
            (pos(from.0, from.1), pos(to.0, to.1))
        }

        let mut ranges = BufferRangesParser("");
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3");
        assert_eq!(Some(range((2, 0), (2, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3,");
        assert_eq!(Some(range((2, 0), (2, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3-");
        assert_eq!(Some(range((2, 0), (2, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3x");
        assert_eq!(Some(range((2, 0), (2, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2");
        assert_eq!(Some(range((2, 1), (2, 1))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2,");
        assert_eq!(Some(range((2, 1), (2, 1))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2-");
        assert_eq!(Some(range((2, 1), (2, 1))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2x");
        assert_eq!(Some(range((2, 1), (2, 1))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2-6:1");
        assert_eq!(Some(range((2, 1), (5, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2-6:1,");
        assert_eq!(Some(range((2, 1), (5, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2-6:1-");
        assert_eq!(Some(range((2, 1), (5, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2-6:1x");
        assert_eq!(Some(range((2, 1), (5, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:2-6:1,99,3-6");
        assert_eq!(Some(range((2, 1), (5, 0))), ranges.next());
        assert_eq!(Some(range((98, 0), (98, 0))), ranges.next());
        assert_eq!(Some(range((2, 0), (5, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3:4:5:6");
        assert_eq!(Some(range((2, 3), (2, 3))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3-4-5-6");
        assert_eq!(Some(range((2, 0), (3, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("3,4,5,6");
        assert_eq!(Some(range((2, 0), (2, 0))), ranges.next());
        assert_eq!(Some(range((3, 0), (3, 0))), ranges.next());
        assert_eq!(Some(range((4, 0), (4, 0))), ranges.next());
        assert_eq!(Some(range((5, 0), (5, 0))), ranges.next());
        assert_eq!(None, ranges.next());

        let mut ranges = BufferRangesParser("2:3-4:5,6:7-8:9,10:11-12:13");
        assert_eq!(Some(range((1, 2), (3, 4))), ranges.next());
        assert_eq!(Some(range((5, 6), (7, 8))), ranges.next());
        assert_eq!(Some(range((9, 10), (11, 12))), ranges.next());
        assert_eq!(None, ranges.next());
    }
}