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