fm/modes/utils/
selectable_content.rs

1use std::iter::{Chain, Skip, Take};
2use std::slice::Iter;
3
4use ratatui::style::Style;
5
6/// Allow selection of a element and basic navigation.
7/// Its implementation is mostly made by the macro [`crate::impl_selectable`]
8/// which allows to manipulate all sort of content in a common manner.
9/// It simplifies a lot the creation of menus for any action and is used everywhere.
10pub trait Selectable {
11    /// True iff the content is empty
12    fn is_empty(&self) -> bool;
13    /// Number of element in content
14    fn len(&self) -> usize;
15    /// Select next element in content
16    fn next(&mut self);
17    /// Select previous element in content
18    fn prev(&mut self);
19    /// Current index of selected element.
20    /// 0 if the content is empty (I know, could be an option)
21    fn index(&self) -> usize;
22    /// set the index to the value if possible
23    fn set_index(&mut self, index: usize);
24    /// true if the selected element is the last of content
25    fn selected_is_last(&self) -> bool;
26}
27
28/// Allow access to a content element of any type.
29/// It allows to access the selected element, the whole content,
30/// to push new elements and to get the style of an element for display.
31///
32/// Its implementation should be done using the [`crate::impl_content`] macro
33/// which allows to manipulate any kind of content.
34/// It's used for almost every menu or list of things, as long as the whole content
35/// is known at some point.
36///
37/// Major exception is [`crate::modes::FuzzyFinder`] which _doesn't store_ the matches.
38pub trait Content<T>: Selectable {
39    /// Returns the selected element if content isn't empty
40    fn selected(&self) -> Option<&T>;
41    /// Reference to the content as a vector.
42    fn content(&self) -> &Vec<T>;
43    /// add an element to the content
44    fn push(&mut self, t: T);
45    /// [`ratatui::style::Style`] used to display an element
46    fn style(&self, index: usize, style: &Style) -> Style;
47}
48
49/// Returns a reference to itself as a `[std::path::Path]`.
50/// Usefull for different kind of strings (`&str` or `String`).
51pub trait ToPath {
52    fn to_path(&self) -> &std::path::Path;
53}
54
55/// Iterate over line from current index to bottom then from top to current index.
56///
57/// Useful when going to next match in search results
58pub trait IndexToIndex<T> {
59    /// Iterate over line from current index to bottom then from top to current index.
60    ///
61    /// Useful when going to next match in search results
62    fn index_to_index(&self) -> Chain<Skip<Iter<'_, T>>, Take<Iter<'_, T>>>;
63}
64
65/// Implement the `SelectableContent` for struct `$struc` with content type `$content_type`.
66/// This trait allows to navigate through a vector of element `content_type`.
67/// It implements: `is_empty`, `len`, `next`, `prev`, `set_index` and `selected_is_last`.
68/// This macro should not be called directly. Use [crate::impl_content] instead.
69#[macro_export]
70macro_rules! impl_selectable {
71    ($struct:ident) => {
72        use $crate::modes::Selectable;
73
74        /// Implement a selectable content for this struct.
75        /// This trait allows to navigate through a vector of element `content_type`.
76        /// It implements: `is_empty`, `len`, `next`, `prev`, `set_index` and `selected_is_last`.
77        impl Selectable for $struct {
78            /// True if the content is empty.
79            fn is_empty(&self) -> bool {
80                self.content.is_empty()
81            }
82
83            /// The size of the content.
84            fn len(&self) -> usize {
85                self.content.len()
86            }
87
88            /// Select the prev item.
89            fn prev(&mut self) {
90                if self.is_empty() {
91                    self.index = 0
92                } else if self.index > 0 {
93                    self.index -= 1;
94                } else {
95                    self.index = self.len() - 1
96                }
97            }
98
99            /// Select the next item.
100            fn next(&mut self) {
101                if self.is_empty() {
102                    self.index = 0;
103                } else {
104                    self.index = (self.index + 1) % self.len()
105                }
106            }
107
108            /// Returns the index of the selected item.
109            fn index(&self) -> usize {
110                self.index
111            }
112
113            /// Set the index to a new value if the value is below the length.
114            fn set_index(&mut self, index: usize) {
115                if index < self.len() {
116                    self.index = index;
117                }
118            }
119
120            fn selected_is_last(&self) -> bool {
121                return self.index() + 1 == self.len();
122            }
123        }
124    };
125}
126
127/// Implement an iterator from next index of content to the same index,
128/// starting back from 0 when the last element is reached.
129/// It's used to search an element in content below current and
130/// then from the first index to the current index.
131#[macro_export]
132macro_rules! impl_index_to_index {
133    ($content_type:ident, $struct:ident) => {
134        use std::iter::{Chain, Enumerate, Skip, Take};
135        use std::slice::Iter;
136        use $crate::modes::IndexToIndex;
137
138        impl IndexToIndex<$content_type> for $struct {
139            /// Iterate over line from current index to bottom then from top to current index.
140            ///
141            /// Useful when going to next match in search results
142            fn index_to_index(
143                &self,
144            ) -> Chain<Skip<Iter<'_, $content_type>>, Take<Iter<'_, $content_type>>> {
145                let index = self.index;
146                let elems = self.content();
147                elems.iter().skip(index + 1).chain(elems.iter().take(index))
148            }
149        }
150    };
151}
152
153/// Implement two traits allowing to select and navigate into a struct.
154/// It's used for menu which share a common structure.
155///
156/// Implement the `Selectable` trait for struct `$struc` with content type `$content_type`.
157/// This trait allows to navigate through a vector of element `content_type`.
158/// It implements: `is_empty`, `len`, `next`, `prev`, `set_index` and `selected_is_last`.
159///
160/// Implement the `Content` trait for struct `$struct` with content type `$content_type`.
161/// This trait allows to navigate through a vector of element `content_type`.
162/// It implements: `selected`, `content`, `push`, `style`.
163/// `selected` returns an optional reference to the value.
164#[macro_export]
165macro_rules! impl_content {
166    ($struct:ident, $content_type:ident) => {
167        impl_selectable!($struct);
168
169        use $crate::modes::Content;
170
171        /// Implement a selectable content for this struct.
172        /// This trait allows to navigate through a vector of element `content_type`.
173        /// It implements: `selected`, `content`, `push`, `style`.
174        /// `selected` returns an optional reference to the value.
175        impl Content<$content_type> for $struct {
176            /// Returns a reference to the selected content.
177            fn selected(&self) -> Option<&$content_type> {
178                match self.is_empty() {
179                    true => None,
180                    false => Some(&self.content[self.index]),
181                }
182            }
183
184            /// A reference to the content.
185            fn content(&self) -> &Vec<$content_type> {
186                &self.content
187            }
188
189            /// Reverse the received effect if the index match the selected index.
190            fn style(&self, index: usize, style: &ratatui::style::Style) -> ratatui::style::Style {
191                let mut style = *style;
192                if index == self.index() {
193                    style.add_modifier |= ratatui::style::Modifier::REVERSED;
194                }
195                style
196            }
197
198            /// Push a new element at the end of content
199            fn push(&mut self, element: $content_type) {
200                self.content.push(element)
201            }
202        }
203    };
204}