1use crossterm::event::KeyEvent;
10use ratatui::layout::Rect;
11
12use crate::cache::CacheConfig;
13use crate::change::Change;
14use crate::cli::Command;
15use crate::lock::NestedInput;
16
17use super::completions::uri_completion_items;
18use super::components::confirm::ConfirmAction;
19use super::components::input::{Input, InputAction, InputResult, InputState};
20use super::components::list::{ListAction, ListResult, ListState};
21use super::workflow::{AddStep, ConfirmResultAction, FollowStep, WorkflowData};
22
23pub use super::workflow::{AppResult, MultiSelectResultData, SingleSelectResult, UpdateResult};
25
26const MAX_LIST_HEIGHT: u16 = 12;
27
28#[derive(Debug, Clone)]
29pub struct App {
30 context: String,
31 flake_text: String,
32 show_diff: bool,
33 cache_config: CacheConfig,
34 screen: Screen,
35 data: WorkflowData,
36}
37
38#[derive(Debug, Clone)]
40pub enum Screen {
41 Input(InputScreen),
42 List(ListScreen),
43 Confirm(ConfirmScreen),
44}
45
46#[derive(Debug, Clone)]
48pub struct InputScreen {
49 pub state: InputState,
50 pub prompt: String,
51 pub label: Option<String>,
52}
53
54#[derive(Debug, Clone)]
56pub struct ListScreen {
57 pub state: ListState,
58 pub items: Vec<String>,
59 pub prompt: String,
60}
61
62impl ListScreen {
63 pub fn single(items: Vec<String>, prompt: impl Into<String>, show_diff: bool) -> Self {
64 let len = items.len();
65 Self {
66 state: ListState::new(len, false, show_diff),
67 items,
68 prompt: prompt.into(),
69 }
70 }
71
72 pub fn multi(items: Vec<String>, prompt: impl Into<String>, show_diff: bool) -> Self {
73 let len = items.len();
74 Self {
75 state: ListState::new(len, true, show_diff),
76 items,
77 prompt: prompt.into(),
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct ConfirmScreen {
85 pub diff: String,
86}
87
88impl App {
89 pub fn add(
94 context: impl Into<String>,
95 flake_text: impl Into<String>,
96 prefill_uri: Option<&str>,
97 cache_config: CacheConfig,
98 ) -> Self {
99 let completions = uri_completion_items(None, &cache_config);
100 Self {
101 context: context.into(),
102 flake_text: flake_text.into(),
103 show_diff: false,
104 cache_config,
105 screen: Screen::Input(InputScreen {
106 state: InputState::with_completions(prefill_uri, completions),
107 prompt: "Enter flake URI".into(),
108 label: None,
109 }),
110 data: WorkflowData::Add {
111 step: AddStep::Uri,
112 uri: None,
113 id: None,
114 },
115 }
116 }
117
118 pub fn change(
123 context: impl Into<String>,
124 flake_text: impl Into<String>,
125 inputs: Vec<(String, String)>,
126 cache_config: CacheConfig,
127 ) -> Self {
128 let input_ids: Vec<String> = inputs.iter().map(|(id, _)| id.clone()).collect();
129 let input_uris: std::collections::HashMap<String, String> = inputs.into_iter().collect();
130 Self {
131 context: context.into(),
132 flake_text: flake_text.into(),
133 show_diff: false,
134 cache_config,
135 screen: Screen::List(ListScreen::single(
136 input_ids.clone(),
137 "Select input to change",
138 false,
139 )),
140 data: WorkflowData::Change {
141 selected_input: None,
142 uri: None,
143 input_uris,
144 all_inputs: input_ids,
145 },
146 }
147 }
148
149 pub fn remove(
151 context: impl Into<String>,
152 flake_text: impl Into<String>,
153 inputs: Vec<String>,
154 ) -> Self {
155 Self {
156 context: context.into(),
157 flake_text: flake_text.into(),
158 show_diff: false,
159 cache_config: CacheConfig::default(),
160 screen: Screen::List(ListScreen::multi(
161 inputs.clone(),
162 "Select inputs to remove",
163 false,
164 )),
165 data: WorkflowData::Remove {
166 selected_inputs: Vec::new(),
167 all_inputs: inputs,
168 },
169 }
170 }
171
172 pub fn change_uri(
176 context: impl Into<String>,
177 flake_text: impl Into<String>,
178 id: impl Into<String>,
179 current_uri: Option<&str>,
180 show_diff: bool,
181 cache_config: CacheConfig,
182 ) -> Self {
183 let id_string = id.into();
184 let completions = uri_completion_items(Some(&id_string), &cache_config);
185 Self {
186 context: context.into(),
187 flake_text: flake_text.into(),
188 show_diff,
189 cache_config,
190 screen: Screen::Input(InputScreen {
191 state: InputState::with_completions(current_uri, completions),
192 prompt: format!("for {}", id_string),
193 label: Some("URI".into()),
194 }),
195 data: WorkflowData::Change {
196 selected_input: Some(id_string),
197 uri: None,
198 input_uris: std::collections::HashMap::new(),
199 all_inputs: Vec::new(),
200 },
201 }
202 }
203
204 pub fn select_one(
206 context: impl Into<String>,
207 prompt: impl Into<String>,
208 items: Vec<String>,
209 initial_diff: bool,
210 ) -> Self {
211 Self {
212 context: context.into(),
213 flake_text: String::new(),
214 show_diff: initial_diff,
215 cache_config: CacheConfig::default(),
216 screen: Screen::List(ListScreen::single(items, prompt, initial_diff)),
217 data: WorkflowData::SelectOne {
218 selected_input: None,
219 },
220 }
221 }
222
223 pub fn select_many(
225 context: impl Into<String>,
226 prompt: impl Into<String>,
227 items: Vec<String>,
228 initial_diff: bool,
229 ) -> Self {
230 Self {
231 context: context.into(),
232 flake_text: String::new(),
233 show_diff: initial_diff,
234 cache_config: CacheConfig::default(),
235 screen: Screen::List(ListScreen::multi(items, prompt, initial_diff)),
236 data: WorkflowData::SelectMany {
237 selected_inputs: Vec::new(),
238 },
239 }
240 }
241
242 pub fn confirm(context: impl Into<String>, diff: impl Into<String>) -> Self {
244 Self {
245 context: context.into(),
246 flake_text: String::new(),
247 show_diff: true,
248 cache_config: CacheConfig::default(),
249 screen: Screen::Confirm(ConfirmScreen { diff: diff.into() }),
250 data: WorkflowData::ConfirmOnly { action: None },
251 }
252 }
253
254 pub fn follow(
260 context: impl Into<String>,
261 flake_text: impl Into<String>,
262 nested_inputs: Vec<NestedInput>,
263 top_level_inputs: Vec<String>,
264 ) -> Self {
265 let display_items: Vec<String> = nested_inputs
267 .iter()
268 .map(|i| i.to_display_string())
269 .collect();
270 Self {
271 context: context.into(),
272 flake_text: flake_text.into(),
273 show_diff: false,
274 cache_config: CacheConfig::default(),
275 screen: Screen::List(ListScreen::single(
276 display_items,
277 "Select input to add follows",
278 false,
279 )),
280 data: WorkflowData::Follow {
281 step: FollowStep::SelectInput,
282 selected_input: None,
283 selected_target: None,
284 nested_inputs,
285 top_level_inputs,
286 },
287 }
288 }
289
290 pub fn follow_target(
294 context: impl Into<String>,
295 flake_text: impl Into<String>,
296 input: impl Into<String>,
297 top_level_inputs: Vec<String>,
298 ) -> Self {
299 let input = input.into();
300 Self {
301 context: context.into(),
302 flake_text: flake_text.into(),
303 show_diff: false,
304 cache_config: CacheConfig::default(),
305 screen: Screen::List(ListScreen::single(
306 top_level_inputs.clone(),
307 format!("Select target for {input}"),
308 false,
309 )),
310 data: WorkflowData::Follow {
311 step: FollowStep::SelectTarget,
312 selected_input: Some(input),
313 selected_target: None,
314 nested_inputs: Vec::<NestedInput>::new(),
315 top_level_inputs,
316 },
317 }
318 }
319
320 pub fn from_command(
333 command: &Command,
334 flake_text: impl Into<String>,
335 inputs: Vec<(String, String)>,
336 diff: bool,
337 cache_config: CacheConfig,
338 ) -> Option<Self> {
339 let flake_text = flake_text.into();
340 let input_ids: Vec<String> = inputs.iter().map(|(id, _)| id.clone()).collect();
341
342 match command {
343 Command::Add { id, uri, .. } => {
345 if id.is_some() && uri.is_some() {
346 None } else {
348 let prefill = id.as_deref();
350 Some(Self::add("Add", flake_text, prefill, cache_config).with_diff(diff))
351 }
352 }
353
354 Command::Remove { id } => {
356 if id.is_some() {
357 None
358 } else {
359 Some(Self::remove("Remove", flake_text, input_ids).with_diff(diff))
360 }
361 }
362
363 Command::Change { id, uri, .. } => {
365 if id.is_some() && uri.is_some() {
366 None } else if let Some(id) = id {
368 let current_uri = inputs
370 .iter()
371 .find(|(i, _)| i == id)
372 .map(|(_, u)| u.as_str());
373 Some(Self::change_uri(
374 "Change",
375 flake_text,
376 id,
377 current_uri,
378 diff,
379 cache_config,
380 ))
381 } else {
382 Some(Self::change("Change", flake_text, inputs, cache_config).with_diff(diff))
384 }
385 }
386
387 Command::Pin { id, .. } => {
389 if id.is_some() {
390 None
391 } else {
392 Some(Self::select_one(
393 "Pin",
394 "Select input to pin",
395 input_ids,
396 diff,
397 ))
398 }
399 }
400
401 Command::Unpin { id } => {
403 if id.is_some() {
404 None
405 } else {
406 Some(Self::select_one(
407 "Unpin",
408 "Select input to unpin",
409 input_ids,
410 diff,
411 ))
412 }
413 }
414
415 Command::Update { id, .. } => {
417 if id.is_some() {
418 None
419 } else {
420 Some(Self::select_many(
421 "Update",
422 "Space select, U all, ^D diff",
423 input_ids,
424 diff,
425 ))
426 }
427 }
428
429 Command::List { .. }
431 | Command::Completion { .. }
432 | Command::Follow { .. }
433 | Command::AddFollow { .. }
434 | Command::Config { .. } => None,
435 }
436 }
437
438 pub fn show_diff(&self) -> bool {
439 self.show_diff
440 }
441
442 pub fn screen(&self) -> &Screen {
443 &self.screen
444 }
445
446 pub fn context(&self) -> &str {
447 &self.context
448 }
449
450 pub fn pending_diff(&self) -> String {
456 let change = self.build_preview_change();
457 self.compute_diff(&change)
458 }
459
460 fn build_preview_change(&self) -> Change {
463 match &self.screen {
464 Screen::Input(screen) => {
466 let current_text = screen.state.text();
467 if current_text.is_empty() {
468 return Change::None;
469 }
470 match &self.data {
471 WorkflowData::Add { step, uri, .. } => match step {
472 AddStep::Uri => Change::Add {
473 id: None,
474 uri: Some(current_text.to_string()),
475 flake: true,
476 },
477 AddStep::Id => Change::Add {
478 id: crate::change::ChangeId::parse(current_text).ok(),
479 uri: uri.clone(),
480 flake: true,
481 },
482 },
483 WorkflowData::Change { selected_input, .. } => Change::Change {
484 id: selected_input
485 .as_deref()
486 .and_then(|s| crate::change::ChangeId::parse(s).ok()),
487 uri: Some(current_text.to_string()),
488 },
489 _ => self.build_change(),
490 }
491 }
492 Screen::List(screen) => {
494 let selected_items: Vec<String> = screen
495 .state
496 .selected_indices()
497 .iter()
498 .filter_map(|&i| screen.items.get(i).cloned())
499 .collect();
500
501 if !selected_items.is_empty() {
502 return match &self.data {
503 WorkflowData::Remove { .. } => Change::Remove {
504 ids: selected_items
505 .into_iter()
506 .filter_map(|s| crate::change::ChangeId::parse(&s).ok())
507 .collect(),
508 },
509 WorkflowData::Follow {
510 step,
511 selected_input,
512 ..
513 } => {
514 if *step == FollowStep::SelectTarget {
517 if let Some(input) = selected_input {
518 let target_str: String =
519 selected_items.into_iter().next().unwrap_or_default();
520 let parsed = crate::change::ChangeId::parse(input)
521 .ok()
522 .zip(crate::follows::AttrPath::parse(&target_str).ok());
523 if let Some((change_id, target_path)) = parsed {
524 Change::Follows {
525 input: change_id,
526 target: target_path,
527 }
528 } else {
529 Change::None
530 }
531 } else {
532 Change::None
533 }
534 } else {
535 Change::None
537 }
538 }
539 _ => self.build_change(),
540 };
541 }
542 if matches!(
543 &self.data,
544 WorkflowData::Follow { .. } | WorkflowData::Change { .. }
545 ) {
546 return Change::None;
547 }
548 self.build_change()
549 }
550 Screen::Confirm(_) => self.build_change(),
551 }
552 }
553
554 pub fn with_diff(mut self, show_diff: bool) -> Self {
556 self.show_diff = show_diff;
557 if let Screen::List(ref mut screen) = self.screen {
559 screen.state =
560 ListState::new(screen.items.len(), screen.state.multi_select(), show_diff);
561 }
562 self
563 }
564
565 pub fn update(&mut self, key: KeyEvent) -> UpdateResult {
566 let screen = self.screen.clone();
567 match screen {
568 Screen::Input(s) => self.update_input(s, key),
569 Screen::List(s) => self.update_list(s, key),
570 Screen::Confirm(_) => self.update_confirm(key),
571 }
572 }
573
574 fn update_input(&mut self, mut screen: InputScreen, key: KeyEvent) -> UpdateResult {
575 let action = InputAction::from_key(key);
576 match action {
577 InputAction::ToggleDiff => {
578 self.show_diff = !self.show_diff;
579 UpdateResult::Continue
580 }
581 _ => {
582 if let Some(result) = screen.state.handle(action) {
583 match result {
584 InputResult::Submit(text) => self.handle_input_submit(text),
585 InputResult::Cancel => {
586 if let WorkflowData::Add { step, uri, .. } = &mut self.data
588 && *step == AddStep::Id
589 {
590 *step = AddStep::Uri;
591 self.screen = Screen::Input(InputScreen {
592 state: InputState::with_completions(
593 uri.as_deref(),
594 uri_completion_items(None, &self.cache_config),
595 ),
596 prompt: "Enter flake URI".into(),
597 label: None,
598 });
599 return UpdateResult::Continue;
600 }
601 if let WorkflowData::Change { all_inputs, .. } = &self.data
603 && !all_inputs.is_empty()
604 {
605 self.screen = Screen::List(ListScreen::single(
606 all_inputs.clone(),
607 "Select input to change",
608 self.show_diff,
609 ));
610 return UpdateResult::Continue;
611 }
612 UpdateResult::Cancelled
613 }
614 }
615 } else {
616 if let Screen::Input(s) = &mut self.screen {
617 s.state = screen.state;
618 }
619 UpdateResult::Continue
620 }
621 }
622 }
623 }
624
625 fn update_list(&mut self, mut screen: ListScreen, key: KeyEvent) -> UpdateResult {
626 let action = ListAction::from_key(key);
627 if let Some(result) = screen.state.handle(action) {
628 match result {
629 ListResult::Select(indices, show_diff) => {
630 self.show_diff = show_diff;
631 let items: Vec<String> =
632 indices.iter().map(|&i| screen.items[i].clone()).collect();
633 self.handle_list_submit(indices, items)
634 }
635 ListResult::Cancel => {
636 if let WorkflowData::Follow {
638 step,
639 nested_inputs,
640 ..
641 } = &mut self.data
642 && *step == FollowStep::SelectTarget
643 && !nested_inputs.is_empty()
644 {
645 *step = FollowStep::SelectInput;
646 let display_items: Vec<String> = nested_inputs
647 .iter()
648 .map(|i| i.to_display_string())
649 .collect();
650 self.screen = Screen::List(ListScreen::single(
651 display_items,
652 "Select input to add follows",
653 self.show_diff,
654 ));
655 return UpdateResult::Continue;
656 }
657 UpdateResult::Cancelled
658 }
659 }
660 } else {
661 if let Screen::List(s) = &mut self.screen {
662 s.state = screen.state;
663 }
664 UpdateResult::Continue
665 }
666 }
667
668 fn update_confirm(&mut self, key: KeyEvent) -> UpdateResult {
669 let action = ConfirmAction::from_key(key);
670 match action {
671 ConfirmAction::Apply => {
672 if let WorkflowData::ConfirmOnly { action, .. } = &mut self.data {
673 *action = Some(ConfirmResultAction::Apply);
674 }
675 UpdateResult::Done
676 }
677 ConfirmAction::Back => {
678 if let WorkflowData::ConfirmOnly { action, .. } = &mut self.data {
680 *action = Some(ConfirmResultAction::Back);
681 UpdateResult::Done
682 } else {
683 self.go_back();
684 UpdateResult::Continue
685 }
686 }
687 ConfirmAction::Exit => {
688 if let WorkflowData::ConfirmOnly { action, .. } = &mut self.data {
689 *action = Some(ConfirmResultAction::Exit);
690 }
691 UpdateResult::Cancelled
692 }
693 ConfirmAction::None => UpdateResult::Continue,
694 }
695 }
696
697 fn handle_input_submit(&mut self, text: String) -> UpdateResult {
698 match &mut self.data {
699 WorkflowData::Add { step, uri, id } => match step {
700 AddStep::Uri => {
701 let (inferred_id, normalized_uri) = Self::parse_uri_and_infer_id(&text);
702 *uri = Some(normalized_uri);
703 *step = AddStep::Id;
704 self.screen = Screen::Input(InputScreen {
705 state: InputState::new(inferred_id.as_deref()),
706 prompt: format!("for {}", text),
707 label: Some("ID".into()),
708 });
709 UpdateResult::Continue
710 }
711 AddStep::Id => {
712 *id = Some(text);
713 self.transition_to_confirm()
714 }
715 },
716 WorkflowData::Change { uri, .. } => {
717 *uri = Some(text);
718 self.transition_to_confirm()
719 }
720 WorkflowData::Remove { .. }
722 | WorkflowData::SelectOne { .. }
723 | WorkflowData::SelectMany { .. }
724 | WorkflowData::ConfirmOnly { .. }
725 | WorkflowData::Follow { .. } => UpdateResult::Continue,
726 }
727 }
728
729 fn handle_list_submit(&mut self, indices: Vec<usize>, items: Vec<String>) -> UpdateResult {
730 match &mut self.data {
731 WorkflowData::Change {
732 selected_input,
733 input_uris,
734 ..
735 } => {
736 let item = items.into_iter().next().unwrap_or_default();
738 let current_uri = input_uris.get(&item).map(|s| s.as_str());
739 *selected_input = Some(item.clone());
740 self.screen = Screen::Input(InputScreen {
741 state: InputState::with_completions(
742 current_uri,
743 uri_completion_items(Some(&item), &self.cache_config),
744 ),
745 prompt: "Enter new URI".into(),
746 label: Some(item),
747 });
748 UpdateResult::Continue
749 }
750 WorkflowData::SelectOne { selected_input } => {
751 *selected_input = items.into_iter().next();
753 UpdateResult::Done
754 }
755 WorkflowData::Remove {
756 selected_inputs, ..
757 } => {
758 *selected_inputs = items;
759 self.transition_to_confirm()
760 }
761 WorkflowData::SelectMany { selected_inputs } => {
762 *selected_inputs = items;
763 UpdateResult::Done
764 }
765 WorkflowData::Follow {
766 step,
767 selected_input,
768 selected_target,
769 nested_inputs,
770 top_level_inputs,
771 } => {
772 match step {
773 FollowStep::SelectInput => {
774 let index = indices.first().copied().unwrap_or(0);
776 let path = nested_inputs
777 .get(index)
778 .map(|i| i.path.to_string())
779 .unwrap_or_default();
780 *selected_input = Some(path.clone());
781 *step = FollowStep::SelectTarget;
782 self.screen = Screen::List(ListScreen::single(
783 top_level_inputs.clone(),
784 format!("Select target for {path}"),
785 self.show_diff,
786 ));
787 UpdateResult::Continue
788 }
789 FollowStep::SelectTarget => {
790 let item = items.into_iter().next().unwrap_or_default();
791 *selected_target = Some(item);
792 self.transition_to_confirm()
793 }
794 }
795 }
796 _ => UpdateResult::Continue,
797 }
798 }
799
800 fn transition_to_confirm(&mut self) -> UpdateResult {
801 if !self.show_diff {
802 return UpdateResult::Done;
803 }
804
805 let change = self.build_change();
806 let diff_str = self.compute_diff(&change);
807 self.screen = Screen::Confirm(ConfirmScreen { diff: diff_str });
808 UpdateResult::Continue
809 }
810
811 fn go_back(&mut self) {
812 match &mut self.data {
813 WorkflowData::Add { step, id, uri } => {
814 *step = AddStep::Id;
815 self.screen = Screen::Input(InputScreen {
816 state: InputState::new(id.as_deref()),
817 prompt: format!("for {}", uri.as_deref().unwrap_or("")),
818 label: Some("ID".into()),
819 });
820 }
821 WorkflowData::Change {
822 selected_input,
823 uri,
824 ..
825 } => {
826 self.screen = Screen::Input(InputScreen {
827 state: InputState::with_completions(
828 uri.as_deref(),
829 uri_completion_items(selected_input.as_deref(), &self.cache_config),
830 ),
831 prompt: "Enter new URI".into(),
832 label: selected_input.clone(),
833 });
834 }
835 WorkflowData::Remove { all_inputs, .. } => {
836 self.screen = Screen::List(ListScreen::multi(
837 all_inputs.clone(),
838 "Select inputs to remove",
839 self.show_diff,
840 ));
841 }
842 WorkflowData::Follow {
843 step,
844 nested_inputs,
845 top_level_inputs,
846 ..
847 } => {
848 if *step == FollowStep::SelectTarget {
851 self.screen = Screen::List(ListScreen::single(
852 top_level_inputs.clone(),
853 "Select target to follow",
854 self.show_diff,
855 ));
856 } else if !nested_inputs.is_empty() {
857 let display_items: Vec<String> = nested_inputs
859 .iter()
860 .map(|i| i.to_display_string())
861 .collect();
862 self.screen = Screen::List(ListScreen::single(
863 display_items,
864 "Select input to add follows",
865 self.show_diff,
866 ));
867 }
868 }
869 WorkflowData::SelectOne { .. }
871 | WorkflowData::SelectMany { .. }
872 | WorkflowData::ConfirmOnly { .. } => {}
873 }
874 }
875
876 fn build_change(&self) -> Change {
877 self.data.build_change()
878 }
879
880 fn compute_diff(&self, change: &Change) -> String {
881 super::workflow::compute_diff(&self.flake_text, change)
882 }
883
884 fn parse_uri_and_infer_id(uri: &str) -> (Option<String>, String) {
885 super::workflow::parse_uri_and_infer_id(uri)
886 }
887
888 pub fn cursor_position(&self, area: Rect) -> Option<(u16, u16)> {
889 match &self.screen {
890 Screen::Input(screen) => {
891 let input = Input::new(
892 &screen.state,
893 &screen.prompt,
894 &self.context,
895 screen.label.as_deref(),
896 self.show_diff,
897 );
898 Some(input.cursor_position(area))
899 }
900 _ => None,
901 }
902 }
903
904 pub fn terminal_height(&self) -> u16 {
905 match &self.screen {
906 Screen::Input(screen) => {
907 let input = Input::new(
908 &screen.state,
909 &screen.prompt,
910 &self.context,
911 screen.label.as_deref(),
912 self.show_diff,
913 );
914 input.required_height()
915 }
916 Screen::List(s) => super::helpers::list_height(s.items.len(), MAX_LIST_HEIGHT),
917 Screen::Confirm(s) => super::helpers::diff_height(s.diff.lines().count()),
918 }
919 }
920
921 pub fn extract_result(self) -> Option<AppResult> {
922 match self.data {
923 WorkflowData::Add { .. }
924 | WorkflowData::Change { .. }
925 | WorkflowData::Remove { .. }
926 | WorkflowData::Follow { .. } => {
927 let change = self.build_change();
928 if matches!(change, Change::None) {
929 None
930 } else {
931 Some(AppResult::Change(change))
932 }
933 }
934 WorkflowData::SelectOne { selected_input } => selected_input.map(|item| {
935 AppResult::SingleSelect(SingleSelectResult {
936 item,
937 show_diff: self.show_diff,
938 })
939 }),
940 WorkflowData::SelectMany { selected_inputs } => {
941 if selected_inputs.is_empty() {
942 None
943 } else {
944 Some(AppResult::MultiSelect(MultiSelectResultData {
945 items: selected_inputs,
946 show_diff: self.show_diff,
947 }))
948 }
949 }
950 WorkflowData::ConfirmOnly { action } => action.map(AppResult::Confirm),
951 }
952 }
953}