Skip to main content

rush_sync_server/commands/theme/
command.rs

1use super::ThemeSystem;
2use crate::commands::command::Command;
3use crate::core::prelude::*;
4
5#[derive(Debug)]
6pub struct ThemeCommand {
7    theme_system: std::sync::Mutex<Option<ThemeSystem>>,
8}
9
10impl ThemeCommand {
11    pub fn new() -> Self {
12        Self {
13            theme_system: std::sync::Mutex::new(None),
14        }
15    }
16
17    fn get_or_init_theme_system(&self) -> Result<std::sync::MutexGuard<'_, Option<ThemeSystem>>> {
18        let mut guard = self.theme_system.lock().unwrap_or_else(|poisoned| {
19            log::warn!("Recovered from poisoned mutex");
20            poisoned.into_inner()
21        });
22        if guard.is_none() {
23            *guard = Some(ThemeSystem::load()?);
24        }
25        Ok(guard)
26    }
27}
28
29impl Command for ThemeCommand {
30    fn name(&self) -> &'static str {
31        "theme"
32    }
33
34    fn description(&self) -> &'static str {
35        "Change application theme (live update without restart, loaded from TOML)"
36    }
37
38    fn matches(&self, command: &str) -> bool {
39        command.trim().to_lowercase().starts_with("theme")
40    }
41
42    fn execute_sync(&self, args: &[&str]) -> Result<String> {
43        log::info!("ThemeCommand::execute_sync called with args: {:?}", args);
44
45        let mut guard = match self.get_or_init_theme_system() {
46            Ok(guard) => {
47                log::info!("ThemeSystem loaded successfully");
48                guard
49            }
50            Err(e) => {
51                log::error!("ThemeSystem load failed: {}", e);
52                return Ok(format!(
53                    "{} {}\n\n{}",
54                    get_command_translation("system.commands.theme.load_failed", &[&e.to_string()]),
55                    get_command_translation("system.commands.theme.no_themes_hint", &[]),
56                    get_command_translation("system.commands.theme.add_sections_hint", &[])
57                ));
58            }
59        };
60
61        let theme_system = guard.as_mut().unwrap();
62
63        match args.first() {
64            None => {
65                log::info!("Calling theme_system.show_status()");
66                let result = theme_system.show_status_i18n();
67                log::info!("show_status result: '{}'", result);
68                Ok(result)
69            }
70            Some(&"--help" | &"-h") => {
71                log::info!("Calling create_help_text()");
72                let result = Self::create_help_text_i18n(theme_system);
73                log::info!(
74                    "create_help_text result length: {} chars",
75                    result.chars().count()
76                );
77                Ok(result)
78            }
79            Some(&"debug") => match args.get(1) {
80                Some(&theme_name) => Ok(theme_system.debug_theme_details_i18n(theme_name)),
81                None => Ok(get_command_translation(
82                    "system.commands.theme.debug_missing_name",
83                    &[],
84                )),
85            },
86            Some(&"preview") => match args.get(1) {
87                Some(&theme_name) => theme_system.preview_theme_i18n(theme_name),
88                None => Ok(get_command_translation(
89                    "system.commands.theme.preview_missing_name",
90                    &[],
91                )),
92            },
93            Some(&theme_name) => {
94                log::info!("Calling change_theme({})", theme_name);
95                theme_system.change_theme_i18n(theme_name)
96            }
97        }
98    }
99
100    fn priority(&self) -> u8 {
101        65
102    }
103}
104
105impl ThemeCommand {
106    fn create_help_text_i18n(theme_system: &ThemeSystem) -> String {
107        let available_themes = theme_system.get_available_names();
108
109        if available_themes.is_empty() {
110            return format!(
111                "{}\n\n{}",
112                get_command_translation("system.commands.theme.no_themes_available", &[]),
113                get_command_translation("system.commands.theme.how_to_add_themes", &[])
114            );
115        }
116
117        let themes_list = available_themes.join(", ");
118
119        format!(
120            "{}\n{}\n{}\n{}\n{}\n\n{}\n{}\n{}\n{}\n\n{}",
121            get_command_translation("system.commands.theme.help.header", &[]),
122            get_command_translation("system.commands.theme.help.show_themes", &[]),
123            get_command_translation("system.commands.theme.help.select_theme", &[&themes_list]),
124            get_command_translation("system.commands.theme.help.preview_theme", &[]),
125            get_command_translation("system.commands.theme.help.show_help", &[]),
126            get_command_translation("system.commands.theme.help.live_loaded", &[]),
127            get_command_translation("system.commands.theme.help.cursor_config", &[]),
128            get_command_translation("system.commands.theme.help.add_sections", &[]),
129            get_command_translation("system.commands.theme.help.live_changes", &[]),
130            get_command_translation("system.commands.theme.help.cursor_options", &[])
131        )
132    }
133}
134
135impl Default for ThemeCommand {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141impl ThemeSystem {
142    pub fn show_status_i18n(&self) -> String {
143        if self.themes.is_empty() {
144            return get_command_translation("system.commands.theme.no_themes_found", &[]);
145        }
146
147        let themes_list = self.themes.keys().cloned().collect::<Vec<_>>().join(", ");
148        get_command_translation(
149            "system.commands.theme.current_status",
150            &[&self.current_name.to_uppercase(), &themes_list],
151        )
152    }
153
154    pub fn change_theme_i18n(&mut self, theme_name: &str) -> Result<String> {
155        let theme_name_lower = theme_name.to_lowercase();
156
157        if !self.themes.contains_key(&theme_name_lower) {
158            return Ok(if self.themes.is_empty() {
159                get_command_translation("system.commands.theme.no_themes_found", &[])
160            } else {
161                let available = self.themes.keys().cloned().collect::<Vec<_>>().join(", ");
162                get_command_translation(
163                    "system.commands.theme.not_found",
164                    &[theme_name, &available],
165                )
166            });
167        }
168
169        self.current_name = theme_name_lower.clone();
170
171        // Log cursor details
172        if let Some(theme_def) = self.themes.get(&theme_name_lower) {
173            log::info!(
174                "Theme '{}': input_cursor='{}' ({}), output_cursor='{}' ({}), prefix='{}'",
175                theme_name_lower.to_uppercase(),
176                theme_def.input_cursor,
177                theme_def.input_cursor_color,
178                theme_def.output_cursor,
179                theme_def.output_cursor_color,
180                theme_def.input_cursor_prefix
181            );
182        }
183
184        // Async save
185        let name_clone = theme_name_lower.clone();
186        let paths_clone = self.config_paths.clone();
187        tokio::spawn(async move {
188            if let Err(e) = Self::save_current_theme_to_config(&paths_clone, &name_clone).await {
189                log::error!("Failed to save theme: {}", e);
190            }
191        });
192
193        Ok(format!(
194            "__LIVE_THEME_UPDATE__{}__MESSAGE__{}",
195            theme_name_lower,
196            get_command_translation(
197                "system.commands.theme.changed_success",
198                &[&theme_name_lower.to_uppercase()]
199            )
200        ))
201    }
202
203    pub fn preview_theme_i18n(&self, theme_name: &str) -> Result<String> {
204        let theme_name_lower = theme_name.to_lowercase();
205
206        if let Some(theme_def) = self.themes.get(&theme_name_lower) {
207            Ok(get_command_translation(
208                "system.commands.theme.preview_details",
209                &[
210                    &theme_name_lower.to_uppercase(),
211                    &theme_def.input_text,
212                    &theme_def.input_bg,
213                    &theme_def.output_text,
214                    &theme_def.output_bg,
215                    &theme_def.input_cursor_prefix,
216                    &theme_def.input_cursor_color,
217                    &theme_def.input_cursor,
218                    &theme_def.output_cursor,
219                    &theme_def.output_cursor_color,
220                    &theme_name_lower,
221                ],
222            ))
223        } else {
224            let available = self.themes.keys().cloned().collect::<Vec<_>>().join(", ");
225            Ok(get_command_translation(
226                "system.commands.theme.not_found",
227                &[theme_name, &available],
228            ))
229        }
230    }
231
232    pub fn debug_theme_details_i18n(&self, theme_name: &str) -> String {
233        if let Some(theme_def) = self.themes.get(&theme_name.to_lowercase()) {
234            get_command_translation(
235                "system.commands.theme.debug_details",
236                &[
237                    &theme_name.to_uppercase(),
238                    &theme_def.input_text,
239                    &theme_def.input_bg,
240                    &theme_def.output_text,
241                    &theme_def.output_bg,
242                    &theme_def.input_cursor_prefix,
243                    &theme_def.input_cursor_color,
244                    &theme_def.input_cursor,
245                    &theme_def.output_cursor,
246                    &theme_def.output_cursor_color,
247                ],
248            )
249        } else {
250            get_command_translation("system.commands.theme.debug_not_found", &[theme_name])
251        }
252    }
253}