tty_form/step/
keyvalue.rs

1use crossterm::event::{KeyCode, KeyEvent};
2use tty_interface::{pos, Interface, Position};
3use tty_text::Key;
4
5use crate::{
6    dependency::{DependencyId, DependencyState, Evaluation},
7    style::help_style,
8    text::{DrawerContents, Segment, Text},
9    Form,
10};
11
12use super::{InputResult, Step};
13
14/// A key-value-pair set entry step.
15pub struct KeyValueStep {
16    prompt: String,
17    pairs: Vec<(tty_text::Text, tty_text::Text)>,
18    focused_pair: usize,
19    key_focused: bool,
20    evaluation: Option<(DependencyId, Evaluation)>,
21}
22
23impl KeyValueStep {
24    pub fn new(prompt: &str) -> Self {
25        Self {
26            prompt: prompt.to_string(),
27            pairs: vec![(tty_text::Text::new(false), tty_text::Text::new(false))],
28            focused_pair: 0,
29            key_focused: true,
30            evaluation: None,
31        }
32    }
33
34    pub fn set_evaluation(&mut self, evaluation: Evaluation) -> DependencyId {
35        let id = DependencyId::new();
36        self.evaluation = Some((id, evaluation));
37        id
38    }
39}
40
41impl Step for KeyValueStep {
42    fn initialize(&mut self, _dependency_state: &mut DependencyState, _index: usize) {}
43
44    fn render(
45        &self,
46        interface: &mut Interface,
47        _dependency_state: &DependencyState,
48        mut position: Position,
49        is_focused: bool,
50    ) -> u16 {
51        for (pair_index, (key, value)) in self.pairs.iter().enumerate() {
52            let line = if value.value().is_empty() {
53                key.value()
54            } else {
55                format!("{}: {}", key.value(), value.value())
56            };
57
58            interface.set(position, &line);
59
60            if is_focused && pair_index == self.focused_pair {
61                let cursor = pos!(
62                    if self.key_focused {
63                        key.cursor().0
64                    } else {
65                        key.value().len() + 2 + value.cursor().0
66                    } as u16,
67                    position.y()
68                );
69
70                interface.set_cursor(Some(cursor));
71            }
72
73            position = pos!(position.x(), position.y() + 1);
74        }
75
76        self.pairs.len() as u16
77    }
78
79    fn update(
80        &mut self,
81        _dependency_state: &mut DependencyState,
82        input: KeyEvent,
83    ) -> Option<InputResult> {
84        let text = if self.key_focused {
85            &mut self.pairs[self.focused_pair].0
86        } else {
87            &mut self.pairs[self.focused_pair].1
88        };
89
90        match input.code {
91            KeyCode::Enter | KeyCode::Tab => {
92                if text.value().is_empty() {
93                    if self.key_focused {
94                        if self.focused_pair > 0 {
95                            self.pairs.remove(self.focused_pair);
96                            self.focused_pair -= 1;
97                        }
98
99                        // Advance past this step
100                        return Some(InputResult::AdvanceForm);
101                    } else {
102                        // Append a new KVP
103                        self.key_focused = true;
104                        self.focused_pair += 1;
105                        if self.focused_pair == self.pairs.len() {
106                            self.pairs
107                                .push((tty_text::Text::new(false), tty_text::Text::new(false)));
108                        }
109                    }
110                } else {
111                    if self.key_focused {
112                        // Switch to the value entry
113                        self.key_focused = false;
114                    } else {
115                        // Append a new KVP
116                        self.key_focused = true;
117                        self.focused_pair += 1;
118                        if self.focused_pair == self.pairs.len() {
119                            self.pairs
120                                .push((tty_text::Text::new(false), tty_text::Text::new(false)));
121                        }
122                    }
123                }
124            }
125            KeyCode::Esc | KeyCode::BackTab => {
126                if !self.key_focused {
127                    self.key_focused = true;
128                } else {
129                    if self.focused_pair > 0 {
130                        self.focused_pair -= 1;
131                        self.key_focused = false;
132                    } else {
133                        return Some(InputResult::RetreatForm);
134                    }
135                }
136            }
137            KeyCode::Char(ch) => text.handle_input(Key::Char(ch)),
138            KeyCode::Backspace => {
139                if text.value().is_empty() {
140                    if !self.key_focused {
141                        self.key_focused = true;
142                    } else {
143                        if self.focused_pair > 0 {
144                            self.pairs.remove(self.focused_pair);
145                            self.focused_pair -= 1;
146                            self.key_focused = false;
147                        } else {
148                            return Some(InputResult::RetreatForm);
149                        }
150                    }
151                } else {
152                    text.handle_input(Key::Backspace);
153                }
154            }
155            KeyCode::Left => text.handle_input(Key::Left),
156            KeyCode::Right => text.handle_input(Key::Right),
157            _ => {}
158        };
159
160        None
161    }
162
163    fn help(&self) -> Segment {
164        Text::new_styled(self.prompt.to_string(), help_style()).as_segment()
165    }
166
167    fn drawer(&self) -> Option<DrawerContents> {
168        None
169    }
170
171    fn result(&self, _dependency_state: &DependencyState) -> String {
172        let mut result = String::new();
173
174        for (_, (key, value)) in self.pairs.iter().enumerate() {
175            result.push_str(&key.value());
176
177            if !value.value().is_empty() {
178                result.push_str(&format!(": {}", value.value()));
179            }
180
181            result.push('\n');
182        }
183
184        result
185    }
186
187    fn add_to(self, form: &mut Form) {
188        form.add_step(Box::new(self));
189    }
190}