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
154 .windows
155 .get_mut(&__active_id)
156 .and_then(|w| w.lsp.as_mut())
157 {
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 explorer.set_compact_directories(self.config.file_explorer.compact_directories);
206 }
207
208 #[cfg(windows)]
211 if old_mouse_hover != self.config.editor.mouse_hover_enabled {
212 let mode = if self.config.editor.mouse_hover_enabled {
213 fresh_winterm::MouseMode::AllMotion
214 } else {
215 self.active_window_mut().mouse_state.lsp_hover_state = None;
217 self.active_window_mut().mouse_state.lsp_hover_request_sent = false;
218 fresh_winterm::MouseMode::CellMotion
219 };
220 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
221 tracing::error!("Failed to switch mouse mode: {}", e);
222 }
223 }
224
225 for (_, state) in self
228 .windows
229 .get_mut(&self.active_window)
230 .map(|w| &mut w.buffers)
231 .expect("active window present")
232 {
233 let mut whitespace =
234 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
235 state.buffer_settings.auto_close = self.config.editor.auto_close;
236 if let Some(lang_config) = self.config.languages.get(&state.language) {
237 state.buffer_settings.tab_size =
238 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
239 state.buffer_settings.use_tabs =
240 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
241 whitespace =
242 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
243 if state.buffer_settings.auto_close {
245 if let Some(lang_auto_close) = lang_config.auto_close {
246 state.buffer_settings.auto_close = lang_auto_close;
247 }
248 }
249 if let Some(ref wc) = lang_config.word_characters {
251 state.buffer_settings.word_characters = wc.clone();
252 } else {
253 state.buffer_settings.word_characters.clear();
254 }
255 } else {
256 state.buffer_settings.tab_size = self.config.editor.tab_size;
257 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
258 }
259 state.buffer_settings.whitespace = whitespace;
260 }
261
262 let resolver =
264 ConfigResolver::new(self.dir_context.clone(), self.working_dir().to_path_buf());
265
266 let layer_name = match target_layer {
267 ConfigLayer::User => "User",
268 ConfigLayer::Project => "Project",
269 ConfigLayer::Session => "Session",
270 ConfigLayer::System => "System", };
272
273 match resolver.save_changes_to_layer(&pending_changes, &pending_deletions, target_layer) {
274 Ok(()) => {
275 self.set_status_message(
276 t!("settings.saved_to_layer", layer = layer_name).to_string(),
277 );
278 self.settings_state = None;
282 }
283 Err(e) => {
284 self.set_status_message(
285 t!("settings.failed_to_save", error = e.to_string()).to_string(),
286 );
287 }
288 }
289 }
290
291 pub fn open_config_file(&mut self, layer: ConfigLayer) -> AnyhowResult<()> {
295 if let Some(ref state) = self.settings_state {
297 if state.has_changes() {
298 self.set_status_message(t!("settings.pending_changes").to_string());
299 return Ok(());
300 }
301 }
302
303 let resolver =
304 ConfigResolver::new(self.dir_context.clone(), self.working_dir().to_path_buf());
305
306 let path = match layer {
307 ConfigLayer::User => resolver.user_config_path(),
308 ConfigLayer::Project => resolver.project_config_write_path(),
309 ConfigLayer::Session => resolver.session_config_path(),
310 ConfigLayer::System => {
311 self.set_status_message(t!("settings.cannot_edit_system").to_string());
312 return Ok(());
313 }
314 };
315
316 if let Some(parent) = path.parent() {
318 self.authority.filesystem.create_dir_all(parent)?;
319 }
320
321 if !self.authority.filesystem.exists(&path) {
323 let template = match layer {
324 ConfigLayer::User => {
325 r#"{
326 "version": 1,
327 "theme": "default",
328 "editor": {
329 "tab_size": 4,
330 "line_numbers": true
331 }
332}
333"#
334 }
335 ConfigLayer::Project => {
336 r#"{
337 "version": 1,
338 "editor": {
339 "tab_size": 4
340 },
341 "languages": {}
342}
343"#
344 }
345 ConfigLayer::Session => {
346 r#"{
347 "version": 1
348}
349"#
350 }
351 ConfigLayer::System => unreachable!(),
352 };
353 self.authority
354 .filesystem
355 .write_file(&path, template.as_bytes())?;
356 }
357
358 self.settings_state = None;
360 match self.open_file(&path) {
361 Ok(_) => {
362 let layer_name = match layer {
363 ConfigLayer::User => "User",
364 ConfigLayer::Project => "Project",
365 ConfigLayer::Session => "Session",
366 ConfigLayer::System => "System",
367 };
368 self.set_status_message(
369 t!(
370 "settings.editing_config",
371 layer = layer_name,
372 path = path.display().to_string()
373 )
374 .to_string(),
375 );
376 }
377 Err(e) => {
378 if let Some(confirmation) =
380 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
381 {
382 self.start_large_file_encoding_confirmation(confirmation);
383 } else {
384 self.set_status_message(
385 t!("file.error_opening", error = e.to_string()).to_string(),
386 );
387 }
388 }
389 }
390
391 Ok(())
392 }
393
394 pub fn settings_navigate_up(&mut self) {
396 if let Some(ref mut state) = self.settings_state {
397 state.select_prev();
398 }
399 }
400
401 pub fn settings_navigate_down(&mut self) {
403 if let Some(ref mut state) = self.settings_state {
404 state.select_next();
405 }
406 }
407
408 pub fn settings_activate_current(&mut self) {
410 use crate::view::settings::items::SettingControl;
411 use crate::view::settings::FocusPanel;
412
413 let focus_panel = self
415 .settings_state
416 .as_ref()
417 .map(|s| s.focus_panel())
418 .unwrap_or(FocusPanel::Categories);
419
420 if focus_panel == FocusPanel::Footer {
421 let button_index = self
422 .settings_state
423 .as_ref()
424 .map(|s| s.footer_button_index)
425 .unwrap_or(2);
426 match button_index {
427 0 => {
428 if let Some(ref mut state) = self.settings_state {
430 state.cycle_target_layer();
431 }
432 }
433 1 => {
434 if let Some(ref mut state) = self.settings_state {
437 let is_nullable_set = state
438 .current_item()
439 .map(|item| item.nullable && !item.is_null)
440 .unwrap_or(false);
441 if is_nullable_set {
442 state.set_current_to_null();
443 } else {
444 state.reset_current_to_default();
445 }
446 }
447 }
448 2 => {
449 self.close_settings(true);
451 }
452 3 => {
453 self.close_settings(false);
455 }
456 _ => {}
457 }
458 return;
459 }
460
461 if focus_panel == FocusPanel::Categories {
464 return;
465 }
466
467 let control_type = {
469 if let Some(ref state) = self.settings_state {
470 state.current_item().map(|item| match &item.control {
471 SettingControl::Toggle(_) => "toggle",
472 SettingControl::Number(_) => "number",
473 SettingControl::Dropdown(_) => "dropdown",
474 SettingControl::Text(_) => "text",
475 SettingControl::TextList(_) => "textlist",
476 SettingControl::DualList(_) => "duallist",
477 SettingControl::Map(_) => "map",
478 SettingControl::ObjectArray(_) => "objectarray",
479 SettingControl::Json(_) => "json",
480 SettingControl::Complex { .. } => "complex",
481 })
482 } else {
483 None
484 }
485 };
486
487 match control_type {
489 Some("toggle") => {
490 if let Some(ref mut state) = self.settings_state {
491 if let Some(item) = state.current_item_mut() {
492 if let SettingControl::Toggle(ref mut toggle_state) = item.control {
493 toggle_state.checked = !toggle_state.checked;
494 }
495 }
496 state.on_value_changed();
497 }
498 }
499 Some("dropdown") => {
500 if let Some(ref mut state) = self.settings_state {
502 if state.is_dropdown_open() {
503 state.dropdown_confirm();
504 } else {
505 state.dropdown_toggle();
506 }
507 }
508 }
509 Some("textlist") => {
510 if let Some(ref mut state) = self.settings_state {
512 state.start_editing();
513 }
514 }
515 Some("map") => {
516 if let Some(ref mut state) = self.settings_state {
518 if let Some(item) = state.current_item_mut() {
519 if let SettingControl::Map(ref mut map_state) = item.control {
520 if map_state.focused_entry.is_none() {
521 if map_state.value_schema.is_some() {
523 state.open_add_entry_dialog();
524 }
525 } else if map_state.value_schema.is_some() {
526 state.open_entry_dialog();
528 } else {
529 if let Some(idx) = map_state.focused_entry {
531 if map_state.expanded.contains(&idx) {
532 map_state.expanded.retain(|&i| i != idx);
533 } else {
534 map_state.expanded.push(idx);
535 }
536 }
537 }
538 }
539 }
540 state.on_value_changed();
541 }
542 }
543 Some("text") => {
544 if let Some(ref mut state) = self.settings_state {
546 state.start_editing();
547 }
548 }
549 Some("number") => {
550 if let Some(ref mut state) = self.settings_state {
552 state.start_number_editing();
553 }
554 }
555 _ => {}
556 }
557 }
558
559 pub fn settings_increment_current(&mut self) {
561 use crate::view::settings::items::SettingControl;
562 use crate::view::settings::FocusPanel;
563
564 let focus_panel = self
566 .settings_state
567 .as_ref()
568 .map(|s| s.focus_panel())
569 .unwrap_or(FocusPanel::Categories);
570
571 if focus_panel == FocusPanel::Footer {
572 if let Some(ref mut state) = self.settings_state {
573 state.footer_button_index = (state.footer_button_index + 1) % 4;
575 }
576 return;
577 }
578
579 if focus_panel == FocusPanel::Categories {
581 return;
582 }
583
584 let control_type = {
585 if let Some(ref state) = self.settings_state {
586 state.current_item().map(|item| match &item.control {
587 SettingControl::Number(_) => "number",
588 SettingControl::Dropdown(_) => "dropdown",
589 _ => "other",
590 })
591 } else {
592 None
593 }
594 };
595
596 match control_type {
597 Some("dropdown") => {
600 if let Some(ref mut state) = self.settings_state {
601 if let Some(item) = state.current_item_mut() {
602 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
603 dropdown_state.select_next();
604 }
605 }
606 state.on_value_changed();
607 }
608 }
609 _ => {}
610 }
611 }
612
613 pub fn settings_decrement_current(&mut self) {
615 use crate::view::settings::items::SettingControl;
616 use crate::view::settings::FocusPanel;
617
618 let focus_panel = self
620 .settings_state
621 .as_ref()
622 .map(|s| s.focus_panel())
623 .unwrap_or(FocusPanel::Categories);
624
625 if focus_panel == FocusPanel::Footer {
626 if let Some(ref mut state) = self.settings_state {
627 state.footer_button_index = if state.footer_button_index == 0 {
629 3
630 } else {
631 state.footer_button_index - 1
632 };
633 }
634 return;
635 }
636
637 if focus_panel == FocusPanel::Categories {
639 return;
640 }
641
642 let control_type = {
643 if let Some(ref state) = self.settings_state {
644 state.current_item().map(|item| match &item.control {
645 SettingControl::Number(_) => "number",
646 SettingControl::Dropdown(_) => "dropdown",
647 _ => "other",
648 })
649 } else {
650 None
651 }
652 };
653
654 match control_type {
655 Some("dropdown") => {
658 if let Some(ref mut state) = self.settings_state {
659 if let Some(item) = state.current_item_mut() {
660 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
661 dropdown_state.select_prev();
662 }
663 }
664 state.on_value_changed();
665 }
666 }
667 _ => {}
668 }
669 }
670
671 fn apply_plugin_config_changes(
673 &mut self,
674 old_plugins: &std::collections::HashMap<String, crate::config::PluginConfig>,
675 ) {
676 let changes: Vec<_> = self
678 .config
679 .plugins
680 .iter()
681 .filter_map(|(name, new_config)| {
682 let was_enabled = old_plugins.get(name).map(|c| c.enabled).unwrap_or(true);
683 if new_config.enabled != was_enabled {
684 Some((name.clone(), new_config.enabled, new_config.path.clone()))
685 } else {
686 None
687 }
688 })
689 .collect();
690
691 for (name, now_enabled, path) in changes {
693 if now_enabled {
694 if let Some(ref path) = path {
696 tracing::info!("Loading newly enabled plugin: {}", name);
697 let load_result = self.plugin_manager.read().unwrap().load_plugin(path);
698 if let Err(e) = load_result {
699 tracing::error!("Failed to load plugin '{}': {}", name, e);
700 self.set_status_message(format!("Failed to load plugin '{}': {}", name, e));
701 }
702 }
703 } else {
704 tracing::info!("Unloading disabled plugin: {}", name);
706 let unload_result = self.plugin_manager.write().unwrap().unload_plugin(&name);
707 if let Err(e) = unload_result {
708 tracing::error!("Failed to unload plugin '{}': {}", name, e);
709 self.set_status_message(format!("Failed to unload plugin '{}': {}", name, e));
710 } else {
711 self.remove_plugin_status_bar_elements(&name);
713 }
714 }
715 }
716 }
717}