1use crossterm::event::{KeyCode, KeyEvent};
4
5use super::types::{
6 ArrayEditSession, ArrayState, StepKindRef, WizardEvent, WizardState, WizardStep,
7 WizardStepKind,
8};
9use super::helpers::{next_char_boundary, prev_char_boundary, step_kind_at};
10
11impl WizardState {
12 pub fn new(steps: &'static [WizardStep], input_count: usize) -> Self {
16 Self {
17 steps, input_count, current: 0,
18 values: Vec::new(), buffer: String::new(), cursor: 0,
19 array_states: std::collections::HashMap::new(), button_selected: 0,
20 }
21 }
22
23 pub fn completed(
25 steps: &'static [WizardStep],
26 input_count: usize,
27 values: Vec<String>,
28 start_current: usize,
29 ) -> Self {
30 Self {
31 steps, input_count, current: start_current,
32 values, buffer: String::new(), cursor: 0,
33 array_states: std::collections::HashMap::new(), button_selected: 0,
34 }
35 }
36
37 pub fn preload_array(&mut self, step_idx: usize, items: Vec<Vec<String>>) {
39 let arr = self.array_states.entry(step_idx).or_insert_with(ArrayState::new);
40 arr.items = items;
41 arr.expanded = true;
42 }
43
44 pub fn is_complete(&self) -> bool { self.current >= self.input_count }
46
47 pub fn set_current(&mut self, leaf_idx: usize) { self.current = leaf_idx; }
49
50 pub fn array_state(&self, leaf_idx: usize) -> Option<&ArrayState> {
52 self.array_states.get(&leaf_idx)
53 }
54
55 pub fn array_state_mut(&mut self, leaf_idx: usize) -> Option<&mut ArrayState> {
57 self.array_states.get_mut(&leaf_idx)
58 }
59
60 pub fn array_selected_item(&self, leaf_idx: usize) -> Option<usize> {
63 self.array_states.get(&leaf_idx).and_then(|a| {
64 if a.expanded && !a.items.is_empty() { Some(a.selected.min(a.items.len().saturating_sub(1))) }
65 else { None }
66 })
67 }
68
69 pub fn is_array_editing(&self) -> bool {
71 matches!(self.current_kind(), Some(StepKindRef::Array(_)))
72 && self.array_states.get(&self.current)
73 .and_then(|a| a.editing.as_ref()).is_some()
74 }
75
76 pub(super) fn current_kind(&self) -> Option<StepKindRef> {
79 step_kind_at(self.steps, self.current, &mut 0)
80 }
81
82 fn reset_array_on_arrival(&mut self) {
84 if matches!(self.current_kind(), Some(StepKindRef::Array(_))) {
85 if let Some(arr) = self.array_states.get_mut(&self.current) {
86 arr.expanded = false;
87 arr.selected = 0;
88 arr.header_sel = 0;
89 arr.item_btn_sel = 0;
90 }
91 }
92 }
93
94 fn commit_value(&mut self, value: String) {
96 if self.current < self.values.len() {
97 self.values[self.current] = value;
98 } else {
99 self.values.push(value);
100 }
101 }
102
103 fn load_buffer_for(&mut self, leaf_idx: usize) {
105 self.buffer = self.values.get(leaf_idx).cloned().unwrap_or_default();
106 self.cursor = self.buffer.len();
107 }
108
109 pub fn handle_key(&mut self, key: KeyEvent) -> WizardEvent {
113 if self.is_complete() { return WizardEvent::None; }
114 let kind = match self.current_kind() {
115 Some(k) => k,
116 None => return WizardEvent::None,
117 };
118 match kind {
119 StepKindRef::Array(sub_steps) => {
120 let array_idx = self.current;
121 let is_editing = self.array_states.get(&array_idx)
122 .and_then(|a| a.editing.as_ref()).is_some();
123 if is_editing {
124 self.handle_array_edit_key(key, array_idx, sub_steps)
125 } else {
126 self.handle_array_browse_key(key, array_idx, sub_steps)
127 }
128 }
129 StepKindRef::Leaf | StepKindRef::Optional => self.handle_leaf_key(key),
130 StepKindRef::Select(_) => match key.code {
131 KeyCode::Tab | KeyCode::Enter => {
132 let index = self.current;
133 self.commit_value(String::new());
134 self.current += 1;
135 if self.is_complete() { WizardEvent::Done(self.values.clone()) }
136 else { WizardEvent::StepCompleted { index, value: String::new() } }
137 }
138 KeyCode::Esc => WizardEvent::Cancelled,
139 _ => WizardEvent::None,
140 },
141 StepKindRef::Buttons(labels) => self.handle_buttons_key(key, labels),
142 }
143 }
144
145 fn handle_leaf_key(&mut self, key: KeyEvent) -> WizardEvent {
148 match key.code {
149 KeyCode::Char(c) => {
150 self.buffer.insert(self.cursor, c);
151 self.cursor += c.len_utf8();
152 WizardEvent::None
153 }
154 KeyCode::Backspace => {
155 if self.cursor > 0 {
156 let start = prev_char_boundary(&self.buffer, self.cursor);
157 let len = self.cursor - start;
158 self.buffer.drain(start..self.cursor);
159 self.cursor -= len;
160 }
161 WizardEvent::None
162 }
163 KeyCode::Delete => {
164 if self.cursor < self.buffer.len() { self.buffer.remove(self.cursor); }
165 WizardEvent::None
166 }
167 KeyCode::Left => { if self.cursor > 0 { self.cursor = prev_char_boundary(&self.buffer, self.cursor); } WizardEvent::None }
168 KeyCode::Right => { if self.cursor < self.buffer.len() { self.cursor = next_char_boundary(&self.buffer, self.cursor); } WizardEvent::None }
169 KeyCode::Home => { self.cursor = 0; WizardEvent::None }
170 KeyCode::End => { self.cursor = self.buffer.len(); WizardEvent::None }
171 KeyCode::Enter | KeyCode::Tab => {
172 let value = std::mem::take(&mut self.buffer);
173 self.cursor = 0;
174 let index = self.current;
175 self.commit_value(value.clone());
176 self.current += 1;
177 if self.is_complete() {
178 WizardEvent::Done(self.values.clone())
179 } else {
180 self.load_buffer_for(self.current);
181 self.reset_array_on_arrival();
182 WizardEvent::StepCompleted { index, value }
183 }
184 }
185 KeyCode::BackTab => {
186 if self.current > 0 {
187 self.current -= 1;
188 } else {
189 self.current = self.input_count.saturating_sub(1);
190 }
191 self.load_buffer_for(self.current);
192 self.reset_array_on_arrival();
193 WizardEvent::None
194 }
195 KeyCode::Esc => WizardEvent::Cancelled,
196 _ => WizardEvent::None,
197 }
198 }
199
200 fn handle_buttons_key(&mut self, key: KeyEvent, labels: &'static [&'static str]) -> WizardEvent {
203 match key.code {
204 KeyCode::Left | KeyCode::Char('h') => {
205 if self.button_selected > 0 { self.button_selected -= 1; }
206 WizardEvent::None
207 }
208 KeyCode::BackTab => {
209 if self.button_selected > 0 {
210 self.button_selected -= 1;
211 } else {
212 self.button_selected = 0;
213 self.current = self.current.saturating_sub(1);
214 self.load_buffer_for(self.current);
215 self.reset_array_on_arrival();
216 }
217 WizardEvent::None
218 }
219 KeyCode::Right | KeyCode::Char('l') | KeyCode::Tab => {
220 if self.button_selected + 1 < labels.len() {
221 self.button_selected += 1;
222 } else {
223 self.button_selected = 0;
225 self.current = 0;
226 self.load_buffer_for(0);
227 self.reset_array_on_arrival();
228 }
229 WizardEvent::None
230 }
231 KeyCode::Enter => {
232 let selected = self.button_selected;
233 self.button_selected = 0;
234 if selected == 0 {
235 self.commit_value(labels[0].to_string());
236 self.current += 1;
237 WizardEvent::Done(self.values.clone())
238 } else {
239 WizardEvent::Cancelled
240 }
241 }
242 KeyCode::Esc => {
243 self.button_selected = 0;
244 WizardEvent::Cancelled
245 }
246 _ => WizardEvent::None,
247 }
248 }
249
250 fn handle_array_browse_key(
253 &mut self, key: KeyEvent,
254 array_idx: usize,
255 sub_steps: &'static [WizardStep],
256 ) -> WizardEvent {
257 let expanded = self.array_states.get(&array_idx).map(|a| a.expanded).unwrap_or(false);
258 let items_len = self.array_states.get(&array_idx).map(|a| a.items.len()).unwrap_or(0);
259
260 if !expanded {
261 let header_sel = self.array_states.get(&array_idx).map(|a| a.header_sel).unwrap_or(0);
263 return match key.code {
264 KeyCode::Left => {
265 if let Some(arr) = self.array_states.get_mut(&array_idx) {
266 if arr.header_sel > 0 { arr.header_sel -= 1; }
267 }
268 WizardEvent::None
269 }
270 KeyCode::Right => {
271 if let Some(arr) = self.array_states.get_mut(&array_idx) {
272 if arr.header_sel < 1 { arr.header_sel += 1; }
273 }
274 WizardEvent::None
275 }
276 KeyCode::Enter => {
277 let arr = self.array_states.entry(array_idx).or_insert_with(ArrayState::new);
278 arr.expanded = true;
279 arr.header_sel = 0;
280 if header_sel == 0 {
281 let _ = arr; self.array_start_add(array_idx, sub_steps)
284 } else {
285 WizardEvent::None
287 }
288 }
289 KeyCode::Tab => self.array_advance_wizard(array_idx),
290 KeyCode::BackTab => {
291 self.array_retreat_wizard();
292 WizardEvent::None
293 }
294 KeyCode::Esc => WizardEvent::Cancelled,
295 _ => WizardEvent::None,
296 };
297 }
298
299 let selected = self.array_states.get(&array_idx).map(|a| a.selected).unwrap_or(0);
301 let item_btn_sel = self.array_states.get(&array_idx).map(|a| a.item_btn_sel).unwrap_or(0);
302
303 match key.code {
304 KeyCode::Tab => {
305 if items_len > 0 {
306 if let Some(arr) = self.array_states.get_mut(&array_idx) {
307 arr.selected = (arr.selected + 1) % items_len;
308 arr.item_btn_sel = 0;
309 }
310 }
311 WizardEvent::None
312 }
313 KeyCode::BackTab | KeyCode::Esc => {
314 if let Some(arr) = self.array_states.get_mut(&array_idx) {
315 arr.expanded = false;
316 arr.header_sel = 0;
317 arr.item_btn_sel = 0;
318 }
319 WizardEvent::None
320 }
321 KeyCode::Left => {
323 if let Some(arr) = self.array_states.get_mut(&array_idx) {
324 if arr.item_btn_sel > 0 { arr.item_btn_sel -= 1; }
325 }
326 WizardEvent::None
327 }
328 KeyCode::Right => {
329 if let Some(arr) = self.array_states.get_mut(&array_idx) {
330 if arr.item_btn_sel < 1 { arr.item_btn_sel += 1; }
331 }
332 WizardEvent::None
333 }
334 KeyCode::Enter => {
335 if items_len > 0 {
336 if item_btn_sel == 1 {
337 let item_idx = selected.min(items_len - 1);
339 if let Some(arr) = self.array_states.get_mut(&array_idx) {
340 arr.items.remove(item_idx);
341 if arr.items.is_empty() {
342 arr.selected = 0;
343 } else {
344 arr.selected = arr.selected.min(arr.items.len() - 1);
345 }
346 arr.item_btn_sel = 0;
347 }
348 WizardEvent::ArrayItemDeleted { array_step_idx: array_idx, item_idx }
349 } else {
350 let item_idx = selected.min(items_len - 1);
352 self.array_start_edit(array_idx, sub_steps, item_idx)
353 }
354 } else {
355 WizardEvent::None
356 }
357 }
358 KeyCode::Up | KeyCode::Down => WizardEvent::None,
360 _ => WizardEvent::None,
361 }
362 }
363
364 fn handle_array_edit_key(
367 &mut self, key: KeyEvent,
368 array_idx: usize,
369 sub_steps: &'static [WizardStep],
370 ) -> WizardEvent {
371 let (sub_step, is_new, item_idx) = {
372 let s = self.array_states[&array_idx].editing.as_ref().unwrap();
373 (s.sub_step, s.is_new, s.item_idx)
374 };
375 let is_last_sub = sub_step + 1 >= sub_steps.len();
376
377 let sub_kind = match sub_steps.get(sub_step) {
378 None => return WizardEvent::None,
379 Some(s) => match &s.kind {
380 WizardStepKind::Select(opts) => StepKindRef::Select(opts),
381 WizardStepKind::Optional => StepKindRef::Optional,
382 _ => StepKindRef::Leaf,
383 },
384 };
385
386 match sub_kind {
387 StepKindRef::Leaf | StepKindRef::Optional => {
388 self.handle_array_edit_leaf(key, array_idx, sub_steps, sub_step, item_idx, is_new, is_last_sub)
389 }
390 StepKindRef::Select(opts) => {
391 self.handle_array_edit_select(key, array_idx, sub_steps, sub_step, item_idx, is_new, is_last_sub, opts)
392 }
393 _ => WizardEvent::None,
394 }
395 }
396
397 fn handle_array_edit_leaf(
398 &mut self, key: KeyEvent,
399 array_idx: usize,
400 sub_steps: &'static [WizardStep],
401 sub_step: usize, item_idx: usize, is_new: bool, is_last_sub: bool,
402 ) -> WizardEvent {
403 match key.code {
404 KeyCode::Char(c) => {
405 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
406 s.buffer.insert(s.buf_cursor, c);
407 s.buf_cursor += c.len_utf8();
408 WizardEvent::None
409 }
410 KeyCode::Backspace => {
411 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
412 if s.buf_cursor > 0 {
413 let start = prev_char_boundary(&s.buffer, s.buf_cursor);
414 let len = s.buf_cursor - start;
415 s.buffer.drain(start..s.buf_cursor);
416 s.buf_cursor -= len;
417 }
418 WizardEvent::None
419 }
420 KeyCode::Delete => {
421 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
422 if s.buf_cursor < s.buffer.len() { s.buffer.remove(s.buf_cursor); }
423 WizardEvent::None
424 }
425 KeyCode::Left => {
426 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
427 if s.buf_cursor > 0 { s.buf_cursor = prev_char_boundary(&s.buffer, s.buf_cursor); }
428 WizardEvent::None
429 }
430 KeyCode::Right => {
431 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
432 if s.buf_cursor < s.buffer.len() { s.buf_cursor = next_char_boundary(&s.buffer, s.buf_cursor); }
433 WizardEvent::None
434 }
435 KeyCode::Home => {
436 self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap().buf_cursor = 0;
437 WizardEvent::None
438 }
439 KeyCode::End => {
440 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
441 s.buf_cursor = s.buffer.len();
442 WizardEvent::None
443 }
444 KeyCode::Tab | KeyCode::Enter => {
445 let buf_val = self.array_states[&array_idx].editing.as_ref().unwrap().buffer.clone();
446 self.array_states.get_mut(&array_idx).unwrap().items[item_idx][sub_step] = buf_val;
447 if is_last_sub { self.complete_array_item(array_idx) }
448 else { self.advance_array_sub_step(array_idx, sub_steps, sub_step, item_idx) }
449 }
450 KeyCode::Esc => self.cancel_array_edit(array_idx, is_new, item_idx),
451 _ => WizardEvent::None,
452 }
453 }
454
455 fn handle_array_edit_select(
456 &mut self, key: KeyEvent,
457 array_idx: usize,
458 sub_steps: &'static [WizardStep],
459 sub_step: usize, item_idx: usize, is_new: bool, is_last_sub: bool,
460 opts: &'static [&'static str],
461 ) -> WizardEvent {
462 match key.code {
463 KeyCode::Left | KeyCode::Char('h') | KeyCode::BackTab => {
464 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
465 s.select_idx = if s.select_idx > 0 { s.select_idx - 1 } else { opts.len() - 1 };
466 let si = s.select_idx;
467 self.array_states.get_mut(&array_idx).unwrap().items[item_idx][sub_step] = opts[si].to_string();
468 WizardEvent::None
469 }
470 KeyCode::Right | KeyCode::Char('l') => {
471 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
472 s.select_idx = (s.select_idx + 1) % opts.len();
473 let si = s.select_idx;
474 self.array_states.get_mut(&array_idx).unwrap().items[item_idx][sub_step] = opts[si].to_string();
475 WizardEvent::None
476 }
477 KeyCode::Tab | KeyCode::Enter => {
478 let si = self.array_states[&array_idx].editing.as_ref().unwrap().select_idx;
479 self.array_states.get_mut(&array_idx).unwrap().items[item_idx][sub_step] = opts[si].to_string();
480 if is_last_sub { self.complete_array_item(array_idx) }
481 else { self.advance_array_sub_step(array_idx, sub_steps, sub_step, item_idx) }
482 }
483 KeyCode::Esc => self.cancel_array_edit(array_idx, is_new, item_idx),
484 _ => WizardEvent::None,
485 }
486 }
487
488 fn make_initial_item_vals(&self, sub_steps: &'static [WizardStep]) -> Vec<String> {
491 sub_steps.iter().map(|s| match &s.kind {
492 WizardStepKind::Select(opts) => opts.first().copied().unwrap_or("").to_string(),
493 _ => String::new(),
494 }).collect()
495 }
496
497 fn array_start_add(&mut self, array_idx: usize, sub_steps: &'static [WizardStep]) -> WizardEvent {
498 let initial_vals = self.make_initial_item_vals(sub_steps);
499 let first_select_idx = match sub_steps.first().map(|s| &s.kind) {
500 Some(WizardStepKind::Select(opts)) =>
501 opts.iter().position(|&o| o == initial_vals[0].as_str()).unwrap_or(0),
502 _ => 0,
503 };
504 let sub_count = sub_steps.len();
505 let arr = self.array_states.entry(array_idx).or_insert_with(ArrayState::new);
506 arr.expanded = true;
507 let item_idx = arr.items.len();
508 arr.items.push(initial_vals);
509 arr.selected = item_idx; arr.editing = Some(ArrayEditSession {
511 item_idx,
512 sub_step: 0,
513 is_new: true,
514 original_values: vec![String::new(); sub_count],
515 buffer: String::new(),
516 buf_cursor: 0,
517 select_idx: first_select_idx,
518 });
519 WizardEvent::ArrayItemAdded { array_step_idx: array_idx, item_idx }
520 }
521
522 fn array_start_edit(
523 &mut self, array_idx: usize,
524 sub_steps: &'static [WizardStep],
525 item_idx: usize,
526 ) -> WizardEvent {
527 let arr = self.array_states.entry(array_idx).or_insert_with(ArrayState::new);
528 let orig = arr.items[item_idx].clone();
529 let first_val = orig.first().cloned().unwrap_or_default();
530 let first_select_idx = match sub_steps.first().map(|s| &s.kind) {
531 Some(WizardStepKind::Select(opts)) =>
532 opts.iter().position(|&o| o == first_val.as_str()).unwrap_or(0),
533 _ => 0,
534 };
535 arr.editing = Some(ArrayEditSession {
536 item_idx,
537 sub_step: 0,
538 is_new: false,
539 original_values: orig,
540 buffer: first_val.clone(),
541 buf_cursor: first_val.len(),
542 select_idx: first_select_idx,
543 });
544 WizardEvent::None
545 }
546
547 fn advance_array_sub_step(
548 &mut self, array_idx: usize,
549 sub_steps: &'static [WizardStep],
550 current_sub: usize, item_idx: usize,
551 ) -> WizardEvent {
552 let next_sub = current_sub + 1;
553 let next_val = self.array_states[&array_idx].items[item_idx][next_sub].clone();
554 match sub_steps[next_sub].kind {
555 WizardStepKind::Select(opts) => {
556 let si = opts.iter().position(|&o| o == next_val.as_str()).unwrap_or(0);
557 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
558 s.sub_step = next_sub;
559 s.select_idx = si;
560 if next_val.is_empty() {
561 self.array_states.get_mut(&array_idx).unwrap().items[item_idx][next_sub] = opts[si].to_string();
562 }
563 }
564 _ => {
565 let s = self.array_states.get_mut(&array_idx).unwrap().editing.as_mut().unwrap();
566 s.sub_step = next_sub;
567 s.buffer = next_val;
568 s.buf_cursor = s.buffer.len();
569 }
570 }
571 WizardEvent::None
572 }
573
574 fn complete_array_item(&mut self, array_idx: usize) -> WizardEvent {
575 let arr = self.array_states.get_mut(&array_idx).unwrap();
576 let session = arr.editing.take().unwrap();
577 let item_idx = session.item_idx;
578 let values = arr.items[item_idx].clone();
579 arr.selected = item_idx; WizardEvent::ArrayItemCompleted { array_step_idx: array_idx, item_idx, values }
581 }
582
583 fn cancel_array_edit(&mut self, array_idx: usize, is_new: bool, item_idx: usize) -> WizardEvent {
584 let arr = self.array_states.get_mut(&array_idx).unwrap();
585 let session = arr.editing.take().unwrap();
586 if is_new {
587 arr.items.remove(item_idx);
588 arr.selected = if arr.items.is_empty() { 0 } else { arr.selected.min(arr.items.len() - 1) };
589 WizardEvent::ArrayItemDeleted { array_step_idx: array_idx, item_idx }
590 } else {
591 arr.items[item_idx] = session.original_values;
592 arr.selected = item_idx;
593 WizardEvent::None
594 }
595 }
596
597 fn array_advance_wizard(&mut self, _array_idx: usize) -> WizardEvent {
598 let index = self.current;
599 self.commit_value(String::new());
600 self.current += 1;
601 self.reset_array_on_arrival();
602 if self.is_complete() { WizardEvent::Done(self.values.clone()) }
603 else { WizardEvent::StepCompleted { index, value: String::new() } }
604 }
605
606 fn array_retreat_wizard(&mut self) {
607 if self.current > 0 {
608 self.current -= 1;
609 } else {
610 self.current = self.input_count.saturating_sub(1);
611 }
612 self.load_buffer_for(self.current);
613 self.reset_array_on_arrival();
614 }
615}