Skip to main content

intelli_shell/widgets/items/
variable.rs

1use ratatui::{
2    backend::FromCrossterm,
3    buffer::Buffer,
4    layout::{Rect, Size},
5    style::Style,
6    text::Line,
7    widgets::Widget,
8};
9
10use crate::{
11    config::Theme,
12    model::{VariableSuggestion, VariableValue},
13    utils::format_env_var,
14    widgets::{CustomListItem, CustomTextArea},
15};
16
17const SECRET_VARIABLE_TITLE: &str = "(secret)";
18const NEW_VARIABLE_TITLE: &str = "(new)";
19const EDIT_VARIABLE_TITLE: &str = "(edit)";
20
21/// An identifier for a [`VariableSuggestionItem`]
22#[derive(PartialEq, Eq)]
23pub enum VariableSuggestionItemIdentifier {
24    New,
25    Previous(String),
26    Environment(String),
27    Existing(Option<i32>, String),
28    Completion(String),
29    Derived(String),
30}
31
32/// Actual item to be listed for a [`VariableSuggestion`], preserving editing state
33#[derive(Clone)]
34pub enum VariableSuggestionItem<'a> {
35    New {
36        sort_index: u8,
37        is_secret: bool,
38        textarea: CustomTextArea<'a>,
39    },
40    Previous {
41        sort_index: u8,
42        value: String,
43        score: f64,
44    },
45    Environment {
46        sort_index: u8,
47        content: String,
48        is_value: bool,
49        score: f64,
50    },
51    Existing {
52        sort_index: u8,
53        value: VariableValue,
54        score: f64,
55        completion_merged: bool,
56        editing: Option<CustomTextArea<'a>>,
57    },
58    Completion {
59        sort_index: u8,
60        value: String,
61        score: f64,
62    },
63    Derived {
64        sort_index: u8,
65        value: String,
66    },
67}
68
69impl<'a> VariableSuggestionItem<'a> {
70    /// Retrieves an identifier for this item, two items are considered the same if their identifiers are equal
71    pub fn identifier(&self) -> VariableSuggestionItemIdentifier {
72        match self {
73            VariableSuggestionItem::New { .. } => VariableSuggestionItemIdentifier::New,
74            VariableSuggestionItem::Previous { value, .. } => VariableSuggestionItemIdentifier::Previous(value.clone()),
75            VariableSuggestionItem::Environment { content, .. } => {
76                VariableSuggestionItemIdentifier::Environment(content.clone())
77            }
78            VariableSuggestionItem::Existing { value, .. } => {
79                VariableSuggestionItemIdentifier::Existing(value.id, value.value.clone())
80            }
81            VariableSuggestionItem::Completion { value, .. } => {
82                VariableSuggestionItemIdentifier::Completion(value.clone())
83            }
84            VariableSuggestionItem::Derived { value, .. } => VariableSuggestionItemIdentifier::Derived(value.clone()),
85        }
86    }
87
88    /// On [Existing](VariableSuggestionItem::Existing) variants, enter edit mode if not already entered
89    pub fn enter_edit_mode(&mut self) {
90        if let VariableSuggestionItem::Existing { value, editing, .. } = self
91            && editing.is_none()
92        {
93            *editing = Some(
94                CustomTextArea::new(Style::default(), true, false, value.value.clone())
95                    .title(EDIT_VARIABLE_TITLE)
96                    .focused(),
97            );
98        }
99    }
100
101    pub fn sort_index(&self) -> u8 {
102        match self {
103            VariableSuggestionItem::New { sort_index, .. }
104            | VariableSuggestionItem::Previous { sort_index, .. }
105            | VariableSuggestionItem::Environment { sort_index, .. }
106            | VariableSuggestionItem::Existing { sort_index, .. }
107            | VariableSuggestionItem::Completion { sort_index, .. }
108            | VariableSuggestionItem::Derived { sort_index, .. } => *sort_index,
109        }
110    }
111
112    pub fn score(&self) -> f64 {
113        match self {
114            VariableSuggestionItem::Previous { score, .. }
115            | VariableSuggestionItem::Environment { score, .. }
116            | VariableSuggestionItem::Existing { score, .. }
117            | VariableSuggestionItem::Completion { score, .. } => *score,
118            _ => 0.0,
119        }
120    }
121}
122
123impl<'a> From<(u8, VariableSuggestion, f64)> for VariableSuggestionItem<'a> {
124    fn from((sort_index, suggestion, score): (u8, VariableSuggestion, f64)) -> Self {
125        match suggestion {
126            VariableSuggestion::Secret => Self::New {
127                sort_index,
128                is_secret: true,
129                textarea: CustomTextArea::new(Style::default(), true, false, "")
130                    .title(SECRET_VARIABLE_TITLE)
131                    .focused(),
132            },
133            VariableSuggestion::New => Self::New {
134                sort_index,
135                is_secret: false,
136                textarea: CustomTextArea::new(Style::default(), true, false, "")
137                    .title(NEW_VARIABLE_TITLE)
138                    .focused(),
139            },
140            VariableSuggestion::Previous(value) => Self::Previous {
141                sort_index,
142                value,
143                score,
144            },
145            VariableSuggestion::Environment { env_var_name, value } => {
146                if let Some(value) = value {
147                    Self::Environment {
148                        sort_index,
149                        content: value,
150                        is_value: true,
151                        score,
152                    }
153                } else {
154                    Self::Environment {
155                        sort_index,
156                        content: format_env_var(env_var_name),
157                        is_value: false,
158                        score,
159                    }
160                }
161            }
162            VariableSuggestion::Existing(value) => Self::Existing {
163                sort_index,
164                value,
165                score,
166                completion_merged: false,
167                editing: None,
168            },
169            VariableSuggestion::Completion(value) => Self::Completion {
170                sort_index,
171                value,
172                score,
173            },
174            VariableSuggestion::Derived(value) => Self::Derived { sort_index, value },
175        }
176    }
177}
178
179impl<'i> CustomListItem for VariableSuggestionItem<'i> {
180    type Widget<'w>
181        = VariableSuggestionItemWidget<'w>
182    where
183        'i: 'w;
184
185    fn as_widget<'a>(
186        &'a self,
187        theme: &Theme,
188        inline: bool,
189        is_highlighted: bool,
190        is_discarded: bool,
191    ) -> (Self::Widget<'a>, Size) {
192        match self {
193            VariableSuggestionItem::New { textarea, .. }
194            | VariableSuggestionItem::Existing {
195                editing: Some(textarea),
196                ..
197            } => {
198                let style = match (is_highlighted, is_discarded) {
199                    (true, true) => theme.highlight_secondary_full(),
200                    (true, false) => theme.highlight_primary_full(),
201                    (false, true) => theme.secondary,
202                    (false, false) => theme.primary,
203                };
204
205                let mut ta_render = textarea.clone();
206                ta_render.set_focus(is_highlighted);
207                ta_render.set_style(Style::from_crossterm(style));
208                (
209                    VariableSuggestionItemWidget(VariableSuggestionItemWidgetInner::TextArea(ta_render)),
210                    Size::new(10, 1),
211                )
212            }
213            VariableSuggestionItem::Existing {
214                value: VariableValue { value: text, .. },
215                ..
216            }
217            | VariableSuggestionItem::Previous { value: text, .. }
218            | VariableSuggestionItem::Environment { content: text, .. }
219            | VariableSuggestionItem::Completion { value: text, .. }
220            | VariableSuggestionItem::Derived { value: text, .. } => {
221                let (line, size) = text.as_widget(theme, inline, is_highlighted, is_discarded);
222                (
223                    VariableSuggestionItemWidget(VariableSuggestionItemWidgetInner::Literal(line)),
224                    size,
225                )
226            }
227        }
228    }
229}
230
231/// Widget for a [`VariableSuggestionItem`]
232pub struct VariableSuggestionItemWidget<'a>(VariableSuggestionItemWidgetInner<'a>);
233#[allow(clippy::large_enum_variant)]
234enum VariableSuggestionItemWidgetInner<'a> {
235    TextArea(CustomTextArea<'a>),
236    Literal(Line<'a>),
237}
238
239impl<'a> Widget for VariableSuggestionItemWidget<'a> {
240    fn render(self, area: Rect, buf: &mut Buffer)
241    where
242        Self: Sized,
243    {
244        match self.0 {
245            VariableSuggestionItemWidgetInner::TextArea(ta) => ta.render(area, buf),
246            VariableSuggestionItemWidgetInner::Literal(l) => l.render(area, buf),
247        }
248    }
249}