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;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone)]
16pub struct EntryDialogState {
17 pub entry_key: String,
19 pub map_path: String,
21 pub title: String,
23 pub is_new: bool,
25 pub items: Vec<SettingItem>,
27 pub selected_item: usize,
29 pub sub_focus: Option<usize>,
31 pub editing_text: bool,
33 pub focused_button: usize,
35 pub focus_on_buttons: bool,
37 pub delete_requested: bool,
39 pub scroll_offset: usize,
41 pub viewport_height: usize,
43 pub hover_item: Option<usize>,
45 pub hover_button: Option<usize>,
47 pub original_value: Value,
49 pub first_editable_index: usize,
52 pub no_delete: bool,
54 pub is_single_value: bool,
57 pub is_array_item: bool,
62 pub user_edited: bool,
67}
68
69impl EntryDialogState {
70 pub fn from_schema(
76 key: String,
77 value: &Value,
78 schema: &SettingSchema,
79 map_path: &str,
80 is_new: bool,
81 no_delete: bool,
82 available_status_bar_tokens: &HashMap<String, String>,
83 ) -> Self {
84 let mut items = Vec::new();
85
86 let key_item = SettingItem {
88 path: "__key__".to_string(),
89 name: "Key".to_string(),
90 description: Some("unique identifier for this entry".to_string()),
91 control: SettingControl::Text(TextInputState::new("Key").with_value(&key)),
92 default: None,
93 modified: false,
94 layer_source: crate::config_io::ConfigLayer::System,
95 read_only: !is_new, is_auto_managed: false,
97 nullable: false,
98 is_null: false,
99 section: None,
100 is_section_start: false,
101 style: ItemBoxStyle::default(),
102 dual_list_sibling: None,
103 };
104 items.push(key_item);
105
106 let is_single_value = !matches!(&schema.setting_type, SettingType::Object { .. });
108 if let SettingType::Object { properties } = &schema.setting_type {
109 for prop in properties {
110 let field_name = prop.path.trim_start_matches('/');
111 let field_value = value.get(field_name);
112 let item = build_item_from_value(prop, field_value, available_status_bar_tokens);
113 items.push(item);
114 }
115 } else {
116 let item = build_item_from_value(schema, Some(value), available_status_bar_tokens);
119 items.push(item);
120 }
121
122 items.sort_by_key(|item| !item.read_only);
124
125 Self::compute_section_starts(&mut items);
127
128 let first_editable_index = items
130 .iter()
131 .position(|item| !item.read_only)
132 .unwrap_or(items.len());
133
134 let focus_on_buttons = first_editable_index >= items.len();
136 let selected_item = if focus_on_buttons {
137 0
138 } else {
139 first_editable_index
140 };
141
142 let title = if is_new {
143 format!("Add {}", schema.name)
144 } else {
145 format!("Edit {}", schema.name)
146 };
147
148 let mut result = Self {
149 entry_key: key,
150 map_path: map_path.to_string(),
151 title,
152 is_new,
153 items,
154 selected_item,
155 sub_focus: None,
156 editing_text: false,
157 focused_button: 0,
158 focus_on_buttons,
159 delete_requested: false,
160 scroll_offset: 0,
161 viewport_height: 20, hover_item: None,
163 hover_button: None,
164 original_value: value.clone(),
165 first_editable_index,
166 no_delete,
167 is_single_value,
168 is_array_item: false,
169 user_edited: false,
170 };
171 result.init_object_array_focus();
174 result
175 }
176
177 pub fn for_array_item(
181 index: Option<usize>,
182 value: &Value,
183 schema: &SettingSchema,
184 array_path: &str,
185 is_new: bool,
186 available_status_bar_tokens: &HashMap<String, String>,
187 ) -> Self {
188 let mut items = Vec::new();
189
190 if let SettingType::Object { properties } = &schema.setting_type {
192 for prop in properties {
193 let field_name = prop.path.trim_start_matches('/');
194 let field_value = value.get(field_name);
195 let item = build_item_from_value(prop, field_value, available_status_bar_tokens);
196 items.push(item);
197 }
198 }
199
200 items.sort_by_key(|item| !item.read_only);
202
203 Self::compute_section_starts(&mut items);
205
206 let first_editable_index = items
208 .iter()
209 .position(|item| !item.read_only)
210 .unwrap_or(items.len());
211
212 let focus_on_buttons = first_editable_index >= items.len();
214 let selected_item = if focus_on_buttons {
215 0
216 } else {
217 first_editable_index
218 };
219
220 let title = if is_new {
221 format!("Add {}", schema.name)
222 } else {
223 format!("Edit {}", schema.name)
224 };
225
226 Self {
227 entry_key: index.map_or(String::new(), |i| i.to_string()),
228 map_path: array_path.to_string(),
229 title,
230 is_new,
231 items,
232 selected_item,
233 sub_focus: None,
234 editing_text: false,
235 focused_button: 0,
236 focus_on_buttons,
237 delete_requested: false,
238 scroll_offset: 0,
239 viewport_height: 20,
240 hover_item: None,
241 hover_button: None,
242 original_value: value.clone(),
243 first_editable_index,
244 no_delete: false, is_single_value: false,
246 is_array_item: true,
247 user_edited: false,
248 }
249 }
250
251 fn compute_section_starts(items: &mut [SettingItem]) {
254 let mut last_section: Option<&str> = None;
255 for item in items.iter_mut() {
256 let current = item.section.as_deref();
257 if current.is_some() && current != last_section {
258 item.is_section_start = true;
259 }
260 if current.is_some() {
261 last_section = current;
262 }
263 }
264 }
265
266 pub fn get_key(&self) -> String {
268 for item in &self.items {
270 if item.path == "__key__" {
271 if let SettingControl::Text(state) = &item.control {
272 return state.value.clone();
273 }
274 }
275 }
276 self.entry_key.clone()
277 }
278
279 pub fn entry_path(&self) -> String {
291 let key = self.get_key();
296 if key.is_empty() {
297 self.map_path.clone()
298 } else {
299 format!("{}/{}", self.map_path, key)
300 }
301 }
302
303 pub fn button_count(&self) -> usize {
305 if self.is_new || self.no_delete {
306 2 } else {
308 3
309 }
310 }
311
312 pub fn is_dirty(&self) -> bool {
323 self.user_edited
324 }
325
326 pub fn mark_edited(&mut self) {
330 self.user_edited = true;
331 }
332
333 pub fn commit_pending_list_drafts(&mut self) {
343 for item in &mut self.items {
344 if let SettingControl::TextList(state) = &mut item.control {
345 if !state.new_item_text.is_empty() {
346 state.add_item();
347 }
348 }
349 }
350 }
351
352 pub fn to_value(&self) -> Value {
353 if self.is_single_value {
356 for item in &self.items {
357 if item.path != "__key__" {
358 return control_to_value(&item.control);
359 }
360 }
361 }
362
363 let mut obj = serde_json::Map::new();
364
365 for item in &self.items {
366 if item.path == "__key__" {
368 continue;
369 }
370
371 let field_name = item.path.trim_start_matches('/');
372 let value = control_to_value(&item.control);
373 obj.insert(field_name.to_string(), value);
374 }
375
376 Value::Object(obj)
377 }
378
379 pub fn current_item(&self) -> Option<&SettingItem> {
381 if self.focus_on_buttons {
382 None
383 } else {
384 self.items.get(self.selected_item)
385 }
386 }
387
388 pub fn current_item_mut(&mut self) -> Option<&mut SettingItem> {
390 if self.focus_on_buttons {
391 None
392 } else {
393 self.items.get_mut(self.selected_item)
394 }
395 }
396
397 pub fn focus_next(&mut self) {
404 if self.editing_text {
405 return;
406 }
407
408 if self.focus_on_buttons {
409 if self.focused_button + 1 < self.button_count() {
410 self.focused_button += 1;
411 } else {
412 if self.first_editable_index < self.items.len() {
414 self.focus_on_buttons = false;
415 self.selected_item = self.first_editable_index;
416 self.sub_focus = None;
417 self.init_composite_focus(true);
418 }
419 }
420 } else {
421 let handled = self.try_composite_focus_next();
423 if !handled {
424 if self.selected_item + 1 < self.items.len() {
426 self.selected_item += 1;
427 self.sub_focus = None;
428 self.init_composite_focus(true);
429 } else {
430 self.focus_on_buttons = true;
432 self.focused_button = 0;
433 }
434 }
435 }
436
437 self.update_focus_states();
438 self.ensure_selected_visible(self.viewport_height);
439 }
440
441 pub fn focus_prev(&mut self) {
447 if self.editing_text {
448 return;
449 }
450
451 if self.focus_on_buttons {
452 if self.focused_button > 0 {
453 self.focused_button -= 1;
454 } else {
455 if self.first_editable_index < self.items.len() {
457 self.focus_on_buttons = false;
458 self.selected_item = self.items.len().saturating_sub(1);
459 self.sub_focus = None;
460 self.init_composite_focus(false);
461 }
462 }
463 } else {
464 let handled = self.try_composite_focus_prev();
466 if !handled {
467 if self.selected_item > self.first_editable_index {
469 self.selected_item -= 1;
470 self.sub_focus = None;
471 self.init_composite_focus(false);
472 } else {
473 self.focus_on_buttons = true;
475 self.focused_button = self.button_count().saturating_sub(1);
476 }
477 }
478 }
479
480 self.update_focus_states();
481 self.ensure_selected_visible(self.viewport_height);
482 }
483
484 fn try_composite_focus_next(&mut self) -> bool {
487 let item = match self.items.get(self.selected_item) {
488 Some(item) => item,
489 None => return false,
490 };
491 match &item.control {
492 SettingControl::Map(state) => {
493 let at_boundary = state.focused_entry.is_none(); if at_boundary {
496 return false;
497 }
498 if let Some(item) = self.items.get_mut(self.selected_item) {
499 if let SettingControl::Map(state) = &mut item.control {
500 return state.focus_next();
501 }
502 }
503 false
504 }
505 SettingControl::ObjectArray(state) => {
506 if state.focused_index.is_none() {
508 return false;
509 }
510 if let Some(item) = self.items.get_mut(self.selected_item) {
511 if let SettingControl::ObjectArray(state) = &mut item.control {
512 state.focus_next();
513 return true;
514 }
515 }
516 false
517 }
518 SettingControl::TextList(state) => {
519 if state.focused_item.is_none() {
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_next();
526 return true;
527 }
528 }
529 false
530 }
531 _ => false,
532 }
533 }
534
535 fn try_composite_focus_prev(&mut self) -> bool {
538 let item = match self.items.get(self.selected_item) {
539 Some(item) => item,
540 None => return false,
541 };
542 match &item.control {
543 SettingControl::Map(state) => {
544 let at_boundary = matches!(state.focused_entry, Some(0))
546 || (state.focused_entry.is_none() && state.entries.is_empty());
547 if at_boundary {
548 return false;
549 }
550 if let Some(item) = self.items.get_mut(self.selected_item) {
551 if let SettingControl::Map(state) = &mut item.control {
552 return state.focus_prev();
553 }
554 }
555 false
556 }
557 SettingControl::ObjectArray(state) => {
558 if matches!(state.focused_index, Some(0))
560 || (state.focused_index.is_none() && state.bindings.is_empty())
561 {
562 return false;
563 }
564 if let Some(item) = self.items.get_mut(self.selected_item) {
565 if let SettingControl::ObjectArray(state) = &mut item.control {
566 state.focus_prev();
567 return true;
568 }
569 }
570 false
571 }
572 SettingControl::TextList(state) => {
573 if matches!(state.focused_item, Some(0))
575 || (state.focused_item.is_none() && state.items.is_empty())
576 {
577 return false;
578 }
579 if let Some(item) = self.items.get_mut(self.selected_item) {
580 if let SettingControl::TextList(state) = &mut item.control {
581 state.focus_prev();
582 return true;
583 }
584 }
585 false
586 }
587 _ => false,
588 }
589 }
590
591 fn init_composite_focus(&mut self, from_above: bool) {
595 if let Some(item) = self.items.get_mut(self.selected_item) {
596 match &mut item.control {
597 SettingControl::Map(state) => {
598 state.init_focus(from_above);
599 }
600 SettingControl::ObjectArray(state) => {
601 if from_above {
602 state.focused_index = if state.bindings.is_empty() {
603 None
604 } else {
605 Some(0)
606 };
607 } else {
608 state.focused_index = None;
610 }
611 }
612 SettingControl::TextList(state) => {
613 if from_above {
614 state.focused_item = if state.items.is_empty() {
615 None
616 } else {
617 Some(0)
618 };
619 } else {
620 state.focused_item = None;
622 }
623 }
624 _ => {}
625 }
626 }
627 }
628
629 pub fn toggle_focus_region(&mut self) {
632 self.toggle_focus_region_direction(true);
633 }
634
635 pub fn toggle_focus_region_direction(&mut self, forward: bool) {
639 if self.editing_text {
640 return;
641 }
642
643 if self.focus_on_buttons {
644 if forward {
645 if self.focused_button + 1 < self.button_count() {
647 self.focused_button += 1;
648 } else {
649 if self.first_editable_index < self.items.len() {
651 self.focus_on_buttons = false;
652 if self.selected_item < self.first_editable_index {
653 self.selected_item = self.first_editable_index;
654 }
655 } else {
656 self.focused_button = 0;
658 }
659 }
660 } else {
661 if self.focused_button > 0 {
663 self.focused_button -= 1;
664 } else {
665 if self.first_editable_index < self.items.len() {
667 self.focus_on_buttons = false;
668 if self.selected_item < self.first_editable_index {
669 self.selected_item = self.first_editable_index;
670 }
671 } else {
672 self.focused_button = self.button_count().saturating_sub(1);
674 }
675 }
676 }
677 } else {
678 self.focus_on_buttons = true;
680 self.focused_button = if forward {
681 0
682 } else {
683 self.button_count().saturating_sub(1)
684 };
685 }
686
687 self.update_focus_states();
688 self.ensure_selected_visible(self.viewport_height);
689 }
690
691 fn init_object_array_focus(&mut self) {
693 self.init_composite_focus(true);
694 }
695
696 pub fn update_focus_states(&mut self) {
698 for (idx, item) in self.items.iter_mut().enumerate() {
699 let state = if !self.focus_on_buttons && idx == self.selected_item {
700 FocusState::Focused
701 } else {
702 FocusState::Normal
703 };
704
705 match &mut item.control {
706 SettingControl::Toggle(s) => s.focus = state,
707 SettingControl::Number(s) => s.focus = state,
708 SettingControl::Dropdown(s) => s.focus = state,
709 SettingControl::Text(s) => s.focus = state,
710 SettingControl::TextList(s) => s.focus = state,
711 SettingControl::DualList(s) => s.focus = state,
712 SettingControl::Map(s) => s.focus = state,
713 SettingControl::ObjectArray(s) => s.focus = state,
714 SettingControl::Json(s) => s.focus = state,
715 SettingControl::Complex { .. } => {}
716 }
717 }
718 }
719
720 const SECTION_HEADER_HEIGHT: usize = 2;
722
723 pub fn total_content_height(&self) -> usize {
725 let items_height: usize = self
726 .items
727 .iter()
728 .map(|item| {
729 let section_h = if item.is_section_start {
730 Self::SECTION_HEADER_HEIGHT
731 } else {
732 0
733 };
734 item.control.control_height() as usize + section_h
735 })
736 .sum();
737 let separator_height =
739 if self.first_editable_index > 0 && self.first_editable_index < self.items.len() {
740 1
741 } else {
742 0
743 };
744 items_height + separator_height
745 }
746
747 pub fn selected_item_offset(&self) -> usize {
749 let items_offset: usize = self
750 .items
751 .iter()
752 .take(self.selected_item)
753 .map(|item| {
754 let section_h = if item.is_section_start {
755 Self::SECTION_HEADER_HEIGHT
756 } else {
757 0
758 };
759 item.control.control_height() as usize + section_h
760 })
761 .sum();
762 let separator_offset = if self.first_editable_index > 0
764 && self.first_editable_index < self.items.len()
765 && self.selected_item >= self.first_editable_index
766 {
767 1
768 } else {
769 0
770 };
771 let own_section_h = self
773 .items
774 .get(self.selected_item)
775 .map(|item| {
776 if item.is_section_start {
777 Self::SECTION_HEADER_HEIGHT
778 } else {
779 0
780 }
781 })
782 .unwrap_or(0);
783 items_offset + separator_offset + own_section_h
784 }
785
786 pub fn selected_item_height(&self) -> usize {
788 self.items
789 .get(self.selected_item)
790 .map(|item| item.control.control_height() as usize)
791 .unwrap_or(1)
792 }
793
794 pub fn ensure_selected_visible(&mut self, viewport_height: usize) {
796 if self.focus_on_buttons {
797 let total = self.total_content_height();
799 if total > viewport_height {
800 self.scroll_offset = total.saturating_sub(viewport_height);
801 }
802 return;
803 }
804
805 let item_start = self.selected_item_offset();
806 let item_end = item_start + self.selected_item_height();
807
808 if item_start < self.scroll_offset {
810 self.scroll_offset = item_start;
811 }
812 else if item_end > self.scroll_offset + viewport_height {
814 self.scroll_offset = item_end.saturating_sub(viewport_height);
815 }
816 }
817
818 pub fn ensure_cursor_visible(&mut self) {
823 if !self.editing_text || self.focus_on_buttons {
824 return;
825 }
826
827 let cursor_row = if let Some(item) = self.items.get(self.selected_item) {
829 if let SettingControl::Json(state) = &item.control {
830 state.cursor_pos().0
831 } else {
832 return; }
834 } else {
835 return;
836 };
837
838 let item_offset = self.selected_item_offset();
841 let cursor_content_row = item_offset + 1 + cursor_row;
842
843 let viewport_height = self.viewport_height;
844
845 if cursor_content_row < self.scroll_offset {
847 self.scroll_offset = cursor_content_row;
848 }
849 else if cursor_content_row >= self.scroll_offset + viewport_height {
851 self.scroll_offset = cursor_content_row.saturating_sub(viewport_height) + 1;
852 }
853 }
854
855 pub fn scroll_up(&mut self) {
857 self.scroll_offset = self.scroll_offset.saturating_sub(1);
858 }
859
860 pub fn scroll_down(&mut self, viewport_height: usize) {
862 let max_scroll = self.total_content_height().saturating_sub(viewport_height);
863 if self.scroll_offset < max_scroll {
864 self.scroll_offset += 1;
865 }
866 }
867
868 pub fn scroll_to_ratio(&mut self, ratio: f32) {
872 let max_scroll = self
873 .total_content_height()
874 .saturating_sub(self.viewport_height);
875 let new_offset = (ratio * max_scroll as f32).round() as usize;
876 self.scroll_offset = new_offset.min(max_scroll);
877 }
878
879 pub fn start_editing(&mut self) {
881 if let Some(item) = self.current_item_mut() {
882 if item.read_only {
884 return;
885 }
886 match &mut item.control {
887 SettingControl::Text(state) => {
888 state.cursor = state.value.len();
889 state.editing = true;
890 self.editing_text = true;
891 }
892 SettingControl::TextList(state) => {
893 if state.focused_item.is_none() {
899 state.activate_pending();
900 }
901 self.editing_text = true;
902 }
903 SettingControl::Number(state) => {
904 state.start_editing();
905 self.editing_text = true;
906 }
907 SettingControl::Json(state) => {
908 state.clear_placeholder_for_edit();
911 self.editing_text = true;
912 }
913 _ => {}
914 }
915 }
916 }
917
918 pub fn stop_editing(&mut self) {
920 if let Some(item) = self.current_item_mut() {
921 match &mut item.control {
922 SettingControl::Number(state) => state.cancel_editing(),
923 SettingControl::Text(state) => state.editing = false,
924 SettingControl::TextList(state) if state.focused_item.is_none() => {
931 state.cancel_pending();
932 }
933 SettingControl::Json(state) => state.restore_unset_if_empty(),
937 _ => {}
938 }
939 }
940 self.editing_text = false;
941 }
942
943 pub fn insert_char(&mut self, c: char) {
945 if !self.editing_text {
946 return;
947 }
948 self.user_edited = true;
949 if let Some(item) = self.current_item_mut() {
950 match &mut item.control {
951 SettingControl::Text(state) => {
952 state.insert(c);
953 }
954 SettingControl::TextList(state) => {
955 state.insert(c);
956 }
957 SettingControl::Number(state) => {
958 state.insert_char(c);
959 }
960 SettingControl::Json(state) => {
961 state.insert(c);
962 }
963 _ => {}
964 }
965 }
966 }
967
968 pub fn insert_str(&mut self, s: &str) {
969 if !self.editing_text {
970 return;
971 }
972 self.user_edited = true;
973 if let Some(item) = self.current_item_mut() {
974 match &mut item.control {
975 SettingControl::Text(state) => {
976 state.insert_str(s);
977 }
978 SettingControl::TextList(state) => {
979 state.insert_str(s);
980 }
981 SettingControl::Number(state) => {
982 for c in s.chars() {
983 state.insert_char(c);
984 }
985 }
986 SettingControl::Json(state) => {
987 state.insert_str(s);
988 }
989 _ => {}
990 }
991 }
992 }
993
994 pub fn backspace(&mut self) {
996 if !self.editing_text {
997 return;
998 }
999 self.user_edited = true;
1000 if let Some(item) = self.current_item_mut() {
1001 match &mut item.control {
1002 SettingControl::Text(state) => {
1003 state.backspace();
1004 }
1005 SettingControl::TextList(state) => {
1006 state.backspace();
1007 }
1008 SettingControl::Number(state) => {
1009 state.backspace();
1010 }
1011 SettingControl::Json(state) => {
1012 state.backspace();
1013 }
1014 _ => {}
1015 }
1016 }
1017 }
1018
1019 pub fn cursor_left(&mut self) {
1021 if !self.editing_text {
1022 return;
1023 }
1024 if let Some(item) = self.current_item_mut() {
1025 match &mut item.control {
1026 SettingControl::Text(state) => {
1027 state.move_left();
1028 }
1029 SettingControl::TextList(state) => {
1030 state.move_left();
1031 }
1032 SettingControl::Json(state) => {
1033 state.move_left();
1034 }
1035 _ => {}
1036 }
1037 }
1038 }
1039
1040 pub fn cursor_left_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_left_selecting();
1048 }
1049 }
1050 }
1051
1052 pub fn cursor_right(&mut self) {
1054 if !self.editing_text {
1055 return;
1056 }
1057 if let Some(item) = self.current_item_mut() {
1058 match &mut item.control {
1059 SettingControl::Text(state) => {
1060 state.move_right();
1061 }
1062 SettingControl::TextList(state) => {
1063 state.move_right();
1064 }
1065 SettingControl::Json(state) => {
1066 state.move_right();
1067 }
1068 _ => {}
1069 }
1070 }
1071 }
1072
1073 pub fn cursor_right_selecting(&mut self) {
1075 if !self.editing_text {
1076 return;
1077 }
1078 if let Some(item) = self.current_item_mut() {
1079 if let SettingControl::Json(state) = &mut item.control {
1080 state.editor.move_right_selecting();
1081 }
1082 }
1083 }
1084
1085 pub fn cursor_up(&mut self) {
1087 if !self.editing_text {
1088 return;
1089 }
1090 if let Some(item) = self.current_item_mut() {
1091 if let SettingControl::Json(state) = &mut item.control {
1092 state.move_up();
1093 }
1094 }
1095 self.ensure_cursor_visible();
1096 }
1097
1098 pub fn cursor_up_selecting(&mut self) {
1100 if !self.editing_text {
1101 return;
1102 }
1103 if let Some(item) = self.current_item_mut() {
1104 if let SettingControl::Json(state) = &mut item.control {
1105 state.editor.move_up_selecting();
1106 }
1107 }
1108 self.ensure_cursor_visible();
1109 }
1110
1111 pub fn cursor_down(&mut self) {
1113 if !self.editing_text {
1114 return;
1115 }
1116 if let Some(item) = self.current_item_mut() {
1117 if let SettingControl::Json(state) = &mut item.control {
1118 state.move_down();
1119 }
1120 }
1121 self.ensure_cursor_visible();
1122 }
1123
1124 pub fn cursor_down_selecting(&mut self) {
1126 if !self.editing_text {
1127 return;
1128 }
1129 if let Some(item) = self.current_item_mut() {
1130 if let SettingControl::Json(state) = &mut item.control {
1131 state.editor.move_down_selecting();
1132 }
1133 }
1134 self.ensure_cursor_visible();
1135 }
1136
1137 pub fn insert_newline(&mut self) {
1139 self.user_edited = true;
1140 if !self.editing_text {
1141 return;
1142 }
1143 if let Some(item) = self.current_item_mut() {
1144 if let SettingControl::Json(state) = &mut item.control {
1145 state.insert('\n');
1146 }
1147 }
1148 }
1149
1150 pub fn revert_json_and_stop(&mut self) {
1152 if let Some(item) = self.current_item_mut() {
1153 if let SettingControl::Json(state) = &mut item.control {
1154 state.revert();
1155 }
1156 }
1157 self.editing_text = false;
1158 }
1159
1160 pub fn is_editing_json(&self) -> bool {
1162 if !self.editing_text {
1163 return false;
1164 }
1165 self.current_item()
1166 .map(|item| matches!(&item.control, SettingControl::Json(_)))
1167 .unwrap_or(false)
1168 }
1169
1170 pub fn toggle_bool(&mut self) {
1172 self.user_edited = true;
1173 if let Some(item) = self.current_item_mut() {
1174 if item.read_only {
1176 return;
1177 }
1178 if let SettingControl::Toggle(state) = &mut item.control {
1179 state.checked = !state.checked;
1180 }
1181 }
1182 }
1183
1184 pub fn toggle_dropdown(&mut self) {
1186 if let Some(item) = self.current_item_mut() {
1187 if item.read_only {
1189 return;
1190 }
1191 if let SettingControl::Dropdown(state) = &mut item.control {
1192 state.open = !state.open;
1193 }
1194 }
1195 }
1196
1197 pub fn dropdown_prev(&mut self) {
1199 self.user_edited = true;
1200 if let Some(item) = self.current_item_mut() {
1201 if let SettingControl::Dropdown(state) = &mut item.control {
1202 if state.open {
1203 state.select_prev();
1204 }
1205 }
1206 }
1207 }
1208
1209 pub fn dropdown_next(&mut self) {
1211 self.user_edited = true;
1212 if let Some(item) = self.current_item_mut() {
1213 if let SettingControl::Dropdown(state) = &mut item.control {
1214 if state.open {
1215 state.select_next();
1216 }
1217 }
1218 }
1219 }
1220
1221 pub fn dropdown_confirm(&mut self) {
1223 if let Some(item) = self.current_item_mut() {
1224 if let SettingControl::Dropdown(state) = &mut item.control {
1225 state.open = false;
1226 }
1227 }
1228 }
1229
1230 pub fn delete_list_item(&mut self) {
1232 self.user_edited = true;
1233 if let Some(item) = self.current_item_mut() {
1234 if let SettingControl::TextList(state) = &mut item.control {
1235 if let Some(idx) = state.focused_item {
1237 state.remove_item(idx);
1238 }
1239 }
1240 }
1241 }
1242
1243 pub fn delete(&mut self) {
1245 if !self.editing_text {
1246 return;
1247 }
1248 self.user_edited = true;
1249 if let Some(item) = self.current_item_mut() {
1250 match &mut item.control {
1251 SettingControl::Text(state) => {
1252 state.delete();
1253 }
1254 SettingControl::TextList(state) => {
1255 state.delete();
1256 }
1257 SettingControl::Json(state) => {
1258 state.delete();
1259 }
1260 _ => {}
1261 }
1262 }
1263 }
1264
1265 pub fn cursor_home(&mut self) {
1267 if !self.editing_text {
1268 return;
1269 }
1270 if let Some(item) = self.current_item_mut() {
1271 match &mut item.control {
1272 SettingControl::Text(state) => {
1273 state.move_home();
1274 }
1275 SettingControl::TextList(state) => {
1276 state.move_home();
1277 }
1278 SettingControl::Json(state) => {
1279 state.move_home();
1280 }
1281 _ => {}
1282 }
1283 }
1284 }
1285
1286 pub fn cursor_end(&mut self) {
1288 if !self.editing_text {
1289 return;
1290 }
1291 if let Some(item) = self.current_item_mut() {
1292 match &mut item.control {
1293 SettingControl::Text(state) => {
1294 state.move_end();
1295 }
1296 SettingControl::TextList(state) => {
1297 state.move_end();
1298 }
1299 SettingControl::Json(state) => {
1300 state.move_end();
1301 }
1302 _ => {}
1303 }
1304 }
1305 }
1306
1307 pub fn select_all(&mut self) {
1309 if !self.editing_text {
1310 return;
1311 }
1312 if let Some(item) = self.current_item_mut() {
1313 if let SettingControl::Json(state) = &mut item.control {
1314 state.select_all();
1315 }
1316 }
1318 }
1319
1320 pub fn selected_text(&self) -> Option<String> {
1322 if !self.editing_text {
1323 return None;
1324 }
1325 if let Some(item) = self.current_item() {
1326 if let SettingControl::Json(state) = &item.control {
1327 return state.selected_text();
1328 }
1329 }
1330 None
1331 }
1332
1333 pub fn is_editing(&self) -> bool {
1335 self.editing_text
1336 || self
1337 .current_item()
1338 .map(|item| {
1339 matches!(
1340 &item.control,
1341 SettingControl::Dropdown(s) if s.open
1342 )
1343 })
1344 .unwrap_or(false)
1345 }
1346}
1347
1348#[cfg(test)]
1349mod tests {
1350 use super::*;
1351
1352 fn create_test_schema() -> SettingSchema {
1353 SettingSchema {
1354 path: "/test".to_string(),
1355 name: "Test".to_string(),
1356 description: Some("Test schema".to_string()),
1357 setting_type: SettingType::Object {
1358 properties: vec![
1359 SettingSchema {
1360 path: "/enabled".to_string(),
1361 name: "Enabled".to_string(),
1362 description: Some("Enable this".to_string()),
1363 setting_type: SettingType::Boolean,
1364 default: Some(serde_json::json!(true)),
1365 read_only: false,
1366 section: None,
1367 order: None,
1368 nullable: false,
1369 enum_from: None,
1370 dual_list_sibling: None,
1371 dynamically_extendable_status_bar_elements: false,
1372 },
1373 SettingSchema {
1374 path: "/command".to_string(),
1375 name: "Command".to_string(),
1376 description: Some("Command to run".to_string()),
1377 setting_type: SettingType::String,
1378 default: Some(serde_json::json!("")),
1379 read_only: false,
1380 section: None,
1381 order: None,
1382 nullable: false,
1383 enum_from: None,
1384 dual_list_sibling: None,
1385 dynamically_extendable_status_bar_elements: false,
1386 },
1387 ],
1388 },
1389 default: None,
1390 read_only: false,
1391 section: None,
1392 order: None,
1393 nullable: false,
1394 enum_from: None,
1395 dual_list_sibling: None,
1396 dynamically_extendable_status_bar_elements: false,
1397 }
1398 }
1399
1400 #[test]
1401 fn from_schema_creates_key_item_first() {
1402 let schema = create_test_schema();
1403 let dialog = EntryDialogState::from_schema(
1404 "test".to_string(),
1405 &serde_json::json!({}),
1406 &schema,
1407 "/test",
1408 false,
1409 false,
1410 &HashMap::new(),
1411 );
1412
1413 assert!(!dialog.items.is_empty());
1414 assert_eq!(dialog.items[0].path, "__key__");
1415 assert_eq!(dialog.items[0].name, "Key");
1416 }
1417
1418 #[test]
1419 fn from_schema_creates_items_from_properties() {
1420 let schema = create_test_schema();
1421 let dialog = EntryDialogState::from_schema(
1422 "test".to_string(),
1423 &serde_json::json!({"enabled": true, "command": "test-cmd"}),
1424 &schema,
1425 "/test",
1426 false,
1427 false,
1428 &HashMap::new(),
1429 );
1430
1431 assert_eq!(dialog.items.len(), 3);
1433 assert_eq!(dialog.items[1].name, "Enabled");
1434 assert_eq!(dialog.items[2].name, "Command");
1435 }
1436
1437 #[test]
1438 fn get_key_returns_key_value() {
1439 let schema = create_test_schema();
1440 let dialog = EntryDialogState::from_schema(
1441 "mykey".to_string(),
1442 &serde_json::json!({}),
1443 &schema,
1444 "/test",
1445 false,
1446 false,
1447 &HashMap::new(),
1448 );
1449
1450 assert_eq!(dialog.get_key(), "mykey");
1451 }
1452
1453 #[test]
1454 fn to_value_excludes_key() {
1455 let schema = create_test_schema();
1456 let dialog = EntryDialogState::from_schema(
1457 "test".to_string(),
1458 &serde_json::json!({"enabled": true, "command": "cmd"}),
1459 &schema,
1460 "/test",
1461 false,
1462 false,
1463 &HashMap::new(),
1464 );
1465
1466 let value = dialog.to_value();
1467 assert!(value.get("__key__").is_none());
1468 assert!(value.get("enabled").is_some());
1469 }
1470
1471 #[test]
1472 fn focus_navigation_works() {
1473 let schema = create_test_schema();
1474 let mut dialog = EntryDialogState::from_schema(
1475 "test".to_string(),
1476 &serde_json::json!({}),
1477 &schema,
1478 "/test",
1479 false, false, &HashMap::new(),
1482 );
1483
1484 assert_eq!(dialog.first_editable_index, 1);
1488 assert_eq!(dialog.selected_item, 1); assert!(!dialog.focus_on_buttons);
1490
1491 dialog.focus_next();
1492 assert_eq!(dialog.selected_item, 2); dialog.focus_next();
1495 assert!(dialog.focus_on_buttons); assert_eq!(dialog.focused_button, 0);
1497
1498 dialog.focus_prev();
1500 assert!(!dialog.focus_on_buttons);
1501 assert_eq!(dialog.selected_item, 2); dialog.focus_prev();
1504 assert_eq!(dialog.selected_item, 1); dialog.focus_prev();
1507 assert!(dialog.focus_on_buttons); }
1509
1510 #[test]
1511 fn entry_path_joins_map_path_and_entry_key() {
1512 let schema = create_test_schema();
1513
1514 let existing = EntryDialogState::from_schema(
1516 "rust".to_string(),
1517 &serde_json::json!({}),
1518 &schema,
1519 "/lsp",
1520 false,
1521 false,
1522 &HashMap::new(),
1523 );
1524 assert_eq!(existing.entry_path(), "/lsp/rust");
1525
1526 let new_entry = EntryDialogState::from_schema(
1529 String::new(),
1530 &serde_json::json!({}),
1531 &schema,
1532 "/lsp",
1533 true,
1534 false,
1535 &HashMap::new(),
1536 );
1537 assert_eq!(new_entry.entry_path(), "/lsp");
1538 }
1539
1540 #[test]
1541 fn entry_path_tracks_live_key_edits_for_new_entries() {
1542 let schema = create_test_schema();
1543 let mut dialog = EntryDialogState::from_schema(
1544 String::new(),
1545 &serde_json::json!({}),
1546 &schema,
1547 "/universal_lsp",
1548 true,
1549 false,
1550 &HashMap::new(),
1551 );
1552
1553 for item in dialog.items.iter_mut() {
1555 if item.path == "__key__" {
1556 if let SettingControl::Text(state) = &mut item.control {
1557 state.value = "myserver".to_string();
1558 }
1559 }
1560 }
1561
1562 assert_eq!(dialog.entry_path(), "/universal_lsp/myserver");
1563 }
1564
1565 #[test]
1566 fn button_count_differs_for_new_vs_existing() {
1567 let schema = create_test_schema();
1568
1569 let new_dialog = EntryDialogState::from_schema(
1570 "test".to_string(),
1571 &serde_json::json!({}),
1572 &schema,
1573 "/test",
1574 true,
1575 false,
1576 &HashMap::new(),
1577 );
1578 assert_eq!(new_dialog.button_count(), 2); let existing_dialog = EntryDialogState::from_schema(
1581 "test".to_string(),
1582 &serde_json::json!({}),
1583 &schema,
1584 "/test",
1585 false,
1586 false, &HashMap::new(),
1588 );
1589 assert_eq!(existing_dialog.button_count(), 3); let no_delete_dialog = EntryDialogState::from_schema(
1593 "test".to_string(),
1594 &serde_json::json!({}),
1595 &schema,
1596 "/test",
1597 false,
1598 true, &HashMap::new(),
1600 );
1601 assert_eq!(no_delete_dialog.button_count(), 2); }
1603}