fm/modes/utils/
selectable_content.rs

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