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