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}