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
70 let (target_layer, new_config, pending_changes, pending_deletions) = {
72 if let Some(ref state) = self.settings_state {
73 if !state.has_changes() {
74 return;
75 }
76 match state.apply_changes(&self.config) {
77 Ok(config) => (
78 state.target_layer,
79 config,
80 state.pending_changes.clone(),
81 state.pending_deletions.clone(),
82 ),
83 Err(e) => {
84 self.set_status_message(
85 t!("settings.failed_to_apply", error = e.to_string()).to_string(),
86 );
87 return;
88 }
89 }
90 } else {
91 return;
92 }
93 };
94
95 self.config = new_config.clone();
97
98 self.user_config_raw = Config::read_user_config_raw(&self.working_dir);
100
101 if old_theme != self.config.theme {
103 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
104 self.theme = theme;
105 tracing::info!("Theme changed to '{}'", self.config.theme.0);
106 } else {
107 tracing::error!("Theme '{}' not found", self.config.theme.0);
108 self.set_status_message(format!("Theme '{}' not found", self.config.theme.0));
109 }
110 }
111
112 if old_locale != self.config.locale {
114 if let Some(locale) = self.config.locale.as_option() {
115 crate::i18n::set_locale(locale);
116 self.menus = crate::config::MenuConfig::translated();
118 tracing::info!("Locale changed to '{}'", locale);
119 } else {
120 crate::i18n::init();
122 self.menus = crate::config::MenuConfig::translated();
123 tracing::info!("Locale reset to auto-detect");
124 }
125 if let Ok(mut registry) = self.command_registry.write() {
127 registry.refresh_builtin_commands();
128 }
129 }
130
131 self.apply_plugin_config_changes(&old_plugins);
133
134 self.keybindings = KeybindingResolver::new(&self.config);
136
137 if let Some(ref mut lsp) = self.lsp {
139 for (language, lsp_config) in &self.config.lsp {
140 lsp.set_language_config(language.clone(), lsp_config.clone());
141 }
142 }
143
144 for view_state in self.split_view_states.values_mut() {
146 view_state.show_line_numbers = self.config.editor.line_numbers;
147 for buf_state in view_state.keyed_states.values_mut() {
148 buf_state.rulers = self.config.editor.rulers.clone();
149 }
150 }
151
152 self.menu_bar_visible = self.config.editor.show_menu_bar;
154 self.tab_bar_visible = self.config.editor.show_tab_bar;
155 self.status_bar_visible = self.config.editor.show_status_bar;
156
157 for state in self.buffers.values_mut() {
160 let mut whitespace =
161 crate::config::WhitespaceVisibility::from_editor_config(&self.config.editor);
162 state.buffer_settings.auto_close = self.config.editor.auto_close;
163 if let Some(lang_config) = self.config.languages.get(&state.language) {
164 state.buffer_settings.tab_size =
165 lang_config.tab_size.unwrap_or(self.config.editor.tab_size);
166 state.buffer_settings.use_tabs = lang_config.use_tabs;
167 whitespace =
168 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
169 if state.buffer_settings.auto_close {
171 if let Some(lang_auto_close) = lang_config.auto_close {
172 state.buffer_settings.auto_close = lang_auto_close;
173 }
174 }
175 } else {
176 state.buffer_settings.tab_size = self.config.editor.tab_size;
177 }
178 state.buffer_settings.whitespace = whitespace;
179 }
180
181 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
183
184 let layer_name = match target_layer {
185 ConfigLayer::User => "User",
186 ConfigLayer::Project => "Project",
187 ConfigLayer::Session => "Session",
188 ConfigLayer::System => "System", };
190
191 match resolver.save_changes_to_layer(&pending_changes, &pending_deletions, target_layer) {
192 Ok(()) => {
193 self.set_status_message(
194 t!("settings.saved_to_layer", layer = layer_name).to_string(),
195 );
196 self.settings_state = None;
200 }
201 Err(e) => {
202 self.set_status_message(
203 t!("settings.failed_to_save", error = e.to_string()).to_string(),
204 );
205 }
206 }
207 }
208
209 pub fn open_config_file(&mut self, layer: ConfigLayer) -> AnyhowResult<()> {
213 if let Some(ref state) = self.settings_state {
215 if state.has_changes() {
216 self.set_status_message(t!("settings.pending_changes").to_string());
217 return Ok(());
218 }
219 }
220
221 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
222
223 let path = match layer {
224 ConfigLayer::User => resolver.user_config_path(),
225 ConfigLayer::Project => resolver.project_config_write_path(),
226 ConfigLayer::Session => resolver.session_config_path(),
227 ConfigLayer::System => {
228 self.set_status_message(t!("settings.cannot_edit_system").to_string());
229 return Ok(());
230 }
231 };
232
233 if let Some(parent) = path.parent() {
235 self.filesystem.create_dir_all(parent)?;
236 }
237
238 if !self.filesystem.exists(&path) {
240 let template = match layer {
241 ConfigLayer::User => {
242 r#"{
243 "version": 1,
244 "theme": "default",
245 "editor": {
246 "tab_size": 4,
247 "line_numbers": true
248 }
249}
250"#
251 }
252 ConfigLayer::Project => {
253 r#"{
254 "version": 1,
255 "editor": {
256 "tab_size": 4
257 },
258 "languages": {}
259}
260"#
261 }
262 ConfigLayer::Session => {
263 r#"{
264 "version": 1
265}
266"#
267 }
268 ConfigLayer::System => unreachable!(),
269 };
270 self.filesystem.write_file(&path, template.as_bytes())?;
271 }
272
273 self.settings_state = None;
275 match self.open_file(&path) {
276 Ok(_) => {
277 let layer_name = match layer {
278 ConfigLayer::User => "User",
279 ConfigLayer::Project => "Project",
280 ConfigLayer::Session => "Session",
281 ConfigLayer::System => "System",
282 };
283 self.set_status_message(
284 t!(
285 "settings.editing_config",
286 layer = layer_name,
287 path = path.display().to_string()
288 )
289 .to_string(),
290 );
291 }
292 Err(e) => {
293 if let Some(confirmation) =
295 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
296 {
297 self.start_large_file_encoding_confirmation(confirmation);
298 } else {
299 self.set_status_message(
300 t!("file.error_opening", error = e.to_string()).to_string(),
301 );
302 }
303 }
304 }
305
306 Ok(())
307 }
308
309 pub fn settings_navigate_up(&mut self) {
311 if let Some(ref mut state) = self.settings_state {
312 state.select_prev();
313 }
314 }
315
316 pub fn settings_navigate_down(&mut self) {
318 if let Some(ref mut state) = self.settings_state {
319 state.select_next();
320 }
321 }
322
323 pub fn settings_activate_current(&mut self) {
325 use crate::view::settings::items::SettingControl;
326 use crate::view::settings::FocusPanel;
327
328 let focus_panel = self
330 .settings_state
331 .as_ref()
332 .map(|s| s.focus_panel())
333 .unwrap_or(FocusPanel::Categories);
334
335 if focus_panel == FocusPanel::Footer {
336 let button_index = self
337 .settings_state
338 .as_ref()
339 .map(|s| s.footer_button_index)
340 .unwrap_or(2);
341 match button_index {
342 0 => {
343 if let Some(ref mut state) = self.settings_state {
345 state.cycle_target_layer();
346 }
347 }
348 1 => {
349 if let Some(ref mut state) = self.settings_state {
351 state.reset_current_to_default();
352 }
353 }
354 2 => {
355 self.close_settings(true);
357 }
358 3 => {
359 self.close_settings(false);
361 }
362 _ => {}
363 }
364 return;
365 }
366
367 if focus_panel == FocusPanel::Categories {
370 return;
371 }
372
373 let control_type = {
375 if let Some(ref state) = self.settings_state {
376 state.current_item().map(|item| match &item.control {
377 SettingControl::Toggle(_) => "toggle",
378 SettingControl::Number(_) => "number",
379 SettingControl::Dropdown(_) => "dropdown",
380 SettingControl::Text(_) => "text",
381 SettingControl::TextList(_) => "textlist",
382 SettingControl::Map(_) => "map",
383 SettingControl::ObjectArray(_) => "objectarray",
384 SettingControl::Json(_) => "json",
385 SettingControl::Complex { .. } => "complex",
386 })
387 } else {
388 None
389 }
390 };
391
392 match control_type {
394 Some("toggle") => {
395 if let Some(ref mut state) = self.settings_state {
396 if let Some(item) = state.current_item_mut() {
397 if let SettingControl::Toggle(ref mut toggle_state) = item.control {
398 toggle_state.checked = !toggle_state.checked;
399 }
400 }
401 state.on_value_changed();
402 }
403 }
404 Some("dropdown") => {
405 if let Some(ref mut state) = self.settings_state {
407 if state.is_dropdown_open() {
408 state.dropdown_confirm();
409 } else {
410 state.dropdown_toggle();
411 }
412 }
413 }
414 Some("textlist") => {
415 if let Some(ref mut state) = self.settings_state {
417 state.start_editing();
418 }
419 }
420 Some("map") => {
421 if let Some(ref mut state) = self.settings_state {
423 if let Some(item) = state.current_item_mut() {
424 if let SettingControl::Map(ref mut map_state) = item.control {
425 if map_state.focused_entry.is_none() {
426 if map_state.value_schema.is_some() {
428 state.open_add_entry_dialog();
429 }
430 } else if map_state.value_schema.is_some() {
431 state.open_entry_dialog();
433 } else {
434 if let Some(idx) = map_state.focused_entry {
436 if map_state.expanded.contains(&idx) {
437 map_state.expanded.retain(|&i| i != idx);
438 } else {
439 map_state.expanded.push(idx);
440 }
441 }
442 }
443 }
444 }
445 state.on_value_changed();
446 }
447 }
448 Some("text") => {
449 if let Some(ref mut state) = self.settings_state {
451 state.start_editing();
452 }
453 }
454 Some("number") => {
455 if let Some(ref mut state) = self.settings_state {
457 state.start_number_editing();
458 }
459 }
460 _ => {}
461 }
462 }
463
464 pub fn settings_increment_current(&mut self) {
466 use crate::view::settings::items::SettingControl;
467 use crate::view::settings::FocusPanel;
468
469 let focus_panel = self
471 .settings_state
472 .as_ref()
473 .map(|s| s.focus_panel())
474 .unwrap_or(FocusPanel::Categories);
475
476 if focus_panel == FocusPanel::Footer {
477 if let Some(ref mut state) = self.settings_state {
478 state.footer_button_index = (state.footer_button_index + 1) % 4;
480 }
481 return;
482 }
483
484 if focus_panel == FocusPanel::Categories {
486 return;
487 }
488
489 let control_type = {
490 if let Some(ref state) = self.settings_state {
491 state.current_item().map(|item| match &item.control {
492 SettingControl::Number(_) => "number",
493 SettingControl::Dropdown(_) => "dropdown",
494 _ => "other",
495 })
496 } else {
497 None
498 }
499 };
500
501 match control_type {
502 Some("number") => {
503 if let Some(ref mut state) = self.settings_state {
504 if let Some(item) = state.current_item_mut() {
505 if let SettingControl::Number(ref mut num_state) = item.control {
506 num_state.increment();
507 }
508 }
509 state.on_value_changed();
510 }
511 }
512 Some("dropdown") => {
513 if let Some(ref mut state) = self.settings_state {
514 if let Some(item) = state.current_item_mut() {
515 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
516 dropdown_state.select_next();
517 }
518 }
519 state.on_value_changed();
520 }
521 }
522 _ => {}
523 }
524 }
525
526 pub fn settings_decrement_current(&mut self) {
528 use crate::view::settings::items::SettingControl;
529 use crate::view::settings::FocusPanel;
530
531 let focus_panel = self
533 .settings_state
534 .as_ref()
535 .map(|s| s.focus_panel())
536 .unwrap_or(FocusPanel::Categories);
537
538 if focus_panel == FocusPanel::Footer {
539 if let Some(ref mut state) = self.settings_state {
540 state.footer_button_index = if state.footer_button_index == 0 {
542 3
543 } else {
544 state.footer_button_index - 1
545 };
546 }
547 return;
548 }
549
550 if focus_panel == FocusPanel::Categories {
552 return;
553 }
554
555 let control_type = {
556 if let Some(ref state) = self.settings_state {
557 state.current_item().map(|item| match &item.control {
558 SettingControl::Number(_) => "number",
559 SettingControl::Dropdown(_) => "dropdown",
560 _ => "other",
561 })
562 } else {
563 None
564 }
565 };
566
567 match control_type {
568 Some("number") => {
569 if let Some(ref mut state) = self.settings_state {
570 if let Some(item) = state.current_item_mut() {
571 if let SettingControl::Number(ref mut num_state) = item.control {
572 num_state.decrement();
573 }
574 }
575 state.on_value_changed();
576 }
577 }
578 Some("dropdown") => {
579 if let Some(ref mut state) = self.settings_state {
580 if let Some(item) = state.current_item_mut() {
581 if let SettingControl::Dropdown(ref mut dropdown_state) = item.control {
582 dropdown_state.select_prev();
583 }
584 }
585 state.on_value_changed();
586 }
587 }
588 _ => {}
589 }
590 }
591
592 fn apply_plugin_config_changes(
594 &mut self,
595 old_plugins: &std::collections::HashMap<String, crate::config::PluginConfig>,
596 ) {
597 let changes: Vec<_> = self
599 .config
600 .plugins
601 .iter()
602 .filter_map(|(name, new_config)| {
603 let was_enabled = old_plugins.get(name).map(|c| c.enabled).unwrap_or(true);
604 if new_config.enabled != was_enabled {
605 Some((name.clone(), new_config.enabled, new_config.path.clone()))
606 } else {
607 None
608 }
609 })
610 .collect();
611
612 for (name, now_enabled, path) in changes {
614 if now_enabled {
615 if let Some(ref path) = path {
617 tracing::info!("Loading newly enabled plugin: {}", name);
618 if let Err(e) = self.plugin_manager.load_plugin(path) {
619 tracing::error!("Failed to load plugin '{}': {}", name, e);
620 self.set_status_message(format!("Failed to load plugin '{}': {}", name, e));
621 }
622 }
623 } else {
624 tracing::info!("Unloading disabled plugin: {}", name);
626 if let Err(e) = self.plugin_manager.unload_plugin(&name) {
627 tracing::error!("Failed to unload plugin '{}': {}", name, e);
628 self.set_status_message(format!("Failed to unload plugin '{}': {}", name, e));
629 }
630 }
631 }
632 }
633}