fresh/app/
settings_actions.rs1use crate::config_io::{ConfigLayer, ConfigResolver};
11use crate::input::keybindings::KeybindingResolver;
12use anyhow::Result as AnyhowResult;
13use rust_i18n::t;
14
15use super::Editor;
16
17impl Editor {
18 pub fn open_settings(&mut self) {
20 const SCHEMA_JSON: &str = include_str!("../../plugins/config-schema.json");
22
23 if self.settings_state.is_none() {
25 match crate::view::settings::SettingsState::new(SCHEMA_JSON, &self.config) {
26 Ok(mut state) => {
27 let resolver =
29 ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
30 if let Ok(sources) = resolver.get_layer_sources() {
31 state.set_layer_sources(sources);
32 }
33 state.show();
34 self.settings_state = Some(state);
35 }
36 Err(e) => {
37 self.set_status_message(
38 t!("settings.failed_to_open", error = e.to_string()).to_string(),
39 );
40 }
41 }
42 } else if let Some(ref mut state) = self.settings_state {
43 state.show();
44 }
45 }
46
47 pub fn close_settings(&mut self, save: bool) {
51 if save {
52 self.save_settings();
53 }
54 if let Some(ref mut state) = self.settings_state {
55 if !save && state.has_changes() {
56 state.discard_changes();
58 }
59 state.hide();
60 }
61 }
62
63 pub fn save_settings(&mut self) {
65 let old_theme = self.config.theme.clone();
66 let old_locale = self.config.locale.clone();
67 let old_plugins = self.config.plugins.clone();
68
69 let (target_layer, new_config, pending_changes, pending_deletions) = {
71 if let Some(ref state) = self.settings_state {
72 if !state.has_changes() {
73 return;
74 }
75 match state.apply_changes(&self.config) {
76 Ok(config) => (
77 state.target_layer,
78 config,
79 state.pending_changes.clone(),
80 state.pending_deletions.clone(),
81 ),
82 Err(e) => {
83 self.set_status_message(
84 t!("settings.failed_to_apply", error = e.to_string()).to_string(),
85 );
86 return;
87 }
88 }
89 } else {
90 return;
91 }
92 };
93
94 self.config = new_config.clone();
96
97 if old_theme != self.config.theme {
99 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
100 self.theme = theme;
101 tracing::info!("Theme changed to '{}'", self.config.theme.0);
102 } else {
103 tracing::error!("Theme '{}' not found", self.config.theme.0);
104 self.set_status_message(format!("Theme '{}' not found", self.config.theme.0));
105 }
106 }
107
108 if old_locale != self.config.locale {
110 if let Some(locale) = self.config.locale.as_option() {
111 crate::i18n::set_locale(locale);
112 self.menus = crate::config::MenuConfig::translated();
114 tracing::info!("Locale changed to '{}'", locale);
115 } else {
116 crate::i18n::init();
118 self.menus = crate::config::MenuConfig::translated();
119 tracing::info!("Locale reset to auto-detect");
120 }
121 if let Ok(mut registry) = self.command_registry.write() {
123 registry.refresh_builtin_commands();
124 }
125 }
126
127 self.apply_plugin_config_changes(&old_plugins);
129
130 self.keybindings = KeybindingResolver::new(&self.config);
132
133 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
135
136 let layer_name = match target_layer {
137 ConfigLayer::User => "User",
138 ConfigLayer::Project => "Project",
139 ConfigLayer::Session => "Session",
140 ConfigLayer::System => "System", };
142
143 match resolver.save_changes_to_layer(&pending_changes, &pending_deletions, target_layer) {
144 Ok(()) => {
145 self.set_status_message(
146 t!("settings.saved_to_layer", layer = layer_name).to_string(),
147 );
148 self.settings_state = None;
152 }
153 Err(e) => {
154 self.set_status_message(
155 t!("settings.failed_to_save", error = e.to_string()).to_string(),
156 );
157 }
158 }
159 }
160
161 pub fn open_config_file(&mut self, layer: ConfigLayer) -> AnyhowResult<()> {
165 if let Some(ref state) = self.settings_state {
167 if state.has_changes() {
168 self.set_status_message(t!("settings.pending_changes").to_string());
169 return Ok(());
170 }
171 }
172
173 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
174
175 let path = match layer {
176 ConfigLayer::User => resolver.user_config_path(),
177 ConfigLayer::Project => resolver.project_config_write_path(),
178 ConfigLayer::Session => resolver.session_config_path(),
179 ConfigLayer::System => {
180 self.set_status_message(t!("settings.cannot_edit_system").to_string());
181 return Ok(());
182 }
183 };
184
185 if let Some(parent) = path.parent() {
187 self.filesystem.create_dir_all(parent)?;
188 }
189
190 if !self.filesystem.exists(&path) {
192 let template = match layer {
193 ConfigLayer::User => {
194 r#"{
195 "version": 1,
196 "theme": "default",
197 "editor": {
198 "tab_size": 4,
199 "line_numbers": true
200 }
201}
202"#
203 }
204 ConfigLayer::Project => {
205 r#"{
206 "version": 1,
207 "editor": {
208 "tab_size": 4
209 },
210 "languages": {}
211}
212"#
213 }
214 ConfigLayer::Session => {
215 r#"{
216 "version": 1
217}
218"#
219 }
220 ConfigLayer::System => unreachable!(),
221 };
222 self.filesystem.write_file(&path, template.as_bytes())?;
223 }
224
225 self.settings_state = None;
227 self.open_file(&path)?;
228
229 let layer_name = match layer {
230 ConfigLayer::User => "User",
231 ConfigLayer::Project => "Project",
232 ConfigLayer::Session => "Session",
233 ConfigLayer::System => "System",
234 };
235 self.set_status_message(
236 t!(
237 "settings.editing_config",
238 layer = layer_name,
239 path = path.display().to_string()
240 )
241 .to_string(),
242 );
243
244 Ok(())
245 }
246
247 pub fn settings_navigate_up(&mut self) {
249 if let Some(ref mut state) = self.settings_state {
250 state.select_prev();
251 }
252 }
253
254 pub fn settings_navigate_down(&mut self) {
256 if let Some(ref mut state) = self.settings_state {
257 state.select_next();
258 }
259 }
260
261 pub fn settings_activate_current(&mut self) {
263 use crate::view::settings::items::SettingControl;
264 use crate::view::settings::FocusPanel;
265
266 let focus_panel = self
268 .settings_state
269 .as_ref()
270 .map(|s| s.focus_panel())
271 .unwrap_or(FocusPanel::Categories);
272
273 if focus_panel == FocusPanel::Footer {
274 let button_index = self
275 .settings_state
276 .as_ref()
277 .map(|s| s.footer_button_index)
278 .unwrap_or(2);
279 match button_index {
280 0 => {
281 if let Some(ref mut state) = self.settings_state {
283 state.cycle_target_layer();
284 }
285 }
286 1 => {
287 if let Some(ref mut state) = self.settings_state {
289 state.reset_current_to_default();
290 }
291 }
292 2 => {
293 self.close_settings(true);
295 }
296 3 => {
297 self.close_settings(false);
299 }
300 _ => {}
301 }
302 return;
303 }
304
305 if focus_panel == FocusPanel::Categories {
308 return;
309 }
310
311 let control_type = {
313 if let Some(ref state) = self.settings_state {
314 state.current_item().map(|item| match &item.control {
315 SettingControl::Toggle(_) => "toggle",
316 SettingControl::Number(_) => "number",
317 SettingControl::Dropdown(_) => "dropdown",
318 SettingControl::Text(_) => "text",
319 SettingControl::TextList(_) => "textlist",
320 SettingControl::Map(_) => "map",
321 SettingControl::ObjectArray(_) => "objectarray",
322 SettingControl::Json(_) => "json",
323 SettingControl::Complex { .. } => "complex",
324 })
325 } else {
326 None
327 }
328 };
329
330 match control_type {
332 Some("toggle") => {
333 if let Some(ref mut state) = self.settings_state {
334 if let Some(item) = state.current_item_mut() {
335 if let SettingControl::Toggle(ref mut toggle_state) = item.control {
336 toggle_state.checked = !toggle_state.checked;
337 }
338 }
339 state.on_value_changed();
340 }
341 }
342 Some("dropdown") => {
343 if let Some(ref mut state) = self.settings_state {
345 if state.is_dropdown_open() {
346 state.dropdown_confirm();
347 } else {
348 state.dropdown_toggle();
349 }
350 }
351 }
352 Some("textlist") => {
353 if let Some(ref mut state) = self.settings_state {
355 state.start_editing();
356 }
357 }
358 Some("map") => {
359 if let Some(ref mut state) = self.settings_state {
361 if let Some(item) = state.current_item_mut() {
362 if let SettingControl::Map(ref mut map_state) = item.control {
363 if map_state.focused_entry.is_none() {
364 if map_state.value_schema.is_some() {
366 state.open_add_entry_dialog();
367 }
368 } else if map_state.value_schema.is_some() {
369 state.open_entry_dialog();
371 } else {
372 if let Some(idx) = map_state.focused_entry {
374 if map_state.expanded.contains(&idx) {
375 map_state.expanded.retain(|&i| i != idx);
376 } else {
377 map_state.expanded.push(idx);
378 }
379 }
380 }
381 }
382 }
383 state.on_value_changed();
384 }
385 }
386 Some("text") => {
387 if let Some(ref mut state) = self.settings_state {
389 state.start_editing();
390 }
391 }
392 Some("number") => {
393 if let Some(ref mut state) = self.settings_state {
395 state.start_number_editing();
396 }
397 }
398 _ => {}
399 }
400 }
401
402 pub fn settings_increment_current(&mut self) {
404 use crate::view::settings::items::SettingControl;
405 use crate::view::settings::FocusPanel;
406
407 let focus_panel = self
409 .settings_state
410 .as_ref()
411 .map(|s| s.focus_panel())
412 .unwrap_or(FocusPanel::Categories);
413
414 if focus_panel == FocusPanel::Footer {
415 if let Some(ref mut state) = self.settings_state {
416 state.footer_button_index = (state.footer_button_index + 1) % 4;
418 }
419 return;
420 }
421
422 if focus_panel == FocusPanel::Categories {
424 return;
425 }
426
427 let control_type = {
428 if let Some(ref state) = self.settings_state {
429 state.current_item().map(|item| match &item.control {
430 SettingControl::Number(_) => "number",
431 SettingControl::Dropdown(_) => "dropdown",
432 _ => "other",
433 })
434 } else {
435 None
436 }
437 };
438
439 match control_type {
440 Some("number") => {
441 if let Some(ref mut state) = self.settings_state {
442 if let Some(item) = state.current_item_mut() {
443 if let SettingControl::Number(ref mut num_state) = item.control {
444 num_state.increment();
445 }
446 }
447 state.on_value_changed();
448 }
449 }
450 Some("dropdown") => {
451 if let Some(ref mut state) = self.settings_state {
452 if let Some(item) = state.current_item_mut() {
453 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
454 dropdown_state.select_next();
455 }
456 }
457 state.on_value_changed();
458 }
459 }
460 _ => {}
461 }
462 }
463
464 pub fn settings_decrement_current(&mut self) {
466 use crate::view::settings::items::SettingControl;
467 use crate::view::settings::FocusPanel;
468
469 let focus_panel = self
471 .settings_state
472 .as_ref()
473 .map(|s| s.focus_panel())
474 .unwrap_or(FocusPanel::Categories);
475
476 if focus_panel == FocusPanel::Footer {
477 if let Some(ref mut state) = self.settings_state {
478 state.footer_button_index = if state.footer_button_index == 0 {
480 3
481 } else {
482 state.footer_button_index - 1
483 };
484 }
485 return;
486 }
487
488 if focus_panel == FocusPanel::Categories {
490 return;
491 }
492
493 let control_type = {
494 if let Some(ref state) = self.settings_state {
495 state.current_item().map(|item| match &item.control {
496 SettingControl::Number(_) => "number",
497 SettingControl::Dropdown(_) => "dropdown",
498 _ => "other",
499 })
500 } else {
501 None
502 }
503 };
504
505 match control_type {
506 Some("number") => {
507 if let Some(ref mut state) = self.settings_state {
508 if let Some(item) = state.current_item_mut() {
509 if let SettingControl::Number(ref mut num_state) = item.control {
510 num_state.decrement();
511 }
512 }
513 state.on_value_changed();
514 }
515 }
516 Some("dropdown") => {
517 if let Some(ref mut state) = self.settings_state {
518 if let Some(item) = state.current_item_mut() {
519 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
520 dropdown_state.select_prev();
521 }
522 }
523 state.on_value_changed();
524 }
525 }
526 _ => {}
527 }
528 }
529
530 fn apply_plugin_config_changes(
532 &mut self,
533 old_plugins: &std::collections::HashMap<String, crate::config::PluginConfig>,
534 ) {
535 let changes: Vec<_> = self
537 .config
538 .plugins
539 .iter()
540 .filter_map(|(name, new_config)| {
541 let was_enabled = old_plugins.get(name).map(|c| c.enabled).unwrap_or(true);
542 if new_config.enabled != was_enabled {
543 Some((name.clone(), new_config.enabled, new_config.path.clone()))
544 } else {
545 None
546 }
547 })
548 .collect();
549
550 for (name, now_enabled, path) in changes {
552 if now_enabled {
553 if let Some(ref path) = path {
555 tracing::info!("Loading newly enabled plugin: {}", name);
556 if let Err(e) = self.plugin_manager.load_plugin(path) {
557 tracing::error!("Failed to load plugin '{}': {}", name, e);
558 self.set_status_message(format!("Failed to load plugin '{}': {}", name, e));
559 }
560 }
561 } else {
562 tracing::info!("Unloading disabled plugin: {}", name);
564 if let Err(e) = self.plugin_manager.unload_plugin(&name) {
565 tracing::error!("Failed to unload plugin '{}': {}", name, e);
566 self.set_status_message(format!("Failed to unload plugin '{}': {}", name, e));
567 }
568 }
569 }
570 }
571}