1use ratatui::style::Color;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6pub enum OperationMode {
7 Normal,
9 AcceptEdits,
11 PlanMode,
13 BypassAll,
15}
16
17impl Default for OperationMode {
18 fn default() -> Self {
19 Self::Normal
20 }
21}
22
23impl OperationMode {
24 pub fn cycle(&self) -> Self {
26 match self {
27 Self::Normal => Self::AcceptEdits,
28 Self::AcceptEdits => Self::PlanMode,
29 Self::PlanMode => Self::BypassAll,
30 Self::BypassAll => Self::Normal,
31 }
32 }
33
34 pub fn cycle_reverse(&self) -> Self {
36 match self {
37 Self::Normal => Self::BypassAll,
38 Self::BypassAll => Self::PlanMode,
39 Self::PlanMode => Self::AcceptEdits,
40 Self::AcceptEdits => Self::Normal,
41 }
42 }
43
44 pub fn display_name(&self) -> &str {
46 match self {
47 Self::Normal => "Normal",
48 Self::AcceptEdits => "Accept Edits",
49 Self::PlanMode => "Plan Mode",
50 Self::BypassAll => "Bypass All",
51 }
52 }
53
54 pub fn short_name(&self) -> &str {
56 match self {
57 Self::Normal => "N",
58 Self::AcceptEdits => "A",
59 Self::PlanMode => "P",
60 Self::BypassAll => "B",
61 }
62 }
63
64 pub fn color(&self) -> Color {
66 match self {
67 Self::Normal => Color::Green,
68 Self::AcceptEdits => Color::Yellow,
69 Self::PlanMode => Color::Blue,
70 Self::BypassAll => Color::Red,
71 }
72 }
73
74 pub fn description(&self) -> &str {
76 match self {
77 Self::Normal => "Asks for confirmation on all operations",
78 Self::AcceptEdits => "Auto-accepts file edits, confirms other operations",
79 Self::PlanMode => "Shows what would happen without executing anything",
80 Self::BypassAll => "Automatically accepts all operations (use with caution)",
81 }
82 }
83
84 pub fn auto_accept_files(&self) -> bool {
86 matches!(self, Self::AcceptEdits | Self::BypassAll)
87 }
88
89 pub fn auto_accept_commands(&self) -> bool {
91 matches!(self, Self::BypassAll)
92 }
93
94 pub fn auto_accept_git(&self) -> bool {
96 matches!(self, Self::BypassAll)
97 }
98
99 pub fn is_planning_only(&self) -> bool {
101 matches!(self, Self::PlanMode)
102 }
103
104 pub fn needs_safety_confirmation(&self) -> bool {
106 matches!(self, Self::BypassAll)
107 }
108
109 pub fn keyboard_hint(&self) -> &str {
111 match self {
112 Self::Normal => "Shift+Tab to cycle modes",
113 Self::AcceptEdits => "Ctrl+E for Accept Edits mode",
114 Self::PlanMode => "Ctrl+P for Plan mode",
115 Self::BypassAll => "Ctrl+Y to toggle Bypass All",
116 }
117 }
118
119 pub fn from_str(s: &str) -> Option<Self> {
121 match s.to_lowercase().as_str() {
122 "normal" => Some(Self::Normal),
123 "accept_edits" | "accept-edits" | "acceptedits" => Some(Self::AcceptEdits),
124 "plan_mode" | "plan-mode" | "planmode" | "plan" => Some(Self::PlanMode),
125 "bypass_all" | "bypass-all" | "bypassall" | "bypass" | "yolo" => Some(Self::BypassAll),
126 _ => None,
127 }
128 }
129
130 pub fn to_str(&self) -> &str {
132 match self {
133 Self::Normal => "normal",
134 Self::AcceptEdits => "accept_edits",
135 Self::PlanMode => "plan_mode",
136 Self::BypassAll => "bypass_all",
137 }
138 }
139
140 pub fn warning_level(&self) -> WarningLevel {
142 match self {
143 Self::Normal => WarningLevel::None,
144 Self::AcceptEdits => WarningLevel::Low,
145 Self::PlanMode => WarningLevel::None,
146 Self::BypassAll => WarningLevel::High,
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum WarningLevel {
153 None,
154 Low,
155 High,
156}
157
158impl WarningLevel {
159 pub fn color(&self) -> Color {
160 match self {
161 Self::None => Color::Green,
162 Self::Low => Color::Yellow,
163 Self::High => Color::Red,
164 }
165 }
166
167 pub fn message(&self) -> Option<&str> {
168 match self {
169 Self::None => None,
170 Self::Low => Some("Auto-accept files"),
171 Self::High => Some("WARNING: BYPASS ALL"),
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_mode_cycling() {
182 let mut mode = OperationMode::Normal;
183
184 mode = mode.cycle();
185 assert_eq!(mode, OperationMode::AcceptEdits);
186
187 mode = mode.cycle();
188 assert_eq!(mode, OperationMode::PlanMode);
189
190 mode = mode.cycle();
191 assert_eq!(mode, OperationMode::BypassAll);
192
193 mode = mode.cycle();
194 assert_eq!(mode, OperationMode::Normal);
195 }
196
197 #[test]
198 fn test_mode_cycling_reverse() {
199 let mut mode = OperationMode::Normal;
200
201 mode = mode.cycle_reverse();
202 assert_eq!(mode, OperationMode::BypassAll);
203
204 mode = mode.cycle_reverse();
205 assert_eq!(mode, OperationMode::PlanMode);
206
207 mode = mode.cycle_reverse();
208 assert_eq!(mode, OperationMode::AcceptEdits);
209
210 mode = mode.cycle_reverse();
211 assert_eq!(mode, OperationMode::Normal);
212 }
213
214 #[test]
215 fn test_mode_permissions() {
216 assert!(!OperationMode::Normal.auto_accept_files());
217 assert!(!OperationMode::Normal.auto_accept_commands());
218
219 assert!(OperationMode::AcceptEdits.auto_accept_files());
220 assert!(!OperationMode::AcceptEdits.auto_accept_commands());
221
222 assert!(!OperationMode::PlanMode.auto_accept_files());
223 assert!(OperationMode::PlanMode.is_planning_only());
224
225 assert!(OperationMode::BypassAll.auto_accept_files());
226 assert!(OperationMode::BypassAll.auto_accept_commands());
227 assert!(OperationMode::BypassAll.auto_accept_git());
228 }
229
230 #[test]
231 fn test_mode_from_str() {
232 assert_eq!(
233 OperationMode::from_str("normal"),
234 Some(OperationMode::Normal)
235 );
236 assert_eq!(
237 OperationMode::from_str("accept_edits"),
238 Some(OperationMode::AcceptEdits)
239 );
240 assert_eq!(
241 OperationMode::from_str("plan-mode"),
242 Some(OperationMode::PlanMode)
243 );
244 assert_eq!(
245 OperationMode::from_str("yolo"),
246 Some(OperationMode::BypassAll)
247 );
248 assert_eq!(OperationMode::from_str("invalid"), None);
249 }
250}