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)
}
}
};
}