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