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