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