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.config = new_config.clone();
100
101 self.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 #[cfg(windows)]
173 if old_mouse_hover != self.config.editor.mouse_hover_enabled {
174 let mode = if self.config.editor.mouse_hover_enabled {
175 fresh_winterm::MouseMode::AllMotion
176 } else {
177 self.mouse_state.lsp_hover_state = None;
179 self.mouse_state.lsp_hover_request_sent = false;
180 fresh_winterm::MouseMode::CellMotion
181 };
182 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
183 tracing::error!("Failed to switch mouse mode: {}", e);
184 }
185 }
186
187 for state in self.buffers.values_mut() {
190 let mut whitespace =
191 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
192 state.buffer_settings.auto_close = self.config.editor.auto_close;
193 if let Some(lang_config) = self.config.languages.get(&state.language) {
194 state.buffer_settings.tab_size =
195 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
196 state.buffer_settings.use_tabs =
197 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
198 whitespace =
199 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
200 if state.buffer_settings.auto_close {
202 if let Some(lang_auto_close) = lang_config.auto_close {
203 state.buffer_settings.auto_close = lang_auto_close;
204 }
205 }
206 if let Some(ref wc) = lang_config.word_characters {
208 state.buffer_settings.word_characters = wc.clone();
209 } else {
210 state.buffer_settings.word_characters.clear();
211 }
212 } else {
213 state.buffer_settings.tab_size = self.config.editor.tab_size;
214 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
215 }
216 state.buffer_settings.whitespace = whitespace;
217 }
218
219 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
221
222 let layer_name = match target_layer {
223 ConfigLayer::User => "User",
224 ConfigLayer::Project => "Project",
225 ConfigLayer::Session => "Session",
226 ConfigLayer::System => "System", };
228
229 match resolver.save_changes_to_layer(&pending_changes, &pending_deletions, target_layer) {
230 Ok(()) => {
231 self.set_status_message(
232 t!("settings.saved_to_layer", layer = layer_name).to_string(),
233 );
234 self.settings_state = None;
238 }
239 Err(e) => {
240 self.set_status_message(
241 t!("settings.failed_to_save", error = e.to_string()).to_string(),
242 );
243 }
244 }
245 }
246
247 pub fn open_config_file(&mut self, layer: ConfigLayer) -> AnyhowResult<()> {
251 if let Some(ref state) = self.settings_state {
253 if state.has_changes() {
254 self.set_status_message(t!("settings.pending_changes").to_string());
255 return Ok(());
256 }
257 }
258
259 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
260
261 let path = match layer {
262 ConfigLayer::User => resolver.user_config_path(),
263 ConfigLayer::Project => resolver.project_config_write_path(),
264 ConfigLayer::Session => resolver.session_config_path(),
265 ConfigLayer::System => {
266 self.set_status_message(t!("settings.cannot_edit_system").to_string());
267 return Ok(());
268 }
269 };
270
271 if let Some(parent) = path.parent() {
273 self.filesystem.create_dir_all(parent)?;
274 }
275
276 if !self.filesystem.exists(&path) {
278 let template = match layer {
279 ConfigLayer::User => {
280 r#"{
281 "version": 1,
282 "theme": "default",
283 "editor": {
284 "tab_size": 4,
285 "line_numbers": true
286 }
287}
288"#
289 }
290 ConfigLayer::Project => {
291 r#"{
292 "version": 1,
293 "editor": {
294 "tab_size": 4
295 },
296 "languages": {}
297}
298"#
299 }
300 ConfigLayer::Session => {
301 r#"{
302 "version": 1
303}
304"#
305 }
306 ConfigLayer::System => unreachable!(),
307 };
308 self.filesystem.write_file(&path, template.as_bytes())?;
309 }
310
311 self.settings_state = None;
313 match self.open_file(&path) {
314 Ok(_) => {
315 let layer_name = match layer {
316 ConfigLayer::User => "User",
317 ConfigLayer::Project => "Project",
318 ConfigLayer::Session => "Session",
319 ConfigLayer::System => "System",
320 };
321 self.set_status_message(
322 t!(
323 "settings.editing_config",
324 layer = layer_name,
325 path = path.display().to_string()
326 )
327 .to_string(),
328 );
329 }
330 Err(e) => {
331 if let Some(confirmation) =
333 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
334 {
335 self.start_large_file_encoding_confirmation(confirmation);
336 } else {
337 self.set_status_message(
338 t!("file.error_opening", error = e.to_string()).to_string(),
339 );
340 }
341 }
342 }
343
344 Ok(())
345 }
346
347 pub fn settings_navigate_up(&mut self) {
349 if let Some(ref mut state) = self.settings_state {
350 state.select_prev();
351 }
352 }
353
354 pub fn settings_navigate_down(&mut self) {
356 if let Some(ref mut state) = self.settings_state {
357 state.select_next();
358 }
359 }
360
361 pub fn settings_activate_current(&mut self) {
363 use crate::view::settings::items::SettingControl;
364 use crate::view::settings::FocusPanel;
365
366 let focus_panel = self
368 .settings_state
369 .as_ref()
370 .map(|s| s.focus_panel())
371 .unwrap_or(FocusPanel::Categories);
372
373 if focus_panel == FocusPanel::Footer {
374 let button_index = self
375 .settings_state
376 .as_ref()
377 .map(|s| s.footer_button_index)
378 .unwrap_or(2);
379 match button_index {
380 0 => {
381 if let Some(ref mut state) = self.settings_state {
383 state.cycle_target_layer();
384 }
385 }
386 1 => {
387 if let Some(ref mut state) = self.settings_state {
390 let is_nullable_set = state
391 .current_item()
392 .map(|item| item.nullable && !item.is_null)
393 .unwrap_or(false);
394 if is_nullable_set {
395 state.set_current_to_null();
396 } else {
397 state.reset_current_to_default();
398 }
399 }
400 }
401 2 => {
402 self.close_settings(true);
404 }
405 3 => {
406 self.close_settings(false);
408 }
409 _ => {}
410 }
411 return;
412 }
413
414 if focus_panel == FocusPanel::Categories {
417 return;
418 }
419
420 let control_type = {
422 if let Some(ref state) = self.settings_state {
423 state.current_item().map(|item| match &item.control {
424 SettingControl::Toggle(_) => "toggle",
425 SettingControl::Number(_) => "number",
426 SettingControl::Dropdown(_) => "dropdown",
427 SettingControl::Text(_) => "text",
428 SettingControl::TextList(_) => "textlist",
429 SettingControl::DualList(_) => "duallist",
430 SettingControl::Map(_) => "map",
431 SettingControl::ObjectArray(_) => "objectarray",
432 SettingControl::Json(_) => "json",
433 SettingControl::Complex { .. } => "complex",
434 })
435 } else {
436 None
437 }
438 };
439
440 match control_type {
442 Some("toggle") => {
443 if let Some(ref mut state) = self.settings_state {
444 if let Some(item) = state.current_item_mut() {
445 if let SettingControl::Toggle(ref mut toggle_state) = item.control {
446 toggle_state.checked = !toggle_state.checked;
447 }
448 }
449 state.on_value_changed();
450 }
451 }
452 Some("dropdown") => {
453 if let Some(ref mut state) = self.settings_state {
455 if state.is_dropdown_open() {
456 state.dropdown_confirm();
457 } else {
458 state.dropdown_toggle();
459 }
460 }
461 }
462 Some("textlist") => {
463 if let Some(ref mut state) = self.settings_state {
465 state.start_editing();
466 }
467 }
468 Some("map") => {
469 if let Some(ref mut state) = self.settings_state {
471 if let Some(item) = state.current_item_mut() {
472 if let SettingControl::Map(ref mut map_state) = item.control {
473 if map_state.focused_entry.is_none() {
474 if map_state.value_schema.is_some() {
476 state.open_add_entry_dialog();
477 }
478 } else if map_state.value_schema.is_some() {
479 state.open_entry_dialog();
481 } else {
482 if let Some(idx) = map_state.focused_entry {
484 if map_state.expanded.contains(&idx) {
485 map_state.expanded.retain(|&i| i != idx);
486 } else {
487 map_state.expanded.push(idx);
488 }
489 }
490 }
491 }
492 }
493 state.on_value_changed();
494 }
495 }
496 Some("text") => {
497 if let Some(ref mut state) = self.settings_state {
499 state.start_editing();
500 }
501 }
502 Some("number") => {
503 if let Some(ref mut state) = self.settings_state {
505 state.start_number_editing();
506 }
507 }
508 _ => {}
509 }
510 }
511
512 pub fn settings_increment_current(&mut self) {
514 use crate::view::settings::items::SettingControl;
515 use crate::view::settings::FocusPanel;
516
517 let focus_panel = self
519 .settings_state
520 .as_ref()
521 .map(|s| s.focus_panel())
522 .unwrap_or(FocusPanel::Categories);
523
524 if focus_panel == FocusPanel::Footer {
525 if let Some(ref mut state) = self.settings_state {
526 state.footer_button_index = (state.footer_button_index + 1) % 4;
528 }
529 return;
530 }
531
532 if focus_panel == FocusPanel::Categories {
534 return;
535 }
536
537 let control_type = {
538 if let Some(ref state) = self.settings_state {
539 state.current_item().map(|item| match &item.control {
540 SettingControl::Number(_) => "number",
541 SettingControl::Dropdown(_) => "dropdown",
542 _ => "other",
543 })
544 } else {
545 None
546 }
547 };
548
549 match control_type {
550 Some("number") => {
551 if let Some(ref mut state) = self.settings_state {
552 if let Some(item) = state.current_item_mut() {
553 if let SettingControl::Number(ref mut num_state) = item.control {
554 num_state.increment();
555 }
556 }
557 state.on_value_changed();
558 }
559 }
560 Some("dropdown") => {
561 if let Some(ref mut state) = self.settings_state {
562 if let Some(item) = state.current_item_mut() {
563 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
564 dropdown_state.select_next();
565 }
566 }
567 state.on_value_changed();
568 }
569 }
570 _ => {}
571 }
572 }
573
574 pub fn settings_decrement_current(&mut self) {
576 use crate::view::settings::items::SettingControl;
577 use crate::view::settings::FocusPanel;
578
579 let focus_panel = self
581 .settings_state
582 .as_ref()
583 .map(|s| s.focus_panel())
584 .unwrap_or(FocusPanel::Categories);
585
586 if focus_panel == FocusPanel::Footer {
587 if let Some(ref mut state) = self.settings_state {
588 state.footer_button_index = if state.footer_button_index == 0 {
590 3
591 } else {
592 state.footer_button_index - 1
593 };
594 }
595 return;
596 }
597
598 if focus_panel == FocusPanel::Categories {
600 return;
601 }
602
603 let control_type = {
604 if let Some(ref state) = self.settings_state {
605 state.current_item().map(|item| match &item.control {
606 SettingControl::Number(_) => "number",
607 SettingControl::Dropdown(_) => "dropdown",
608 _ => "other",
609 })
610 } else {
611 None
612 }
613 };
614
615 match control_type {
616 Some("number") => {
617 if let Some(ref mut state) = self.settings_state {
618 if let Some(item) = state.current_item_mut() {
619 if let SettingControl::Number(ref mut num_state) = item.control {
620 num_state.decrement();
621 }
622 }
623 state.on_value_changed();
624 }
625 }
626 Some("dropdown") => {
627 if let Some(ref mut state) = self.settings_state {
628 if let Some(item) = state.current_item_mut() {
629 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
630 dropdown_state.select_prev();
631 }
632 }
633 state.on_value_changed();
634 }
635 }
636 _ => {}
637 }
638 }
639
640 fn apply_plugin_config_changes(
642 &mut self,
643 old_plugins: &std::collections::HashMap<String, crate::config::PluginConfig>,
644 ) {
645 let changes: Vec<_> = self
647 .config
648 .plugins
649 .iter()
650 .filter_map(|(name, new_config)| {
651 let was_enabled = old_plugins.get(name).map(|c| c.enabled).unwrap_or(true);
652 if new_config.enabled != was_enabled {
653 Some((name.clone(), new_config.enabled, new_config.path.clone()))
654 } else {
655 None
656 }
657 })
658 .collect();
659
660 for (name, now_enabled, path) in changes {
662 if now_enabled {
663 if let Some(ref path) = path {
665 tracing::info!("Loading newly enabled plugin: {}", name);
666 if let Err(e) = self.plugin_manager.load_plugin(path) {
667 tracing::error!("Failed to load plugin '{}': {}", name, e);
668 self.set_status_message(format!("Failed to load plugin '{}': {}", name, e));
669 }
670 }
671 } else {
672 tracing::info!("Unloading disabled plugin: {}", name);
674 if let Err(e) = self.plugin_manager.unload_plugin(&name) {
675 tracing::error!("Failed to unload plugin '{}': {}", name, e);
676 self.set_status_message(format!("Failed to unload plugin '{}': {}", name, e));
677 }
678 }
679 }
680 }
681}