rho-coding-agent 2.3.1

A fast Rust agent harness with a small footprint and opinionated defaults
use std::{ops::Range, sync::Arc};

use ratatui::layout::{Position, Rect};

use super::text_selection::SelectionPosition;

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct CodeBlockCopyTarget {
    pub(super) line: usize,
    pub(super) columns: Range<usize>,
    pub(super) text: Arc<str>,
}

/// Copy button produced with a row of text: line index, columns, payload.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct CopyHit {
    /// Line index in the painted lines, before any visible-start skip.
    pub row: usize,
    pub columns: Range<usize>,
    pub text: String,
}

impl CopyHit {
    /// Hit-test a pointer against this target as painted at `origin`, skipping
    /// `start` lines the same way the renderer does.
    pub(super) fn text_at(
        &self,
        origin: Rect,
        start: usize,
        column: u16,
        row: u16,
    ) -> Option<&str> {
        if !origin.contains(Position { x: column, y: row }) {
            return None;
        }
        let line = start.saturating_add(row.saturating_sub(origin.y) as usize);
        if line != self.row {
            return None;
        }
        let rel_col = column.saturating_sub(origin.x) as usize;
        self.columns
            .contains(&rel_col)
            .then_some(self.text.as_str())
    }
}

pub(super) fn selection_position(
    history: Rect,
    history_start: usize,
    column: u16,
    row: u16,
) -> Option<SelectionPosition> {
    history
        .contains(Position { x: column, y: row })
        .then(|| SelectionPosition {
            line: history_start.saturating_add(row.saturating_sub(history.y) as usize),
            column: column.saturating_sub(history.x) as usize,
        })
}

pub(super) fn selection_position_clamped(
    history: Rect,
    history_start: usize,
    column: u16,
    row: u16,
) -> Option<SelectionPosition> {
    if history.width == 0 || history.height == 0 {
        return None;
    }
    let column = column.clamp(
        history.x,
        history.x.saturating_add(history.width.saturating_sub(1)),
    );
    let row = row.clamp(
        history.y,
        history.y.saturating_add(history.height.saturating_sub(1)),
    );
    Some(SelectionPosition {
        line: history_start.saturating_add(row.saturating_sub(history.y) as usize),
        column: column.saturating_sub(history.x) as usize,
    })
}