1use std::{
2 fmt::{Debug, Display},
3 str::FromStr
4};
5
6use crate::{InsertPosition, TextPosition};
7
8#[derive(Default, Clone, PartialEq, Eq)]
13pub struct TextRange {
14 start: InsertPosition,
15 end: InsertPosition,
16}
17
18impl TextRange {
19 pub fn new(start: InsertPosition, end: InsertPosition) -> Self {
24 assert!(start <= end);
25 TextRange { start, end }
26 }
27 pub fn new_empty(pos: InsertPosition) -> Self {
29 Self::new(pos.clone(), pos)
30 }
31
32 pub fn inc_col(&mut self) {
34 self.end.inc_col();
35 }
36 pub fn inc_line(&mut self) {
38 self.end.inc_line();
39 }
40 pub fn inc(&mut self, c: char) {
44 if c == '\n' {
45 self.inc_line();
46 } else {
47 self.inc_col();
48 }
49 }
50
51 pub fn merge(&mut self, other: TextRange) {
56 assert!(self.end() == other.start());
57
58 self.end = other.end;
59 }
60
61 pub fn begin_next_range(&self) -> Self {
63 TextRange::new_empty(self.end().clone())
64 }
65
66 pub fn start(&self) -> &InsertPosition { &self.start }
68 pub fn end(&self) -> &InsertPosition { &self.end }
70}
71
72impl Display for TextRange {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 if self.start() == self.end() {
75 return write!(f, "empty after {}", self.start());
76 }
77
78 let start = self.start().text_pos_right();
79 let end = self.end().text_pos_left();
80
81 if start == end {
82 write!(f, "{}", start)
83 } else {
84 write!(f, "{} - {}", start, end)
85 }
86 }
87}
88impl Debug for TextRange {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 write!(f, "{}", self)
91 }
92}
93
94impl FromStr for TextRange {
95 type Err = ();
96
97 fn from_str(s: &str) -> Result<Self, Self::Err> {
98 match s.split_once('-') {
99 None => {
100 let pos: TextPosition = s.parse()?;
102 Ok(TextRange::new(pos.insert_pos_left(), pos.insert_pos_right()))
103 }
104 Some((start, end)) => {
105 let start = start.trim().parse::<TextPosition>()?.insert_pos_left();
107 let end = end.trim().parse::<TextPosition>()?.insert_pos_right();
108
109 if end < start { return Err(()); }
111
112 Ok(TextRange::new(start, end))
113 }
114 }
115 }
116}
117
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 #[should_panic]
125 fn disallow_reverse_base_next() {
126 let mut pos1 = InsertPosition::default();
127 let pos2 = InsertPosition::default();
128
129 pos1.inc_col();
130
131 let _ = TextRange::new(pos1, pos2);
132 }
133
134
135 #[test]
136 fn allow_creaton_empty_range() {
137 let pos1 = InsertPosition::default();
138
139 let empty = TextRange::new_empty(pos1);
140 assert_eq!(empty, TextRange::default());
141 }
142
143 #[test]
144 fn merge() {
145 let mut ran1 = TextRange::default();
146 ran1.inc_col();
147
148 let mut pos1 = InsertPosition::default();
149 pos1.inc_col();
150 let mut pos2 = pos1.clone();
151 pos2.inc_col();
152 let ran2 = TextRange::new(pos1, pos2.clone());
153
154 let ran3 = TextRange::new_empty(pos2);
155
156 ran1.merge(ran2);
157 ran1.merge(ran3);
158 }
159
160 #[test]
161 #[should_panic]
162 fn merge_should_fail() {
163 let mut ran1 = TextRange::default();
164
165 let mut pos1 = InsertPosition::default();
166 pos1.inc_col();
167 pos1.inc_col();
168 let ran2 = TextRange::new_empty(pos1);
169
170 ran1.merge(ran2);
171 }
172
173 #[test]
174 fn begin_next_range() {
175 let mut ran1 = TextRange::default();
176 ran1.inc_line();
177 ran1.inc_col();
178 ran1.inc_col();
179 let ran2 = ran1.begin_next_range();
180
181 assert_eq!(ran2, TextRange::new_empty(InsertPosition::new(2, 2)));
182 }
183
184 #[test]
185 fn display_print() {
186 let mut ran1 = TextRange::default();
187 assert_eq!(ran1.to_string(), "empty after 1:0");
188
189 ran1.inc_line();
190 ran1.inc_line();
191 ran1.inc_col();
192 ran1.inc_col();
193 ran1.inc_col();
194 assert_eq!(ran1.to_string(), "1:1 - 3:3");
195
196 ran1.inc_line();
198 assert_eq!(ran1.to_string(), "1:1 - 4:0");
199 }
200
201 #[test]
202 fn debug_print() {
203 let mut ran1 = TextRange::default();
204 assert_eq!(format!("{:?}", ran1), "empty after 1:0");
205
206 ran1.inc_line();
207 ran1.inc_line();
208 ran1.inc_col();
209 ran1.inc_col();
210 ran1.inc_col();
211 assert_eq!(format!("{:?}", ran1), "1:1 - 3:3");
212
213 ran1.inc_line();
215 assert_eq!(format!("{:?}", ran1), "1:1 - 4:0");
216 }
217
218 #[test]
219 fn parsing() {
220 assert_eq!("4:7".parse(), Ok(TextRange::new(
221 InsertPosition::new(4, 6),
222 InsertPosition::new(4, 7))));
223 assert_eq!("1:1 - 4:3".parse(), Ok(TextRange::new(
224 InsertPosition::new(1, 0),
225 InsertPosition::new(4, 3))));
226 assert_eq!("3:7-3:7".parse(), Ok(TextRange::new(
227 InsertPosition::new(3, 6),
228 InsertPosition::new(3, 7))));
229 assert_eq!("7:3 - 2:1".parse::<TextRange>(), Err(()));
230 assert_eq!("7:3:2".parse::<TextRange>(), Err(()));
231 }
232}