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