revi_core/
line_number.rs

1use std::ops::RangeInclusive;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4#[allow(dead_code)]
5pub enum LineNumbers {
6    AbsoluteNumber,
7    RelativeNumber,
8    Both,
9    None,
10}
11
12impl LineNumbers {
13    #[must_use]
14    pub fn lines(
15        &self,
16        width: usize,
17        height: usize,
18        offset: usize,
19        cursor: usize,
20        len_lines: usize,
21    ) -> String {
22        match self {
23            Self::AbsoluteNumber => absolute_number(width, height, offset, cursor, len_lines),
24            Self::RelativeNumber | Self::Both => {
25                both_number(self, width, height, offset, cursor, len_lines)
26            }
27            Self::None => String::new(),
28        }
29    }
30}
31
32fn absolute_number(
33    width: usize,
34    height: usize,
35    offset: usize,
36    _cursor: usize,
37    len_lines: usize,
38) -> String {
39    let bottom = (offset + height).min(len_lines);
40    let numbers = abs_lines(offset..=bottom.saturating_sub(1));
41    let blanks = height.saturating_sub(numbers.len());
42    let total = (bottom + blanks).saturating_sub(1);
43    numbers
44        .iter()
45        .chain(&blank_lines(bottom..=total))
46        .enumerate()
47        .map(|(i, n)| {
48            let padding = make_padding(width - 2, n.len());
49            let new_line = if i == total { "" } else { "\r\n" };
50            format!("{}{} {}", padding, n, new_line)
51        })
52        .collect::<String>()
53}
54
55fn both_number(
56    number_type: &LineNumbers,
57    width: usize,
58    height: usize,
59    offset: usize,
60    cursor: usize,
61    len_lines: usize,
62) -> String {
63    let top: usize = cursor;
64    let bottom: usize = height
65        .saturating_sub(1)
66        .min(len_lines)
67        .saturating_sub(cursor);
68    let blanks = height.saturating_sub(bottom + top + 1);
69    let top_lines = rabs_lines(1..=top);
70    let bottom_lines = abs_lines(1..=bottom);
71    let blank_lines = blank_lines(1..=blanks);
72    let cursor_in_file = offset + cursor;
73    format_rel_lines(
74        number_type,
75        width,
76        cursor_in_file,
77        &top_lines,
78        &bottom_lines,
79        &blank_lines,
80    )
81}
82
83fn format_rel_lines(
84    number_type: &LineNumbers,
85    width: usize,
86    current_line: usize,
87    top: &[String],
88    bottom: &[String],
89    blanks: &[String],
90) -> String {
91    let mut all = top.iter().chain(bottom).chain(blanks).peekable();
92    let mut lines = String::new();
93    let mut prev: Option<&str> = None;
94    while let Some(string_number) = all.next() {
95        let number_len = string_number.len();
96        let padding = (0..(std::cmp::max(3, width as usize - 1) - number_len))
97            .map(|_| " ")
98            .collect::<String>();
99
100        match (string_number.as_str(), all.peek().map(|i| i.as_str())) {
101            ("1", Some("1")) => {
102                lines.push_str(&format!("{}{} \r\n", padding, string_number));
103                cursor_line(number_type, &mut lines, width, current_line);
104            }
105            ("1", Some("~")) if prev != Some("1") => {
106                lines.push_str(&format!("{}{} \r\n", padding, string_number));
107                cursor_line(number_type, &mut lines, width, current_line);
108            }
109            ("1", None) => {
110                lines.push_str(&format!("{}{} \r\n", padding, string_number));
111                last_cursor(number_type, &mut lines, width, current_line);
112            }
113            (_, None) => last_number(&mut lines, &padding, &string_number),
114            ("1", Some("2")) if prev.is_none() => {
115                cursor_line(number_type, &mut lines, width, current_line);
116                lines.push_str(&format!("{}{} \r\n", padding, string_number));
117            }
118            _ => lines.push_str(&format!("{}{} \r\n", padding, string_number)),
119        }
120        prev = Some(string_number.as_str());
121    }
122    lines
123}
124
125fn rabs_lines(range: RangeInclusive<usize>) -> Vec<String> {
126    range.rev().map(|i| i.to_string()).collect::<Vec<String>>()
127}
128
129fn abs_lines(range: RangeInclusive<usize>) -> Vec<String> {
130    range.map(|i| i.to_string()).collect::<Vec<String>>()
131}
132
133fn blank_lines(range: RangeInclusive<usize>) -> Vec<String> {
134    range.map(|_| "~".to_string()).collect::<Vec<String>>()
135}
136
137fn last_number(lines: &mut String, padding: &str, string_number: &str) {
138    lines.push_str(&format!("{}{} ", padding, string_number));
139}
140fn last_cursor(number_type: &LineNumbers, lines: &mut String, width: usize, current_line: usize) {
141    if number_type == &LineNumbers::Both {
142        let padding = make_padding(width, current_line.to_string().len());
143        lines.push_str(&format!("{}{}", current_line, padding));
144    } else {
145        let padding = make_padding(width - 2, 1);
146        lines.push_str(&format!("{}0 ", padding));
147    }
148}
149fn cursor_line(number_type: &LineNumbers, lines: &mut String, width: usize, current_line: usize) {
150    if number_type == &LineNumbers::Both {
151        let padding = make_padding(width, current_line.to_string().len());
152        lines.push_str(&format!("{}{}\r\n", current_line, padding));
153    } else {
154        let padding = make_padding(width - 2, 1);
155        lines.push_str(&format!("{}0 \r\n", padding));
156    }
157}
158
159fn make_padding(width: usize, number_len: usize) -> String {
160    (0..=(width - number_len)).map(|_| " ").collect::<String>()
161}
162#[test]
163fn test_relative_number_bottom() {
164    let width = 4;
165    let height = 10;
166    let offset = 0;
167    let cursor = 9;
168    let len_lines = 20;
169    let line_type = LineNumbers::RelativeNumber;
170    let line_numbers = both_number(&line_type, width, height, offset, cursor, len_lines);
171    eprintln!("{}", line_numbers);
172    let right = "  9 \r\n  8 \r\n  7 \r\n  6 \r\n  5 \r\n  4 \r\n  3 \r\n  2 \r\n  1 \r\n  0 ";
173    assert_eq!(line_numbers, right.to_string());
174}
175
176#[test]
177fn test_relative_number_bottom_with_emtpy_lines() {
178    let width = 4;
179    let height = 10;
180    let offset = 0;
181    let cursor = 7;
182    let len_lines = 7;
183    let line_type = LineNumbers::RelativeNumber;
184    let line_numbers = both_number(&line_type, width, height, offset, cursor, len_lines);
185    eprintln!("{}", line_numbers);
186    let right = "  7 \r\n  6 \r\n  5 \r\n  4 \r\n  3 \r\n  2 \r\n  1 \r\n  0 \r\n  ~ \r\n  ~ ";
187    assert_eq!(line_numbers, right.to_string());
188}
189
190#[test]
191fn test_relative_number_with_empty_lines() {
192    let width = 4;
193    let height = 10;
194    let offset = 0;
195    let cursor = 3;
196    let len_lines = 7;
197    let line_type = LineNumbers::RelativeNumber;
198    let line_numbers = both_number(&line_type, width, height, offset, cursor, len_lines);
199    eprintln!("{}", line_numbers);
200    let right = "  3 \r\n  2 \r\n  1 \r\n  0 \r\n  1 \r\n  2 \r\n  3 \r\n  4 \r\n  ~ \r\n  ~ ";
201    assert_eq!(line_numbers, right.to_string());
202}
203
204#[test]
205fn test_relative_number_without_empty_lines() {
206    let width = 4;
207    let height = 10;
208    let offset = 0;
209    let cursor = 3;
210    let len_lines = 20;
211    let line_type = LineNumbers::RelativeNumber;
212    let line_numbers = both_number(&line_type, width, height, offset, cursor, len_lines);
213    eprintln!("{}", line_numbers);
214    let right = "  3 \r\n  2 \r\n  1 \r\n  0 \r\n  1 \r\n  2 \r\n  3 \r\n  4 \r\n  5 \r\n  6 ";
215    assert_eq!(line_numbers, right.to_string());
216}
217
218#[test]
219fn test_absolute_number_without_empty_lines() {
220    let width = 4;
221    let height = 10;
222    let offset = 0;
223    let cursor = 3;
224    let len_lines = 20;
225    let line_numbers = absolute_number(width, height, offset, cursor, len_lines);
226    eprintln!("{}", line_numbers);
227    let right = "  0 \r\n  1 \r\n  2 \r\n  3 \r\n  4 \r\n  5 \r\n  6 \r\n  7 \r\n  8 \r\n  9 ";
228    assert_eq!(line_numbers, right.to_string());
229}
230
231#[test]
232fn test_absolute_number_with_empty_lines() {
233    let width = 4;
234    let height = 10;
235    let offset = 0;
236    let cursor = 3;
237    let len_lines = 6;
238    let line_numbers = absolute_number(width, height, offset, cursor, len_lines);
239    eprintln!("{}", line_numbers);
240    let right = "  0 \r\n  1 \r\n  2 \r\n  3 \r\n  4 \r\n  5 \r\n  ~ \r\n  ~ \r\n  ~ \r\n  ~ ";
241    assert_eq!(line_numbers, right.to_string());
242}