ai-agent 0.13.4

Idiomatic agent sdk inspired by the claude code source leak
Documentation
use std::cell::RefCell;
use std::rc::Rc;

const DEFAULT_MAX_VISIBLE: usize = 5;

pub struct PaginationOptions {
    pub total_items: usize,
    pub max_visible: Option<usize>,
    pub selected_index: Option<usize>,
}

pub struct PaginationResult<T> {
    pub current_page: usize,
    pub total_pages: usize,
    pub start_index: usize,
    pub end_index: usize,
    pub needs_pagination: bool,
    pub page_size: usize,
    pub get_visible_items: Box<dyn Fn(&[T]) -> Vec<T>>,
    pub to_actual_index: Box<dyn Fn(usize) -> usize>,
    pub is_on_current_page: Box<dyn Fn(usize) -> bool>,
    pub go_to_page: Box<dyn Fn(usize)>,
    pub next_page: Box<dyn Fn()>,
    pub prev_page: Box<dyn Fn()>,
    pub handle_selection_change: Box<dyn Fn(usize, &mut usize)>,
    pub handle_pagenavigation: Box<dyn Fn(&str, &mut usize) -> bool>,
    pub scroll_position: ScrollPosition,
}

pub struct ScrollPosition {
    pub current: usize,
    pub total: usize,
    pub can_scroll_up: bool,
    pub can_scroll_down: bool,
}

pub fn use_pagination<T: Clone>(options: PaginationOptions) -> PaginationResult<T> {
    let total_items = options.total_items;
    let max_visible = options.max_visible.unwrap_or(DEFAULT_MAX_VISIBLE);
    let selected_index = options.selected_index.unwrap_or(0);

    let needs_pagination = total_items > max_visible;

    let scroll_offset_ref: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));

    let scroll_offset = if !needs_pagination {
        0
    } else {
        let prev_offset = *scroll_offset_ref.borrow();

        if selected_index < prev_offset {
            *scroll_offset_ref.borrow_mut() = selected_index;
            selected_index
        } else if selected_index >= prev_offset + max_visible {
            let new_offset = selected_index - max_visible + 1;
            *scroll_offset_ref.borrow_mut() = new_offset;
            new_offset
        } else {
            let max_offset = total_items.saturating_sub(max_visible);
            let clamped_offset = prev_offset.min(max_offset);
            *scroll_offset_ref.borrow_mut() = clamped_offset;
            clamped_offset
        }
    };

    let start_index = scroll_offset;
    let end_index = (scroll_offset + max_visible).min(total_items);

    let needs_pagination_clone = needs_pagination;
    let start_index_clone = start_index;
    let end_index_clone = end_index;
    let get_visible_items = Box::new(move |items: &[T]| -> Vec<T> {
        if !needs_pagination_clone {
            items.to_vec()
        } else {
            items[start_index_clone..end_index_clone].to_vec()
        }
    });

    let start_index_for_actual = start_index;
    let to_actual_index =
        Box::new(move |visible_index: usize| -> usize { start_index_for_actual + visible_index });

    let start_index_for_page = start_index;
    let end_index_for_page = end_index;
    let is_on_current_page = Box::new(move |actual_index: usize| -> bool {
        actual_index >= start_index_for_page && actual_index < end_index_for_page
    });

    let go_to_page = Box::new(|_page: usize| {});
    let next_page = Box::new(|| {});
    let prev_page = Box::new(|| {});

    let total_items_for_selection = total_items;
    let handle_selection_change = Box::new(move |new_index: usize, selected: &mut usize| {
        let clamped_index = new_index.min(total_items_for_selection.saturating_sub(1));
        *selected = clamped_index;
    });

    let handle_page_navigation =
        Box::new(|_direction: &str, _set_selected_index: &mut usize| -> bool { false });

    let total_pages = ((total_items + max_visible - 1) / max_visible).max(1);
    let current_page = scroll_offset / max_visible;

    PaginationResult {
        current_page,
        total_pages,
        start_index,
        end_index,
        needs_pagination,
        page_size: max_visible,
        get_visible_items,
        to_actual_index,
        is_on_current_page,
        go_to_page,
        next_page,
        prev_page,
        handle_selection_change,
        handle_pagenavigation: handle_page_navigation,
        scroll_position: ScrollPosition {
            current: selected_index + 1,
            total: total_items,
            can_scroll_up: scroll_offset > 0,
            can_scroll_down: scroll_offset + max_visible < total_items,
        },
    }
}