idet-core 0.1.0

Shared text-editing library for a Micro-like terminal editor and a gedit-like egui GUI
Documentation
//! Moving the current line up or down.

/// Direction in which [`move_line`] shifts the current line.
#[derive(Clone, Copy)]
pub enum LineMove {
    /// Move the line towards the start of the text.
    Up,
    /// Move the line towards the end of the text.
    Down,
}

/// Swaps the line at `cursor` with its neighbour in `direction`.
///
/// `cursor` is a character index; the cursor stays on the moved line. Returns
/// the new text and cursor, or `None` when there is no neighbouring line.
#[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))
}

/// Locates the zero-indexed `(line, column)` of the character at `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)
}

/// Converts a `(line, column)` position back into a character index, given
/// `text` already split on `\n`.
#[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
}