intelli_shell/widgets/items/
variable.rs

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