bubbletea_widgets/paginator.rs
1//! A paginator component for bubbletea-rs, ported from the Go version.
2//!
3//! This component is used for calculating pagination and rendering pagination info.
4//! Note that this package does not render actual pages of content; it's purely
5//! for handling the state and view of the pagination control itself.
6
7use crate::key::{self, KeyMap as KeyMapTrait};
8use bubbletea_rs::{KeyMsg, Msg};
9
10/// The type of pagination to display.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum Type {
13 /// Display pagination as Arabic numerals (e.g., "1/5").
14 #[default]
15 Arabic,
16 /// Display pagination as dots (e.g., "● ○ ○ ○ ○").
17 Dots,
18}
19
20/// Key bindings for different actions within the paginator.
21///
22/// This structure defines the key bindings that control pagination navigation.
23/// It implements the `KeyMap` trait to provide help information for the
24/// paginator component.
25///
26/// # Examples
27///
28/// ```rust
29/// use bubbletea_widgets::paginator::PaginatorKeyMap;
30/// use bubbletea_widgets::key;
31///
32/// let keymap = PaginatorKeyMap::default();
33///
34/// // Create custom key bindings
35/// let custom_keymap = PaginatorKeyMap {
36/// prev_page: key::new_binding(vec![
37/// key::with_keys_str(&["a", "left"]),
38/// key::with_help("a/←", "previous page"),
39/// ]),
40/// next_page: key::new_binding(vec![
41/// key::with_keys_str(&["d", "right"]),
42/// key::with_help("d/→", "next page"),
43/// ]),
44/// };
45/// ```
46#[derive(Debug, Clone)]
47pub struct PaginatorKeyMap {
48 /// Key binding for navigating to the previous page.
49 /// Default keys: PageUp, Left Arrow, 'h'
50 pub prev_page: key::Binding,
51 /// Key binding for navigating to the next page.
52 /// Default keys: PageDown, Right Arrow, 'l'
53 pub next_page: key::Binding,
54}
55
56impl Default for PaginatorKeyMap {
57 /// Creates default key bindings for paginator navigation.
58 ///
59 /// The default key bindings are:
60 /// - **Previous page**: PageUp, Left Arrow, 'h'
61 /// - **Next page**: PageDown, Right Arrow, 'l'
62 ///
63 /// These bindings are commonly used in terminal applications and provide
64 /// both arrow key navigation and vim-style 'h'/'l' keys.
65 ///
66 /// # Examples
67 ///
68 /// ```rust
69 /// use bubbletea_widgets::paginator::PaginatorKeyMap;
70 /// use bubbletea_widgets::key::KeyMap;
71 ///
72 /// let keymap = PaginatorKeyMap::default();
73 /// let help = keymap.short_help();
74 /// assert_eq!(help.len(), 2); // prev and next bindings
75 /// ```
76 fn default() -> Self {
77 Self {
78 prev_page: key::new_binding(vec![
79 key::with_keys_str(&["pgup", "left", "h"]),
80 key::with_help("←/h", "prev page"),
81 ]),
82 next_page: key::new_binding(vec![
83 key::with_keys_str(&["pgdown", "right", "l"]),
84 key::with_help("→/l", "next page"),
85 ]),
86 }
87 }
88}
89
90impl KeyMapTrait for PaginatorKeyMap {
91 /// Returns key bindings for the short help view.
92 ///
93 /// This provides the essential pagination key bindings that will be
94 /// displayed in compact help views.
95 ///
96 /// # Returns
97 ///
98 /// A vector containing references to the previous page and next page bindings.
99 fn short_help(&self) -> Vec<&key::Binding> {
100 vec![&self.prev_page, &self.next_page]
101 }
102
103 /// Returns key bindings for the full help view.
104 ///
105 /// This organizes all pagination key bindings into columns for display
106 /// in expanded help views. Since pagination only has two keys, they're
107 /// grouped together in a single column.
108 ///
109 /// # Returns
110 ///
111 /// A vector of vectors, where each inner vector represents a column
112 /// of related key bindings.
113 fn full_help(&self) -> Vec<Vec<&key::Binding>> {
114 vec![vec![&self.prev_page, &self.next_page]]
115 }
116}
117
118/// A paginator model for handling pagination state and rendering.
119///
120/// This component manages pagination state including current page, total pages,
121/// and pagination display style. It can render pagination in two modes:
122/// - **Arabic**: Shows page numbers (e.g., "3/10")
123/// - **Dots**: Shows dots representing pages (e.g., "○ ○ ● ○ ○")
124///
125/// The paginator handles key bindings for navigation and provides helper methods
126/// for calculating slice bounds and page information.
127///
128/// # Examples
129///
130/// ## Basic Usage
131///
132/// ```rust
133/// use bubbletea_widgets::paginator::{Model, Type};
134///
135/// let mut paginator = Model::new()
136/// .with_per_page(10)
137/// .with_total_items(150); // Creates 15 pages
138///
139/// assert_eq!(paginator.total_pages, 15);
140/// assert!(paginator.on_first_page());
141///
142/// paginator.next_page();
143/// assert_eq!(paginator.page, 1);
144/// ```
145///
146/// ## Different Display Types
147///
148/// ```rust
149/// use bubbletea_widgets::paginator::{Model, Type};
150///
151/// let mut paginator = Model::new()
152/// .with_total_items(50)
153/// .with_per_page(10);
154///
155/// // Arabic mode (default): "1/5"
156/// paginator.paginator_type = Type::Arabic;
157/// let arabic_view = paginator.view();
158///
159/// // Dots mode: "● ○ ○ ○ ○"
160/// paginator.paginator_type = Type::Dots;
161/// let dots_view = paginator.view();
162/// ```
163///
164/// ## Integration with bubbletea-rs
165///
166/// ```rust
167/// use bubbletea_widgets::paginator::Model as Paginator;
168/// use bubbletea_rs::{Model, Cmd, Msg};
169///
170/// struct App {
171/// paginator: Paginator,
172/// items: Vec<String>,
173/// }
174///
175/// impl Model for App {
176/// fn init() -> (Self, Option<Cmd>) {
177/// let items: Vec<String> = (1..=100).map(|i| format!("Item {}", i)).collect();
178/// let paginator = Paginator::new()
179/// .with_per_page(10)
180/// .with_total_items(items.len());
181///
182/// (Self { paginator, items }, None)
183/// }
184///
185/// fn update(&mut self, msg: Msg) -> Option<Cmd> {
186/// self.paginator.update(&msg);
187/// None
188/// }
189///
190/// fn view(&self) -> String {
191/// let (start, end) = self.paginator.get_slice_bounds(self.items.len());
192/// let page_items: Vec<String> = self.items[start..end].to_vec();
193///
194/// format!(
195/// "Items:\n{}\n\nPage: {}",
196/// page_items.join("\n"),
197/// self.paginator.view()
198/// )
199/// }
200/// }
201/// ```
202#[derive(Debug, Clone)]
203pub struct Model {
204 /// The type of pagination to display (Dots or Arabic).
205 pub paginator_type: Type,
206 /// The current page.
207 pub page: usize,
208 /// The number of items per page.
209 pub per_page: usize,
210 /// The total number of pages.
211 pub total_pages: usize,
212
213 /// The character to use for the active page in Dots mode.
214 pub active_dot: String,
215 /// The character to use for inactive pages in Dots mode.
216 pub inactive_dot: String,
217 /// The format string for Arabic mode (e.g., "%d/%d").
218 pub arabic_format: String,
219
220 /// Key bindings.
221 pub keymap: PaginatorKeyMap,
222}
223
224impl Default for Model {
225 /// Creates a paginator with default settings.
226 ///
227 /// Default configuration:
228 /// - Type: Arabic ("1/5" style)
229 /// - Current page: 0 (first page)
230 /// - Items per page: 1
231 /// - Total pages: 1
232 /// - Active dot: "•" (for dots mode)
233 /// - Inactive dot: "○" (for dots mode)
234 /// - Arabic format: "%d/%d" (current/total)
235 /// - Default key bindings
236 ///
237 /// # Examples
238 ///
239 /// ```rust
240 /// use bubbletea_widgets::paginator::{Model, Type};
241 ///
242 /// let paginator = Model::default();
243 /// assert_eq!(paginator.paginator_type, Type::Arabic);
244 /// assert_eq!(paginator.page, 0);
245 /// assert_eq!(paginator.per_page, 1);
246 /// assert_eq!(paginator.total_pages, 1);
247 /// ```
248 fn default() -> Self {
249 Self {
250 paginator_type: Type::default(),
251 page: 0,
252 per_page: 1,
253 total_pages: 1,
254 active_dot: "•".to_string(),
255 inactive_dot: "○".to_string(),
256 arabic_format: "%d/%d".to_string(),
257 keymap: PaginatorKeyMap::default(),
258 }
259 }
260}
261
262impl Model {
263 /// Creates a new paginator model with default settings.
264 ///
265 /// This is equivalent to calling `Model::default()` but provides a more
266 /// conventional constructor-style API.
267 ///
268 /// # Examples
269 ///
270 /// ```rust
271 /// use bubbletea_widgets::paginator::Model;
272 ///
273 /// let paginator = Model::new();
274 /// assert_eq!(paginator.page, 0);
275 /// assert_eq!(paginator.total_pages, 1);
276 /// ```
277 pub fn new() -> Self {
278 Self::default()
279 }
280
281 /// Sets the total number of items and calculates total pages (builder pattern).
282 ///
283 /// This method automatically calculates the total number of pages based on
284 /// the total items and the current `per_page` setting. If the current page
285 /// becomes out of bounds, it will be adjusted to the last valid page.
286 ///
287 /// # Arguments
288 ///
289 /// * `items` - The total number of items to paginate
290 ///
291 /// # Examples
292 ///
293 /// ```rust
294 /// use bubbletea_widgets::paginator::Model;
295 ///
296 /// let paginator = Model::new()
297 /// .with_per_page(10)
298 /// .with_total_items(95); // Will create 10 pages (95/10 = 9.5 -> 10)
299 ///
300 /// assert_eq!(paginator.total_pages, 10);
301 /// ```
302 pub fn with_total_items(mut self, items: usize) -> Self {
303 self.set_total_items(items);
304 self
305 }
306
307 /// Sets the number of items per page (builder pattern).
308 ///
309 /// The minimum value is 1; any value less than 1 will be clamped to 1.
310 /// This setting affects how total pages are calculated when using
311 /// `set_total_items()` or `with_total_items()`.
312 ///
313 /// # Arguments
314 ///
315 /// * `per_page` - Number of items to display per page (minimum 1)
316 ///
317 /// # Examples
318 ///
319 /// ```rust
320 /// use bubbletea_widgets::paginator::Model;
321 ///
322 /// let paginator = Model::new()
323 /// .with_per_page(25)
324 /// .with_total_items(100); // Will create 4 pages
325 ///
326 /// assert_eq!(paginator.per_page, 25);
327 /// assert_eq!(paginator.total_pages, 4);
328 ///
329 /// // Values less than 1 are clamped to 1
330 /// let clamped = Model::new().with_per_page(0);
331 /// assert_eq!(clamped.per_page, 1);
332 /// ```
333 pub fn with_per_page(mut self, per_page: usize) -> Self {
334 self.per_page = per_page.max(1);
335 self
336 }
337
338 /// Sets the number of items per page (mutable version).
339 ///
340 /// The minimum value is 1; any value less than 1 will be clamped to 1.
341 /// This method modifies the paginator in place.
342 ///
343 /// # Arguments
344 ///
345 /// * `per_page` - Number of items to display per page (minimum 1)
346 ///
347 /// # Examples
348 ///
349 /// ```rust
350 /// use bubbletea_widgets::paginator::Model;
351 ///
352 /// let mut paginator = Model::new();
353 /// paginator.set_per_page(15);
354 /// assert_eq!(paginator.per_page, 15);
355 ///
356 /// // Values less than 1 are clamped to 1
357 /// paginator.set_per_page(0);
358 /// assert_eq!(paginator.per_page, 1);
359 /// ```
360 pub fn set_per_page(&mut self, per_page: usize) {
361 self.per_page = per_page.max(1);
362 }
363
364 /// Sets the total number of pages directly.
365 ///
366 /// The minimum value is 1; any value less than 1 will be clamped to 1.
367 /// If the current page becomes out of bounds after setting the total pages,
368 /// it will be adjusted to the last valid page.
369 ///
370 /// **Note**: This method sets pages directly. If you want to calculate pages
371 /// based on total items, use `set_total_items()` instead.
372 ///
373 /// # Arguments
374 ///
375 /// * `pages` - The total number of pages (minimum 1)
376 ///
377 /// # Examples
378 ///
379 /// ```rust
380 /// use bubbletea_widgets::paginator::Model;
381 ///
382 /// let mut paginator = Model::new();
383 /// paginator.set_total_pages(10);
384 /// assert_eq!(paginator.total_pages, 10);
385 ///
386 /// // If current page is out of bounds, it gets adjusted
387 /// paginator.page = 15; // Out of bounds
388 /// paginator.set_total_pages(5);
389 /// assert_eq!(paginator.page, 4); // Adjusted to last page (0-indexed)
390 /// ```
391 pub fn set_total_pages(&mut self, pages: usize) {
392 self.total_pages = pages.max(1);
393 // Ensure the current page is not out of bounds
394 if self.page >= self.total_pages {
395 self.page = self.total_pages.saturating_sub(1);
396 }
397 }
398
399 /// Calculates and sets the total number of pages based on the total items.
400 ///
401 /// This method divides the total number of items by the current `per_page`
402 /// setting to calculate the total pages. The result is always at least 1,
403 /// even for 0 items. If the current page becomes out of bounds after
404 /// recalculation, it will be adjusted to the last valid page.
405 ///
406 /// # Arguments
407 ///
408 /// * `items` - The total number of items to paginate
409 ///
410 /// # Examples
411 ///
412 /// ```rust
413 /// use bubbletea_widgets::paginator::Model;
414 ///
415 /// let mut paginator = Model::new().with_per_page(10);
416 ///
417 /// // 95 items with 10 per page = 10 pages (95/10 = 9.5 -> 10)
418 /// paginator.set_total_items(95);
419 /// assert_eq!(paginator.total_pages, 10);
420 ///
421 /// // 0 items still results in 1 page minimum
422 /// paginator.set_total_items(0);
423 /// assert_eq!(paginator.total_pages, 1);
424 ///
425 /// // Exact division
426 /// paginator.set_total_items(100);
427 /// assert_eq!(paginator.total_pages, 10);
428 /// ```
429 pub fn set_total_items(&mut self, items: usize) {
430 if items == 0 {
431 self.total_pages = 1;
432 } else {
433 self.total_pages = items.div_ceil(self.per_page);
434 }
435
436 // Ensure the current page is not out of bounds
437 if self.page >= self.total_pages {
438 self.page = self.total_pages.saturating_sub(1);
439 }
440 }
441
442 /// Returns the number of items on the current page.
443 ///
444 /// This method calculates how many items are actually present on the
445 /// current page, which may be less than `per_page` on the last page
446 /// or when there are fewer total items than `per_page`.
447 ///
448 /// # Arguments
449 ///
450 /// * `total_items` - The total number of items being paginated
451 ///
452 /// # Returns
453 ///
454 /// The number of items on the current page, or 0 if there are no items.
455 ///
456 /// # Examples
457 ///
458 /// ```rust
459 /// use bubbletea_widgets::paginator::Model;
460 ///
461 /// let mut paginator = Model::new().with_per_page(10);
462 ///
463 /// // Full page
464 /// assert_eq!(paginator.items_on_page(100), 10);
465 ///
466 /// // Partial last page
467 /// paginator.page = 9; // Last page (0-indexed)
468 /// assert_eq!(paginator.items_on_page(95), 5); // Only 5 items on page 10
469 ///
470 /// // No items
471 /// assert_eq!(paginator.items_on_page(0), 0);
472 /// ```
473 pub fn items_on_page(&self, total_items: usize) -> usize {
474 if total_items == 0 {
475 return 0;
476 }
477 let (start, end) = self.get_slice_bounds(total_items);
478 end - start
479 }
480
481 /// Calculates slice bounds for the current page.
482 ///
483 /// This is a helper function for paginating slices. Given the total length
484 /// of your data, it returns the start and end indices for the current page.
485 /// The returned bounds can be used directly with slice notation.
486 ///
487 /// # Arguments
488 ///
489 /// * `length` - The total length of the data being paginated
490 ///
491 /// # Returns
492 ///
493 /// A tuple `(start, end)` where:
494 /// - `start` is the inclusive start index for the current page
495 /// - `end` is the exclusive end index for the current page
496 ///
497 /// # Examples
498 ///
499 /// ```rust
500 /// use bubbletea_widgets::paginator::Model;
501 ///
502 /// let items: Vec<i32> = (1..=100).collect();
503 /// let mut paginator = Model::new().with_per_page(10);
504 ///
505 /// // First page (0)
506 /// let (start, end) = paginator.get_slice_bounds(items.len());
507 /// assert_eq!((start, end), (0, 10));
508 /// let page_items = &items[start..end]; // Items 1-10
509 ///
510 /// // Third page (2)
511 /// paginator.page = 2;
512 /// let (start, end) = paginator.get_slice_bounds(items.len());
513 /// assert_eq!((start, end), (20, 30));
514 /// let page_items = &items[start..end]; // Items 21-30
515 /// ```
516 pub fn get_slice_bounds(&self, length: usize) -> (usize, usize) {
517 let start = self.page * self.per_page;
518 let end = (start + self.per_page).min(length);
519 (start, end)
520 }
521
522 /// Returns slice bounds assuming maximum possible data length.
523 ///
524 /// This is a convenience method that calls `get_slice_bounds()` with
525 /// the maximum possible data length (`per_page * total_pages`). It's
526 /// useful when you know your data exactly fills the pagination structure.
527 ///
528 /// # Returns
529 ///
530 /// A tuple `(start, end)` representing slice bounds for the current page.
531 ///
532 /// # Examples
533 ///
534 /// ```rust
535 /// use bubbletea_widgets::paginator::Model;
536 ///
537 /// let mut paginator = Model::new()
538 /// .with_per_page(10)
539 /// .with_total_items(100); // Exactly 10 pages
540 ///
541 /// paginator.page = 3;
542 /// let (start, end) = paginator.start_index_end_index();
543 /// assert_eq!((start, end), (30, 40));
544 /// ```
545 pub fn start_index_end_index(&self) -> (usize, usize) {
546 self.get_slice_bounds(self.per_page * self.total_pages)
547 }
548
549 /// Navigates to the previous page.
550 ///
551 /// If the paginator is already on the first page (page 0), this method
552 /// has no effect. The page number will not go below 0.
553 ///
554 /// # Examples
555 ///
556 /// ```rust
557 /// use bubbletea_widgets::paginator::Model;
558 ///
559 /// let mut paginator = Model::new().with_per_page(10).with_total_items(100);
560 /// paginator.page = 5;
561 ///
562 /// paginator.prev_page();
563 /// assert_eq!(paginator.page, 4);
564 ///
565 /// // Won't go below 0
566 /// paginator.page = 0;
567 /// paginator.prev_page();
568 /// assert_eq!(paginator.page, 0);
569 /// ```
570 pub fn prev_page(&mut self) {
571 if self.page > 0 {
572 self.page -= 1;
573 }
574 }
575
576 /// Navigates to the next page.
577 ///
578 /// If the paginator is already on the last page, this method has no effect.
579 /// The page number will not exceed `total_pages - 1`.
580 ///
581 /// # Examples
582 ///
583 /// ```rust
584 /// use bubbletea_widgets::paginator::Model;
585 ///
586 /// let mut paginator = Model::new().with_per_page(10).with_total_items(100);
587 /// // total_pages = 10, so last page is 9 (0-indexed)
588 ///
589 /// paginator.page = 5;
590 /// paginator.next_page();
591 /// assert_eq!(paginator.page, 6);
592 ///
593 /// // Won't go beyond last page
594 /// paginator.page = 8; // Second to last page
595 /// paginator.next_page();
596 /// assert_eq!(paginator.page, 9); // Should go to last page (9 is the last valid page)
597 /// paginator.next_page();
598 /// assert_eq!(paginator.page, 9); // Should stay at last page
599 /// ```
600 pub fn next_page(&mut self) {
601 if !self.on_last_page() {
602 self.page += 1;
603 }
604 }
605
606 /// Returns true if the paginator is on the first page.
607 ///
608 /// The first page is always page 0 in the 0-indexed pagination system.
609 ///
610 /// # Examples
611 ///
612 /// ```rust
613 /// use bubbletea_widgets::paginator::Model;
614 ///
615 /// let mut paginator = Model::new().with_per_page(10).with_total_items(100);
616 ///
617 /// assert!(paginator.on_first_page());
618 ///
619 /// paginator.next_page();
620 /// assert!(!paginator.on_first_page());
621 /// ```
622 pub fn on_first_page(&self) -> bool {
623 self.page == 0
624 }
625
626 /// Returns true if the paginator is on the last page.
627 ///
628 /// The last page is `total_pages - 1` in the 0-indexed pagination system.
629 ///
630 /// # Examples
631 ///
632 /// ```rust
633 /// use bubbletea_widgets::paginator::Model;
634 ///
635 /// let mut paginator = Model::new().with_per_page(10).with_total_items(90);
636 /// // Creates 9 pages (0-8), so last page is 8
637 ///
638 /// assert!(!paginator.on_last_page());
639 ///
640 /// paginator.page = 8; // Last page
641 /// assert!(paginator.on_last_page());
642 /// ```
643 pub fn on_last_page(&self) -> bool {
644 self.page == self.total_pages.saturating_sub(1)
645 }
646
647 /// Updates the paginator based on received messages.
648 ///
649 /// This method should be called from your application's `update()` method
650 /// to handle pagination key presses. It automatically responds to the
651 /// configured key bindings for next/previous page navigation.
652 ///
653 /// # Arguments
654 ///
655 /// * `msg` - The message to process, typically containing key press events
656 ///
657 /// # Examples
658 ///
659 /// ```rust
660 /// use bubbletea_widgets::paginator::Model as Paginator;
661 /// use bubbletea_rs::{Model, Msg};
662 ///
663 /// struct App {
664 /// paginator: Paginator,
665 /// }
666 ///
667 /// impl Model for App {
668 /// fn update(&mut self, msg: Msg) -> Option<bubbletea_rs::Cmd> {
669 /// // Forward messages to paginator
670 /// self.paginator.update(&msg);
671 /// None
672 /// }
673 ///
674 /// // ... other methods
675 /// # fn init() -> (Self, Option<bubbletea_rs::Cmd>) { (Self { paginator: Paginator::new() }, None) }
676 /// # fn view(&self) -> String { String::new() }
677 /// }
678 /// ```
679 pub fn update(&mut self, msg: &Msg) {
680 if let Some(key_msg) = msg.downcast_ref::<KeyMsg>() {
681 if self.keymap.next_page.matches(key_msg) {
682 self.next_page();
683 } else if self.keymap.prev_page.matches(key_msg) {
684 self.prev_page();
685 }
686 }
687 }
688
689 /// Renders the paginator as a string.
690 ///
691 /// The output format depends on the `paginator_type` setting:
692 /// - **Arabic**: Shows "current/total" (e.g., "3/10")
693 /// - **Dots**: Shows dots with active page highlighted (e.g., "○ ○ ● ○ ○")
694 ///
695 /// # Returns
696 ///
697 /// A string representation of the current pagination state.
698 ///
699 /// # Examples
700 ///
701 /// ```rust
702 /// use bubbletea_widgets::paginator::{Model, Type};
703 ///
704 /// let mut paginator = Model::new().with_per_page(10).with_total_items(50);
705 /// // Creates 5 pages, currently on page 0
706 ///
707 /// // Arabic mode (default)
708 /// paginator.paginator_type = Type::Arabic;
709 /// assert_eq!(paginator.view(), "1/5"); // 1-indexed for display
710 ///
711 /// // Dots mode
712 /// paginator.paginator_type = Type::Dots;
713 /// assert_eq!(paginator.view(), "• ○ ○ ○ ○");
714 ///
715 /// // Move to page 2
716 /// paginator.page = 2;
717 /// assert_eq!(paginator.view(), "○ ○ • ○ ○");
718 /// ```
719 pub fn view(&self) -> String {
720 match self.paginator_type {
721 Type::Arabic => self.arabic_view(),
722 Type::Dots => self.dots_view(),
723 }
724 }
725
726 fn arabic_view(&self) -> String {
727 self.arabic_format
728 .replacen("%d", &(self.page + 1).to_string(), 1)
729 .replacen("%d", &self.total_pages.to_string(), 1)
730 }
731
732 fn dots_view(&self) -> String {
733 let mut s = String::new();
734 for i in 0..self.total_pages {
735 if i == self.page {
736 s.push_str(&self.active_dot);
737 } else {
738 s.push_str(&self.inactive_dot);
739 }
740 if i < self.total_pages - 1 {
741 s.push(' ');
742 }
743 }
744 s
745 }
746}