1use super::*;
2
3pub trait StrExt {
4 fn point_delta(&self) -> Point;
7}
8
9impl StrExt for str {
10 fn point_delta(&self) -> Point {
11 let (mut rows, mut column) = (0usize, 0usize);
12
13 let mut chars = self.chars().peekable();
14
15 while let Some(ch) = chars.next() {
16 match ch {
17 '\r' => {
18 if matches!(chars.peek().copied(), Some('\n')) {
19 chars.next();
20 }
21
22 rows += 1;
23 column = 0;
24 }
25 '\n' | '\u{000B}' | '\u{000C}' | '\u{0085}' | '\u{2028}'
26 | '\u{2029}' => {
27 rows += 1;
28 column = 0;
29 }
30 _ => {
31 column += ch.len_utf8();
32 }
33 }
34 }
35
36 Point::new(rows, column)
37 }
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn empty_string_produces_origin() {
46 assert_eq!("".point_delta(), Point::new(0, 0));
47 }
48
49 #[test]
50 fn ascii_text_advances_column_by_bytes() {
51 assert_eq!("abc".point_delta(), Point::new(0, 3));
52 }
53
54 #[test]
55 fn multibyte_chars_count_their_utf8_width() {
56 assert_eq!("šĆ©".point_delta(), Point::new(0, "šĆ©".len()));
57 }
58
59 #[test]
60 fn newline_moves_to_next_row_and_resets_column() {
61 assert_eq!("hi\nš".point_delta(), Point::new(1, "š".len()));
62 }
63
64 #[test]
65 fn crlf_sequences_count_as_single_newline() {
66 assert_eq!("\r\nabc".point_delta(), Point::new(1, "abc".len()));
67 }
68
69 #[test]
70 fn bare_carriage_return_counts_as_line_break() {
71 assert_eq!("foo\rbar".point_delta(), Point::new(1, "bar".len()));
72 }
73}