ai_agent/commands/
plugin_pagination.rs1use std::cell::RefCell;
2use std::rc::Rc;
3
4const DEFAULT_MAX_VISIBLE: usize = 5;
5
6pub struct PaginationOptions {
7 pub total_items: usize,
8 pub max_visible: Option<usize>,
9 pub selected_index: Option<usize>,
10}
11
12pub struct PaginationResult<T> {
13 pub current_page: usize,
14 pub total_pages: usize,
15 pub start_index: usize,
16 pub end_index: usize,
17 pub needs_pagination: bool,
18 pub page_size: usize,
19 pub get_visible_items: Box<dyn Fn(&[T]) -> Vec<T>>,
20 pub to_actual_index: Box<dyn Fn(usize) -> usize>,
21 pub is_on_current_page: Box<dyn Fn(usize) -> bool>,
22 pub go_to_page: Box<dyn Fn(usize)>,
23 pub next_page: Box<dyn Fn()>,
24 pub prev_page: Box<dyn Fn()>,
25 pub handle_selection_change: Box<dyn Fn(usize, &mut usize)>,
26 pub handle_pagenavigation: Box<dyn Fn(&str, &mut usize) -> bool>,
27 pub scroll_position: ScrollPosition,
28}
29
30pub struct ScrollPosition {
31 pub current: usize,
32 pub total: usize,
33 pub can_scroll_up: bool,
34 pub can_scroll_down: bool,
35}
36
37pub fn use_pagination<T: Clone>(options: PaginationOptions) -> PaginationResult<T> {
38 let total_items = options.total_items;
39 let max_visible = options.max_visible.unwrap_or(DEFAULT_MAX_VISIBLE);
40 let selected_index = options.selected_index.unwrap_or(0);
41
42 let needs_pagination = total_items > max_visible;
43
44 let scroll_offset_ref: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));
45
46 let scroll_offset = if !needs_pagination {
47 0
48 } else {
49 let prev_offset = *scroll_offset_ref.borrow();
50
51 if selected_index < prev_offset {
52 *scroll_offset_ref.borrow_mut() = selected_index;
53 selected_index
54 } else if selected_index >= prev_offset + max_visible {
55 let new_offset = selected_index - max_visible + 1;
56 *scroll_offset_ref.borrow_mut() = new_offset;
57 new_offset
58 } else {
59 let max_offset = total_items.saturating_sub(max_visible);
60 let clamped_offset = prev_offset.min(max_offset);
61 *scroll_offset_ref.borrow_mut() = clamped_offset;
62 clamped_offset
63 }
64 };
65
66 let start_index = scroll_offset;
67 let end_index = (scroll_offset + max_visible).min(total_items);
68
69 let needs_pagination_clone = needs_pagination;
70 let start_index_clone = start_index;
71 let end_index_clone = end_index;
72 let get_visible_items = Box::new(move |items: &[T]| -> Vec<T> {
73 if !needs_pagination_clone {
74 items.to_vec()
75 } else {
76 items[start_index_clone..end_index_clone].to_vec()
77 }
78 });
79
80 let start_index_for_actual = start_index;
81 let to_actual_index =
82 Box::new(move |visible_index: usize| -> usize { start_index_for_actual + visible_index });
83
84 let start_index_for_page = start_index;
85 let end_index_for_page = end_index;
86 let is_on_current_page = Box::new(move |actual_index: usize| -> bool {
87 actual_index >= start_index_for_page && actual_index < end_index_for_page
88 });
89
90 let go_to_page = Box::new(|_page: usize| {});
91 let next_page = Box::new(|| {});
92 let prev_page = Box::new(|| {});
93
94 let total_items_for_selection = total_items;
95 let handle_selection_change = Box::new(move |new_index: usize, selected: &mut usize| {
96 let clamped_index = new_index.min(total_items_for_selection.saturating_sub(1));
97 *selected = clamped_index;
98 });
99
100 let handle_page_navigation =
101 Box::new(|_direction: &str, _set_selected_index: &mut usize| -> bool { false });
102
103 let total_pages = ((total_items + max_visible - 1) / max_visible).max(1);
104 let current_page = scroll_offset / max_visible;
105
106 PaginationResult {
107 current_page,
108 total_pages,
109 start_index,
110 end_index,
111 needs_pagination,
112 page_size: max_visible,
113 get_visible_items,
114 to_actual_index,
115 is_on_current_page,
116 go_to_page,
117 next_page,
118 prev_page,
119 handle_selection_change,
120 handle_pagenavigation: handle_page_navigation,
121 scroll_position: ScrollPosition {
122 current: selected_index + 1,
123 total: total_items,
124 can_scroll_up: scroll_offset > 0,
125 can_scroll_down: scroll_offset + max_visible < total_items,
126 },
127 }
128}