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}