sac-cli 0.1.0

Terminal-based AI coding agent — fork of NAC with extended backend support and context management
Documentation
use super::*;

pub(super) fn selection_bounds_for_panel(
    selection: Option<&SelectionState>,
    panel: PanelId,
) -> Option<(SelectionPoint, SelectionPoint)> {
    let selection = selection?;
    if selection.anchor.panel != panel || selection.focus.panel != panel {
        return None;
    }
    let (start, end) = ordered_points(&selection.anchor, &selection.focus);
    Some((start.clone(), end.clone()))
}

pub(super) fn ordered_points<'a>(
    left: &'a SelectionPoint,
    right: &'a SelectionPoint,
) -> (&'a SelectionPoint, &'a SelectionPoint) {
    if compare_points(left, right).is_le() {
        (left, right)
    } else {
        (right, left)
    }
}

pub(super) fn compare_points(left: &SelectionPoint, right: &SelectionPoint) -> Ordering {
    left.logical_line
        .cmp(&right.logical_line)
        .then_with(|| left.char_index.cmp(&right.char_index))
}

pub(super) fn selection_overlap_for_row(
    row: &WrappedRow,
    start: &SelectionPoint,
    end: &SelectionPoint,
) -> Option<(usize, usize)> {
    if row.logical_line < start.logical_line || row.logical_line > end.logical_line {
        return None;
    }

    let row_start = row.start_char;
    let mut row_end = row.end_char;
    if row_start == row_end && row.text.is_empty() {
        row_end = row_start;
    }

    let selection_start = if row.logical_line == start.logical_line {
        start.char_index.max(row_start)
    } else {
        row_start
    };
    let selection_end = if row.logical_line == end.logical_line {
        end.char_index.min(row_end)
    } else {
        row_end
    };

    if selection_start >= selection_end {
        return None;
    }

    Some((
        selection_start.saturating_sub(row.start_char),
        selection_end.saturating_sub(row.start_char),
    ))
}

pub(super) fn extract_selection_text(view: &PanelView, selection: &SelectionState) -> String {
    let (start, end) = ordered_points(&selection.anchor, &selection.focus);
    if start.panel != view.id || end.panel != view.id {
        return String::new();
    }
    if compare_points(start, end) == Ordering::Equal {
        return String::new();
    }

    let mut out = String::new();
    for logical_line in start.logical_line..=end.logical_line {
        let Some(line) = view.logical_lines.get(logical_line) else {
            continue;
        };
        let line_len = line.chars().count();
        let start_char = if logical_line == start.logical_line {
            start.char_index.min(line_len)
        } else {
            0
        };
        let end_char = if logical_line == end.logical_line {
            end.char_index.min(line_len)
        } else {
            line_len
        };
        if end_char > start_char {
            out.push_str(&slice_chars(line, start_char, end_char));
        }
        if logical_line < end.logical_line {
            out.push('\n');
        }
    }
    out
}

pub(super) fn slice_chars(text: &str, start: usize, end: usize) -> String {
    text.chars()
        .skip(start)
        .take(end.saturating_sub(start))
        .collect()
}