1#[derive(Clone, Copy)]
5pub enum LineMove {
6 Up,
8 Down,
10}
11
12#[must_use]
17pub fn move_line(text: &str, cursor: usize, direction: LineMove) -> Option<(String, usize)> {
18 let mut lines: Vec<&str> = text.split('\n').collect();
19 let (line, column) = line_column(text, cursor);
20 let target = match direction {
21 LineMove::Up => line.checked_sub(1)?,
22 LineMove::Down => (line + 1 < lines.len()).then_some(line + 1)?,
23 };
24 lines.swap(line, target);
25 let moved_cursor = cursor_at(&lines, target, column);
26 Some((lines.join("\n"), moved_cursor))
27}
28
29#[must_use]
31pub fn line_column(text: &str, cursor: usize) -> (usize, usize) {
32 let mut line = 0;
33 let mut column = 0;
34 for (index, character) in text.chars().enumerate() {
35 if index == cursor {
36 break;
37 }
38 if character == '\n' {
39 line += 1;
40 column = 0;
41 } else {
42 column += 1;
43 }
44 }
45 (line, column)
46}
47
48#[must_use]
51pub fn cursor_at(lines: &[&str], line: usize, column: usize) -> usize {
52 let preceding: usize = lines[..line].iter().map(|l| l.chars().count() + 1).sum();
53 preceding + column
54}