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