1use crate::config::Config;
11use crate::config_io::{ConfigLayer, ConfigResolver};
12use crate::input::keybindings::KeybindingResolver;
13use anyhow::Result as AnyhowResult;
14use rust_i18n::t;
15
16use super::Editor;
17
18impl Editor {
19 pub fn open_settings(&mut self) {
21 const SCHEMA_JSON: &str = include_str!("../../plugins/config-schema.json");
23
24 if self.settings_state.is_none() {
26 match crate::view::settings::SettingsState::new(SCHEMA_JSON, &self.config) {
27 Ok(mut state) => {
28 let resolver =
30 ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
31 if let Ok(sources) = resolver.get_layer_sources() {
32 state.set_layer_sources(sources);
33 }
34 state.show();
35 self.settings_state = Some(state);
36 }
37 Err(e) => {
38 self.set_status_message(
39 t!("settings.failed_to_open", error = e.to_string()).to_string(),
40 );
41 }
42 }
43 } else if let Some(ref mut state) = self.settings_state {
44 state.show();
45 }
46 }
47
48 pub fn close_settings(&mut self, save: bool) {
52 if save {
53 self.save_settings();
54 }
55 if let Some(ref mut state) = self.settings_state {
56 if !save && state.has_changes() {
57 state.discard_changes();
59 }
60 state.hide();
61 }
62 }
63
64 pub fn save_settings(&mut self) {
66 let old_theme = self.config.theme.clone();
67 let old_locale = self.config.locale.clone();
68 let old_plugins = self.config.plugins.clone();
69 #[cfg(windows)]
70 let old_mouse_hover = self.config.editor.mouse_hover_enabled;
71
72 let (target_layer, new_config, pending_changes, pending_deletions) = {
74 if let Some(ref state) = self.settings_state {
75 if !state.has_changes() {
76 return;
77 }
78 match state.apply_changes(&self.config) {
79 Ok(config) => (
80 state.target_layer,
81 config,
82 state.pending_changes.clone(),
83 state.pending_deletions.clone(),
84 ),
85 Err(e) => {
86 self.set_status_message(
87 t!("settings.failed_to_apply", error = e.to_string()).to_string(),
88 );
89 return;
90 }
91 }
92 } else {
93 return;
94 }
95 };
96
97 self.config = new_config.clone();
99
100 self.user_config_raw = Config::read_user_config_raw(&self.working_dir);
102
103 if old_theme != self.config.theme {
105 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
106 self.theme = theme;
107 tracing::info!("Theme changed to '{}'", self.config.theme.0);
108 } else {
109 tracing::error!("Theme '{}' not found", self.config.theme.0);
110 self.set_status_message(format!("Theme '{}' not found", self.config.theme.0));
111 }
112 }
113
114 if old_locale != self.config.locale {
116 if let Some(locale) = self.config.locale.as_option() {
117 crate::i18n::set_locale(locale);
118 self.menus = crate::config::MenuConfig::translated();
120 tracing::info!("Locale changed to '{}'", locale);
121 } else {
122 crate::i18n::init();
124 self.menus = crate::config::MenuConfig::translated();
125 tracing::info!("Locale reset to auto-detect");
126 }
127 if let Ok(mut registry) = self.command_registry.write() {
129 registry.refresh_builtin_commands();
130 }
131 }
132
133 self.apply_plugin_config_changes(&old_plugins);
135
136 self.keybindings = KeybindingResolver::new(&self.config);
138
139 if let Some(ref mut lsp) = self.lsp {
141 for (language, lsp_configs) in &self.config.lsp {
142 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
143 }
144 }
145
146 for view_state in self.split_view_states.values_mut() {
148 view_state.show_line_numbers = self.config.editor.line_numbers;
149 for buf_state in view_state.keyed_states.values_mut() {
150 buf_state.rulers = self.config.editor.rulers.clone();
151 }
152 }
153
154 self.menu_bar_visible = self.config.editor.show_menu_bar;
156 self.tab_bar_visible = self.config.editor.show_tab_bar;
157 self.status_bar_visible = self.config.editor.show_status_bar;
158 self.prompt_line_visible = self.config.editor.show_prompt_line;
159
160 #[cfg(windows)]
163 if old_mouse_hover != self.config.editor.mouse_hover_enabled {
164 let mode = if self.config.editor.mouse_hover_enabled {
165 fresh_winterm::MouseMode::AllMotion
166 } else {
167 self.mouse_state.lsp_hover_state = None;
169 self.mouse_state.lsp_hover_request_sent = false;
170 fresh_winterm::MouseMode::CellMotion
171 };
172 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
173 tracing::error!("Failed to switch mouse mode: {}", e);
174 }
175 }
176
177 for state in self.buffers.values_mut() {
180 let mut whitespace =
181 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
182 state.buffer_settings.auto_close = self.config.editor.auto_close;
183 if let Some(lang_config) = self.config.languages.get(&state.language) {
184 state.buffer_settings.tab_size =
185 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
186 state.buffer_settings.use_tabs =
187 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs);
188 whitespace =
189 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
190 if state.buffer_settings.auto_close {
192 if let Some(lang_auto_close) = lang_config.auto_close {
193 state.buffer_settings.auto_close = lang_auto_close;
194 }
195 }
196 if let Some(ref wc) = lang_config.word_characters {
198 state.buffer_settings.word_characters = wc.clone();
199 } else {
200 state.buffer_settings.word_characters.clear();
201 }
202 } else {
203 state.buffer_settings.tab_size = self.config.editor.tab_size;
204 state.buffer_settings.use_tabs = self.config.editor.use_tabs;
205 }
206 state.buffer_settings.whitespace = whitespace;
207 }
208
209 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
211
212 let layer_name = match target_layer {
213 ConfigLayer::User => "User",
214 ConfigLayer::Project => "Project",
215 ConfigLayer::Session => "Session",
216 ConfigLayer::System => "System", };
218
219 match resolver.save_changes_to_layer(&pending_changes, &pending_deletions, target_layer) {
220 Ok(()) => {
221 self.set_status_message(
222 t!("settings.saved_to_layer", layer = layer_name).to_string(),
223 );
224 self.settings_state = None;
228 }
229 Err(e) => {
230 self.set_status_message(
231 t!("settings.failed_to_save", error = e.to_string()).to_string(),
232 );
233 }
234 }
235 }
236
237 pub fn open_config_file(&mut self, layer: ConfigLayer) -> AnyhowResult<()> {
241 if let Some(ref state) = self.settings_state {
243 if state.has_changes() {
244 self.set_status_message(t!("settings.pending_changes").to_string());
245 return Ok(());
246 }
247 }
248
249 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
250
251 let path = match layer {
252 ConfigLayer::User => resolver.user_config_path(),
253 ConfigLayer::Project => resolver.project_config_write_path(),
254 ConfigLayer::Session => resolver.session_config_path(),
255 ConfigLayer::System => {
256 self.set_status_message(t!("settings.cannot_edit_system").to_string());
257 return Ok(());
258 }
259 };
260
261 if let Some(parent) = path.parent() {
263 self.filesystem.create_dir_all(parent)?;
264 }
265
266 if !self.filesystem.exists(&path) {
268 let template = match layer {
269 ConfigLayer::User => {
270 r#"{
271 "version": 1,
272 "theme": "default",
273 "editor": {
274 "tab_size": 4,
275 "line_numbers": true
276 }
277}
278"#
279 }
280 ConfigLayer::Project => {
281 r#"{
282 "version": 1,
283 "editor": {
284 "tab_size": 4
285 },
286 "languages": {}
287}
288"#
289 }
290 ConfigLayer::Session => {
291 r#"{
292 "version": 1
293}
294"#
295 }
296 ConfigLayer::System => unreachable!(),
297 };
298 self.filesystem.write_file(&path, template.as_bytes())?;
299 }
300
301 self.settings_state = None;
303 match self.open_file(&path) {
304 Ok(_) => {
305 let layer_name = match layer {
306 ConfigLayer::User => "User",
307 ConfigLayer::Project => "Project",
308 ConfigLayer::Session => "Session",
309 ConfigLayer::System => "System",
310 };
311 self.set_status_message(
312 t!(
313 "settings.editing_config",
314 layer = layer_name,
315 path = path.display().to_string()
316 )
317 .to_string(),
318 );
319 }
320 Err(e) => {
321 if let Some(confirmation) =
323 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
324 {
325 self.start_large_file_encoding_confirmation(confirmation);
326 } else {
327 self.set_status_message(
328 t!("file.error_opening", error = e.to_string()).to_string(),
329 );
330 }
331 }
332 }
333
334 Ok(())
335 }
336
337 pub fn settings_navigate_up(&mut self) {
339 if let Some(ref mut state) = self.settings_state {
340 state.select_prev();
341 }
342 }
343
344 pub fn settings_navigate_down(&mut self) {
346 if let Some(ref mut state) = self.settings_state {
347 state.select_next();
348 }
349 }
350
351 pub fn settings_activate_current(&mut self) {
353 use crate::view::settings::items::SettingControl;
354 use crate::view::settings::FocusPanel;
355
356 let focus_panel = self
358 .settings_state
359 .as_ref()
360 .map(|s| s.focus_panel())
361 .unwrap_or(FocusPanel::Categories);
362
363 if focus_panel == FocusPanel::Footer {
364 let button_index = self
365 .settings_state
366 .as_ref()
367 .map(|s| s.footer_button_index)
368 .unwrap_or(2);
369 match button_index {
370 0 => {
371 if let Some(ref mut state) = self.settings_state {
373 state.cycle_target_layer();
374 }
375 }
376 1 => {
377 if let Some(ref mut state) = self.settings_state {
379 state.reset_current_to_default();
380 }
381 }
382 2 => {
383 self.close_settings(true);
385 }
386 3 => {
387 self.close_settings(false);
389 }
390 _ => {}
391 }
392 return;
393 }
394
395 if focus_panel == FocusPanel::Categories {
398 return;
399 }
400
401 let control_type = {
403 if let Some(ref state) = self.settings_state {
404 state.current_item().map(|item| match &item.control {
405 SettingControl::Toggle(_) => "toggle",
406 SettingControl::Number(_) => "number",
407 SettingControl::Dropdown(_) => "dropdown",
408 SettingControl::Text(_) => "text",
409 SettingControl::TextList(_) => "textlist",
410 SettingControl::Map(_) => "map",
411 SettingControl::ObjectArray(_) => "objectarray",
412 SettingControl::Json(_) => "json",
413 SettingControl::Complex { .. } => "complex",
414 })
415 } else {
416 None
417 }
418 };
419
420 match control_type {
422 Some("toggle") => {
423 if let Some(ref mut state) = self.settings_state {
424 if let Some(item) = state.current_item_mut() {
425 if let SettingControl::Toggle(ref mut toggle_state) = item.control {
426 toggle_state.checked = !toggle_state.checked;
427 }
428 }
429 state.on_value_changed();
430 }
431 }
432 Some("dropdown") => {
433 if let Some(ref mut state) = self.settings_state {
435 if state.is_dropdown_open() {
436 state.dropdown_confirm();
437 } else {
438 state.dropdown_toggle();
439 }
440 }
441 }
442 Some("textlist") => {
443 if let Some(ref mut state) = self.settings_state {
445 state.start_editing();
446 }
447 }
448 Some("map") => {
449 if let Some(ref mut state) = self.settings_state {
451 if let Some(item) = state.current_item_mut() {
452 if let SettingControl::Map(ref mut map_state) = item.control {
453 if map_state.focused_entry.is_none() {
454 if map_state.value_schema.is_some() {
456 state.open_add_entry_dialog();
457 }
458 } else if map_state.value_schema.is_some() {
459 state.open_entry_dialog();
461 } else {
462 if let Some(idx) = map_state.focused_entry {
464 if map_state.expanded.contains(&idx) {
465 map_state.expanded.retain(|&i| i != idx);
466 } else {
467 map_state.expanded.push(idx);
468 }
469 }
470 }
471 }
472 }
473 state.on_value_changed();
474 }
475 }
476 Some("text") => {
477 if let Some(ref mut state) = self.settings_state {
479 state.start_editing();
480 }
481 }
482 Some("number") => {
483 if let Some(ref mut state) = self.settings_state {
485 state.start_number_editing();
486 }
487 }
488 _ => {}
489 }
490 }
491
492 pub fn settings_increment_current(&mut self) {
494 use crate::view::settings::items::SettingControl;
495 use crate::view::settings::FocusPanel;
496
497 let focus_panel = self
499 .settings_state
500 .as_ref()
501 .map(|s| s.focus_panel())
502 .unwrap_or(FocusPanel::Categories);
503
504 if focus_panel == FocusPanel::Footer {
505 if let Some(ref mut state) = self.settings_state {
506 state.footer_button_index = (state.footer_button_index + 1) % 4;
508 }
509 return;
510 }
511
512 if focus_panel == FocusPanel::Categories {
514 return;
515 }
516
517 let control_type = {
518 if let Some(ref state) = self.settings_state {
519 state.current_item().map(|item| match &item.control {
520 SettingControl::Number(_) => "number",
521 SettingControl::Dropdown(_) => "dropdown",
522 _ => "other",
523 })
524 } else {
525 None
526 }
527 };
528
529 match control_type {
530 Some("number") => {
531 if let Some(ref mut state) = self.settings_state {
532 if let Some(item) = state.current_item_mut() {
533 if let SettingControl::Number(ref mut num_state) = item.control {
534 num_state.increment();
535 }
536 }
537 state.on_value_changed();
538 }
539 }
540 Some("dropdown") => {
541 if let Some(ref mut state) = self.settings_state {
542 if let Some(item) = state.current_item_mut() {
543 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
544 dropdown_state.select_next();
545 }
546 }
547 state.on_value_changed();
548 }
549 }
550 _ => {}
551 }
552 }
553
554 pub fn settings_decrement_current(&mut self) {
556 use crate::view::settings::items::SettingControl;
557 use crate::view::settings::FocusPanel;
558
559 let focus_panel = self
561 .settings_state
562 .as_ref()
563 .map(|s| s.focus_panel())
564 .unwrap_or(FocusPanel::Categories);
565
566 if focus_panel == FocusPanel::Footer {
567 if let Some(ref mut state) = self.settings_state {
568 state.footer_button_index = if state.footer_button_index == 0 {
570 3
571 } else {
572 state.footer_button_index - 1
573 };
574 }
575 return;
576 }
577
578 if focus_panel == FocusPanel::Categories {
580 return;
581 }
582
583 let control_type = {
584 if let Some(ref state) = self.settings_state {
585 state.current_item().map(|item| match &item.control {
586 SettingControl::Number(_) => "number",
587 SettingControl::Dropdown(_) => "dropdown",
588 _ => "other",
589 })
590 } else {
591 None
592 }
593 };
594
595 match control_type {
596 Some("number") => {
597 if let Some(ref mut state) = self.settings_state {
598 if let Some(item) = state.current_item_mut() {
599 if let SettingControl::Number(ref mut num_state) = item.control {
600 num_state.decrement();
601 }
602 }
603 state.on_value_changed();
604 }
605 }
606 Some("dropdown") => {
607 if let Some(ref mut state) = self.settings_state {
608 if let Some(item) = state.current_item_mut() {
609 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
610 dropdown_state.select_prev();
611 }
612 }
613 state.on_value_changed();
614 }
615 }
616 _ => {}
617 }
618 }
619
620 fn apply_plugin_config_changes(
622 &mut self,
623 old_plugins: &std::collections::HashMap<String, crate::config::PluginConfig>,
624 ) {
625 let changes: Vec<_> = self
627 .config
628 .plugins
629 .iter()
630 .filter_map(|(name, new_config)| {
631 let was_enabled = old_plugins.get(name).map(|c| c.enabled).unwrap_or(true);
632 if new_config.enabled != was_enabled {
633 Some((name.clone(), new_config.enabled, new_config.path.clone()))
634 } else {
635 None
636 }
637 })
638 .collect();
639
640 for (name, now_enabled, path) in changes {
642 if now_enabled {
643 if let Some(ref path) = path {
645 tracing::info!("Loading newly enabled plugin: {}", name);
646 if let Err(e) = self.plugin_manager.load_plugin(path) {
647 tracing::error!("Failed to load plugin '{}': {}", name, e);
648 self.set_status_message(format!("Failed to load plugin '{}': {}", name, e));
649 }
650 }
651 } else {
652 tracing::info!("Unloading disabled plugin: {}", name);
654 if let Err(e) = self.plugin_manager.unload_plugin(&name) {
655 tracing::error!("Failed to unload plugin '{}': {}", name, e);
656 self.set_status_message(format!("Failed to unload plugin '{}': {}", name, e));
657 }
658 }
659 }
660 }
661}