#[derive(Clone, Copy)]
pub enum LineMove {
Up,
Down,
}
#[must_use]
pub fn move_line(text: &str, cursor: usize, direction: LineMove) -> Option<(String, usize)> {
let mut lines: Vec<&str> = text.split('\n').collect();
let (line, column) = line_column(text, cursor);
let target = match direction {
LineMove::Up => line.checked_sub(1)?,
LineMove::Down => (line + 1 < lines.len()).then_some(line + 1)?,
};
lines.swap(line, target);
let moved_cursor = cursor_at(&lines, target, column);
Some((lines.join("\n"), moved_cursor))
}
#[must_use]
pub fn line_column(text: &str, cursor: usize) -> (usize, usize) {
let mut line = 0;
let mut column = 0;
for (index, character) in text.chars().enumerate() {
if index == cursor {
break;
}
if character == '\n' {
line += 1;
column = 0;
} else {
column += 1;
}
}
(line, column)
}
#[must_use]
pub fn cursor_at(lines: &[&str], line: usize, column: usize) -> usize {
let preceding: usize = lines[..line].iter().map(|l| l.chars().count() + 1).sum();
preceding + column
}