rush_sync_server/commands/theme/
command.rs1use 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 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 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}