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