intelli_shell/widgets/
variable.rs

1use std::{
2    borrow::Cow,
3    ops::{Deref, DerefMut},
4};
5
6use ratatui::{
7    buffer::Buffer,
8    layout::{Rect, Size},
9    style::Style,
10    text::Line,
11    widgets::Widget,
12};
13
14use crate::{
15    config::Theme,
16    model::VariableValue,
17    widgets::{AsWidget, CustomTextArea},
18};
19
20const SECRET_VARIABLE_TITLE: &str = "(secret)";
21const NEW_VARIABLE_TITLE: &str = "(new)";
22const EDIT_VARIABLE_TITLE: &str = "(edit)";
23
24/// Represents a single row in a list of suggestions for a given variable's value
25#[derive(Clone)]
26pub enum VariableSuggestionRow<'a> {
27    New(NewVariableValue<'a>),
28    Environment(LiteralVariableValue<'a>, bool),
29    Existing(ExistingVariableValue<'a>),
30    Derived(LiteralVariableValue<'a>),
31}
32
33#[derive(Clone)]
34pub struct NewVariableValue<'a> {
35    highlighted: bool,
36    primary: Style,
37    highlight_primary: Style,
38    secret: bool,
39    textarea: CustomTextArea<'a>,
40}
41
42#[derive(Clone)]
43pub struct ExistingVariableValue<'a> {
44    highlighted: bool,
45    primary: Style,
46    highlight_primary: Style,
47    pub value: VariableValue,
48    pub editing: Option<CustomTextArea<'a>>,
49}
50
51#[derive(Clone)]
52pub struct LiteralVariableValue<'a> {
53    highlighted: bool,
54    primary: Style,
55    highlight_primary: Style,
56    value: Cow<'a, str>,
57}
58
59impl<'a> NewVariableValue<'a> {
60    pub fn new(theme: &Theme, secret: bool) -> Self {
61        Self {
62            highlighted: false,
63            primary: theme.primary.into(),
64            highlight_primary: theme.highlight_primary_full().into(),
65            secret,
66            textarea: CustomTextArea::new(theme.primary, true, false, "").title(if secret {
67                SECRET_VARIABLE_TITLE
68            } else {
69                NEW_VARIABLE_TITLE
70            }),
71        }
72    }
73
74    pub fn is_secret(&self) -> bool {
75        self.secret
76    }
77}
78
79impl<'a> ExistingVariableValue<'a> {
80    pub fn new(theme: &Theme, value: VariableValue) -> Self {
81        Self {
82            highlighted: false,
83            primary: theme.primary.into(),
84            highlight_primary: theme.highlight_primary_full().into(),
85            value,
86            editing: None,
87        }
88    }
89
90    pub fn enter_edit_mode(&mut self) {
91        if self.editing.is_none() {
92            self.editing = Some(
93                CustomTextArea::new(self.highlight_primary, true, false, self.value.value.clone())
94                    .title(EDIT_VARIABLE_TITLE)
95                    .focused(),
96            );
97        }
98    }
99}
100
101impl<'a> LiteralVariableValue<'a> {
102    pub fn new(theme: &Theme, value: impl Into<Cow<'a, str>>) -> Self {
103        Self {
104            highlighted: false,
105            primary: theme.primary.into(),
106            highlight_primary: theme.highlight_primary_full().into(),
107            value: value.into(),
108        }
109    }
110}
111
112impl<'a> Widget for &'a VariableSuggestionRow<'a> {
113    fn render(self, area: Rect, buf: &mut Buffer)
114    where
115        Self: Sized,
116    {
117        match &self {
118            VariableSuggestionRow::New(n) => n.render(area, buf),
119            VariableSuggestionRow::Existing(e) => {
120                if let Some(editing) = e.editing.as_ref() {
121                    editing.render(area, buf);
122                } else {
123                    Line::from(e.value.value.as_str())
124                        .style(if e.highlighted { e.highlight_primary } else { e.primary })
125                        .render(area, buf);
126                }
127            }
128            VariableSuggestionRow::Environment(l, _) | VariableSuggestionRow::Derived(l) => Line::from(l.as_ref())
129                .style(if l.highlighted { l.highlight_primary } else { l.primary })
130                .render(area, buf),
131        }
132    }
133}
134
135impl<'a> AsWidget for VariableSuggestionRow<'a> {
136    fn set_highlighted(&mut self, is_highlighted: bool) {
137        match self {
138            VariableSuggestionRow::New(n) => {
139                n.highlighted = is_highlighted;
140                let style = if is_highlighted { n.highlight_primary } else { n.primary };
141                n.set_focus(is_highlighted);
142                n.set_style(style);
143            }
144            VariableSuggestionRow::Existing(e) => {
145                e.highlighted = is_highlighted;
146                if let Some(ta) = e.editing.as_mut() {
147                    let style = if is_highlighted { e.highlight_primary } else { e.primary };
148                    ta.set_focus(is_highlighted);
149                    ta.set_style(style);
150                }
151            }
152            VariableSuggestionRow::Environment(l, _) | VariableSuggestionRow::Derived(l) => {
153                l.highlighted = is_highlighted;
154            }
155        }
156    }
157
158    fn as_widget<'b>(&'b self, _is_highlighted: bool) -> (impl Widget + 'b, Size) {
159        (self, Size::new(10, 1))
160    }
161}
162
163impl<'a> From<NewVariableValue<'a>> for CustomTextArea<'a> {
164    fn from(val: NewVariableValue<'a>) -> Self {
165        val.textarea
166    }
167}
168impl<'a> Deref for NewVariableValue<'a> {
169    type Target = CustomTextArea<'a>;
170
171    fn deref(&self) -> &Self::Target {
172        &self.textarea
173    }
174}
175impl<'a> DerefMut for NewVariableValue<'a> {
176    fn deref_mut(&mut self) -> &mut Self::Target {
177        &mut self.textarea
178    }
179}
180
181impl<'a> From<ExistingVariableValue<'a>> for VariableValue {
182    fn from(val: ExistingVariableValue<'a>) -> Self {
183        val.value
184    }
185}
186
187impl<'a> From<LiteralVariableValue<'a>> for Cow<'a, str> {
188    fn from(val: LiteralVariableValue<'a>) -> Self {
189        val.value
190    }
191}
192impl<'a> Deref for LiteralVariableValue<'a> {
193    type Target = str;
194
195    fn deref(&self) -> &Self::Target {
196        self.value.as_ref()
197    }
198}