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 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.write().unwrap() = KeybindingResolver::new(&self.config);
150
151 let __active_id = self.active_window;
153 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
154 for (language, lsp_configs) in &self.config.lsp {
155 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
156 }
157 let universal_servers: Vec<LspServerConfig> = self
159 .config
160 .universal_lsp
161 .values()
162 .flat_map(|lc| lc.as_slice().to_vec())
163 .filter(|c| c.enabled)
164 .collect();
165 lsp.set_universal_configs(universal_servers);
166 }
167
168 for view_state in self
170 .windows
171 .get_mut(&self.active_window)
172 .and_then(|w| w.split_view_states_mut())
173 .expect("active window must have a populated split layout")
174 .values_mut()
175 {
176 view_state.show_line_numbers = self.config.editor.line_numbers;
177 for buf_state in view_state.keyed_states.values_mut() {
178 buf_state.rulers = self.config.editor.rulers.clone();
179 }
180 }
181
182 self.active_window_mut().menu_bar_visible = self.config.editor.show_menu_bar;
184 self.active_window_mut().tab_bar_visible = self.config.editor.show_tab_bar;
185 self.active_window_mut().status_bar_visible = self.config.editor.show_status_bar;
186 self.active_window_mut().prompt_line_visible = self.config.editor.show_prompt_line;
187
188 self.active_window_mut().file_explorer_width = self.config.file_explorer.width;
191 self.active_window_mut().file_explorer_side = self.config.file_explorer.side;
192 let active_id = self.active_window;
193 if let Some(explorer) = self
194 .windows
195 .get_mut(&active_id)
196 .and_then(|w| w.file_explorer.as_mut())
197 {
198 let patterns = explorer.ignore_patterns_mut();
199 patterns.set_show_hidden(self.config.file_explorer.show_hidden);
200 patterns.set_show_gitignored(self.config.file_explorer.show_gitignored);
201 explorer.set_compact_directories(self.config.file_explorer.compact_directories);
202 }
203
204 #[cfg(windows)]
207 if old_mouse_hover != self.config.editor.mouse_hover_enabled {
208 let mode = if self.config.editor.mouse_hover_enabled {
209 fresh_winterm::MouseMode::AllMotion
210 } else {
211 self.active_window_mut().mouse_state.lsp_hover_state = None;
213 self.active_window_mut().mouse_state.lsp_hover_request_sent = false;
214 fresh_winterm::MouseMode::CellMotion
215 };
216 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
217 tracing::error!("Failed to switch mouse mode: {}", e);
218 }
219 }
220
221 for (_, state) in self
224 .windows
225 .get_mut(&self.active_window)
226 .map(|w| &mut w.buffers)
227 .expect("active window present")
228 {
229 let mut whitespace =
230 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
231 state.buffer_settings.auto_close = self.config.editor.auto_close;
232 if let Some(lang_config) = self.config.languages.get(&state.language) {
233 state.buffer_settings.tab_size =
234 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
235 state.buffer_settings.use_tabs =
236 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
237 whitespace =
238 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
239 if state.buffer_settings.auto_close {
241 if let Some(lang_auto_close) = lang_config.auto_close {
242 state.buffer_settings.auto_close = lang_auto_close;
243 }
244 }
245 if let Some(ref wc) = lang_config.word_characters {
247 state.buffer_settings.word_characters = wc.clone();
248 } else {
249 state.buffer_settings.word_characters.clear();
250 }
251 } else {
252 state.buffer_settings.tab_size = self.config.editor.tab_size;
253 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
254 }
255 state.buffer_settings.whitespace = whitespace;
256 }
257
258 let resolver =
260 ConfigResolver::new(self.dir_context.clone(), self.working_dir().to_path_buf());
261
262 let layer_name = match target_layer {
263 ConfigLayer::User => "User",
264 ConfigLayer::Project => "Project",
265 ConfigLayer::Session => "Session",
266 ConfigLayer::System => "System", };
268
269 match resolver.save_changes_to_layer(&pending_changes, &pending_deletions, target_layer) {
270 Ok(()) => {
271 self.set_status_message(
272 t!("settings.saved_to_layer", layer = layer_name).to_string(),
273 );
274 self.settings_state = None;
278 }
279 Err(e) => {
280 self.set_status_message(
281 t!("settings.failed_to_save", error = e.to_string()).to_string(),
282 );
283 }
284 }
285 }
286
287 pub fn open_config_file(&mut self, layer: ConfigLayer) -> AnyhowResult<()> {
291 if let Some(ref state) = self.settings_state {
293 if state.has_changes() {
294 self.set_status_message(t!("settings.pending_changes").to_string());
295 return Ok(());
296 }
297 }
298
299 let resolver =
300 ConfigResolver::new(self.dir_context.clone(), self.working_dir().to_path_buf());
301
302 let path = match layer {
303 ConfigLayer::User => resolver.user_config_path(),
304 ConfigLayer::Project => resolver.project_config_write_path(),
305 ConfigLayer::Session => resolver.session_config_path(),
306 ConfigLayer::System => {
307 self.set_status_message(t!("settings.cannot_edit_system").to_string());
308 return Ok(());
309 }
310 };
311
312 if let Some(parent) = path.parent() {
314 self.authority.filesystem.create_dir_all(parent)?;
315 }
316
317 if !self.authority.filesystem.exists(&path) {
319 let template = match layer {
320 ConfigLayer::User => {
321 r#"{
322 "version": 1,
323 "theme": "default",
324 "editor": {
325 "tab_size": 4,
326 "line_numbers": true
327 }
328}
329"#
330 }
331 ConfigLayer::Project => {
332 r#"{
333 "version": 1,
334 "editor": {
335 "tab_size": 4
336 },
337 "languages": {}
338}
339"#
340 }
341 ConfigLayer::Session => {
342 r#"{
343 "version": 1
344}
345"#
346 }
347 ConfigLayer::System => unreachable!(),
348 };
349 self.authority
350 .filesystem
351 .write_file(&path, template.as_bytes())?;
352 }
353
354 self.settings_state = None;
356 match self.open_file(&path) {
357 Ok(_) => {
358 let layer_name = match layer {
359 ConfigLayer::User => "User",
360 ConfigLayer::Project => "Project",
361 ConfigLayer::Session => "Session",
362 ConfigLayer::System => "System",
363 };
364 self.set_status_message(
365 t!(
366 "settings.editing_config",
367 layer = layer_name,
368 path = path.display().to_string()
369 )
370 .to_string(),
371 );
372 }
373 Err(e) => {
374 if let Some(confirmation) =
376 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
377 {
378 self.start_large_file_encoding_confirmation(confirmation);
379 } else {
380 self.set_status_message(
381 t!("file.error_opening", error = e.to_string()).to_string(),
382 );
383 }
384 }
385 }
386
387 Ok(())
388 }
389
390 pub fn settings_navigate_up(&mut self) {
392 if let Some(ref mut state) = self.settings_state {
393 state.select_prev();
394 }
395 }
396
397 pub fn settings_navigate_down(&mut self) {
399 if let Some(ref mut state) = self.settings_state {
400 state.select_next();
401 }
402 }
403
404 pub fn settings_activate_current(&mut self) {
406 use crate::view::settings::items::SettingControl;
407 use crate::view::settings::FocusPanel;
408
409 let focus_panel = self
411 .settings_state
412 .as_ref()
413 .map(|s| s.focus_panel())
414 .unwrap_or(FocusPanel::Categories);
415
416 if focus_panel == FocusPanel::Footer {
417 let button_index = self
418 .settings_state
419 .as_ref()
420 .map(|s| s.footer_button_index)
421 .unwrap_or(2);
422 match button_index {
423 0 => {
424 if let Some(ref mut state) = self.settings_state {
426 state.cycle_target_layer();
427 }
428 }
429 1 => {
430 if let Some(ref mut state) = self.settings_state {
433 let is_nullable_set = state
434 .current_item()
435 .map(|item| item.nullable && !item.is_null)
436 .unwrap_or(false);
437 if is_nullable_set {
438 state.set_current_to_null();
439 } else {
440 state.reset_current_to_default();
441 }
442 }
443 }
444 2 => {
445 self.close_settings(true);
447 }
448 3 => {
449 self.close_settings(false);
451 }
452 _ => {}
453 }
454 return;
455 }
456
457 if focus_panel == FocusPanel::Categories {
460 return;
461 }
462
463 let control_type = {
465 if let Some(ref state) = self.settings_state {
466 state.current_item().map(|item| match &item.control {
467 SettingControl::Toggle(_) => "toggle",
468 SettingControl::Number(_) => "number",
469 SettingControl::Dropdown(_) => "dropdown",
470 SettingControl::Text(_) => "text",
471 SettingControl::TextList(_) => "textlist",
472 SettingControl::DualList(_) => "duallist",
473 SettingControl::Map(_) => "map",
474 SettingControl::ObjectArray(_) => "objectarray",
475 SettingControl::Json(_) => "json",
476 SettingControl::Complex { .. } => "complex",
477 })
478 } else {
479 None
480 }
481 };
482
483 match control_type {
485 Some("toggle") => {
486 if let Some(ref mut state) = self.settings_state {
487 if let Some(item) = state.current_item_mut() {
488 if let SettingControl::Toggle(ref mut toggle_state) = item.control {
489 toggle_state.checked = !toggle_state.checked;
490 }
491 }
492 state.on_value_changed();
493 }
494 }
495 Some("dropdown") => {
496 if let Some(ref mut state) = self.settings_state {
498 if state.is_dropdown_open() {
499 state.dropdown_confirm();
500 } else {
501 state.dropdown_toggle();
502 }
503 }
504 }
505 Some("textlist") => {
506 if let Some(ref mut state) = self.settings_state {
508 state.start_editing();
509 }
510 }
511 Some("map") => {
512 if let Some(ref mut state) = self.settings_state {
514 if let Some(item) = state.current_item_mut() {
515 if let SettingControl::Map(ref mut map_state) = item.control {
516 if map_state.focused_entry.is_none() {
517 if map_state.value_schema.is_some() {
519 state.open_add_entry_dialog();
520 }
521 } else if map_state.value_schema.is_some() {
522 state.open_entry_dialog();
524 } else {
525 if let Some(idx) = map_state.focused_entry {
527 if map_state.expanded.contains(&idx) {
528 map_state.expanded.retain(|&i| i != idx);
529 } else {
530 map_state.expanded.push(idx);
531 }
532 }
533 }
534 }
535 }
536 state.on_value_changed();
537 }
538 }
539 Some("text") => {
540 if let Some(ref mut state) = self.settings_state {
542 state.start_editing();
543 }
544 }
545 Some("number") => {
546 if let Some(ref mut state) = self.settings_state {
548 state.start_number_editing();
549 }
550 }
551 _ => {}
552 }
553 }
554
555 pub fn settings_increment_current(&mut self) {
557 use crate::view::settings::items::SettingControl;
558 use crate::view::settings::FocusPanel;
559
560 let focus_panel = self
562 .settings_state
563 .as_ref()
564 .map(|s| s.focus_panel())
565 .unwrap_or(FocusPanel::Categories);
566
567 if focus_panel == FocusPanel::Footer {
568 if let Some(ref mut state) = self.settings_state {
569 state.footer_button_index = (state.footer_button_index + 1) % 4;
571 }
572 return;
573 }
574
575 if focus_panel == FocusPanel::Categories {
577 return;
578 }
579
580 let control_type = {
581 if let Some(ref state) = self.settings_state {
582 state.current_item().map(|item| match &item.control {
583 SettingControl::Number(_) => "number",
584 SettingControl::Dropdown(_) => "dropdown",
585 _ => "other",
586 })
587 } else {
588 None
589 }
590 };
591
592 match control_type {
593 Some("dropdown") => {
596 if let Some(ref mut state) = self.settings_state {
597 if let Some(item) = state.current_item_mut() {
598 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
599 dropdown_state.select_next();
600 }
601 }
602 state.on_value_changed();
603 }
604 }
605 _ => {}
606 }
607 }
608
609 pub fn settings_decrement_current(&mut self) {
611 use crate::view::settings::items::SettingControl;
612 use crate::view::settings::FocusPanel;
613
614 let focus_panel = self
616 .settings_state
617 .as_ref()
618 .map(|s| s.focus_panel())
619 .unwrap_or(FocusPanel::Categories);
620
621 if focus_panel == FocusPanel::Footer {
622 if let Some(ref mut state) = self.settings_state {
623 state.footer_button_index = if state.footer_button_index == 0 {
625 3
626 } else {
627 state.footer_button_index - 1
628 };
629 }
630 return;
631 }
632
633 if focus_panel == FocusPanel::Categories {
635 return;
636 }
637
638 let control_type = {
639 if let Some(ref state) = self.settings_state {
640 state.current_item().map(|item| match &item.control {
641 SettingControl::Number(_) => "number",
642 SettingControl::Dropdown(_) => "dropdown",
643 _ => "other",
644 })
645 } else {
646 None
647 }
648 };
649
650 match control_type {
651 Some("dropdown") => {
654 if let Some(ref mut state) = self.settings_state {
655 if let Some(item) = state.current_item_mut() {
656 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
657 dropdown_state.select_prev();
658 }
659 }
660 state.on_value_changed();
661 }
662 }
663 _ => {}
664 }
665 }
666
667 fn apply_plugin_config_changes(
669 &mut self,
670 old_plugins: &std::collections::HashMap<String, crate::config::PluginConfig>,
671 ) {
672 let changes: Vec<_> = self
674 .config
675 .plugins
676 .iter()
677 .filter_map(|(name, new_config)| {
678 let was_enabled = old_plugins.get(name).map(|c| c.enabled).unwrap_or(true);
679 if new_config.enabled != was_enabled {
680 Some((name.clone(), new_config.enabled, new_config.path.clone()))
681 } else {
682 None
683 }
684 })
685 .collect();
686
687 for (name, now_enabled, path) in changes {
689 if now_enabled {
690 if let Some(ref path) = path {
692 tracing::info!("Loading newly enabled plugin: {}", name);
693 let load_result = self.plugin_manager.read().unwrap().load_plugin(path);
694 if let Err(e) = load_result {
695 tracing::error!("Failed to load plugin '{}': {}", name, e);
696 self.set_status_message(format!("Failed to load plugin '{}': {}", name, e));
697 }
698 }
699 } else {
700 tracing::info!("Unloading disabled plugin: {}", name);
702 let unload_result = self.plugin_manager.write().unwrap().unload_plugin(&name);
703 if let Err(e) = unload_result {
704 tracing::error!("Failed to unload plugin '{}': {}", name, e);
705 self.set_status_message(format!("Failed to unload plugin '{}': {}", name, e));
706 } else {
707 self.remove_plugin_status_bar_elements(&name);
709 }
710 }
711 }
712 }
713}