1use super::items::{build_item_from_value, control_to_value, SettingControl, SettingItem};
7use super::schema::{SettingSchema, SettingType};
8use crate::view::controls::{FocusState, TextInputState};
9use serde_json::Value;
10
11#[derive(Debug, Clone)]
13pub struct EntryDialogState {
14 pub entry_key: String,
16 pub map_path: String,
18 pub title: String,
20 pub is_new: bool,
22 pub items: Vec<SettingItem>,
24 pub selected_item: usize,
26 pub sub_focus: Option<usize>,
28 pub editing_text: bool,
30 pub focused_button: usize,
32 pub focus_on_buttons: bool,
34 pub delete_requested: bool,
36 pub scroll_offset: usize,
38 pub viewport_height: usize,
40 pub hover_item: Option<usize>,
42 pub hover_button: Option<usize>,
44 pub original_value: Value,
46 pub first_editable_index: usize,
49 pub no_delete: bool,
51 pub is_single_value: bool,
54}
55
56impl EntryDialogState {
57 pub fn from_schema(
63 key: String,
64 value: &Value,
65 schema: &SettingSchema,
66 map_path: &str,
67 is_new: bool,
68 no_delete: bool,
69 ) -> Self {
70 let mut items = Vec::new();
71
72 let key_item = SettingItem {
74 path: "__key__".to_string(),
75 name: "Key".to_string(),
76 description: Some("unique identifier for this entry".to_string()),
77 control: SettingControl::Text(TextInputState::new("Key").with_value(&key)),
78 default: None,
79 modified: false,
80 layer_source: crate::config_io::ConfigLayer::System,
81 read_only: !is_new, is_auto_managed: false,
83 nullable: false,
84 is_null: false,
85 section: None,
86 is_section_start: false,
87 layout_width: 0,
88 dual_list_sibling: None,
89 };
90 items.push(key_item);
91
92 let is_single_value = !matches!(&schema.setting_type, SettingType::Object { .. });
94 if let SettingType::Object { properties } = &schema.setting_type {
95 for prop in properties {
96 let field_name = prop.path.trim_start_matches('/');
97 let field_value = value.get(field_name);
98 let item = build_item_from_value(prop, field_value);
99 items.push(item);
100 }
101 } else {
102 let item = build_item_from_value(schema, Some(value));
105 items.push(item);
106 }
107
108 items.sort_by_key(|item| !item.read_only);
110
111 Self::compute_section_starts(&mut items);
113
114 let first_editable_index = items
116 .iter()
117 .position(|item| !item.read_only)
118 .unwrap_or(items.len());
119
120 let focus_on_buttons = first_editable_index >= items.len();
122 let selected_item = if focus_on_buttons {
123 0
124 } else {
125 first_editable_index
126 };
127
128 let title = if is_new {
129 format!("Add {}", schema.name)
130 } else {
131 format!("Edit {}", schema.name)
132 };
133
134 let mut result = Self {
135 entry_key: key,
136 map_path: map_path.to_string(),
137 title,
138 is_new,
139 items,
140 selected_item,
141 sub_focus: None,
142 editing_text: false,
143 focused_button: 0,
144 focus_on_buttons,
145 delete_requested: false,
146 scroll_offset: 0,
147 viewport_height: 20, hover_item: None,
149 hover_button: None,
150 original_value: value.clone(),
151 first_editable_index,
152 no_delete,
153 is_single_value,
154 };
155 result.init_object_array_focus();
158 result
159 }
160
161 pub fn for_array_item(
165 index: Option<usize>,
166 value: &Value,
167 schema: &SettingSchema,
168 array_path: &str,
169 is_new: bool,
170 ) -> Self {
171 let mut items = Vec::new();
172
173 if let SettingType::Object { properties } = &schema.setting_type {
175 for prop in properties {
176 let field_name = prop.path.trim_start_matches('/');
177 let field_value = value.get(field_name);
178 let item = build_item_from_value(prop, field_value);
179 items.push(item);
180 }
181 }
182
183 items.sort_by_key(|item| !item.read_only);
185
186 Self::compute_section_starts(&mut items);
188
189 let first_editable_index = items
191 .iter()
192 .position(|item| !item.read_only)
193 .unwrap_or(items.len());
194
195 let focus_on_buttons = first_editable_index >= items.len();
197 let selected_item = if focus_on_buttons {
198 0
199 } else {
200 first_editable_index
201 };
202
203 let title = if is_new {
204 format!("Add {}", schema.name)
205 } else {
206 format!("Edit {}", schema.name)
207 };
208
209 Self {
210 entry_key: index.map_or(String::new(), |i| i.to_string()),
211 map_path: array_path.to_string(),
212 title,
213 is_new,
214 items,
215 selected_item,
216 sub_focus: None,
217 editing_text: false,
218 focused_button: 0,
219 focus_on_buttons,
220 delete_requested: false,
221 scroll_offset: 0,
222 viewport_height: 20,
223 hover_item: None,
224 hover_button: None,
225 original_value: value.clone(),
226 first_editable_index,
227 no_delete: false, is_single_value: false,
229 }
230 }
231
232 fn compute_section_starts(items: &mut [SettingItem]) {
235 let mut last_section: Option<&str> = None;
236 for item in items.iter_mut() {
237 let current = item.section.as_deref();
238 if current.is_some() && current != last_section {
239 item.is_section_start = true;
240 }
241 if current.is_some() {
242 last_section = current;
243 }
244 }
245 }
246
247 pub fn get_key(&self) -> String {
249 for item in &self.items {
251 if item.path == "__key__" {
252 if let SettingControl::Text(state) = &item.control {
253 return state.value.clone();
254 }
255 }
256 }
257 self.entry_key.clone()
258 }
259
260 pub fn entry_path(&self) -> String {
272 let key = self.get_key();
277 if key.is_empty() {
278 self.map_path.clone()
279 } else {
280 format!("{}/{}", self.map_path, key)
281 }
282 }
283
284 pub fn button_count(&self) -> usize {
286 if self.is_new || self.no_delete {
287 2 } else {
289 3
290 }
291 }
292
293 pub fn to_value(&self) -> Value {
295 if self.is_single_value {
298 for item in &self.items {
299 if item.path != "__key__" {
300 return control_to_value(&item.control);
301 }
302 }
303 }
304
305 let mut obj = serde_json::Map::new();
306
307 for item in &self.items {
308 if item.path == "__key__" {
310 continue;
311 }
312
313 let field_name = item.path.trim_start_matches('/');
314 let value = control_to_value(&item.control);
315 obj.insert(field_name.to_string(), value);
316 }
317
318 Value::Object(obj)
319 }
320
321 pub fn current_item(&self) -> Option<&SettingItem> {
323 if self.focus_on_buttons {
324 None
325 } else {
326 self.items.get(self.selected_item)
327 }
328 }
329
330 pub fn current_item_mut(&mut self) -> Option<&mut SettingItem> {
332 if self.focus_on_buttons {
333 None
334 } else {
335 self.items.get_mut(self.selected_item)
336 }
337 }
338
339 pub fn focus_next(&mut self) {
346 if self.editing_text {
347 return;
348 }
349
350 if self.focus_on_buttons {
351 if self.focused_button + 1 < self.button_count() {
352 self.focused_button += 1;
353 } else {
354 if self.first_editable_index < self.items.len() {
356 self.focus_on_buttons = false;
357 self.selected_item = self.first_editable_index;
358 self.sub_focus = None;
359 self.init_composite_focus(true);
360 }
361 }
362 } else {
363 let handled = self.try_composite_focus_next();
365 if !handled {
366 if self.selected_item + 1 < self.items.len() {
368 self.selected_item += 1;
369 self.sub_focus = None;
370 self.init_composite_focus(true);
371 } else {
372 self.focus_on_buttons = true;
374 self.focused_button = 0;
375 }
376 }
377 }
378
379 self.update_focus_states();
380 self.ensure_selected_visible(self.viewport_height);
381 }
382
383 pub fn focus_prev(&mut self) {
389 if self.editing_text {
390 return;
391 }
392
393 if self.focus_on_buttons {
394 if self.focused_button > 0 {
395 self.focused_button -= 1;
396 } else {
397 if self.first_editable_index < self.items.len() {
399 self.focus_on_buttons = false;
400 self.selected_item = self.items.len().saturating_sub(1);
401 self.sub_focus = None;
402 self.init_composite_focus(false);
403 }
404 }
405 } else {
406 let handled = self.try_composite_focus_prev();
408 if !handled {
409 if self.selected_item > self.first_editable_index {
411 self.selected_item -= 1;
412 self.sub_focus = None;
413 self.init_composite_focus(false);
414 } else {
415 self.focus_on_buttons = true;
417 self.focused_button = self.button_count().saturating_sub(1);
418 }
419 }
420 }
421
422 self.update_focus_states();
423 self.ensure_selected_visible(self.viewport_height);
424 }
425
426 fn try_composite_focus_next(&mut self) -> bool {
429 let item = match self.items.get(self.selected_item) {
430 Some(item) => item,
431 None => return false,
432 };
433 match &item.control {
434 SettingControl::Map(state) => {
435 let at_boundary = state.focused_entry.is_none(); if at_boundary {
438 return false;
439 }
440 if let Some(item) = self.items.get_mut(self.selected_item) {
441 if let SettingControl::Map(state) = &mut item.control {
442 return state.focus_next();
443 }
444 }
445 false
446 }
447 SettingControl::ObjectArray(state) => {
448 if state.focused_index.is_none() {
450 return false;
451 }
452 if let Some(item) = self.items.get_mut(self.selected_item) {
453 if let SettingControl::ObjectArray(state) = &mut item.control {
454 state.focus_next();
455 return true;
456 }
457 }
458 false
459 }
460 SettingControl::TextList(state) => {
461 if state.focused_item.is_none() {
463 return false;
464 }
465 if let Some(item) = self.items.get_mut(self.selected_item) {
466 if let SettingControl::TextList(state) = &mut item.control {
467 state.focus_next();
468 return true;
469 }
470 }
471 false
472 }
473 _ => false,
474 }
475 }
476
477 fn try_composite_focus_prev(&mut self) -> bool {
480 let item = match self.items.get(self.selected_item) {
481 Some(item) => item,
482 None => return false,
483 };
484 match &item.control {
485 SettingControl::Map(state) => {
486 let at_boundary = matches!(state.focused_entry, Some(0))
488 || (state.focused_entry.is_none() && state.entries.is_empty());
489 if at_boundary {
490 return false;
491 }
492 if let Some(item) = self.items.get_mut(self.selected_item) {
493 if let SettingControl::Map(state) = &mut item.control {
494 return state.focus_prev();
495 }
496 }
497 false
498 }
499 SettingControl::ObjectArray(state) => {
500 if matches!(state.focused_index, Some(0))
502 || (state.focused_index.is_none() && state.bindings.is_empty())
503 {
504 return false;
505 }
506 if let Some(item) = self.items.get_mut(self.selected_item) {
507 if let SettingControl::ObjectArray(state) = &mut item.control {
508 state.focus_prev();
509 return true;
510 }
511 }
512 false
513 }
514 SettingControl::TextList(state) => {
515 if matches!(state.focused_item, Some(0))
517 || (state.focused_item.is_none() && state.items.is_empty())
518 {
519 return false;
520 }
521 if let Some(item) = self.items.get_mut(self.selected_item) {
522 if let SettingControl::TextList(state) = &mut item.control {
523 state.focus_prev();
524 return true;
525 }
526 }
527 false
528 }
529 _ => false,
530 }
531 }
532
533 fn init_composite_focus(&mut self, from_above: bool) {
537 if let Some(item) = self.items.get_mut(self.selected_item) {
538 match &mut item.control {
539 SettingControl::Map(state) => {
540 state.init_focus(from_above);
541 }
542 SettingControl::ObjectArray(state) => {
543 if from_above {
544 state.focused_index = if state.bindings.is_empty() {
545 None
546 } else {
547 Some(0)
548 };
549 } else {
550 state.focused_index = None;
552 }
553 }
554 SettingControl::TextList(state) => {
555 if from_above {
556 state.focused_item = if state.items.is_empty() {
557 None
558 } else {
559 Some(0)
560 };
561 } else {
562 state.focused_item = None;
564 }
565 }
566 _ => {}
567 }
568 }
569 }
570
571 pub fn toggle_focus_region(&mut self) {
574 self.toggle_focus_region_direction(true);
575 }
576
577 pub fn toggle_focus_region_direction(&mut self, forward: bool) {
581 if self.editing_text {
582 return;
583 }
584
585 if self.focus_on_buttons {
586 if forward {
587 if self.focused_button + 1 < self.button_count() {
589 self.focused_button += 1;
590 } else {
591 if self.first_editable_index < self.items.len() {
593 self.focus_on_buttons = false;
594 if self.selected_item < self.first_editable_index {
595 self.selected_item = self.first_editable_index;
596 }
597 } else {
598 self.focused_button = 0;
600 }
601 }
602 } else {
603 if self.focused_button > 0 {
605 self.focused_button -= 1;
606 } else {
607 if self.first_editable_index < self.items.len() {
609 self.focus_on_buttons = false;
610 if self.selected_item < self.first_editable_index {
611 self.selected_item = self.first_editable_index;
612 }
613 } else {
614 self.focused_button = self.button_count().saturating_sub(1);
616 }
617 }
618 }
619 } else {
620 self.focus_on_buttons = true;
622 self.focused_button = if forward {
623 0
624 } else {
625 self.button_count().saturating_sub(1)
626 };
627 }
628
629 self.update_focus_states();
630 self.ensure_selected_visible(self.viewport_height);
631 }
632
633 fn init_object_array_focus(&mut self) {
635 self.init_composite_focus(true);
636 }
637
638 pub fn update_focus_states(&mut self) {
640 for (idx, item) in self.items.iter_mut().enumerate() {
641 let state = if !self.focus_on_buttons && idx == self.selected_item {
642 FocusState::Focused
643 } else {
644 FocusState::Normal
645 };
646
647 match &mut item.control {
648 SettingControl::Toggle(s) => s.focus = state,
649 SettingControl::Number(s) => s.focus = state,
650 SettingControl::Dropdown(s) => s.focus = state,
651 SettingControl::Text(s) => s.focus = state,
652 SettingControl::TextList(s) => s.focus = state,
653 SettingControl::DualList(s) => s.focus = state,
654 SettingControl::Map(s) => s.focus = state,
655 SettingControl::ObjectArray(s) => s.focus = state,
656 SettingControl::Json(s) => s.focus = state,
657 SettingControl::Complex { .. } => {}
658 }
659 }
660 }
661
662 const SECTION_HEADER_HEIGHT: usize = 2;
664
665 pub fn total_content_height(&self) -> usize {
667 let items_height: usize = self
668 .items
669 .iter()
670 .map(|item| {
671 let section_h = if item.is_section_start {
672 Self::SECTION_HEADER_HEIGHT
673 } else {
674 0
675 };
676 item.control.control_height() as usize + section_h
677 })
678 .sum();
679 let separator_height =
681 if self.first_editable_index > 0 && self.first_editable_index < self.items.len() {
682 1
683 } else {
684 0
685 };
686 items_height + separator_height
687 }
688
689 pub fn selected_item_offset(&self) -> usize {
691 let items_offset: usize = self
692 .items
693 .iter()
694 .take(self.selected_item)
695 .map(|item| {
696 let section_h = if item.is_section_start {
697 Self::SECTION_HEADER_HEIGHT
698 } else {
699 0
700 };
701 item.control.control_height() as usize + section_h
702 })
703 .sum();
704 let separator_offset = if self.first_editable_index > 0
706 && self.first_editable_index < self.items.len()
707 && self.selected_item >= self.first_editable_index
708 {
709 1
710 } else {
711 0
712 };
713 let own_section_h = self
715 .items
716 .get(self.selected_item)
717 .map(|item| {
718 if item.is_section_start {
719 Self::SECTION_HEADER_HEIGHT
720 } else {
721 0
722 }
723 })
724 .unwrap_or(0);
725 items_offset + separator_offset + own_section_h
726 }
727
728 pub fn selected_item_height(&self) -> usize {
730 self.items
731 .get(self.selected_item)
732 .map(|item| item.control.control_height() as usize)
733 .unwrap_or(1)
734 }
735
736 pub fn ensure_selected_visible(&mut self, viewport_height: usize) {
738 if self.focus_on_buttons {
739 let total = self.total_content_height();
741 if total > viewport_height {
742 self.scroll_offset = total.saturating_sub(viewport_height);
743 }
744 return;
745 }
746
747 let item_start = self.selected_item_offset();
748 let item_end = item_start + self.selected_item_height();
749
750 if item_start < self.scroll_offset {
752 self.scroll_offset = item_start;
753 }
754 else if item_end > self.scroll_offset + viewport_height {
756 self.scroll_offset = item_end.saturating_sub(viewport_height);
757 }
758 }
759
760 pub fn ensure_cursor_visible(&mut self) {
765 if !self.editing_text || self.focus_on_buttons {
766 return;
767 }
768
769 let cursor_row = if let Some(item) = self.items.get(self.selected_item) {
771 if let SettingControl::Json(state) = &item.control {
772 state.cursor_pos().0
773 } else {
774 return; }
776 } else {
777 return;
778 };
779
780 let item_offset = self.selected_item_offset();
783 let cursor_content_row = item_offset + 1 + cursor_row;
784
785 let viewport_height = self.viewport_height;
786
787 if cursor_content_row < self.scroll_offset {
789 self.scroll_offset = cursor_content_row;
790 }
791 else if cursor_content_row >= self.scroll_offset + viewport_height {
793 self.scroll_offset = cursor_content_row.saturating_sub(viewport_height) + 1;
794 }
795 }
796
797 pub fn scroll_up(&mut self) {
799 self.scroll_offset = self.scroll_offset.saturating_sub(1);
800 }
801
802 pub fn scroll_down(&mut self, viewport_height: usize) {
804 let max_scroll = self.total_content_height().saturating_sub(viewport_height);
805 if self.scroll_offset < max_scroll {
806 self.scroll_offset += 1;
807 }
808 }
809
810 pub fn scroll_to_ratio(&mut self, ratio: f32) {
814 let max_scroll = self
815 .total_content_height()
816 .saturating_sub(self.viewport_height);
817 let new_offset = (ratio * max_scroll as f32).round() as usize;
818 self.scroll_offset = new_offset.min(max_scroll);
819 }
820
821 pub fn start_editing(&mut self) {
823 if let Some(item) = self.current_item_mut() {
824 if item.read_only {
826 return;
827 }
828 match &mut item.control {
829 SettingControl::Text(state) => {
830 state.cursor = state.value.len();
831 state.editing = true;
832 self.editing_text = true;
833 }
834 SettingControl::TextList(state) => {
835 state.focus_new_item();
837 self.editing_text = true;
838 }
839 SettingControl::Number(state) => {
840 state.start_editing();
841 self.editing_text = true;
842 }
843 SettingControl::Json(_) => {
844 self.editing_text = true;
846 }
847 _ => {}
848 }
849 }
850 }
851
852 pub fn stop_editing(&mut self) {
854 if let Some(item) = self.current_item_mut() {
855 match &mut item.control {
856 SettingControl::Number(state) => state.cancel_editing(),
857 SettingControl::Text(state) => state.editing = false,
858 _ => {}
859 }
860 }
861 self.editing_text = false;
862 }
863
864 pub fn insert_char(&mut self, c: char) {
866 if !self.editing_text {
867 return;
868 }
869 if let Some(item) = self.current_item_mut() {
870 match &mut item.control {
871 SettingControl::Text(state) => {
872 state.insert(c);
873 }
874 SettingControl::TextList(state) => {
875 state.insert(c);
876 }
877 SettingControl::Number(state) => {
878 state.insert_char(c);
879 }
880 SettingControl::Json(state) => {
881 state.insert(c);
882 }
883 _ => {}
884 }
885 }
886 }
887
888 pub fn insert_str(&mut self, s: &str) {
889 if !self.editing_text {
890 return;
891 }
892 if let Some(item) = self.current_item_mut() {
893 match &mut item.control {
894 SettingControl::Text(state) => {
895 state.insert_str(s);
896 }
897 SettingControl::TextList(state) => {
898 state.insert_str(s);
899 }
900 SettingControl::Number(state) => {
901 for c in s.chars() {
902 state.insert_char(c);
903 }
904 }
905 SettingControl::Json(state) => {
906 state.insert_str(s);
907 }
908 _ => {}
909 }
910 }
911 }
912
913 pub fn backspace(&mut self) {
915 if !self.editing_text {
916 return;
917 }
918 if let Some(item) = self.current_item_mut() {
919 match &mut item.control {
920 SettingControl::Text(state) => {
921 state.backspace();
922 }
923 SettingControl::TextList(state) => {
924 state.backspace();
925 }
926 SettingControl::Number(state) => {
927 state.backspace();
928 }
929 SettingControl::Json(state) => {
930 state.backspace();
931 }
932 _ => {}
933 }
934 }
935 }
936
937 pub fn cursor_left(&mut self) {
939 if !self.editing_text {
940 return;
941 }
942 if let Some(item) = self.current_item_mut() {
943 match &mut item.control {
944 SettingControl::Text(state) => {
945 state.move_left();
946 }
947 SettingControl::TextList(state) => {
948 state.move_left();
949 }
950 SettingControl::Json(state) => {
951 state.move_left();
952 }
953 _ => {}
954 }
955 }
956 }
957
958 pub fn cursor_left_selecting(&mut self) {
960 if !self.editing_text {
961 return;
962 }
963 if let Some(item) = self.current_item_mut() {
964 if let SettingControl::Json(state) = &mut item.control {
965 state.editor.move_left_selecting();
966 }
967 }
968 }
969
970 pub fn cursor_right(&mut self) {
972 if !self.editing_text {
973 return;
974 }
975 if let Some(item) = self.current_item_mut() {
976 match &mut item.control {
977 SettingControl::Text(state) => {
978 state.move_right();
979 }
980 SettingControl::TextList(state) => {
981 state.move_right();
982 }
983 SettingControl::Json(state) => {
984 state.move_right();
985 }
986 _ => {}
987 }
988 }
989 }
990
991 pub fn cursor_right_selecting(&mut self) {
993 if !self.editing_text {
994 return;
995 }
996 if let Some(item) = self.current_item_mut() {
997 if let SettingControl::Json(state) = &mut item.control {
998 state.editor.move_right_selecting();
999 }
1000 }
1001 }
1002
1003 pub fn cursor_up(&mut self) {
1005 if !self.editing_text {
1006 return;
1007 }
1008 if let Some(item) = self.current_item_mut() {
1009 if let SettingControl::Json(state) = &mut item.control {
1010 state.move_up();
1011 }
1012 }
1013 self.ensure_cursor_visible();
1014 }
1015
1016 pub fn cursor_up_selecting(&mut self) {
1018 if !self.editing_text {
1019 return;
1020 }
1021 if let Some(item) = self.current_item_mut() {
1022 if let SettingControl::Json(state) = &mut item.control {
1023 state.editor.move_up_selecting();
1024 }
1025 }
1026 self.ensure_cursor_visible();
1027 }
1028
1029 pub fn cursor_down(&mut self) {
1031 if !self.editing_text {
1032 return;
1033 }
1034 if let Some(item) = self.current_item_mut() {
1035 if let SettingControl::Json(state) = &mut item.control {
1036 state.move_down();
1037 }
1038 }
1039 self.ensure_cursor_visible();
1040 }
1041
1042 pub fn cursor_down_selecting(&mut self) {
1044 if !self.editing_text {
1045 return;
1046 }
1047 if let Some(item) = self.current_item_mut() {
1048 if let SettingControl::Json(state) = &mut item.control {
1049 state.editor.move_down_selecting();
1050 }
1051 }
1052 self.ensure_cursor_visible();
1053 }
1054
1055 pub fn insert_newline(&mut self) {
1057 if !self.editing_text {
1058 return;
1059 }
1060 if let Some(item) = self.current_item_mut() {
1061 if let SettingControl::Json(state) = &mut item.control {
1062 state.insert('\n');
1063 }
1064 }
1065 }
1066
1067 pub fn revert_json_and_stop(&mut self) {
1069 if let Some(item) = self.current_item_mut() {
1070 if let SettingControl::Json(state) = &mut item.control {
1071 state.revert();
1072 }
1073 }
1074 self.editing_text = false;
1075 }
1076
1077 pub fn is_editing_json(&self) -> bool {
1079 if !self.editing_text {
1080 return false;
1081 }
1082 self.current_item()
1083 .map(|item| matches!(&item.control, SettingControl::Json(_)))
1084 .unwrap_or(false)
1085 }
1086
1087 pub fn toggle_bool(&mut self) {
1089 if let Some(item) = self.current_item_mut() {
1090 if item.read_only {
1092 return;
1093 }
1094 if let SettingControl::Toggle(state) = &mut item.control {
1095 state.checked = !state.checked;
1096 }
1097 }
1098 }
1099
1100 pub fn toggle_dropdown(&mut self) {
1102 if let Some(item) = self.current_item_mut() {
1103 if item.read_only {
1105 return;
1106 }
1107 if let SettingControl::Dropdown(state) = &mut item.control {
1108 state.open = !state.open;
1109 }
1110 }
1111 }
1112
1113 pub fn dropdown_prev(&mut self) {
1115 if let Some(item) = self.current_item_mut() {
1116 if let SettingControl::Dropdown(state) = &mut item.control {
1117 if state.open {
1118 state.select_prev();
1119 }
1120 }
1121 }
1122 }
1123
1124 pub fn dropdown_next(&mut self) {
1126 if let Some(item) = self.current_item_mut() {
1127 if let SettingControl::Dropdown(state) = &mut item.control {
1128 if state.open {
1129 state.select_next();
1130 }
1131 }
1132 }
1133 }
1134
1135 pub fn dropdown_confirm(&mut self) {
1137 if let Some(item) = self.current_item_mut() {
1138 if let SettingControl::Dropdown(state) = &mut item.control {
1139 state.open = false;
1140 }
1141 }
1142 }
1143
1144 pub fn increment_number(&mut self) {
1146 if let Some(item) = self.current_item_mut() {
1147 if item.read_only {
1149 return;
1150 }
1151 if let SettingControl::Number(state) = &mut item.control {
1152 state.increment();
1153 }
1154 }
1155 }
1156
1157 pub fn decrement_number(&mut self) {
1159 if let Some(item) = self.current_item_mut() {
1160 if item.read_only {
1162 return;
1163 }
1164 if let SettingControl::Number(state) = &mut item.control {
1165 state.decrement();
1166 }
1167 }
1168 }
1169
1170 pub fn delete_list_item(&mut self) {
1172 if let Some(item) = self.current_item_mut() {
1173 if let SettingControl::TextList(state) = &mut item.control {
1174 if let Some(idx) = state.focused_item {
1176 state.remove_item(idx);
1177 }
1178 }
1179 }
1180 }
1181
1182 pub fn delete(&mut self) {
1184 if !self.editing_text {
1185 return;
1186 }
1187 if let Some(item) = self.current_item_mut() {
1188 match &mut item.control {
1189 SettingControl::Text(state) => {
1190 state.delete();
1191 }
1192 SettingControl::TextList(state) => {
1193 state.delete();
1194 }
1195 SettingControl::Json(state) => {
1196 state.delete();
1197 }
1198 _ => {}
1199 }
1200 }
1201 }
1202
1203 pub fn cursor_home(&mut self) {
1205 if !self.editing_text {
1206 return;
1207 }
1208 if let Some(item) = self.current_item_mut() {
1209 match &mut item.control {
1210 SettingControl::Text(state) => {
1211 state.move_home();
1212 }
1213 SettingControl::TextList(state) => {
1214 state.move_home();
1215 }
1216 SettingControl::Json(state) => {
1217 state.move_home();
1218 }
1219 _ => {}
1220 }
1221 }
1222 }
1223
1224 pub fn cursor_end(&mut self) {
1226 if !self.editing_text {
1227 return;
1228 }
1229 if let Some(item) = self.current_item_mut() {
1230 match &mut item.control {
1231 SettingControl::Text(state) => {
1232 state.move_end();
1233 }
1234 SettingControl::TextList(state) => {
1235 state.move_end();
1236 }
1237 SettingControl::Json(state) => {
1238 state.move_end();
1239 }
1240 _ => {}
1241 }
1242 }
1243 }
1244
1245 pub fn select_all(&mut self) {
1247 if !self.editing_text {
1248 return;
1249 }
1250 if let Some(item) = self.current_item_mut() {
1251 if let SettingControl::Json(state) = &mut item.control {
1252 state.select_all();
1253 }
1254 }
1256 }
1257
1258 pub fn selected_text(&self) -> Option<String> {
1260 if !self.editing_text {
1261 return None;
1262 }
1263 if let Some(item) = self.current_item() {
1264 if let SettingControl::Json(state) = &item.control {
1265 return state.selected_text();
1266 }
1267 }
1268 None
1269 }
1270
1271 pub fn is_editing(&self) -> bool {
1273 self.editing_text
1274 || self
1275 .current_item()
1276 .map(|item| {
1277 matches!(
1278 &item.control,
1279 SettingControl::Dropdown(s) if s.open
1280 )
1281 })
1282 .unwrap_or(false)
1283 }
1284}
1285
1286#[cfg(test)]
1287mod tests {
1288 use super::*;
1289
1290 fn create_test_schema() -> SettingSchema {
1291 SettingSchema {
1292 path: "/test".to_string(),
1293 name: "Test".to_string(),
1294 description: Some("Test schema".to_string()),
1295 setting_type: SettingType::Object {
1296 properties: vec![
1297 SettingSchema {
1298 path: "/enabled".to_string(),
1299 name: "Enabled".to_string(),
1300 description: Some("Enable this".to_string()),
1301 setting_type: SettingType::Boolean,
1302 default: Some(serde_json::json!(true)),
1303 read_only: false,
1304 section: None,
1305 order: None,
1306 nullable: false,
1307 enum_from: None,
1308 dual_list_sibling: None,
1309 },
1310 SettingSchema {
1311 path: "/command".to_string(),
1312 name: "Command".to_string(),
1313 description: Some("Command to run".to_string()),
1314 setting_type: SettingType::String,
1315 default: Some(serde_json::json!("")),
1316 read_only: false,
1317 section: None,
1318 order: None,
1319 nullable: false,
1320 enum_from: None,
1321 dual_list_sibling: None,
1322 },
1323 ],
1324 },
1325 default: None,
1326 read_only: false,
1327 section: None,
1328 order: None,
1329 nullable: false,
1330 enum_from: None,
1331 dual_list_sibling: None,
1332 }
1333 }
1334
1335 #[test]
1336 fn from_schema_creates_key_item_first() {
1337 let schema = create_test_schema();
1338 let dialog = EntryDialogState::from_schema(
1339 "test".to_string(),
1340 &serde_json::json!({}),
1341 &schema,
1342 "/test",
1343 false,
1344 false,
1345 );
1346
1347 assert!(!dialog.items.is_empty());
1348 assert_eq!(dialog.items[0].path, "__key__");
1349 assert_eq!(dialog.items[0].name, "Key");
1350 }
1351
1352 #[test]
1353 fn from_schema_creates_items_from_properties() {
1354 let schema = create_test_schema();
1355 let dialog = EntryDialogState::from_schema(
1356 "test".to_string(),
1357 &serde_json::json!({"enabled": true, "command": "test-cmd"}),
1358 &schema,
1359 "/test",
1360 false,
1361 false,
1362 );
1363
1364 assert_eq!(dialog.items.len(), 3);
1366 assert_eq!(dialog.items[1].name, "Enabled");
1367 assert_eq!(dialog.items[2].name, "Command");
1368 }
1369
1370 #[test]
1371 fn get_key_returns_key_value() {
1372 let schema = create_test_schema();
1373 let dialog = EntryDialogState::from_schema(
1374 "mykey".to_string(),
1375 &serde_json::json!({}),
1376 &schema,
1377 "/test",
1378 false,
1379 false,
1380 );
1381
1382 assert_eq!(dialog.get_key(), "mykey");
1383 }
1384
1385 #[test]
1386 fn to_value_excludes_key() {
1387 let schema = create_test_schema();
1388 let dialog = EntryDialogState::from_schema(
1389 "test".to_string(),
1390 &serde_json::json!({"enabled": true, "command": "cmd"}),
1391 &schema,
1392 "/test",
1393 false,
1394 false,
1395 );
1396
1397 let value = dialog.to_value();
1398 assert!(value.get("__key__").is_none());
1399 assert!(value.get("enabled").is_some());
1400 }
1401
1402 #[test]
1403 fn focus_navigation_works() {
1404 let schema = create_test_schema();
1405 let mut dialog = EntryDialogState::from_schema(
1406 "test".to_string(),
1407 &serde_json::json!({}),
1408 &schema,
1409 "/test",
1410 false, false, );
1413
1414 assert_eq!(dialog.first_editable_index, 1);
1418 assert_eq!(dialog.selected_item, 1); assert!(!dialog.focus_on_buttons);
1420
1421 dialog.focus_next();
1422 assert_eq!(dialog.selected_item, 2); dialog.focus_next();
1425 assert!(dialog.focus_on_buttons); assert_eq!(dialog.focused_button, 0);
1427
1428 dialog.focus_prev();
1430 assert!(!dialog.focus_on_buttons);
1431 assert_eq!(dialog.selected_item, 2); dialog.focus_prev();
1434 assert_eq!(dialog.selected_item, 1); dialog.focus_prev();
1437 assert!(dialog.focus_on_buttons); }
1439
1440 #[test]
1441 fn entry_path_joins_map_path_and_entry_key() {
1442 let schema = create_test_schema();
1443
1444 let existing = EntryDialogState::from_schema(
1446 "rust".to_string(),
1447 &serde_json::json!({}),
1448 &schema,
1449 "/lsp",
1450 false,
1451 false,
1452 );
1453 assert_eq!(existing.entry_path(), "/lsp/rust");
1454
1455 let new_entry = EntryDialogState::from_schema(
1458 String::new(),
1459 &serde_json::json!({}),
1460 &schema,
1461 "/lsp",
1462 true,
1463 false,
1464 );
1465 assert_eq!(new_entry.entry_path(), "/lsp");
1466 }
1467
1468 #[test]
1469 fn entry_path_tracks_live_key_edits_for_new_entries() {
1470 let schema = create_test_schema();
1471 let mut dialog = EntryDialogState::from_schema(
1472 String::new(),
1473 &serde_json::json!({}),
1474 &schema,
1475 "/universal_lsp",
1476 true,
1477 false,
1478 );
1479
1480 for item in dialog.items.iter_mut() {
1482 if item.path == "__key__" {
1483 if let SettingControl::Text(state) = &mut item.control {
1484 state.value = "myserver".to_string();
1485 }
1486 }
1487 }
1488
1489 assert_eq!(dialog.entry_path(), "/universal_lsp/myserver");
1490 }
1491
1492 #[test]
1493 fn button_count_differs_for_new_vs_existing() {
1494 let schema = create_test_schema();
1495
1496 let new_dialog = EntryDialogState::from_schema(
1497 "test".to_string(),
1498 &serde_json::json!({}),
1499 &schema,
1500 "/test",
1501 true,
1502 false,
1503 );
1504 assert_eq!(new_dialog.button_count(), 2); let existing_dialog = EntryDialogState::from_schema(
1507 "test".to_string(),
1508 &serde_json::json!({}),
1509 &schema,
1510 "/test",
1511 false,
1512 false, );
1514 assert_eq!(existing_dialog.button_count(), 3); let no_delete_dialog = EntryDialogState::from_schema(
1518 "test".to_string(),
1519 &serde_json::json!({}),
1520 &schema,
1521 "/test",
1522 false,
1523 true, );
1525 assert_eq!(no_delete_dialog.button_count(), 2); }
1527}