1use crate::config::Config;
11use crate::config_io::{ConfigLayer, ConfigResolver};
12use crate::input::keybindings::KeybindingResolver;
13use crate::types::LspServerConfig;
14use anyhow::Result as AnyhowResult;
15use rust_i18n::t;
16
17use super::Editor;
18
19impl Editor {
20 pub fn open_settings(&mut self) {
22 const SCHEMA_JSON: &str = include_str!("../../plugins/config-schema.json");
24
25 if self.settings_state.is_none() {
27 match crate::view::settings::SettingsState::new(SCHEMA_JSON, &self.config) {
28 Ok(mut state) => {
29 let resolver =
31 ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
32 if let Ok(sources) = resolver.get_layer_sources() {
33 state.set_layer_sources(sources);
34 }
35 state.show();
36 self.settings_state = Some(state);
37 }
38 Err(e) => {
39 self.set_status_message(
40 t!("settings.failed_to_open", error = e.to_string()).to_string(),
41 );
42 }
43 }
44 } else if let Some(ref mut state) = self.settings_state {
45 state.show();
46 }
47 }
48
49 pub fn close_settings(&mut self, save: bool) {
53 if save {
54 self.save_settings();
55 }
56 if let Some(ref mut state) = self.settings_state {
57 if !save && state.has_changes() {
58 state.discard_changes();
60 }
61 state.hide();
62 }
63 }
64
65 pub fn save_settings(&mut self) {
67 let old_theme = self.config.theme.clone();
68 let old_locale = self.config.locale.clone();
69 let old_plugins = self.config.plugins.clone();
70 #[cfg(windows)]
71 let old_mouse_hover = self.config.editor.mouse_hover_enabled;
72
73 let (target_layer, new_config, pending_changes, pending_deletions) = {
75 if let Some(ref state) = self.settings_state {
76 if !state.has_changes() {
77 return;
78 }
79 match state.apply_changes(&self.config) {
80 Ok(config) => (
81 state.target_layer,
82 config,
83 state.pending_changes.clone(),
84 state.pending_deletions.clone(),
85 ),
86 Err(e) => {
87 self.set_status_message(
88 t!("settings.failed_to_apply", error = e.to_string()).to_string(),
89 );
90 return;
91 }
92 }
93 } else {
94 return;
95 }
96 };
97
98 self.set_config(new_config.clone());
100
101 self.set_user_config_raw(Config::read_user_config_raw(&self.working_dir));
103
104 if old_theme != self.config.theme {
106 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
107 *self.theme.write().unwrap() = theme;
108 tracing::info!("Theme changed to '{}'", self.config.theme.0);
109 } else {
110 tracing::error!("Theme '{}' not found", self.config.theme.0);
111 self.set_status_message(format!("Theme '{}' not found", self.config.theme.0));
112 }
113 }
114
115 if old_locale != self.config.locale {
117 let locale_owned = self.config.locale.as_option().map(|s| s.to_string());
118 if let Some(locale) = locale_owned {
119 crate::i18n::set_locale(&locale);
120 self.set_menus(crate::config::MenuConfig::translated());
122 tracing::info!("Locale changed to '{}'", locale);
123 } else {
124 crate::i18n::init();
126 self.set_menus(crate::config::MenuConfig::translated());
127 tracing::info!("Locale reset to auto-detect");
128 }
129 if let Ok(mut registry) = self.command_registry.write() {
131 registry.refresh_builtin_commands();
132 }
133 }
134
135 self.apply_plugin_config_changes(&old_plugins);
137
138 *self.keybindings.write().unwrap() = KeybindingResolver::new(&self.config);
140
141 let __active_id = self.active_window;
143 if let Some(lsp) = self
144 .windows
145 .get_mut(&__active_id)
146 .and_then(|w| w.lsp.as_mut())
147 {
148 for (language, lsp_configs) in &self.config.lsp {
149 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
150 }
151 let universal_servers: Vec<LspServerConfig> = self
153 .config
154 .universal_lsp
155 .values()
156 .flat_map(|lc| lc.as_slice().to_vec())
157 .filter(|c| c.enabled)
158 .collect();
159 lsp.set_universal_configs(universal_servers);
160 }
161
162 for view_state in self
164 .windows
165 .get_mut(&self.active_window)
166 .and_then(|w| w.split_view_states_mut())
167 .expect("active window must have a populated split layout")
168 .values_mut()
169 {
170 view_state.show_line_numbers = self.config.editor.line_numbers;
171 for buf_state in view_state.keyed_states.values_mut() {
172 buf_state.rulers = self.config.editor.rulers.clone();
173 }
174 }
175
176 self.active_window_mut().menu_bar_visible = self.config.editor.show_menu_bar;
178 self.active_window_mut().tab_bar_visible = self.config.editor.show_tab_bar;
179 self.active_window_mut().status_bar_visible = self.config.editor.show_status_bar;
180 self.active_window_mut().prompt_line_visible = self.config.editor.show_prompt_line;
181
182 self.active_window_mut().file_explorer_width = self.config.file_explorer.width;
185 self.active_window_mut().file_explorer_side = self.config.file_explorer.side;
186 let active_id = self.active_window;
187 if let Some(explorer) = self
188 .windows
189 .get_mut(&active_id)
190 .and_then(|w| w.file_explorer.as_mut())
191 {
192 let patterns = explorer.ignore_patterns_mut();
193 patterns.set_show_hidden(self.config.file_explorer.show_hidden);
194 patterns.set_show_gitignored(self.config.file_explorer.show_gitignored);
195 }
196
197 #[cfg(windows)]
200 if old_mouse_hover != self.config.editor.mouse_hover_enabled {
201 let mode = if self.config.editor.mouse_hover_enabled {
202 fresh_winterm::MouseMode::AllMotion
203 } else {
204 self.active_window_mut().mouse_state.lsp_hover_state = None;
206 self.active_window_mut().mouse_state.lsp_hover_request_sent = false;
207 fresh_winterm::MouseMode::CellMotion
208 };
209 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
210 tracing::error!("Failed to switch mouse mode: {}", e);
211 }
212 }
213
214 for (_, state) in self
217 .windows
218 .get_mut(&self.active_window)
219 .map(|w| &mut w.buffers)
220 .expect("active window present")
221 {
222 let mut whitespace =
223 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
224 state.buffer_settings.auto_close = self.config.editor.auto_close;
225 if let Some(lang_config) = self.config.languages.get(&state.language) {
226 state.buffer_settings.tab_size =
227 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
228 state.buffer_settings.use_tabs =
229 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
230 whitespace =
231 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
232 if state.buffer_settings.auto_close {
234 if let Some(lang_auto_close) = lang_config.auto_close {
235 state.buffer_settings.auto_close = lang_auto_close;
236 }
237 }
238 if let Some(ref wc) = lang_config.word_characters {
240 state.buffer_settings.word_characters = wc.clone();
241 } else {
242 state.buffer_settings.word_characters.clear();
243 }
244 } else {
245 state.buffer_settings.tab_size = self.config.editor.tab_size;
246 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
247 }
248 state.buffer_settings.whitespace = whitespace;
249 }
250
251 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
253
254 let layer_name = match target_layer {
255 ConfigLayer::User => "User",
256 ConfigLayer::Project => "Project",
257 ConfigLayer::Session => "Session",
258 ConfigLayer::System => "System", };
260
261 match resolver.save_changes_to_layer(&pending_changes, &pending_deletions, target_layer) {
262 Ok(()) => {
263 self.set_status_message(
264 t!("settings.saved_to_layer", layer = layer_name).to_string(),
265 );
266 self.settings_state = None;
270 }
271 Err(e) => {
272 self.set_status_message(
273 t!("settings.failed_to_save", error = e.to_string()).to_string(),
274 );
275 }
276 }
277 }
278
279 pub fn open_config_file(&mut self, layer: ConfigLayer) -> AnyhowResult<()> {
283 if let Some(ref state) = self.settings_state {
285 if state.has_changes() {
286 self.set_status_message(t!("settings.pending_changes").to_string());
287 return Ok(());
288 }
289 }
290
291 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
292
293 let path = match layer {
294 ConfigLayer::User => resolver.user_config_path(),
295 ConfigLayer::Project => resolver.project_config_write_path(),
296 ConfigLayer::Session => resolver.session_config_path(),
297 ConfigLayer::System => {
298 self.set_status_message(t!("settings.cannot_edit_system").to_string());
299 return Ok(());
300 }
301 };
302
303 if let Some(parent) = path.parent() {
305 self.authority.filesystem.create_dir_all(parent)?;
306 }
307
308 if !self.authority.filesystem.exists(&path) {
310 let template = match layer {
311 ConfigLayer::User => {
312 r#"{
313 "version": 1,
314 "theme": "default",
315 "editor": {
316 "tab_size": 4,
317 "line_numbers": true
318 }
319}
320"#
321 }
322 ConfigLayer::Project => {
323 r#"{
324 "version": 1,
325 "editor": {
326 "tab_size": 4
327 },
328 "languages": {}
329}
330"#
331 }
332 ConfigLayer::Session => {
333 r#"{
334 "version": 1
335}
336"#
337 }
338 ConfigLayer::System => unreachable!(),
339 };
340 self.authority
341 .filesystem
342 .write_file(&path, template.as_bytes())?;
343 }
344
345 self.settings_state = None;
347 match self.open_file(&path) {
348 Ok(_) => {
349 let layer_name = match layer {
350 ConfigLayer::User => "User",
351 ConfigLayer::Project => "Project",
352 ConfigLayer::Session => "Session",
353 ConfigLayer::System => "System",
354 };
355 self.set_status_message(
356 t!(
357 "settings.editing_config",
358 layer = layer_name,
359 path = path.display().to_string()
360 )
361 .to_string(),
362 );
363 }
364 Err(e) => {
365 if let Some(confirmation) =
367 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
368 {
369 self.start_large_file_encoding_confirmation(confirmation);
370 } else {
371 self.set_status_message(
372 t!("file.error_opening", error = e.to_string()).to_string(),
373 );
374 }
375 }
376 }
377
378 Ok(())
379 }
380
381 pub fn settings_navigate_up(&mut self) {
383 if let Some(ref mut state) = self.settings_state {
384 state.select_prev();
385 }
386 }
387
388 pub fn settings_navigate_down(&mut self) {
390 if let Some(ref mut state) = self.settings_state {
391 state.select_next();
392 }
393 }
394
395 pub fn settings_activate_current(&mut self) {
397 use crate::view::settings::items::SettingControl;
398 use crate::view::settings::FocusPanel;
399
400 let focus_panel = self
402 .settings_state
403 .as_ref()
404 .map(|s| s.focus_panel())
405 .unwrap_or(FocusPanel::Categories);
406
407 if focus_panel == FocusPanel::Footer {
408 let button_index = self
409 .settings_state
410 .as_ref()
411 .map(|s| s.footer_button_index)
412 .unwrap_or(2);
413 match button_index {
414 0 => {
415 if let Some(ref mut state) = self.settings_state {
417 state.cycle_target_layer();
418 }
419 }
420 1 => {
421 if let Some(ref mut state) = self.settings_state {
424 let is_nullable_set = state
425 .current_item()
426 .map(|item| item.nullable && !item.is_null)
427 .unwrap_or(false);
428 if is_nullable_set {
429 state.set_current_to_null();
430 } else {
431 state.reset_current_to_default();
432 }
433 }
434 }
435 2 => {
436 self.close_settings(true);
438 }
439 3 => {
440 self.close_settings(false);
442 }
443 _ => {}
444 }
445 return;
446 }
447
448 if focus_panel == FocusPanel::Categories {
451 return;
452 }
453
454 let control_type = {
456 if let Some(ref state) = self.settings_state {
457 state.current_item().map(|item| match &item.control {
458 SettingControl::Toggle(_) => "toggle",
459 SettingControl::Number(_) => "number",
460 SettingControl::Dropdown(_) => "dropdown",
461 SettingControl::Text(_) => "text",
462 SettingControl::TextList(_) => "textlist",
463 SettingControl::DualList(_) => "duallist",
464 SettingControl::Map(_) => "map",
465 SettingControl::ObjectArray(_) => "objectarray",
466 SettingControl::Json(_) => "json",
467 SettingControl::Complex { .. } => "complex",
468 })
469 } else {
470 None
471 }
472 };
473
474 match control_type {
476 Some("toggle") => {
477 if let Some(ref mut state) = self.settings_state {
478 if let Some(item) = state.current_item_mut() {
479 if let SettingControl::Toggle(ref mut toggle_state) = item.control {
480 toggle_state.checked = !toggle_state.checked;
481 }
482 }
483 state.on_value_changed();
484 }
485 }
486 Some("dropdown") => {
487 if let Some(ref mut state) = self.settings_state {
489 if state.is_dropdown_open() {
490 state.dropdown_confirm();
491 } else {
492 state.dropdown_toggle();
493 }
494 }
495 }
496 Some("textlist") => {
497 if let Some(ref mut state) = self.settings_state {
499 state.start_editing();
500 }
501 }
502 Some("map") => {
503 if let Some(ref mut state) = self.settings_state {
505 if let Some(item) = state.current_item_mut() {
506 if let SettingControl::Map(ref mut map_state) = item.control {
507 if map_state.focused_entry.is_none() {
508 if map_state.value_schema.is_some() {
510 state.open_add_entry_dialog();
511 }
512 } else if map_state.value_schema.is_some() {
513 state.open_entry_dialog();
515 } else {
516 if let Some(idx) = map_state.focused_entry {
518 if map_state.expanded.contains(&idx) {
519 map_state.expanded.retain(|&i| i != idx);
520 } else {
521 map_state.expanded.push(idx);
522 }
523 }
524 }
525 }
526 }
527 state.on_value_changed();
528 }
529 }
530 Some("text") => {
531 if let Some(ref mut state) = self.settings_state {
533 state.start_editing();
534 }
535 }
536 Some("number") => {
537 if let Some(ref mut state) = self.settings_state {
539 state.start_number_editing();
540 }
541 }
542 _ => {}
543 }
544 }
545
546 pub fn settings_increment_current(&mut self) {
548 use crate::view::settings::items::SettingControl;
549 use crate::view::settings::FocusPanel;
550
551 let focus_panel = self
553 .settings_state
554 .as_ref()
555 .map(|s| s.focus_panel())
556 .unwrap_or(FocusPanel::Categories);
557
558 if focus_panel == FocusPanel::Footer {
559 if let Some(ref mut state) = self.settings_state {
560 state.footer_button_index = (state.footer_button_index + 1) % 4;
562 }
563 return;
564 }
565
566 if focus_panel == FocusPanel::Categories {
568 return;
569 }
570
571 let control_type = {
572 if let Some(ref state) = self.settings_state {
573 state.current_item().map(|item| match &item.control {
574 SettingControl::Number(_) => "number",
575 SettingControl::Dropdown(_) => "dropdown",
576 _ => "other",
577 })
578 } else {
579 None
580 }
581 };
582
583 match control_type {
584 Some("number") => {
585 if let Some(ref mut state) = self.settings_state {
586 if let Some(item) = state.current_item_mut() {
587 if let SettingControl::Number(ref mut num_state) = item.control {
588 num_state.increment();
589 }
590 }
591 state.on_value_changed();
592 }
593 }
594 Some("dropdown") => {
595 if let Some(ref mut state) = self.settings_state {
596 if let Some(item) = state.current_item_mut() {
597 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
598 dropdown_state.select_next();
599 }
600 }
601 state.on_value_changed();
602 }
603 }
604 _ => {}
605 }
606 }
607
608 pub fn settings_decrement_current(&mut self) {
610 use crate::view::settings::items::SettingControl;
611 use crate::view::settings::FocusPanel;
612
613 let focus_panel = self
615 .settings_state
616 .as_ref()
617 .map(|s| s.focus_panel())
618 .unwrap_or(FocusPanel::Categories);
619
620 if focus_panel == FocusPanel::Footer {
621 if let Some(ref mut state) = self.settings_state {
622 state.footer_button_index = if state.footer_button_index == 0 {
624 3
625 } else {
626 state.footer_button_index - 1
627 };
628 }
629 return;
630 }
631
632 if focus_panel == FocusPanel::Categories {
634 return;
635 }
636
637 let control_type = {
638 if let Some(ref state) = self.settings_state {
639 state.current_item().map(|item| match &item.control {
640 SettingControl::Number(_) => "number",
641 SettingControl::Dropdown(_) => "dropdown",
642 _ => "other",
643 })
644 } else {
645 None
646 }
647 };
648
649 match control_type {
650 Some("number") => {
651 if let Some(ref mut state) = self.settings_state {
652 if let Some(item) = state.current_item_mut() {
653 if let SettingControl::Number(ref mut num_state) = item.control {
654 num_state.decrement();
655 }
656 }
657 state.on_value_changed();
658 }
659 }
660 Some("dropdown") => {
661 if let Some(ref mut state) = self.settings_state {
662 if let Some(item) = state.current_item_mut() {
663 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
664 dropdown_state.select_prev();
665 }
666 }
667 state.on_value_changed();
668 }
669 }
670 _ => {}
671 }
672 }
673
674 fn apply_plugin_config_changes(
676 &mut self,
677 old_plugins: &std::collections::HashMap<String, crate::config::PluginConfig>,
678 ) {
679 let changes: Vec<_> = self
681 .config
682 .plugins
683 .iter()
684 .filter_map(|(name, new_config)| {
685 let was_enabled = old_plugins.get(name).map(|c| c.enabled).unwrap_or(true);
686 if new_config.enabled != was_enabled {
687 Some((name.clone(), new_config.enabled, new_config.path.clone()))
688 } else {
689 None
690 }
691 })
692 .collect();
693
694 for (name, now_enabled, path) in changes {
696 if now_enabled {
697 if let Some(ref path) = path {
699 tracing::info!("Loading newly enabled plugin: {}", name);
700 let load_result = self.plugin_manager.read().unwrap().load_plugin(path);
701 if let Err(e) = load_result {
702 tracing::error!("Failed to load plugin '{}': {}", name, e);
703 self.set_status_message(format!("Failed to load plugin '{}': {}", name, e));
704 }
705 }
706 } else {
707 tracing::info!("Unloading disabled plugin: {}", name);
709 let unload_result = self.plugin_manager.read().unwrap().unload_plugin(&name);
710 if let Err(e) = unload_result {
711 tracing::error!("Failed to unload plugin '{}': {}", name, e);
712 self.set_status_message(format!("Failed to unload plugin '{}': {}", name, e));
713 }
714 }
715 }
716 }
717}