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