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 user_edited: bool,
62}
63
64impl EntryDialogState {
65 pub fn from_schema(
71 key: String,
72 value: &Value,
73 schema: &SettingSchema,
74 map_path: &str,
75 is_new: bool,
76 no_delete: bool,
77 available_status_bar_tokens: &HashMap<String, String>,
78 ) -> Self {
79 let mut items = Vec::new();
80
81 let key_item = SettingItem {
83 path: "__key__".to_string(),
84 name: "Key".to_string(),
85 description: Some("unique identifier for this entry".to_string()),
86 control: SettingControl::Text(TextInputState::new("Key").with_value(&key)),
87 default: None,
88 modified: false,
89 layer_source: crate::config_io::ConfigLayer::System,
90 read_only: !is_new, is_auto_managed: false,
92 nullable: false,
93 is_null: false,
94 section: None,
95 is_section_start: false,
96 style: ItemBoxStyle::default(),
97 dual_list_sibling: None,
98 };
99 items.push(key_item);
100
101 let is_single_value = !matches!(&schema.setting_type, SettingType::Object { .. });
103 if let SettingType::Object { properties } = &schema.setting_type {
104 for prop in properties {
105 let field_name = prop.path.trim_start_matches('/');
106 let field_value = value.get(field_name);
107 let item = build_item_from_value(prop, field_value, available_status_bar_tokens);
108 items.push(item);
109 }
110 } else {
111 let item = build_item_from_value(schema, Some(value), available_status_bar_tokens);
114 items.push(item);
115 }
116
117 items.sort_by_key(|item| !item.read_only);
119
120 Self::compute_section_starts(&mut items);
122
123 let first_editable_index = items
125 .iter()
126 .position(|item| !item.read_only)
127 .unwrap_or(items.len());
128
129 let focus_on_buttons = first_editable_index >= items.len();
131 let selected_item = if focus_on_buttons {
132 0
133 } else {
134 first_editable_index
135 };
136
137 let title = if is_new {
138 format!("Add {}", schema.name)
139 } else {
140 format!("Edit {}", schema.name)
141 };
142
143 let mut result = Self {
144 entry_key: key,
145 map_path: map_path.to_string(),
146 title,
147 is_new,
148 items,
149 selected_item,
150 sub_focus: None,
151 editing_text: false,
152 focused_button: 0,
153 focus_on_buttons,
154 delete_requested: false,
155 scroll_offset: 0,
156 viewport_height: 20, hover_item: None,
158 hover_button: None,
159 original_value: value.clone(),
160 first_editable_index,
161 no_delete,
162 is_single_value,
163 user_edited: false,
164 };
165 result.init_object_array_focus();
168 result
169 }
170
171 pub fn for_array_item(
175 index: Option<usize>,
176 value: &Value,
177 schema: &SettingSchema,
178 array_path: &str,
179 is_new: bool,
180 available_status_bar_tokens: &HashMap<String, String>,
181 ) -> Self {
182 let mut items = Vec::new();
183
184 if let SettingType::Object { properties } = &schema.setting_type {
186 for prop in properties {
187 let field_name = prop.path.trim_start_matches('/');
188 let field_value = value.get(field_name);
189 let item = build_item_from_value(prop, field_value, available_status_bar_tokens);
190 items.push(item);
191 }
192 }
193
194 items.sort_by_key(|item| !item.read_only);
196
197 Self::compute_section_starts(&mut items);
199
200 let first_editable_index = items
202 .iter()
203 .position(|item| !item.read_only)
204 .unwrap_or(items.len());
205
206 let focus_on_buttons = first_editable_index >= items.len();
208 let selected_item = if focus_on_buttons {
209 0
210 } else {
211 first_editable_index
212 };
213
214 let title = if is_new {
215 format!("Add {}", schema.name)
216 } else {
217 format!("Edit {}", schema.name)
218 };
219
220 Self {
221 entry_key: index.map_or(String::new(), |i| i.to_string()),
222 map_path: array_path.to_string(),
223 title,
224 is_new,
225 items,
226 selected_item,
227 sub_focus: None,
228 editing_text: false,
229 focused_button: 0,
230 focus_on_buttons,
231 delete_requested: false,
232 scroll_offset: 0,
233 viewport_height: 20,
234 hover_item: None,
235 hover_button: None,
236 original_value: value.clone(),
237 first_editable_index,
238 no_delete: false, is_single_value: false,
240 user_edited: false,
241 }
242 }
243
244 fn compute_section_starts(items: &mut [SettingItem]) {
247 let mut last_section: Option<&str> = None;
248 for item in items.iter_mut() {
249 let current = item.section.as_deref();
250 if current.is_some() && current != last_section {
251 item.is_section_start = true;
252 }
253 if current.is_some() {
254 last_section = current;
255 }
256 }
257 }
258
259 pub fn get_key(&self) -> String {
261 for item in &self.items {
263 if item.path == "__key__" {
264 if let SettingControl::Text(state) = &item.control {
265 return state.value.clone();
266 }
267 }
268 }
269 self.entry_key.clone()
270 }
271
272 pub fn entry_path(&self) -> String {
284 let key = self.get_key();
289 if key.is_empty() {
290 self.map_path.clone()
291 } else {
292 format!("{}/{}", self.map_path, key)
293 }
294 }
295
296 pub fn button_count(&self) -> usize {
298 if self.is_new || self.no_delete {
299 2 } else {
301 3
302 }
303 }
304
305 pub fn is_dirty(&self) -> bool {
316 self.user_edited
317 }
318
319 pub fn mark_edited(&mut self) {
323 self.user_edited = true;
324 }
325
326 pub fn commit_pending_list_drafts(&mut self) {
336 for item in &mut self.items {
337 if let SettingControl::TextList(state) = &mut item.control {
338 if !state.new_item_text.is_empty() {
339 state.add_item();
340 }
341 }
342 }
343 }
344
345 pub fn to_value(&self) -> Value {
346 if self.is_single_value {
349 for item in &self.items {
350 if item.path != "__key__" {
351 return control_to_value(&item.control);
352 }
353 }
354 }
355
356 let mut obj = serde_json::Map::new();
357
358 for item in &self.items {
359 if item.path == "__key__" {
361 continue;
362 }
363
364 let field_name = item.path.trim_start_matches('/');
365 let value = control_to_value(&item.control);
366 obj.insert(field_name.to_string(), value);
367 }
368
369 Value::Object(obj)
370 }
371
372 pub fn current_item(&self) -> Option<&SettingItem> {
374 if self.focus_on_buttons {
375 None
376 } else {
377 self.items.get(self.selected_item)
378 }
379 }
380
381 pub fn current_item_mut(&mut self) -> Option<&mut SettingItem> {
383 if self.focus_on_buttons {
384 None
385 } else {
386 self.items.get_mut(self.selected_item)
387 }
388 }
389
390 pub fn focus_next(&mut self) {
397 if self.editing_text {
398 return;
399 }
400
401 if self.focus_on_buttons {
402 if self.focused_button + 1 < self.button_count() {
403 self.focused_button += 1;
404 } else {
405 if self.first_editable_index < self.items.len() {
407 self.focus_on_buttons = false;
408 self.selected_item = self.first_editable_index;
409 self.sub_focus = None;
410 self.init_composite_focus(true);
411 }
412 }
413 } else {
414 let handled = self.try_composite_focus_next();
416 if !handled {
417 if self.selected_item + 1 < self.items.len() {
419 self.selected_item += 1;
420 self.sub_focus = None;
421 self.init_composite_focus(true);
422 } else {
423 self.focus_on_buttons = true;
425 self.focused_button = 0;
426 }
427 }
428 }
429
430 self.update_focus_states();
431 self.ensure_selected_visible(self.viewport_height);
432 }
433
434 pub fn focus_prev(&mut self) {
440 if self.editing_text {
441 return;
442 }
443
444 if self.focus_on_buttons {
445 if self.focused_button > 0 {
446 self.focused_button -= 1;
447 } else {
448 if self.first_editable_index < self.items.len() {
450 self.focus_on_buttons = false;
451 self.selected_item = self.items.len().saturating_sub(1);
452 self.sub_focus = None;
453 self.init_composite_focus(false);
454 }
455 }
456 } else {
457 let handled = self.try_composite_focus_prev();
459 if !handled {
460 if self.selected_item > self.first_editable_index {
462 self.selected_item -= 1;
463 self.sub_focus = None;
464 self.init_composite_focus(false);
465 } else {
466 self.focus_on_buttons = true;
468 self.focused_button = self.button_count().saturating_sub(1);
469 }
470 }
471 }
472
473 self.update_focus_states();
474 self.ensure_selected_visible(self.viewport_height);
475 }
476
477 fn try_composite_focus_next(&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 = state.focused_entry.is_none(); if at_boundary {
489 return false;
490 }
491 if let Some(item) = self.items.get_mut(self.selected_item) {
492 if let SettingControl::Map(state) = &mut item.control {
493 return state.focus_next();
494 }
495 }
496 false
497 }
498 SettingControl::ObjectArray(state) => {
499 if state.focused_index.is_none() {
501 return false;
502 }
503 if let Some(item) = self.items.get_mut(self.selected_item) {
504 if let SettingControl::ObjectArray(state) = &mut item.control {
505 state.focus_next();
506 return true;
507 }
508 }
509 false
510 }
511 SettingControl::TextList(state) => {
512 if state.focused_item.is_none() {
514 return false;
515 }
516 if let Some(item) = self.items.get_mut(self.selected_item) {
517 if let SettingControl::TextList(state) = &mut item.control {
518 state.focus_next();
519 return true;
520 }
521 }
522 false
523 }
524 _ => false,
525 }
526 }
527
528 fn try_composite_focus_prev(&mut self) -> bool {
531 let item = match self.items.get(self.selected_item) {
532 Some(item) => item,
533 None => return false,
534 };
535 match &item.control {
536 SettingControl::Map(state) => {
537 let at_boundary = matches!(state.focused_entry, Some(0))
539 || (state.focused_entry.is_none() && state.entries.is_empty());
540 if at_boundary {
541 return false;
542 }
543 if let Some(item) = self.items.get_mut(self.selected_item) {
544 if let SettingControl::Map(state) = &mut item.control {
545 return state.focus_prev();
546 }
547 }
548 false
549 }
550 SettingControl::ObjectArray(state) => {
551 if matches!(state.focused_index, Some(0))
553 || (state.focused_index.is_none() && state.bindings.is_empty())
554 {
555 return false;
556 }
557 if let Some(item) = self.items.get_mut(self.selected_item) {
558 if let SettingControl::ObjectArray(state) = &mut item.control {
559 state.focus_prev();
560 return true;
561 }
562 }
563 false
564 }
565 SettingControl::TextList(state) => {
566 if matches!(state.focused_item, Some(0))
568 || (state.focused_item.is_none() && state.items.is_empty())
569 {
570 return false;
571 }
572 if let Some(item) = self.items.get_mut(self.selected_item) {
573 if let SettingControl::TextList(state) = &mut item.control {
574 state.focus_prev();
575 return true;
576 }
577 }
578 false
579 }
580 _ => false,
581 }
582 }
583
584 fn init_composite_focus(&mut self, from_above: bool) {
588 if let Some(item) = self.items.get_mut(self.selected_item) {
589 match &mut item.control {
590 SettingControl::Map(state) => {
591 state.init_focus(from_above);
592 }
593 SettingControl::ObjectArray(state) => {
594 if from_above {
595 state.focused_index = if state.bindings.is_empty() {
596 None
597 } else {
598 Some(0)
599 };
600 } else {
601 state.focused_index = None;
603 }
604 }
605 SettingControl::TextList(state) => {
606 if from_above {
607 state.focused_item = if state.items.is_empty() {
608 None
609 } else {
610 Some(0)
611 };
612 } else {
613 state.focused_item = None;
615 }
616 }
617 _ => {}
618 }
619 }
620 }
621
622 pub fn toggle_focus_region(&mut self) {
625 self.toggle_focus_region_direction(true);
626 }
627
628 pub fn toggle_focus_region_direction(&mut self, forward: bool) {
632 if self.editing_text {
633 return;
634 }
635
636 if self.focus_on_buttons {
637 if forward {
638 if self.focused_button + 1 < self.button_count() {
640 self.focused_button += 1;
641 } else {
642 if self.first_editable_index < self.items.len() {
644 self.focus_on_buttons = false;
645 if self.selected_item < self.first_editable_index {
646 self.selected_item = self.first_editable_index;
647 }
648 } else {
649 self.focused_button = 0;
651 }
652 }
653 } else {
654 if self.focused_button > 0 {
656 self.focused_button -= 1;
657 } else {
658 if self.first_editable_index < self.items.len() {
660 self.focus_on_buttons = false;
661 if self.selected_item < self.first_editable_index {
662 self.selected_item = self.first_editable_index;
663 }
664 } else {
665 self.focused_button = self.button_count().saturating_sub(1);
667 }
668 }
669 }
670 } else {
671 self.focus_on_buttons = true;
673 self.focused_button = if forward {
674 0
675 } else {
676 self.button_count().saturating_sub(1)
677 };
678 }
679
680 self.update_focus_states();
681 self.ensure_selected_visible(self.viewport_height);
682 }
683
684 fn init_object_array_focus(&mut self) {
686 self.init_composite_focus(true);
687 }
688
689 pub fn update_focus_states(&mut self) {
691 for (idx, item) in self.items.iter_mut().enumerate() {
692 let state = if !self.focus_on_buttons && idx == self.selected_item {
693 FocusState::Focused
694 } else {
695 FocusState::Normal
696 };
697
698 match &mut item.control {
699 SettingControl::Toggle(s) => s.focus = state,
700 SettingControl::Number(s) => s.focus = state,
701 SettingControl::Dropdown(s) => s.focus = state,
702 SettingControl::Text(s) => s.focus = state,
703 SettingControl::TextList(s) => s.focus = state,
704 SettingControl::DualList(s) => s.focus = state,
705 SettingControl::Map(s) => s.focus = state,
706 SettingControl::ObjectArray(s) => s.focus = state,
707 SettingControl::Json(s) => s.focus = state,
708 SettingControl::Complex { .. } => {}
709 }
710 }
711 }
712
713 const SECTION_HEADER_HEIGHT: usize = 2;
715
716 pub fn total_content_height(&self) -> usize {
718 let items_height: usize = self
719 .items
720 .iter()
721 .map(|item| {
722 let section_h = if item.is_section_start {
723 Self::SECTION_HEADER_HEIGHT
724 } else {
725 0
726 };
727 item.control.control_height() as usize + section_h
728 })
729 .sum();
730 let separator_height =
732 if self.first_editable_index > 0 && self.first_editable_index < self.items.len() {
733 1
734 } else {
735 0
736 };
737 items_height + separator_height
738 }
739
740 pub fn selected_item_offset(&self) -> usize {
742 let items_offset: usize = self
743 .items
744 .iter()
745 .take(self.selected_item)
746 .map(|item| {
747 let section_h = if item.is_section_start {
748 Self::SECTION_HEADER_HEIGHT
749 } else {
750 0
751 };
752 item.control.control_height() as usize + section_h
753 })
754 .sum();
755 let separator_offset = if self.first_editable_index > 0
757 && self.first_editable_index < self.items.len()
758 && self.selected_item >= self.first_editable_index
759 {
760 1
761 } else {
762 0
763 };
764 let own_section_h = self
766 .items
767 .get(self.selected_item)
768 .map(|item| {
769 if item.is_section_start {
770 Self::SECTION_HEADER_HEIGHT
771 } else {
772 0
773 }
774 })
775 .unwrap_or(0);
776 items_offset + separator_offset + own_section_h
777 }
778
779 pub fn selected_item_height(&self) -> usize {
781 self.items
782 .get(self.selected_item)
783 .map(|item| item.control.control_height() as usize)
784 .unwrap_or(1)
785 }
786
787 pub fn ensure_selected_visible(&mut self, viewport_height: usize) {
789 if self.focus_on_buttons {
790 let total = self.total_content_height();
792 if total > viewport_height {
793 self.scroll_offset = total.saturating_sub(viewport_height);
794 }
795 return;
796 }
797
798 let item_start = self.selected_item_offset();
799 let item_end = item_start + self.selected_item_height();
800
801 if item_start < self.scroll_offset {
803 self.scroll_offset = item_start;
804 }
805 else if item_end > self.scroll_offset + viewport_height {
807 self.scroll_offset = item_end.saturating_sub(viewport_height);
808 }
809 }
810
811 pub fn ensure_cursor_visible(&mut self) {
816 if !self.editing_text || self.focus_on_buttons {
817 return;
818 }
819
820 let cursor_row = if let Some(item) = self.items.get(self.selected_item) {
822 if let SettingControl::Json(state) = &item.control {
823 state.cursor_pos().0
824 } else {
825 return; }
827 } else {
828 return;
829 };
830
831 let item_offset = self.selected_item_offset();
834 let cursor_content_row = item_offset + 1 + cursor_row;
835
836 let viewport_height = self.viewport_height;
837
838 if cursor_content_row < self.scroll_offset {
840 self.scroll_offset = cursor_content_row;
841 }
842 else if cursor_content_row >= self.scroll_offset + viewport_height {
844 self.scroll_offset = cursor_content_row.saturating_sub(viewport_height) + 1;
845 }
846 }
847
848 pub fn scroll_up(&mut self) {
850 self.scroll_offset = self.scroll_offset.saturating_sub(1);
851 }
852
853 pub fn scroll_down(&mut self, viewport_height: usize) {
855 let max_scroll = self.total_content_height().saturating_sub(viewport_height);
856 if self.scroll_offset < max_scroll {
857 self.scroll_offset += 1;
858 }
859 }
860
861 pub fn scroll_to_ratio(&mut self, ratio: f32) {
865 let max_scroll = self
866 .total_content_height()
867 .saturating_sub(self.viewport_height);
868 let new_offset = (ratio * max_scroll as f32).round() as usize;
869 self.scroll_offset = new_offset.min(max_scroll);
870 }
871
872 pub fn start_editing(&mut self) {
874 if let Some(item) = self.current_item_mut() {
875 if item.read_only {
877 return;
878 }
879 match &mut item.control {
880 SettingControl::Text(state) => {
881 state.cursor = state.value.len();
882 state.editing = true;
883 self.editing_text = true;
884 }
885 SettingControl::TextList(state) => {
886 if state.focused_item.is_none() {
892 state.activate_pending();
893 }
894 self.editing_text = true;
895 }
896 SettingControl::Number(state) => {
897 state.start_editing();
898 self.editing_text = true;
899 }
900 SettingControl::Json(state) => {
901 state.clear_placeholder_for_edit();
904 self.editing_text = true;
905 }
906 _ => {}
907 }
908 }
909 }
910
911 pub fn stop_editing(&mut self) {
913 if let Some(item) = self.current_item_mut() {
914 match &mut item.control {
915 SettingControl::Number(state) => state.cancel_editing(),
916 SettingControl::Text(state) => state.editing = false,
917 SettingControl::TextList(state) if state.focused_item.is_none() => {
924 state.cancel_pending();
925 }
926 SettingControl::Json(state) => state.restore_unset_if_empty(),
930 _ => {}
931 }
932 }
933 self.editing_text = false;
934 }
935
936 pub fn insert_char(&mut self, c: char) {
938 if !self.editing_text {
939 return;
940 }
941 self.user_edited = true;
942 if let Some(item) = self.current_item_mut() {
943 match &mut item.control {
944 SettingControl::Text(state) => {
945 state.insert(c);
946 }
947 SettingControl::TextList(state) => {
948 state.insert(c);
949 }
950 SettingControl::Number(state) => {
951 state.insert_char(c);
952 }
953 SettingControl::Json(state) => {
954 state.insert(c);
955 }
956 _ => {}
957 }
958 }
959 }
960
961 pub fn insert_str(&mut self, s: &str) {
962 if !self.editing_text {
963 return;
964 }
965 self.user_edited = true;
966 if let Some(item) = self.current_item_mut() {
967 match &mut item.control {
968 SettingControl::Text(state) => {
969 state.insert_str(s);
970 }
971 SettingControl::TextList(state) => {
972 state.insert_str(s);
973 }
974 SettingControl::Number(state) => {
975 for c in s.chars() {
976 state.insert_char(c);
977 }
978 }
979 SettingControl::Json(state) => {
980 state.insert_str(s);
981 }
982 _ => {}
983 }
984 }
985 }
986
987 pub fn backspace(&mut self) {
989 if !self.editing_text {
990 return;
991 }
992 self.user_edited = true;
993 if let Some(item) = self.current_item_mut() {
994 match &mut item.control {
995 SettingControl::Text(state) => {
996 state.backspace();
997 }
998 SettingControl::TextList(state) => {
999 state.backspace();
1000 }
1001 SettingControl::Number(state) => {
1002 state.backspace();
1003 }
1004 SettingControl::Json(state) => {
1005 state.backspace();
1006 }
1007 _ => {}
1008 }
1009 }
1010 }
1011
1012 pub fn cursor_left(&mut self) {
1014 if !self.editing_text {
1015 return;
1016 }
1017 if let Some(item) = self.current_item_mut() {
1018 match &mut item.control {
1019 SettingControl::Text(state) => {
1020 state.move_left();
1021 }
1022 SettingControl::TextList(state) => {
1023 state.move_left();
1024 }
1025 SettingControl::Json(state) => {
1026 state.move_left();
1027 }
1028 _ => {}
1029 }
1030 }
1031 }
1032
1033 pub fn cursor_left_selecting(&mut self) {
1035 if !self.editing_text {
1036 return;
1037 }
1038 if let Some(item) = self.current_item_mut() {
1039 if let SettingControl::Json(state) = &mut item.control {
1040 state.editor.move_left_selecting();
1041 }
1042 }
1043 }
1044
1045 pub fn cursor_right(&mut self) {
1047 if !self.editing_text {
1048 return;
1049 }
1050 if let Some(item) = self.current_item_mut() {
1051 match &mut item.control {
1052 SettingControl::Text(state) => {
1053 state.move_right();
1054 }
1055 SettingControl::TextList(state) => {
1056 state.move_right();
1057 }
1058 SettingControl::Json(state) => {
1059 state.move_right();
1060 }
1061 _ => {}
1062 }
1063 }
1064 }
1065
1066 pub fn cursor_right_selecting(&mut self) {
1068 if !self.editing_text {
1069 return;
1070 }
1071 if let Some(item) = self.current_item_mut() {
1072 if let SettingControl::Json(state) = &mut item.control {
1073 state.editor.move_right_selecting();
1074 }
1075 }
1076 }
1077
1078 pub fn cursor_up(&mut self) {
1080 if !self.editing_text {
1081 return;
1082 }
1083 if let Some(item) = self.current_item_mut() {
1084 if let SettingControl::Json(state) = &mut item.control {
1085 state.move_up();
1086 }
1087 }
1088 self.ensure_cursor_visible();
1089 }
1090
1091 pub fn cursor_up_selecting(&mut self) {
1093 if !self.editing_text {
1094 return;
1095 }
1096 if let Some(item) = self.current_item_mut() {
1097 if let SettingControl::Json(state) = &mut item.control {
1098 state.editor.move_up_selecting();
1099 }
1100 }
1101 self.ensure_cursor_visible();
1102 }
1103
1104 pub fn cursor_down(&mut self) {
1106 if !self.editing_text {
1107 return;
1108 }
1109 if let Some(item) = self.current_item_mut() {
1110 if let SettingControl::Json(state) = &mut item.control {
1111 state.move_down();
1112 }
1113 }
1114 self.ensure_cursor_visible();
1115 }
1116
1117 pub fn cursor_down_selecting(&mut self) {
1119 if !self.editing_text {
1120 return;
1121 }
1122 if let Some(item) = self.current_item_mut() {
1123 if let SettingControl::Json(state) = &mut item.control {
1124 state.editor.move_down_selecting();
1125 }
1126 }
1127 self.ensure_cursor_visible();
1128 }
1129
1130 pub fn insert_newline(&mut self) {
1132 self.user_edited = true;
1133 if !self.editing_text {
1134 return;
1135 }
1136 if let Some(item) = self.current_item_mut() {
1137 if let SettingControl::Json(state) = &mut item.control {
1138 state.insert('\n');
1139 }
1140 }
1141 }
1142
1143 pub fn revert_json_and_stop(&mut self) {
1145 if let Some(item) = self.current_item_mut() {
1146 if let SettingControl::Json(state) = &mut item.control {
1147 state.revert();
1148 }
1149 }
1150 self.editing_text = false;
1151 }
1152
1153 pub fn is_editing_json(&self) -> bool {
1155 if !self.editing_text {
1156 return false;
1157 }
1158 self.current_item()
1159 .map(|item| matches!(&item.control, SettingControl::Json(_)))
1160 .unwrap_or(false)
1161 }
1162
1163 pub fn toggle_bool(&mut self) {
1165 self.user_edited = true;
1166 if let Some(item) = self.current_item_mut() {
1167 if item.read_only {
1169 return;
1170 }
1171 if let SettingControl::Toggle(state) = &mut item.control {
1172 state.checked = !state.checked;
1173 }
1174 }
1175 }
1176
1177 pub fn toggle_dropdown(&mut self) {
1179 if let Some(item) = self.current_item_mut() {
1180 if item.read_only {
1182 return;
1183 }
1184 if let SettingControl::Dropdown(state) = &mut item.control {
1185 state.open = !state.open;
1186 }
1187 }
1188 }
1189
1190 pub fn dropdown_prev(&mut self) {
1192 self.user_edited = true;
1193 if let Some(item) = self.current_item_mut() {
1194 if let SettingControl::Dropdown(state) = &mut item.control {
1195 if state.open {
1196 state.select_prev();
1197 }
1198 }
1199 }
1200 }
1201
1202 pub fn dropdown_next(&mut self) {
1204 self.user_edited = true;
1205 if let Some(item) = self.current_item_mut() {
1206 if let SettingControl::Dropdown(state) = &mut item.control {
1207 if state.open {
1208 state.select_next();
1209 }
1210 }
1211 }
1212 }
1213
1214 pub fn dropdown_confirm(&mut self) {
1216 if let Some(item) = self.current_item_mut() {
1217 if let SettingControl::Dropdown(state) = &mut item.control {
1218 state.open = false;
1219 }
1220 }
1221 }
1222
1223 pub fn delete_list_item(&mut self) {
1225 self.user_edited = true;
1226 if let Some(item) = self.current_item_mut() {
1227 if let SettingControl::TextList(state) = &mut item.control {
1228 if let Some(idx) = state.focused_item {
1230 state.remove_item(idx);
1231 }
1232 }
1233 }
1234 }
1235
1236 pub fn delete(&mut self) {
1238 if !self.editing_text {
1239 return;
1240 }
1241 self.user_edited = true;
1242 if let Some(item) = self.current_item_mut() {
1243 match &mut item.control {
1244 SettingControl::Text(state) => {
1245 state.delete();
1246 }
1247 SettingControl::TextList(state) => {
1248 state.delete();
1249 }
1250 SettingControl::Json(state) => {
1251 state.delete();
1252 }
1253 _ => {}
1254 }
1255 }
1256 }
1257
1258 pub fn cursor_home(&mut self) {
1260 if !self.editing_text {
1261 return;
1262 }
1263 if let Some(item) = self.current_item_mut() {
1264 match &mut item.control {
1265 SettingControl::Text(state) => {
1266 state.move_home();
1267 }
1268 SettingControl::TextList(state) => {
1269 state.move_home();
1270 }
1271 SettingControl::Json(state) => {
1272 state.move_home();
1273 }
1274 _ => {}
1275 }
1276 }
1277 }
1278
1279 pub fn cursor_end(&mut self) {
1281 if !self.editing_text {
1282 return;
1283 }
1284 if let Some(item) = self.current_item_mut() {
1285 match &mut item.control {
1286 SettingControl::Text(state) => {
1287 state.move_end();
1288 }
1289 SettingControl::TextList(state) => {
1290 state.move_end();
1291 }
1292 SettingControl::Json(state) => {
1293 state.move_end();
1294 }
1295 _ => {}
1296 }
1297 }
1298 }
1299
1300 pub fn select_all(&mut self) {
1302 if !self.editing_text {
1303 return;
1304 }
1305 if let Some(item) = self.current_item_mut() {
1306 if let SettingControl::Json(state) = &mut item.control {
1307 state.select_all();
1308 }
1309 }
1311 }
1312
1313 pub fn selected_text(&self) -> Option<String> {
1315 if !self.editing_text {
1316 return None;
1317 }
1318 if let Some(item) = self.current_item() {
1319 if let SettingControl::Json(state) = &item.control {
1320 return state.selected_text();
1321 }
1322 }
1323 None
1324 }
1325
1326 pub fn is_editing(&self) -> bool {
1328 self.editing_text
1329 || self
1330 .current_item()
1331 .map(|item| {
1332 matches!(
1333 &item.control,
1334 SettingControl::Dropdown(s) if s.open
1335 )
1336 })
1337 .unwrap_or(false)
1338 }
1339}
1340
1341#[cfg(test)]
1342mod tests {
1343 use super::*;
1344
1345 fn create_test_schema() -> SettingSchema {
1346 SettingSchema {
1347 path: "/test".to_string(),
1348 name: "Test".to_string(),
1349 description: Some("Test schema".to_string()),
1350 setting_type: SettingType::Object {
1351 properties: vec![
1352 SettingSchema {
1353 path: "/enabled".to_string(),
1354 name: "Enabled".to_string(),
1355 description: Some("Enable this".to_string()),
1356 setting_type: SettingType::Boolean,
1357 default: Some(serde_json::json!(true)),
1358 read_only: false,
1359 section: None,
1360 order: None,
1361 nullable: false,
1362 enum_from: None,
1363 dual_list_sibling: None,
1364 dynamically_extendable_status_bar_elements: false,
1365 },
1366 SettingSchema {
1367 path: "/command".to_string(),
1368 name: "Command".to_string(),
1369 description: Some("Command to run".to_string()),
1370 setting_type: SettingType::String,
1371 default: Some(serde_json::json!("")),
1372 read_only: false,
1373 section: None,
1374 order: None,
1375 nullable: false,
1376 enum_from: None,
1377 dual_list_sibling: None,
1378 dynamically_extendable_status_bar_elements: false,
1379 },
1380 ],
1381 },
1382 default: None,
1383 read_only: false,
1384 section: None,
1385 order: None,
1386 nullable: false,
1387 enum_from: None,
1388 dual_list_sibling: None,
1389 dynamically_extendable_status_bar_elements: false,
1390 }
1391 }
1392
1393 #[test]
1394 fn from_schema_creates_key_item_first() {
1395 let schema = create_test_schema();
1396 let dialog = EntryDialogState::from_schema(
1397 "test".to_string(),
1398 &serde_json::json!({}),
1399 &schema,
1400 "/test",
1401 false,
1402 false,
1403 &HashMap::new(),
1404 );
1405
1406 assert!(!dialog.items.is_empty());
1407 assert_eq!(dialog.items[0].path, "__key__");
1408 assert_eq!(dialog.items[0].name, "Key");
1409 }
1410
1411 #[test]
1412 fn from_schema_creates_items_from_properties() {
1413 let schema = create_test_schema();
1414 let dialog = EntryDialogState::from_schema(
1415 "test".to_string(),
1416 &serde_json::json!({"enabled": true, "command": "test-cmd"}),
1417 &schema,
1418 "/test",
1419 false,
1420 false,
1421 &HashMap::new(),
1422 );
1423
1424 assert_eq!(dialog.items.len(), 3);
1426 assert_eq!(dialog.items[1].name, "Enabled");
1427 assert_eq!(dialog.items[2].name, "Command");
1428 }
1429
1430 #[test]
1431 fn get_key_returns_key_value() {
1432 let schema = create_test_schema();
1433 let dialog = EntryDialogState::from_schema(
1434 "mykey".to_string(),
1435 &serde_json::json!({}),
1436 &schema,
1437 "/test",
1438 false,
1439 false,
1440 &HashMap::new(),
1441 );
1442
1443 assert_eq!(dialog.get_key(), "mykey");
1444 }
1445
1446 #[test]
1447 fn to_value_excludes_key() {
1448 let schema = create_test_schema();
1449 let dialog = EntryDialogState::from_schema(
1450 "test".to_string(),
1451 &serde_json::json!({"enabled": true, "command": "cmd"}),
1452 &schema,
1453 "/test",
1454 false,
1455 false,
1456 &HashMap::new(),
1457 );
1458
1459 let value = dialog.to_value();
1460 assert!(value.get("__key__").is_none());
1461 assert!(value.get("enabled").is_some());
1462 }
1463
1464 #[test]
1465 fn focus_navigation_works() {
1466 let schema = create_test_schema();
1467 let mut dialog = EntryDialogState::from_schema(
1468 "test".to_string(),
1469 &serde_json::json!({}),
1470 &schema,
1471 "/test",
1472 false, false, &HashMap::new(),
1475 );
1476
1477 assert_eq!(dialog.first_editable_index, 1);
1481 assert_eq!(dialog.selected_item, 1); assert!(!dialog.focus_on_buttons);
1483
1484 dialog.focus_next();
1485 assert_eq!(dialog.selected_item, 2); dialog.focus_next();
1488 assert!(dialog.focus_on_buttons); assert_eq!(dialog.focused_button, 0);
1490
1491 dialog.focus_prev();
1493 assert!(!dialog.focus_on_buttons);
1494 assert_eq!(dialog.selected_item, 2); dialog.focus_prev();
1497 assert_eq!(dialog.selected_item, 1); dialog.focus_prev();
1500 assert!(dialog.focus_on_buttons); }
1502
1503 #[test]
1504 fn entry_path_joins_map_path_and_entry_key() {
1505 let schema = create_test_schema();
1506
1507 let existing = EntryDialogState::from_schema(
1509 "rust".to_string(),
1510 &serde_json::json!({}),
1511 &schema,
1512 "/lsp",
1513 false,
1514 false,
1515 &HashMap::new(),
1516 );
1517 assert_eq!(existing.entry_path(), "/lsp/rust");
1518
1519 let new_entry = EntryDialogState::from_schema(
1522 String::new(),
1523 &serde_json::json!({}),
1524 &schema,
1525 "/lsp",
1526 true,
1527 false,
1528 &HashMap::new(),
1529 );
1530 assert_eq!(new_entry.entry_path(), "/lsp");
1531 }
1532
1533 #[test]
1534 fn entry_path_tracks_live_key_edits_for_new_entries() {
1535 let schema = create_test_schema();
1536 let mut dialog = EntryDialogState::from_schema(
1537 String::new(),
1538 &serde_json::json!({}),
1539 &schema,
1540 "/universal_lsp",
1541 true,
1542 false,
1543 &HashMap::new(),
1544 );
1545
1546 for item in dialog.items.iter_mut() {
1548 if item.path == "__key__" {
1549 if let SettingControl::Text(state) = &mut item.control {
1550 state.value = "myserver".to_string();
1551 }
1552 }
1553 }
1554
1555 assert_eq!(dialog.entry_path(), "/universal_lsp/myserver");
1556 }
1557
1558 #[test]
1559 fn button_count_differs_for_new_vs_existing() {
1560 let schema = create_test_schema();
1561
1562 let new_dialog = EntryDialogState::from_schema(
1563 "test".to_string(),
1564 &serde_json::json!({}),
1565 &schema,
1566 "/test",
1567 true,
1568 false,
1569 &HashMap::new(),
1570 );
1571 assert_eq!(new_dialog.button_count(), 2); let existing_dialog = EntryDialogState::from_schema(
1574 "test".to_string(),
1575 &serde_json::json!({}),
1576 &schema,
1577 "/test",
1578 false,
1579 false, &HashMap::new(),
1581 );
1582 assert_eq!(existing_dialog.button_count(), 3); let no_delete_dialog = EntryDialogState::from_schema(
1586 "test".to_string(),
1587 &serde_json::json!({}),
1588 &schema,
1589 "/test",
1590 false,
1591 true, &HashMap::new(),
1593 );
1594 assert_eq!(no_delete_dialog.button_count(), 2); }
1596}