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();
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 if let SettingControl::Number(state) = &mut item.control {
856 state.cancel_editing();
857 }
858 }
859 self.editing_text = false;
860 }
861
862 pub fn insert_char(&mut self, c: char) {
864 if !self.editing_text {
865 return;
866 }
867 if let Some(item) = self.current_item_mut() {
868 match &mut item.control {
869 SettingControl::Text(state) => {
870 state.insert(c);
871 }
872 SettingControl::TextList(state) => {
873 state.insert(c);
874 }
875 SettingControl::Number(state) => {
876 state.insert_char(c);
877 }
878 SettingControl::Json(state) => {
879 state.insert(c);
880 }
881 _ => {}
882 }
883 }
884 }
885
886 pub fn insert_str(&mut self, s: &str) {
887 if !self.editing_text {
888 return;
889 }
890 if let Some(item) = self.current_item_mut() {
891 match &mut item.control {
892 SettingControl::Text(state) => {
893 state.insert_str(s);
894 }
895 SettingControl::TextList(state) => {
896 state.insert_str(s);
897 }
898 SettingControl::Number(state) => {
899 for c in s.chars() {
900 state.insert_char(c);
901 }
902 }
903 SettingControl::Json(state) => {
904 state.insert_str(s);
905 }
906 _ => {}
907 }
908 }
909 }
910
911 pub fn backspace(&mut self) {
913 if !self.editing_text {
914 return;
915 }
916 if let Some(item) = self.current_item_mut() {
917 match &mut item.control {
918 SettingControl::Text(state) => {
919 state.backspace();
920 }
921 SettingControl::TextList(state) => {
922 state.backspace();
923 }
924 SettingControl::Number(state) => {
925 state.backspace();
926 }
927 SettingControl::Json(state) => {
928 state.backspace();
929 }
930 _ => {}
931 }
932 }
933 }
934
935 pub fn cursor_left(&mut self) {
937 if !self.editing_text {
938 return;
939 }
940 if let Some(item) = self.current_item_mut() {
941 match &mut item.control {
942 SettingControl::Text(state) => {
943 state.move_left();
944 }
945 SettingControl::TextList(state) => {
946 state.move_left();
947 }
948 SettingControl::Json(state) => {
949 state.move_left();
950 }
951 _ => {}
952 }
953 }
954 }
955
956 pub fn cursor_left_selecting(&mut self) {
958 if !self.editing_text {
959 return;
960 }
961 if let Some(item) = self.current_item_mut() {
962 if let SettingControl::Json(state) = &mut item.control {
963 state.editor.move_left_selecting();
964 }
965 }
966 }
967
968 pub fn cursor_right(&mut self) {
970 if !self.editing_text {
971 return;
972 }
973 if let Some(item) = self.current_item_mut() {
974 match &mut item.control {
975 SettingControl::Text(state) => {
976 state.move_right();
977 }
978 SettingControl::TextList(state) => {
979 state.move_right();
980 }
981 SettingControl::Json(state) => {
982 state.move_right();
983 }
984 _ => {}
985 }
986 }
987 }
988
989 pub fn cursor_right_selecting(&mut self) {
991 if !self.editing_text {
992 return;
993 }
994 if let Some(item) = self.current_item_mut() {
995 if let SettingControl::Json(state) = &mut item.control {
996 state.editor.move_right_selecting();
997 }
998 }
999 }
1000
1001 pub fn cursor_up(&mut self) {
1003 if !self.editing_text {
1004 return;
1005 }
1006 if let Some(item) = self.current_item_mut() {
1007 if let SettingControl::Json(state) = &mut item.control {
1008 state.move_up();
1009 }
1010 }
1011 self.ensure_cursor_visible();
1012 }
1013
1014 pub fn cursor_up_selecting(&mut self) {
1016 if !self.editing_text {
1017 return;
1018 }
1019 if let Some(item) = self.current_item_mut() {
1020 if let SettingControl::Json(state) = &mut item.control {
1021 state.editor.move_up_selecting();
1022 }
1023 }
1024 self.ensure_cursor_visible();
1025 }
1026
1027 pub fn cursor_down(&mut self) {
1029 if !self.editing_text {
1030 return;
1031 }
1032 if let Some(item) = self.current_item_mut() {
1033 if let SettingControl::Json(state) = &mut item.control {
1034 state.move_down();
1035 }
1036 }
1037 self.ensure_cursor_visible();
1038 }
1039
1040 pub fn cursor_down_selecting(&mut self) {
1042 if !self.editing_text {
1043 return;
1044 }
1045 if let Some(item) = self.current_item_mut() {
1046 if let SettingControl::Json(state) = &mut item.control {
1047 state.editor.move_down_selecting();
1048 }
1049 }
1050 self.ensure_cursor_visible();
1051 }
1052
1053 pub fn insert_newline(&mut self) {
1055 if !self.editing_text {
1056 return;
1057 }
1058 if let Some(item) = self.current_item_mut() {
1059 if let SettingControl::Json(state) = &mut item.control {
1060 state.insert('\n');
1061 }
1062 }
1063 }
1064
1065 pub fn revert_json_and_stop(&mut self) {
1067 if let Some(item) = self.current_item_mut() {
1068 if let SettingControl::Json(state) = &mut item.control {
1069 state.revert();
1070 }
1071 }
1072 self.editing_text = false;
1073 }
1074
1075 pub fn is_editing_json(&self) -> bool {
1077 if !self.editing_text {
1078 return false;
1079 }
1080 self.current_item()
1081 .map(|item| matches!(&item.control, SettingControl::Json(_)))
1082 .unwrap_or(false)
1083 }
1084
1085 pub fn toggle_bool(&mut self) {
1087 if let Some(item) = self.current_item_mut() {
1088 if item.read_only {
1090 return;
1091 }
1092 if let SettingControl::Toggle(state) = &mut item.control {
1093 state.checked = !state.checked;
1094 }
1095 }
1096 }
1097
1098 pub fn toggle_dropdown(&mut self) {
1100 if let Some(item) = self.current_item_mut() {
1101 if item.read_only {
1103 return;
1104 }
1105 if let SettingControl::Dropdown(state) = &mut item.control {
1106 state.open = !state.open;
1107 }
1108 }
1109 }
1110
1111 pub fn dropdown_prev(&mut self) {
1113 if let Some(item) = self.current_item_mut() {
1114 if let SettingControl::Dropdown(state) = &mut item.control {
1115 if state.open {
1116 state.select_prev();
1117 }
1118 }
1119 }
1120 }
1121
1122 pub fn dropdown_next(&mut self) {
1124 if let Some(item) = self.current_item_mut() {
1125 if let SettingControl::Dropdown(state) = &mut item.control {
1126 if state.open {
1127 state.select_next();
1128 }
1129 }
1130 }
1131 }
1132
1133 pub fn dropdown_confirm(&mut self) {
1135 if let Some(item) = self.current_item_mut() {
1136 if let SettingControl::Dropdown(state) = &mut item.control {
1137 state.open = false;
1138 }
1139 }
1140 }
1141
1142 pub fn increment_number(&mut self) {
1144 if let Some(item) = self.current_item_mut() {
1145 if item.read_only {
1147 return;
1148 }
1149 if let SettingControl::Number(state) = &mut item.control {
1150 state.increment();
1151 }
1152 }
1153 }
1154
1155 pub fn decrement_number(&mut self) {
1157 if let Some(item) = self.current_item_mut() {
1158 if item.read_only {
1160 return;
1161 }
1162 if let SettingControl::Number(state) = &mut item.control {
1163 state.decrement();
1164 }
1165 }
1166 }
1167
1168 pub fn delete_list_item(&mut self) {
1170 if let Some(item) = self.current_item_mut() {
1171 if let SettingControl::TextList(state) = &mut item.control {
1172 if let Some(idx) = state.focused_item {
1174 state.remove_item(idx);
1175 }
1176 }
1177 }
1178 }
1179
1180 pub fn delete(&mut self) {
1182 if !self.editing_text {
1183 return;
1184 }
1185 if let Some(item) = self.current_item_mut() {
1186 match &mut item.control {
1187 SettingControl::Text(state) => {
1188 state.delete();
1189 }
1190 SettingControl::TextList(state) => {
1191 state.delete();
1192 }
1193 SettingControl::Json(state) => {
1194 state.delete();
1195 }
1196 _ => {}
1197 }
1198 }
1199 }
1200
1201 pub fn cursor_home(&mut self) {
1203 if !self.editing_text {
1204 return;
1205 }
1206 if let Some(item) = self.current_item_mut() {
1207 match &mut item.control {
1208 SettingControl::Text(state) => {
1209 state.move_home();
1210 }
1211 SettingControl::TextList(state) => {
1212 state.move_home();
1213 }
1214 SettingControl::Json(state) => {
1215 state.move_home();
1216 }
1217 _ => {}
1218 }
1219 }
1220 }
1221
1222 pub fn cursor_end(&mut self) {
1224 if !self.editing_text {
1225 return;
1226 }
1227 if let Some(item) = self.current_item_mut() {
1228 match &mut item.control {
1229 SettingControl::Text(state) => {
1230 state.move_end();
1231 }
1232 SettingControl::TextList(state) => {
1233 state.move_end();
1234 }
1235 SettingControl::Json(state) => {
1236 state.move_end();
1237 }
1238 _ => {}
1239 }
1240 }
1241 }
1242
1243 pub fn select_all(&mut self) {
1245 if !self.editing_text {
1246 return;
1247 }
1248 if let Some(item) = self.current_item_mut() {
1249 if let SettingControl::Json(state) = &mut item.control {
1250 state.select_all();
1251 }
1252 }
1254 }
1255
1256 pub fn selected_text(&self) -> Option<String> {
1258 if !self.editing_text {
1259 return None;
1260 }
1261 if let Some(item) = self.current_item() {
1262 if let SettingControl::Json(state) = &item.control {
1263 return state.selected_text();
1264 }
1265 }
1266 None
1267 }
1268
1269 pub fn is_editing(&self) -> bool {
1271 self.editing_text
1272 || self
1273 .current_item()
1274 .map(|item| {
1275 matches!(
1276 &item.control,
1277 SettingControl::Dropdown(s) if s.open
1278 )
1279 })
1280 .unwrap_or(false)
1281 }
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286 use super::*;
1287
1288 fn create_test_schema() -> SettingSchema {
1289 SettingSchema {
1290 path: "/test".to_string(),
1291 name: "Test".to_string(),
1292 description: Some("Test schema".to_string()),
1293 setting_type: SettingType::Object {
1294 properties: vec![
1295 SettingSchema {
1296 path: "/enabled".to_string(),
1297 name: "Enabled".to_string(),
1298 description: Some("Enable this".to_string()),
1299 setting_type: SettingType::Boolean,
1300 default: Some(serde_json::json!(true)),
1301 read_only: false,
1302 section: None,
1303 order: None,
1304 nullable: false,
1305 enum_from: None,
1306 dual_list_sibling: None,
1307 },
1308 SettingSchema {
1309 path: "/command".to_string(),
1310 name: "Command".to_string(),
1311 description: Some("Command to run".to_string()),
1312 setting_type: SettingType::String,
1313 default: Some(serde_json::json!("")),
1314 read_only: false,
1315 section: None,
1316 order: None,
1317 nullable: false,
1318 enum_from: None,
1319 dual_list_sibling: None,
1320 },
1321 ],
1322 },
1323 default: None,
1324 read_only: false,
1325 section: None,
1326 order: None,
1327 nullable: false,
1328 enum_from: None,
1329 dual_list_sibling: None,
1330 }
1331 }
1332
1333 #[test]
1334 fn from_schema_creates_key_item_first() {
1335 let schema = create_test_schema();
1336 let dialog = EntryDialogState::from_schema(
1337 "test".to_string(),
1338 &serde_json::json!({}),
1339 &schema,
1340 "/test",
1341 false,
1342 false,
1343 );
1344
1345 assert!(!dialog.items.is_empty());
1346 assert_eq!(dialog.items[0].path, "__key__");
1347 assert_eq!(dialog.items[0].name, "Key");
1348 }
1349
1350 #[test]
1351 fn from_schema_creates_items_from_properties() {
1352 let schema = create_test_schema();
1353 let dialog = EntryDialogState::from_schema(
1354 "test".to_string(),
1355 &serde_json::json!({"enabled": true, "command": "test-cmd"}),
1356 &schema,
1357 "/test",
1358 false,
1359 false,
1360 );
1361
1362 assert_eq!(dialog.items.len(), 3);
1364 assert_eq!(dialog.items[1].name, "Enabled");
1365 assert_eq!(dialog.items[2].name, "Command");
1366 }
1367
1368 #[test]
1369 fn get_key_returns_key_value() {
1370 let schema = create_test_schema();
1371 let dialog = EntryDialogState::from_schema(
1372 "mykey".to_string(),
1373 &serde_json::json!({}),
1374 &schema,
1375 "/test",
1376 false,
1377 false,
1378 );
1379
1380 assert_eq!(dialog.get_key(), "mykey");
1381 }
1382
1383 #[test]
1384 fn to_value_excludes_key() {
1385 let schema = create_test_schema();
1386 let dialog = EntryDialogState::from_schema(
1387 "test".to_string(),
1388 &serde_json::json!({"enabled": true, "command": "cmd"}),
1389 &schema,
1390 "/test",
1391 false,
1392 false,
1393 );
1394
1395 let value = dialog.to_value();
1396 assert!(value.get("__key__").is_none());
1397 assert!(value.get("enabled").is_some());
1398 }
1399
1400 #[test]
1401 fn focus_navigation_works() {
1402 let schema = create_test_schema();
1403 let mut dialog = EntryDialogState::from_schema(
1404 "test".to_string(),
1405 &serde_json::json!({}),
1406 &schema,
1407 "/test",
1408 false, false, );
1411
1412 assert_eq!(dialog.first_editable_index, 1);
1416 assert_eq!(dialog.selected_item, 1); assert!(!dialog.focus_on_buttons);
1418
1419 dialog.focus_next();
1420 assert_eq!(dialog.selected_item, 2); dialog.focus_next();
1423 assert!(dialog.focus_on_buttons); assert_eq!(dialog.focused_button, 0);
1425
1426 dialog.focus_prev();
1428 assert!(!dialog.focus_on_buttons);
1429 assert_eq!(dialog.selected_item, 2); dialog.focus_prev();
1432 assert_eq!(dialog.selected_item, 1); dialog.focus_prev();
1435 assert!(dialog.focus_on_buttons); }
1437
1438 #[test]
1439 fn entry_path_joins_map_path_and_entry_key() {
1440 let schema = create_test_schema();
1441
1442 let existing = EntryDialogState::from_schema(
1444 "rust".to_string(),
1445 &serde_json::json!({}),
1446 &schema,
1447 "/lsp",
1448 false,
1449 false,
1450 );
1451 assert_eq!(existing.entry_path(), "/lsp/rust");
1452
1453 let new_entry = EntryDialogState::from_schema(
1456 String::new(),
1457 &serde_json::json!({}),
1458 &schema,
1459 "/lsp",
1460 true,
1461 false,
1462 );
1463 assert_eq!(new_entry.entry_path(), "/lsp");
1464 }
1465
1466 #[test]
1467 fn entry_path_tracks_live_key_edits_for_new_entries() {
1468 let schema = create_test_schema();
1469 let mut dialog = EntryDialogState::from_schema(
1470 String::new(),
1471 &serde_json::json!({}),
1472 &schema,
1473 "/universal_lsp",
1474 true,
1475 false,
1476 );
1477
1478 for item in dialog.items.iter_mut() {
1480 if item.path == "__key__" {
1481 if let SettingControl::Text(state) = &mut item.control {
1482 state.value = "myserver".to_string();
1483 }
1484 }
1485 }
1486
1487 assert_eq!(dialog.entry_path(), "/universal_lsp/myserver");
1488 }
1489
1490 #[test]
1491 fn button_count_differs_for_new_vs_existing() {
1492 let schema = create_test_schema();
1493
1494 let new_dialog = EntryDialogState::from_schema(
1495 "test".to_string(),
1496 &serde_json::json!({}),
1497 &schema,
1498 "/test",
1499 true,
1500 false,
1501 );
1502 assert_eq!(new_dialog.button_count(), 2); let existing_dialog = EntryDialogState::from_schema(
1505 "test".to_string(),
1506 &serde_json::json!({}),
1507 &schema,
1508 "/test",
1509 false,
1510 false, );
1512 assert_eq!(existing_dialog.button_count(), 3); let no_delete_dialog = EntryDialogState::from_schema(
1516 "test".to_string(),
1517 &serde_json::json!({}),
1518 &schema,
1519 "/test",
1520 false,
1521 true, );
1523 assert_eq!(no_delete_dialog.button_count(), 2); }
1525}