use std::{
fmt::{Debug, Display},
str::FromStr
};
use crate::{InsertPosition, TextPosition};
#[derive(Default, Clone, PartialEq, Eq)]
pub struct TextRange {
start: InsertPosition,
end: InsertPosition,
}
impl TextRange {
pub fn new(start: InsertPosition, end: InsertPosition) -> Self {
assert!(start <= end);
TextRange { start, end }
}
pub fn new_empty(pos: InsertPosition) -> Self {
Self::new(pos.clone(), pos)
}
pub fn inc_col(&mut self) {
self.end.inc_col();
}
pub fn inc_line(&mut self) {
self.end.inc_line();
}
pub fn inc(&mut self, c: char) {
if c == '\n' {
self.inc_line();
} else {
self.inc_col();
}
}
pub fn merge(&mut self, other: TextRange) {
assert!(self.end() == other.start());
self.end = other.end;
}
pub fn begin_next_range(&self) -> Self {
TextRange::new_empty(self.end().clone())
}
pub fn start(&self) -> &InsertPosition { &self.start }
pub fn end(&self) -> &InsertPosition { &self.end }
}
impl Display for TextRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.start() == self.end() {
return write!(f, "empty after {}", self.start());
}
let start = self.start().text_pos_right();
let end = self.end().text_pos_left();
if start == end {
write!(f, "{}", start)
} else {
write!(f, "{} - {}", start, end)
}
}
}
impl Debug for TextRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl FromStr for TextRange {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once('-') {
None => {
let pos: TextPosition = s.parse()?;
Ok(TextRange::new(pos.insert_pos_left(), pos.insert_pos_right()))
}
Some((start, end)) => {
let start = start.trim().parse::<TextPosition>()?.insert_pos_left();
let end = end.trim().parse::<TextPosition>()?.insert_pos_right();
if end < start { return Err(()); }
Ok(TextRange::new(start, end))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn disallow_reverse_base_next() {
let mut pos1 = InsertPosition::default();
let pos2 = InsertPosition::default();
pos1.inc_col();
let _ = TextRange::new(pos1, pos2);
}
#[test]
fn allow_creaton_empty_range() {
let pos1 = InsertPosition::default();
let empty = TextRange::new_empty(pos1);
assert_eq!(empty, TextRange::default());
}
#[test]
fn merge() {
let mut ran1 = TextRange::default();
ran1.inc_col();
let mut pos1 = InsertPosition::default();
pos1.inc_col();
let mut pos2 = pos1.clone();
pos2.inc_col();
let ran2 = TextRange::new(pos1, pos2.clone());
let ran3 = TextRange::new_empty(pos2);
ran1.merge(ran2);
ran1.merge(ran3);
}
#[test]
#[should_panic]
fn merge_should_fail() {
let mut ran1 = TextRange::default();
let mut pos1 = InsertPosition::default();
pos1.inc_col();
pos1.inc_col();
let ran2 = TextRange::new_empty(pos1);
ran1.merge(ran2);
}
#[test]
fn begin_next_range() {
let mut ran1 = TextRange::default();
ran1.inc_line();
ran1.inc_col();
ran1.inc_col();
let ran2 = ran1.begin_next_range();
assert_eq!(ran2, TextRange::new_empty(InsertPosition::new(2, 2)));
}
#[test]
fn display_print() {
let mut ran1 = TextRange::default();
assert_eq!(ran1.to_string(), "empty after 1:0");
ran1.inc_line();
ran1.inc_line();
ran1.inc_col();
ran1.inc_col();
ran1.inc_col();
assert_eq!(ran1.to_string(), "1:1 - 3:3");
ran1.inc_line();
assert_eq!(ran1.to_string(), "1:1 - 4:0");
}
#[test]
fn debug_print() {
let mut ran1 = TextRange::default();
assert_eq!(format!("{:?}", ran1), "empty after 1:0");
ran1.inc_line();
ran1.inc_line();
ran1.inc_col();
ran1.inc_col();
ran1.inc_col();
assert_eq!(format!("{:?}", ran1), "1:1 - 3:3");
ran1.inc_line();
assert_eq!(format!("{:?}", ran1), "1:1 - 4:0");
}
#[test]
fn parsing() {
assert_eq!("4:7".parse(), Ok(TextRange::new(
InsertPosition::new(4, 6),
InsertPosition::new(4, 7))));
assert_eq!("1:1 - 4:3".parse(), Ok(TextRange::new(
InsertPosition::new(1, 0),
InsertPosition::new(4, 3))));
assert_eq!("3:7-3:7".parse(), Ok(TextRange::new(
InsertPosition::new(3, 6),
InsertPosition::new(3, 7))));
assert_eq!("7:3 - 2:1".parse::<TextRange>(), Err(()));
assert_eq!("7:3:2".parse::<TextRange>(), Err(()));
}
}