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}