arreliny-term 0.1.2

A simple terminal for keeping multiple shells in one window
use super::{Pane, Selection};
use arreliny_terminal::Screen;

pub(super) fn bounds(selection: Selection) -> ((usize, usize), (usize, usize)) {
    if selection.start <= selection.end {
        (selection.start, selection.end)
    } else {
        (selection.end, selection.start)
    }
}

pub(super) fn contains(selection: Selection, line: usize, column: usize) -> bool {
    let (start, end) = bounds(selection);
    (line, column) >= start && (line, column) <= end
}

pub(super) fn word(screen: &Screen, line: usize, column: usize) -> Selection {
    let mut column = column;
    while column > 0 && screen.line_cell(line, column).width == 0 {
        column -= 1;
    }
    let class = word_class(screen.line_cell(line, column).character);
    let mut start = column;
    while start > 0 {
        let mut previous = start - 1;
        while previous > 0 && screen.line_cell(line, previous).width == 0 {
            previous -= 1;
        }
        if word_class(screen.line_cell(line, previous).character) != class {
            break;
        }
        start = previous;
    }
    let width = screen.line_cell(line, column).width.max(1) as usize;
    let mut end = (column + width - 1).min(screen.columns() - 1);
    let mut next = column + width;
    while next < screen.columns() {
        let cell = screen.line_cell(line, next);
        if word_class(cell.character) != class {
            break;
        }
        end = (next + cell.width.max(1) as usize - 1).min(screen.columns() - 1);
        next += cell.width.max(1) as usize;
    }
    Selection {
        start: (line, start),
        end: (line, end),
    }
}

pub(super) fn text(pane: &Pane, trim_selection: bool) -> Option<String> {
    let selection = pane.selection?;
    let (start, end) = bounds(selection);
    let screen = pane.terminal.screen();
    let mut result = String::new();
    for line in start.0..=end.0 {
        let first = if line == start.0 { start.1 } else { 0 };
        let last = if line == end.0 {
            end.1
        } else {
            screen.columns().saturating_sub(1)
        };
        for column in first..=last {
            let cell = screen.line_cell(line, column);
            if cell.width != 0 {
                result.push_str(&cell.text());
            }
        }
        if trim_selection {
            let trimmed = result.trim_end_matches(' ').len();
            result.truncate(trimmed);
        }
        if line != end.0 {
            result.push('\n');
        }
    }
    Some(result)
}

fn word_class(character: char) -> u8 {
    if character.is_whitespace() {
        0
    } else if character.is_alphanumeric() || character == '_' {
        1
    } else {
        2
    }
}