fm/modes/utils/
selectable_content.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use std::iter::{Chain, Skip, Take};
use std::slice::Iter;

use ratatui::style::Style;

// TODO pick a more telling name. `Selectable` doesn't say what it does.
/// Allow selection of a element and basic navigation.
/// Its implementation is mostly made by the macro [`crate::impl_selectable`]
/// which allows to manipulate all sort of content in a common manner.
/// It simplifies a lot the creation of menus for any action and is used everywhere.
pub trait Selectable {
    /// True iff the content is empty
    fn is_empty(&self) -> bool;
    /// Number of element in content
    fn len(&self) -> usize;
    /// Select next element in content
    fn next(&mut self);
    /// Select previous element in content
    fn prev(&mut self);
    /// Current index of selected element.
    /// 0 if the content is empty (I know, could be an option)
    fn index(&self) -> usize;
    /// set the index to the value if possible
    fn set_index(&mut self, index: usize);
    /// true if the selected element is the last of content
    fn selected_is_last(&self) -> bool;
}

/// Allow access to a content element of any type.
/// It allows to access the selected element, the whole content,
/// to push new elements and to get the style of an element for display.
///
/// Its implementation should be done using the [`crate::impl_content`] macro
/// which allows to manipulate any kind of content.
/// It's used for almost every menu or list of things, as long as the whole content
/// is known at some point.
///
/// Major exception is [`crate::modes::FuzzyFinder`] which _doesn't store_ the matches.
pub trait Content<T>: Selectable {
    /// Returns the selected element if content isn't empty
    fn selected(&self) -> Option<&T>;
    /// Reference to the content as a vector.
    fn content(&self) -> &Vec<T>;
    /// add an element to the content
    fn push(&mut self, t: T);
    /// [`ratatui::style::Style`] used to display an element
    fn style(&self, index: usize, style: &Style) -> Style;
}

/// Returns a reference to itself as a `[std::path::Path]`.
/// Usefull for different kind of strings (`&str` or `String`).
pub trait ToPath {
    fn to_path(&self) -> &std::path::Path;
}

/// Iterate over line from current index to bottom then from top to current index.
///
/// Useful when going to next match in search results
pub trait IndexToIndex<T> {
    /// Iterate over line from current index to bottom then from top to current index.
    ///
    /// Useful when going to next match in search results
    fn index_to_index(&self) -> Chain<Skip<Iter<T>>, Take<Iter<T>>>;
}
/// Implement the `SelectableContent` for struct `$struc` with content type `$content_type`.
/// This trait allows to navigate through a vector of element `content_type`.
/// It implements: `is_empty`, `len`, `next`, `prev`, `selected`.
/// `selected` returns an optional reference to the value.
#[macro_export]
macro_rules! impl_selectable {
    ($struct:ident) => {
        use $crate::modes::Selectable;

        /// Implement a selectable content for this struct.
        /// This trait allows to navigate through a vector of element `content_type`.
        /// It implements: `is_empty`, `len`, `next`, `prev`, `selected`.
        /// `selected` returns an optional reference to the value.
        impl Selectable for $struct {
            /// True if the content is empty.
            fn is_empty(&self) -> bool {
                self.content.is_empty()
            }

            /// The size of the content.
            fn len(&self) -> usize {
                self.content.len()
            }

            /// Select the prev item.
            fn prev(&mut self) {
                if self.is_empty() {
                    self.index = 0
                } else if self.index > 0 {
                    self.index -= 1;
                } else {
                    self.index = self.len() - 1
                }
            }

            /// Select the next item.
            fn next(&mut self) {
                if self.is_empty() {
                    self.index = 0;
                } else {
                    self.index = (self.index + 1) % self.len()
                }
            }

            /// Returns the index of the selected item.
            fn index(&self) -> usize {
                self.index
            }

            /// Set the index to a new value if the value is below the length.
            fn set_index(&mut self, index: usize) {
                if index < self.len() {
                    self.index = index;
                }
            }

            fn selected_is_last(&self) -> bool {
                return self.index() + 1 == self.len();
            }
        }
    };
}

/// Implement an iterator from next index of content to the same index,
/// starting back from 0 when the last element is reached.
/// It's used to search an element in content below current and
/// then from the first index to the current index.
#[macro_export]
macro_rules! impl_index_to_index {
    ($content_type:ident, $struct:ident) => {
        use std::iter::{Chain, Enumerate, Skip, Take};
        use std::slice::Iter;
        use $crate::modes::IndexToIndex;

        impl IndexToIndex<$content_type> for $struct {
            /// Iterate over line from current index to bottom then from top to current index.
            ///
            /// Useful when going to next match in search results
            fn index_to_index(
                &self,
            ) -> Chain<Skip<Iter<$content_type>>, Take<Iter<$content_type>>> {
                let index = self.index;
                let elems = self.content();
                elems.iter().skip(index + 1).chain(elems.iter().take(index))
            }
        }
    };
}

/// Implement the `SelectableContent` for struct `$struc` with content type `$content_type`.
/// This trait allows to navigate through a vector of element `content_type`.
/// It implements: `is_empty`, `len`, `next`, `prev`, `selected`.
/// `selected` returns an optional reference to the value.
#[macro_export]
macro_rules! impl_content {
    ($content_type:ident, $struct:ident) => {
        use $crate::modes::Content;

        /// Implement a selectable content for this struct.
        /// This trait allows to navigate through a vector of element `content_type`.
        /// It implements: `is_empty`, `len`, `next`, `prev`, `selected`.
        /// `selected` returns an optional reference to the value.
        impl Content<$content_type> for $struct {
            /// Returns a reference to the selected content.
            fn selected(&self) -> Option<&$content_type> {
                match self.is_empty() {
                    true => None,
                    false => Some(&self.content[self.index]),
                }
            }

            /// A reference to the content.
            fn content(&self) -> &Vec<$content_type> {
                &self.content
            }

            /// Reverse the received effect if the index match the selected index.
            fn style(&self, index: usize, style: &ratatui::style::Style) -> ratatui::style::Style {
                let mut style = *style;
                if index == self.index() {
                    style.add_modifier |= ratatui::style::Modifier::REVERSED;
                }
                style
            }

            /// Push a new element at the end of content
            fn push(&mut self, element: $content_type) {
                self.content.push(element)
            }
        }
    };
}