holodeck_simctl_tui/state/
palette_command.rs1use holodeck_core::models::{Simulator, SimulatorState};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum PaletteCommand {
5 Appearance,
6 Boot,
7 Delete,
8 Erase,
9 Focus,
10 Inspect,
11 New,
12 Open,
13 Privacy,
14 Record,
15 Screenshot,
16 Shutdown,
17}
18
19impl PaletteCommand {
20 const CASES: [PaletteCommand; 12] = [
21 PaletteCommand::Appearance,
22 PaletteCommand::Boot,
23 PaletteCommand::Delete,
24 PaletteCommand::Erase,
25 PaletteCommand::Focus,
26 PaletteCommand::Inspect,
27 PaletteCommand::New,
28 PaletteCommand::Open,
29 PaletteCommand::Privacy,
30 PaletteCommand::Record,
31 PaletteCommand::Screenshot,
32 PaletteCommand::Shutdown,
33 ];
34
35 pub fn all() -> Vec<PaletteCommand> {
40 let mut all = Self::CASES.to_vec();
41 all.sort_by_key(|c| c.display_name());
42 all
43 }
44
45 pub fn display_name(self) -> &'static str {
46 match self {
47 PaletteCommand::Appearance => "appearance",
48 PaletteCommand::Boot => "boot",
49 PaletteCommand::Delete => "delete",
50 PaletteCommand::Erase => "erase",
51 PaletteCommand::Focus => "focus",
52 PaletteCommand::Inspect => "inspect",
53 PaletteCommand::New => "new",
54 PaletteCommand::Open => "open",
55 PaletteCommand::Privacy => "privacy",
56 PaletteCommand::Record => "record",
57 PaletteCommand::Screenshot => "screenshot",
58 PaletteCommand::Shutdown => "shutdown",
59 }
60 }
61
62 pub fn description(self) -> &'static str {
63 match self {
64 PaletteCommand::Appearance => "Switch the booted simulator between light and dark",
65 PaletteCommand::Boot => "Boot the selected simulator",
66 PaletteCommand::Delete => "Delete the selected simulator",
67 PaletteCommand::Erase => "Erase the selected (shutdown) simulator",
68 PaletteCommand::Focus => "Bring Simulator.app to the front for the selection",
69 PaletteCommand::Inspect => "Open the inspector for the selected simulator",
70 PaletteCommand::New => "Create a new simulator (wizard)",
71 PaletteCommand::Open => "Open a URL or deep link on the booted simulator",
72 PaletteCommand::Privacy => "Grant or revoke privacy permissions for an app",
73 PaletteCommand::Record => "Start screen recording on the booted simulator",
74 PaletteCommand::Screenshot => "Capture a screenshot of the booted simulator",
75 PaletteCommand::Shutdown => "Shut down the selected simulator",
76 }
77 }
78
79 pub fn is_applicable(self, simulator: Option<&Simulator>, is_recording: bool) -> bool {
80 match self {
81 PaletteCommand::New => true,
82 PaletteCommand::Focus | PaletteCommand::Inspect | PaletteCommand::Delete => simulator.is_some(),
83 PaletteCommand::Boot => simulator.is_some_and(|s| s.state == SimulatorState::Shutdown),
84 PaletteCommand::Shutdown => simulator.is_some_and(|s| s.state == SimulatorState::Booted),
85 PaletteCommand::Erase => simulator.is_some_and(|s| s.state == SimulatorState::Shutdown),
86 PaletteCommand::Record => simulator.is_some_and(|s| s.state == SimulatorState::Booted) && !is_recording,
87 PaletteCommand::Screenshot | PaletteCommand::Appearance | PaletteCommand::Open | PaletteCommand::Privacy => {
88 simulator.is_some_and(|s| s.state == SimulatorState::Booted)
89 }
90 }
91 }
92
93 pub fn matches(self, prefix: &str) -> bool {
95 if prefix.is_empty() {
96 return true;
97 }
98 self.display_name().to_lowercase().starts_with(&prefix.to_lowercase())
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn all_is_sorted_alphabetically_by_display_name() {
108 let names: Vec<&str> = PaletteCommand::all().into_iter().map(PaletteCommand::display_name).collect();
109 let mut sorted = names.clone();
110 sorted.sort();
111 assert_eq!(names, sorted);
112 }
113
114 #[test]
115 fn empty_prefix_matches_everything() {
116 assert!(PaletteCommand::Boot.matches(""));
117 }
118
119 #[test]
120 fn prefix_match_is_case_insensitive() {
121 assert!(PaletteCommand::Boot.matches("BO"));
122 assert!(!PaletteCommand::Boot.matches("xyz"));
123 }
124}