sued 0.24.2

shut up editor - a minimalist line-based text editor written in Rust
Documentation
//! Defines Tilde Range Syntax parsing.
//! 
//! Tilde Range Syntax (TRS) is a unique expressive range definition system for
//! specifying ranges of lines, unique to the sued text editor.
//! 
//! For detailed information on TRS, see [parse_tilde_range].

use crate::EditorState;

/// Parses Tilde Range Syntax from `specifier` and returns an Option-wrapped
/// tuple of `(start_point: usize, end_point: usize)`. Requires a mutable
/// reference to an [EditorState].
/// 
/// ## Specifier Types
/// 
/// ### Tilde Ranges
/// 
/// * `X~`: From line `X` to the end of the file.
/// * `~X`: From the beginning of the file to line `X`.
/// * `X~Y`: From line `X` to line `Y`.
/// 
/// ### Special Ranges
/// 
/// * `file`: Selects the entire file (i.e. all lines from the beginning to the end).
/// * `this`/`point`: Selects the line the cursor is currently on.
/// * `around`: Selects three lines above and below the cursor. Intended to be used with `~show`.
pub fn parse_tilde_range(specifier: &str, state: &mut EditorState) -> Option<(usize, usize)> {
    let buffer_len = state.buffer.contents.len();

    if buffer_len == 0 {
        return None;
    }

    let start_point = 1;
    let end_point = buffer_len;

    let cursor = state.cursor + 1;
    match specifier {
        "file" => return Some((start_point, end_point)),
        "around" => {
            let start_around = cursor.saturating_sub(3).max(start_point);
            let end_around = (cursor + 3).min(end_point);

            return Some((start_around, end_around));
        }
        "this" | "point" => return Some((cursor, cursor)),
        _ => (),
    }

    if specifier.matches("~").count() > 1 {
        return None;
    }

    if specifier.starts_with("~") {
        if let Ok(end) = specifier.trim_start_matches("~").parse::<usize>() {
            return Some((start_point, end));
        }
    } else if specifier.ends_with("~") {
        if let Ok(start) = specifier.trim_end_matches("~").parse::<usize>() {
            return Some((start, end_point));
        }
    } else if specifier.contains("~") {
        let range: Vec<&str> = specifier.split("~").collect();
        if let Ok(start) = range[0].parse::<usize>() {
            if let Ok(end) = range[1].parse::<usize>() {
                if start > end {
                    return None;
                }
                return Some((start, end));
            }
        }
    } else if let Ok(specified_line) = specifier.parse::<usize>() {
        return Some((specified_line, specified_line));
    }

    None
}