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 rust_i18n::t;
12use serde_json::Value;
13use std::collections::{HashMap, HashSet};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum FieldAction {
32 Reset,
34 Inherit,
37}
38
39fn is_simple_field_control(control: &SettingControl) -> bool {
43 matches!(
44 control,
45 SettingControl::Toggle(_)
46 | SettingControl::Number(_)
47 | SettingControl::Text(_)
48 | SettingControl::Dropdown(_)
49 )
50}
51
52pub fn layout_field_action_buttons(
57 buttons: &[(FieldAction, String)],
58 right_edge: u16,
59) -> Vec<(FieldAction, u16, u16)> {
60 if buttons.is_empty() {
61 return Vec::new();
62 }
63 let widths: Vec<u16> = buttons
64 .iter()
65 .map(|(_, label)| label.chars().count() as u16)
66 .collect();
67 let gaps = buttons.len().saturating_sub(1) as u16;
68 let total: u16 = widths.iter().sum::<u16>() + gaps + 1;
69 let mut x = right_edge.saturating_sub(total);
70 let mut out = Vec::with_capacity(buttons.len());
71 for ((action, _), w) in buttons.iter().zip(widths) {
72 out.push((*action, x, w));
73 x = x.saturating_add(w + 1);
74 }
75 out
76}
77
78#[derive(Debug, Clone)]
80pub struct EntryDialogState {
81 pub entry_key: String,
83 pub map_path: String,
85 pub title: String,
87 pub is_new: bool,
89 pub items: Vec<SettingItem>,
91 pub selected_item: usize,
93 pub sub_focus: Option<usize>,
95 pub editing_text: bool,
97 pub focused_button: usize,
99 pub focus_on_buttons: bool,
101 pub delete_requested: bool,
103 pub scroll_offset: usize,
105 pub viewport_height: usize,
107 pub hover_item: Option<usize>,
109 pub hover_button: Option<usize>,
111 pub original_value: Value,
113 pub first_editable_index: usize,
116 pub no_delete: bool,
118 pub is_single_value: bool,
121 pub is_array_item: bool,
126 pub user_edited: bool,
131 pub field_button_focus: Option<usize>,
137 pub inheritable_fields: HashSet<String>,
144}
145
146impl EntryDialogState {
147 pub fn from_schema(
153 key: String,
154 value: &Value,
155 schema: &SettingSchema,
156 map_path: &str,
157 is_new: bool,
158 no_delete: bool,
159 available_status_bar_tokens: &HashMap<String, String>,
160 ) -> Self {
161 let mut items = Vec::new();
162
163 let key_item = SettingItem {
165 path: "__key__".to_string(),
166 name: "Key".to_string(),
167 description: Some("unique identifier for this entry".to_string()),
168 control: SettingControl::Text(TextInputState::new("Key").with_value(&key)),
169 default: None,
170 modified: false,
171 layer_source: crate::config_io::ConfigLayer::System,
172 read_only: !is_new, is_auto_managed: false,
174 nullable: false,
175 is_null: false,
176 section: None,
177 is_section_start: false,
178 style: ItemBoxStyle::default(),
179 dual_list_sibling: None,
180 };
181 items.push(key_item);
182
183 let is_single_value = !matches!(&schema.setting_type, SettingType::Object { .. });
185 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 } else {
193 let item = build_item_from_value(schema, Some(value), available_status_bar_tokens);
196 items.push(item);
197 }
198
199 items.sort_by_key(|item| !item.read_only);
201
202 Self::compute_section_starts(&mut items);
204
205 let first_editable_index = items
207 .iter()
208 .position(|item| !item.read_only)
209 .unwrap_or(items.len());
210
211 let focus_on_buttons = first_editable_index >= items.len();
213 let selected_item = if focus_on_buttons {
214 0
215 } else {
216 first_editable_index
217 };
218
219 let title = if is_new {
220 format!("Add {}", schema.name)
221 } else {
222 format!("Edit {}", schema.name)
223 };
224
225 let mut result = Self {
226 entry_key: key,
227 map_path: map_path.to_string(),
228 title,
229 is_new,
230 items,
231 selected_item,
232 sub_focus: None,
233 editing_text: false,
234 focused_button: 0,
235 focus_on_buttons,
236 delete_requested: false,
237 scroll_offset: 0,
238 viewport_height: 20, hover_item: None,
240 hover_button: None,
241 original_value: value.clone(),
242 first_editable_index,
243 no_delete,
244 is_single_value,
245 is_array_item: false,
246 user_edited: false,
247 field_button_focus: None,
248 inheritable_fields: HashSet::new(),
249 };
250 result.init_object_array_focus();
253 result
254 }
255
256 pub fn for_array_item(
260 index: Option<usize>,
261 value: &Value,
262 schema: &SettingSchema,
263 array_path: &str,
264 is_new: bool,
265 available_status_bar_tokens: &HashMap<String, String>,
266 ) -> Self {
267 let mut items = Vec::new();
268
269 if let SettingType::Object { properties } = &schema.setting_type {
271 for prop in properties {
272 let field_name = prop.path.trim_start_matches('/');
273 let field_value = value.get(field_name);
274 let item = build_item_from_value(prop, field_value, available_status_bar_tokens);
275 items.push(item);
276 }
277 }
278
279 items.sort_by_key(|item| !item.read_only);
281
282 Self::compute_section_starts(&mut items);
284
285 let first_editable_index = items
287 .iter()
288 .position(|item| !item.read_only)
289 .unwrap_or(items.len());
290
291 let focus_on_buttons = first_editable_index >= items.len();
293 let selected_item = if focus_on_buttons {
294 0
295 } else {
296 first_editable_index
297 };
298
299 let title = if is_new {
300 format!("Add {}", schema.name)
301 } else {
302 format!("Edit {}", schema.name)
303 };
304
305 Self {
306 entry_key: index.map_or(String::new(), |i| i.to_string()),
307 map_path: array_path.to_string(),
308 title,
309 is_new,
310 items,
311 selected_item,
312 sub_focus: None,
313 editing_text: false,
314 focused_button: 0,
315 focus_on_buttons,
316 delete_requested: false,
317 scroll_offset: 0,
318 viewport_height: 20,
319 hover_item: None,
320 hover_button: None,
321 original_value: value.clone(),
322 first_editable_index,
323 no_delete: false, is_single_value: false,
325 is_array_item: true,
326 user_edited: false,
327 field_button_focus: None,
328 inheritable_fields: HashSet::new(),
329 }
330 }
331
332 fn compute_section_starts(items: &mut [SettingItem]) {
335 let mut last_section: Option<&str> = None;
336 for item in items.iter_mut() {
337 let current = item.section.as_deref();
338 if current.is_some() && current != last_section {
339 item.is_section_start = true;
340 }
341 if current.is_some() {
342 last_section = current;
343 }
344 }
345 }
346
347 pub fn get_key(&self) -> String {
349 for item in &self.items {
351 if item.path == "__key__" {
352 if let SettingControl::Text(state) = &item.control {
353 return state.value.clone();
354 }
355 }
356 }
357 self.entry_key.clone()
358 }
359
360 pub fn entry_path(&self) -> String {
372 let key = self.get_key();
377 if key.is_empty() {
378 self.map_path.clone()
379 } else {
380 format!("{}/{}", self.map_path, key)
381 }
382 }
383
384 pub fn button_count(&self) -> usize {
386 if self.is_new || self.no_delete {
387 2 } else {
389 3
390 }
391 }
392
393 pub fn is_dirty(&self) -> bool {
404 self.user_edited
405 }
406
407 pub fn mark_edited(&mut self) {
411 self.user_edited = true;
412 }
413
414 fn mark_field_edited(&mut self) {
420 self.user_edited = true;
421 if let Some(item) = self.current_item_mut() {
422 item.is_null = false;
423 if let SettingControl::Toggle(state) = &mut item.control {
424 state.inherited = false;
425 }
426 }
427 }
428
429 pub fn inherit_field(&mut self, idx: usize) -> bool {
435 let Some(item) = self.items.get_mut(idx) else {
436 return false;
437 };
438 if item.read_only || !item.nullable || item.is_null {
439 return false;
440 }
441 item.is_null = true;
442 item.modified = false;
443 if let SettingControl::Toggle(state) = &mut item.control {
444 state.inherited = true;
445 }
446 self.user_edited = true;
447 true
448 }
449
450 pub fn reset_field(&mut self, idx: usize) -> bool {
454 let Some(default) = self.reset_distinct_default(idx) else {
455 return false;
456 };
457 let Some(item) = self.items.get_mut(idx) else {
458 return false;
459 };
460 super::state::update_control_from_value(&mut item.control, &default);
461 item.is_null = false;
463 item.modified = false;
464 if let SettingControl::Toggle(state) = &mut item.control {
465 state.inherited = false;
466 }
467 self.user_edited = true;
468 true
469 }
470
471 fn reset_distinct_default(&self, idx: usize) -> Option<Value> {
476 let item = self.items.get(idx)?;
477 let resettable = is_simple_field_control(&item.control)
483 || matches!(item.control, SettingControl::Json(_));
484 if item.read_only || item.is_null || !resettable {
485 return None;
486 }
487 let default = item.default.as_ref()?;
488 if item.nullable && default.is_null() {
491 return None;
492 }
493 if control_to_value(&item.control) == *default {
495 return None;
496 }
497 Some(default.clone())
498 }
499
500 fn field_inherits(&self, idx: usize) -> bool {
503 self.items
504 .get(idx)
505 .map(|item| {
506 self.inheritable_fields
507 .contains(item.path.trim_start_matches('/'))
508 })
509 .unwrap_or(false)
510 }
511
512 pub fn field_action_buttons(&self, idx: usize) -> Vec<(FieldAction, String)> {
517 let Some(item) = self.items.get(idx) else {
518 return Vec::new();
519 };
520 if item.read_only {
521 return Vec::new();
522 }
523 let mut buttons = Vec::new();
524 if self.reset_distinct_default(idx).is_some() {
525 buttons.push((
526 FieldAction::Reset,
527 format!("[{}]", t!("settings.btn_reset")),
528 ));
529 }
530 if item.nullable && !item.is_null {
534 let label = if self.field_inherits(idx) {
535 t!("settings.btn_inherit")
536 } else {
537 t!("settings.btn_clear")
538 };
539 buttons.push((FieldAction::Inherit, format!("[{}]", label)));
540 }
541 buttons
542 }
543
544 fn perform_field_action(&mut self, idx: usize, action: FieldAction) -> bool {
546 match action {
547 FieldAction::Reset => self.reset_field(idx),
548 FieldAction::Inherit => self.inherit_field(idx),
549 }
550 }
551
552 pub fn activate_focused_field_button(&mut self) -> bool {
555 let Some(i) = self.field_button_focus else {
556 return false;
557 };
558 if self.focus_on_buttons {
559 return false;
560 }
561 let idx = self.selected_item;
562 if let Some(action) = self.field_action_buttons(idx).get(i).map(|(a, _)| *a) {
563 self.perform_field_action(idx, action);
564 }
565 self.field_button_focus = None;
566 self.update_focus_states();
567 true
568 }
569
570 pub fn commit_pending_list_drafts(&mut self) {
580 for item in &mut self.items {
581 if let SettingControl::TextList(state) = &mut item.control {
582 if !state.new_item_text.is_empty() {
583 state.add_item();
584 }
585 }
586 }
587 }
588
589 pub fn to_value(&self) -> Value {
590 if self.is_single_value {
593 for item in &self.items {
594 if item.path != "__key__" {
595 return control_to_value(&item.control);
596 }
597 }
598 }
599
600 let mut obj = serde_json::Map::new();
601
602 for item in &self.items {
603 if item.path == "__key__" {
605 continue;
606 }
607
608 let field_name = item.path.trim_start_matches('/');
609
610 if item.nullable && item.is_null {
621 continue;
622 }
623
624 let value = control_to_value(&item.control);
625 obj.insert(field_name.to_string(), value);
626 }
627
628 Value::Object(obj)
629 }
630
631 pub fn current_item(&self) -> Option<&SettingItem> {
633 if self.focus_on_buttons {
634 None
635 } else {
636 self.items.get(self.selected_item)
637 }
638 }
639
640 pub fn current_item_mut(&mut self) -> Option<&mut SettingItem> {
642 if self.focus_on_buttons {
643 None
644 } else {
645 self.items.get_mut(self.selected_item)
646 }
647 }
648
649 fn field_focusable_count(&self, idx: usize) -> usize {
661 self.field_action_buttons(idx).len()
662 }
663
664 pub fn focus_next_field(&mut self) {
670 if self.editing_text {
671 return;
672 }
673 self.field_button_focus = None;
674 if self.selected_item + 1 < self.items.len() {
675 self.selected_item += 1;
676 self.sub_focus = None;
677 self.init_composite_focus(true);
678 } else {
679 self.focus_on_buttons = true;
680 self.focused_button = 0;
681 }
682 self.update_focus_states();
683 self.ensure_selected_visible(self.viewport_height);
684 }
685
686 pub fn focus_prev_field(&mut self) {
689 if self.editing_text {
690 return;
691 }
692 self.field_button_focus = None;
693 if self.selected_item > self.first_editable_index {
694 self.selected_item -= 1;
695 self.sub_focus = None;
696 self.init_composite_focus(false);
697 } else {
698 self.focus_on_buttons = true;
699 self.focused_button = self.button_count().saturating_sub(1);
700 }
701 self.update_focus_states();
702 self.ensure_selected_visible(self.viewport_height);
703 }
704
705 pub fn focus_next(&mut self) {
706 if self.editing_text {
707 return;
708 }
709
710 if self.focus_on_buttons {
711 if self.focused_button + 1 < self.button_count() {
712 self.focused_button += 1;
713 } else {
714 if self.first_editable_index < self.items.len() {
716 self.focus_on_buttons = false;
717 self.selected_item = self.first_editable_index;
718 self.sub_focus = None;
719 self.field_button_focus = None;
720 self.init_composite_focus(true);
721 }
722 }
723 } else if let Some(i) = self.field_button_focus {
724 if i + 1 < self.field_focusable_count(self.selected_item) {
726 self.field_button_focus = Some(i + 1);
727 } else {
728 self.field_button_focus = None;
729 if self.selected_item + 1 < self.items.len() {
730 self.selected_item += 1;
731 self.sub_focus = None;
732 self.init_composite_focus(true);
733 } else {
734 self.focus_on_buttons = true;
735 self.focused_button = 0;
736 }
737 }
738 } else {
739 let handled = self.try_composite_focus_next();
741 if !handled {
742 if self.field_focusable_count(self.selected_item) > 0 {
745 self.field_button_focus = Some(0);
746 } else if self.selected_item + 1 < self.items.len() {
747 self.selected_item += 1;
748 self.sub_focus = None;
749 self.init_composite_focus(true);
750 } else {
751 self.focus_on_buttons = true;
753 self.focused_button = 0;
754 }
755 }
756 }
757
758 self.update_focus_states();
759 self.ensure_selected_visible(self.viewport_height);
760 }
761
762 pub fn focus_prev(&mut self) {
768 if self.editing_text {
769 return;
770 }
771
772 if self.focus_on_buttons {
773 if self.focused_button > 0 {
774 self.focused_button -= 1;
775 } else {
776 if self.first_editable_index < self.items.len() {
778 self.focus_on_buttons = false;
779 self.selected_item = self.items.len().saturating_sub(1);
780 self.sub_focus = None;
781 self.init_composite_focus(false);
782 self.field_button_focus = self
784 .field_focusable_count(self.selected_item)
785 .checked_sub(1);
786 }
787 }
788 } else if let Some(i) = self.field_button_focus {
789 self.field_button_focus = i.checked_sub(1);
791 } else {
792 let handled = self.try_composite_focus_prev();
794 if !handled {
795 if self.selected_item > self.first_editable_index {
797 self.selected_item -= 1;
798 self.sub_focus = None;
799 self.init_composite_focus(false);
800 self.field_button_focus = self
803 .field_focusable_count(self.selected_item)
804 .checked_sub(1);
805 } else {
806 self.focus_on_buttons = true;
808 self.focused_button = self.button_count().saturating_sub(1);
809 }
810 }
811 }
812
813 self.update_focus_states();
814 self.ensure_selected_visible(self.viewport_height);
815 }
816
817 fn try_composite_focus_next(&mut self) -> bool {
820 let item = match self.items.get(self.selected_item) {
821 Some(item) => item,
822 None => return false,
823 };
824 match &item.control {
825 SettingControl::Map(state) => {
826 let at_boundary = state.focused_entry.is_none(); if at_boundary {
829 return false;
830 }
831 if let Some(item) = self.items.get_mut(self.selected_item) {
832 if let SettingControl::Map(state) = &mut item.control {
833 return state.focus_next();
834 }
835 }
836 false
837 }
838 SettingControl::ObjectArray(state) => {
839 if state.focused_index.is_none() {
841 return false;
842 }
843 if let Some(item) = self.items.get_mut(self.selected_item) {
844 if let SettingControl::ObjectArray(state) = &mut item.control {
845 state.focus_next();
846 return true;
847 }
848 }
849 false
850 }
851 SettingControl::TextList(state) => {
852 if state.focused_item.is_none() {
854 return false;
855 }
856 if let Some(item) = self.items.get_mut(self.selected_item) {
857 if let SettingControl::TextList(state) = &mut item.control {
858 state.focus_next();
859 return true;
860 }
861 }
862 false
863 }
864 _ => false,
865 }
866 }
867
868 fn try_composite_focus_prev(&mut self) -> bool {
871 let item = match self.items.get(self.selected_item) {
872 Some(item) => item,
873 None => return false,
874 };
875 match &item.control {
876 SettingControl::Map(state) => {
877 let at_boundary = matches!(state.focused_entry, Some(0))
879 || (state.focused_entry.is_none() && state.entries.is_empty());
880 if at_boundary {
881 return false;
882 }
883 if let Some(item) = self.items.get_mut(self.selected_item) {
884 if let SettingControl::Map(state) = &mut item.control {
885 return state.focus_prev();
886 }
887 }
888 false
889 }
890 SettingControl::ObjectArray(state) => {
891 if matches!(state.focused_index, Some(0))
893 || (state.focused_index.is_none() && state.bindings.is_empty())
894 {
895 return false;
896 }
897 if let Some(item) = self.items.get_mut(self.selected_item) {
898 if let SettingControl::ObjectArray(state) = &mut item.control {
899 state.focus_prev();
900 return true;
901 }
902 }
903 false
904 }
905 SettingControl::TextList(state) => {
906 if matches!(state.focused_item, Some(0))
908 || (state.focused_item.is_none() && state.items.is_empty())
909 {
910 return false;
911 }
912 if let Some(item) = self.items.get_mut(self.selected_item) {
913 if let SettingControl::TextList(state) = &mut item.control {
914 state.focus_prev();
915 return true;
916 }
917 }
918 false
919 }
920 _ => false,
921 }
922 }
923
924 fn init_composite_focus(&mut self, from_above: bool) {
928 if let Some(item) = self.items.get_mut(self.selected_item) {
929 match &mut item.control {
930 SettingControl::Map(state) => {
931 state.init_focus(from_above);
932 }
933 SettingControl::ObjectArray(state) => {
934 if from_above {
935 state.focused_index = if state.bindings.is_empty() {
936 None
937 } else {
938 Some(0)
939 };
940 } else {
941 state.focused_index = None;
943 }
944 }
945 SettingControl::TextList(state) => {
946 if from_above {
947 state.focused_item = if state.items.is_empty() {
948 None
949 } else {
950 Some(0)
951 };
952 } else {
953 state.focused_item = None;
955 }
956 }
957 _ => {}
958 }
959 }
960 }
961
962 pub fn toggle_focus_region(&mut self) {
965 self.toggle_focus_region_direction(true);
966 }
967
968 pub fn toggle_focus_region_direction(&mut self, forward: bool) {
972 if self.editing_text {
973 return;
974 }
975
976 if self.focus_on_buttons {
977 if forward {
978 if self.focused_button + 1 < self.button_count() {
980 self.focused_button += 1;
981 } else {
982 if self.first_editable_index < self.items.len() {
984 self.focus_on_buttons = false;
985 if self.selected_item < self.first_editable_index {
986 self.selected_item = self.first_editable_index;
987 }
988 } else {
989 self.focused_button = 0;
991 }
992 }
993 } else {
994 if self.focused_button > 0 {
996 self.focused_button -= 1;
997 } else {
998 if self.first_editable_index < self.items.len() {
1000 self.focus_on_buttons = false;
1001 if self.selected_item < self.first_editable_index {
1002 self.selected_item = self.first_editable_index;
1003 }
1004 } else {
1005 self.focused_button = self.button_count().saturating_sub(1);
1007 }
1008 }
1009 }
1010 } else {
1011 self.focus_on_buttons = true;
1013 self.focused_button = if forward {
1014 0
1015 } else {
1016 self.button_count().saturating_sub(1)
1017 };
1018 }
1019
1020 self.update_focus_states();
1021 self.ensure_selected_visible(self.viewport_height);
1022 }
1023
1024 fn init_object_array_focus(&mut self) {
1026 self.init_composite_focus(true);
1027 }
1028
1029 pub fn update_focus_states(&mut self) {
1031 for (idx, item) in self.items.iter_mut().enumerate() {
1032 let state = if !self.focus_on_buttons
1036 && idx == self.selected_item
1037 && self.field_button_focus.is_none()
1038 {
1039 FocusState::Focused
1040 } else {
1041 FocusState::Normal
1042 };
1043
1044 match &mut item.control {
1045 SettingControl::Toggle(s) => s.focus = state,
1046 SettingControl::Number(s) => s.focus = state,
1047 SettingControl::Dropdown(s) => s.focus = state,
1048 SettingControl::Text(s) => s.focus = state,
1049 SettingControl::TextList(s) => s.focus = state,
1050 SettingControl::DualList(s) => s.focus = state,
1051 SettingControl::Map(s) => s.focus = state,
1052 SettingControl::ObjectArray(s) => s.focus = state,
1053 SettingControl::Json(s) => s.focus = state,
1054 SettingControl::Complex { .. } => {}
1055 }
1056 }
1057 }
1058
1059 const SECTION_HEADER_HEIGHT: usize = 2;
1061
1062 pub fn total_content_height(&self) -> usize {
1064 let items_height: usize = self
1065 .items
1066 .iter()
1067 .map(|item| {
1068 let section_h = if item.is_section_start {
1069 Self::SECTION_HEADER_HEIGHT
1070 } else {
1071 0
1072 };
1073 item.control.control_height() as usize + section_h
1074 })
1075 .sum();
1076 let separator_height =
1078 if self.first_editable_index > 0 && self.first_editable_index < self.items.len() {
1079 1
1080 } else {
1081 0
1082 };
1083 items_height + separator_height
1084 }
1085
1086 pub fn selected_item_offset(&self) -> usize {
1088 let items_offset: usize = self
1089 .items
1090 .iter()
1091 .take(self.selected_item)
1092 .map(|item| {
1093 let section_h = if item.is_section_start {
1094 Self::SECTION_HEADER_HEIGHT
1095 } else {
1096 0
1097 };
1098 item.control.control_height() as usize + section_h
1099 })
1100 .sum();
1101 let separator_offset = if self.first_editable_index > 0
1103 && self.first_editable_index < self.items.len()
1104 && self.selected_item >= self.first_editable_index
1105 {
1106 1
1107 } else {
1108 0
1109 };
1110 let own_section_h = self
1112 .items
1113 .get(self.selected_item)
1114 .map(|item| {
1115 if item.is_section_start {
1116 Self::SECTION_HEADER_HEIGHT
1117 } else {
1118 0
1119 }
1120 })
1121 .unwrap_or(0);
1122 items_offset + separator_offset + own_section_h
1123 }
1124
1125 pub fn selected_item_height(&self) -> usize {
1127 self.items
1128 .get(self.selected_item)
1129 .map(|item| item.control.control_height() as usize)
1130 .unwrap_or(1)
1131 }
1132
1133 pub fn ensure_selected_visible(&mut self, viewport_height: usize) {
1135 if self.focus_on_buttons {
1136 let total = self.total_content_height();
1138 if total > viewport_height {
1139 self.scroll_offset = total.saturating_sub(viewport_height);
1140 }
1141 return;
1142 }
1143
1144 let item_start = self.selected_item_offset();
1145 let item_end = item_start + self.selected_item_height();
1146
1147 if item_start < self.scroll_offset {
1149 self.scroll_offset = item_start;
1150 }
1151 else if item_end > self.scroll_offset + viewport_height {
1153 self.scroll_offset = item_end.saturating_sub(viewport_height);
1154 }
1155 }
1156
1157 pub fn ensure_cursor_visible(&mut self) {
1162 if !self.editing_text || self.focus_on_buttons {
1163 return;
1164 }
1165
1166 let cursor_row = if let Some(item) = self.items.get(self.selected_item) {
1168 if let SettingControl::Json(state) = &item.control {
1169 state.cursor_pos().0
1170 } else {
1171 return; }
1173 } else {
1174 return;
1175 };
1176
1177 let item_offset = self.selected_item_offset();
1180 let cursor_content_row = item_offset + 1 + cursor_row;
1181
1182 let viewport_height = self.viewport_height;
1183
1184 if cursor_content_row < self.scroll_offset {
1186 self.scroll_offset = cursor_content_row;
1187 }
1188 else if cursor_content_row >= self.scroll_offset + viewport_height {
1190 self.scroll_offset = cursor_content_row.saturating_sub(viewport_height) + 1;
1191 }
1192 }
1193
1194 pub fn scroll_up(&mut self) {
1196 self.scroll_offset = self.scroll_offset.saturating_sub(1);
1197 }
1198
1199 pub fn scroll_down(&mut self, viewport_height: usize) {
1201 let max_scroll = self.total_content_height().saturating_sub(viewport_height);
1202 if self.scroll_offset < max_scroll {
1203 self.scroll_offset += 1;
1204 }
1205 }
1206
1207 pub fn scroll_to_ratio(&mut self, ratio: f32) {
1211 let max_scroll = self
1212 .total_content_height()
1213 .saturating_sub(self.viewport_height);
1214 let new_offset = (ratio * max_scroll as f32).round() as usize;
1215 self.scroll_offset = new_offset.min(max_scroll);
1216 }
1217
1218 pub fn start_editing(&mut self) {
1220 if let Some(item) = self.current_item_mut() {
1221 if item.read_only {
1223 return;
1224 }
1225 match &mut item.control {
1226 SettingControl::Text(state) => {
1227 state.cursor = state.value.len();
1228 state.editing = true;
1229 self.editing_text = true;
1230 }
1231 SettingControl::TextList(state) => {
1232 if state.focused_item.is_none() {
1238 state.activate_pending();
1239 }
1240 self.editing_text = true;
1241 }
1242 SettingControl::Number(state) => {
1243 state.start_editing();
1244 self.editing_text = true;
1245 }
1246 SettingControl::Json(state) => {
1247 state.clear_placeholder_for_edit();
1250 self.editing_text = true;
1251 }
1252 _ => {}
1253 }
1254 }
1255 }
1256
1257 pub fn stop_editing(&mut self) {
1259 if let Some(item) = self.current_item_mut() {
1260 match &mut item.control {
1261 SettingControl::Number(state) => state.cancel_editing(),
1262 SettingControl::Text(state) => state.editing = false,
1263 SettingControl::TextList(state) if state.focused_item.is_none() => {
1270 state.cancel_pending();
1271 }
1272 SettingControl::Json(state) => state.restore_unset_if_empty(),
1276 _ => {}
1277 }
1278 }
1279 self.editing_text = false;
1280 }
1281
1282 pub fn insert_char(&mut self, c: char) {
1284 if !self.editing_text {
1285 return;
1286 }
1287 self.mark_field_edited();
1288 if let Some(item) = self.current_item_mut() {
1289 match &mut item.control {
1290 SettingControl::Text(state) => {
1291 state.insert(c);
1292 }
1293 SettingControl::TextList(state) => {
1294 state.insert(c);
1295 }
1296 SettingControl::Number(state) => {
1297 state.insert_char(c);
1298 }
1299 SettingControl::Json(state) => {
1300 state.insert(c);
1301 }
1302 _ => {}
1303 }
1304 }
1305 }
1306
1307 pub fn insert_str(&mut self, s: &str) {
1308 if !self.editing_text {
1309 return;
1310 }
1311 self.mark_field_edited();
1312 if let Some(item) = self.current_item_mut() {
1313 match &mut item.control {
1314 SettingControl::Text(state) => {
1315 state.insert_str(s);
1316 }
1317 SettingControl::TextList(state) => {
1318 state.insert_str(s);
1319 }
1320 SettingControl::Number(state) => {
1321 for c in s.chars() {
1322 state.insert_char(c);
1323 }
1324 }
1325 SettingControl::Json(state) => {
1326 state.insert_str(s);
1327 }
1328 _ => {}
1329 }
1330 }
1331 }
1332
1333 pub fn backspace(&mut self) {
1335 if !self.editing_text {
1336 return;
1337 }
1338 self.mark_field_edited();
1339 if let Some(item) = self.current_item_mut() {
1340 match &mut item.control {
1341 SettingControl::Text(state) => {
1342 state.backspace();
1343 }
1344 SettingControl::TextList(state) => {
1345 state.backspace();
1346 }
1347 SettingControl::Number(state) => {
1348 state.backspace();
1349 }
1350 SettingControl::Json(state) => {
1351 state.backspace();
1352 }
1353 _ => {}
1354 }
1355 }
1356 }
1357
1358 pub fn cursor_left(&mut self) {
1360 if !self.editing_text {
1361 return;
1362 }
1363 if let Some(item) = self.current_item_mut() {
1364 match &mut item.control {
1365 SettingControl::Text(state) => {
1366 state.move_left();
1367 }
1368 SettingControl::TextList(state) => {
1369 state.move_left();
1370 }
1371 SettingControl::Json(state) => {
1372 state.move_left();
1373 }
1374 _ => {}
1375 }
1376 }
1377 }
1378
1379 pub fn cursor_left_selecting(&mut self) {
1381 if !self.editing_text {
1382 return;
1383 }
1384 if let Some(item) = self.current_item_mut() {
1385 if let SettingControl::Json(state) = &mut item.control {
1386 state.editor.move_left_selecting();
1387 }
1388 }
1389 }
1390
1391 pub fn cursor_right(&mut self) {
1393 if !self.editing_text {
1394 return;
1395 }
1396 if let Some(item) = self.current_item_mut() {
1397 match &mut item.control {
1398 SettingControl::Text(state) => {
1399 state.move_right();
1400 }
1401 SettingControl::TextList(state) => {
1402 state.move_right();
1403 }
1404 SettingControl::Json(state) => {
1405 state.move_right();
1406 }
1407 _ => {}
1408 }
1409 }
1410 }
1411
1412 pub fn cursor_right_selecting(&mut self) {
1414 if !self.editing_text {
1415 return;
1416 }
1417 if let Some(item) = self.current_item_mut() {
1418 if let SettingControl::Json(state) = &mut item.control {
1419 state.editor.move_right_selecting();
1420 }
1421 }
1422 }
1423
1424 pub fn cursor_up(&mut self) {
1426 if !self.editing_text {
1427 return;
1428 }
1429 if let Some(item) = self.current_item_mut() {
1430 if let SettingControl::Json(state) = &mut item.control {
1431 state.move_up();
1432 }
1433 }
1434 self.ensure_cursor_visible();
1435 }
1436
1437 pub fn cursor_up_selecting(&mut self) {
1439 if !self.editing_text {
1440 return;
1441 }
1442 if let Some(item) = self.current_item_mut() {
1443 if let SettingControl::Json(state) = &mut item.control {
1444 state.editor.move_up_selecting();
1445 }
1446 }
1447 self.ensure_cursor_visible();
1448 }
1449
1450 pub fn cursor_down(&mut self) {
1452 if !self.editing_text {
1453 return;
1454 }
1455 if let Some(item) = self.current_item_mut() {
1456 if let SettingControl::Json(state) = &mut item.control {
1457 state.move_down();
1458 }
1459 }
1460 self.ensure_cursor_visible();
1461 }
1462
1463 pub fn cursor_down_selecting(&mut self) {
1465 if !self.editing_text {
1466 return;
1467 }
1468 if let Some(item) = self.current_item_mut() {
1469 if let SettingControl::Json(state) = &mut item.control {
1470 state.editor.move_down_selecting();
1471 }
1472 }
1473 self.ensure_cursor_visible();
1474 }
1475
1476 pub fn insert_newline(&mut self) {
1478 self.mark_field_edited();
1479 if !self.editing_text {
1480 return;
1481 }
1482 if let Some(item) = self.current_item_mut() {
1483 if let SettingControl::Json(state) = &mut item.control {
1484 state.insert('\n');
1485 }
1486 }
1487 }
1488
1489 pub fn revert_json_and_stop(&mut self) {
1491 if let Some(item) = self.current_item_mut() {
1492 if let SettingControl::Json(state) = &mut item.control {
1493 state.revert();
1494 }
1495 }
1496 self.editing_text = false;
1497 }
1498
1499 pub fn is_editing_json(&self) -> bool {
1501 if !self.editing_text {
1502 return false;
1503 }
1504 self.current_item()
1505 .map(|item| matches!(&item.control, SettingControl::Json(_)))
1506 .unwrap_or(false)
1507 }
1508
1509 pub fn toggle_bool(&mut self) {
1511 let editable = self
1514 .current_item()
1515 .map(|i| !i.read_only && matches!(i.control, SettingControl::Toggle(_)))
1516 .unwrap_or(false);
1517 if !editable {
1518 return;
1519 }
1520 self.mark_field_edited();
1521 if let Some(item) = self.current_item_mut() {
1522 if let SettingControl::Toggle(state) = &mut item.control {
1523 state.toggle();
1524 }
1525 }
1526 }
1527
1528 pub fn toggle_dropdown(&mut self) {
1530 if let Some(item) = self.current_item_mut() {
1531 if item.read_only {
1533 return;
1534 }
1535 if let SettingControl::Dropdown(state) = &mut item.control {
1536 state.open = !state.open;
1537 }
1538 }
1539 }
1540
1541 pub fn dropdown_prev(&mut self) {
1543 self.mark_field_edited();
1544 if let Some(item) = self.current_item_mut() {
1545 if let SettingControl::Dropdown(state) = &mut item.control {
1546 if state.open {
1547 state.select_prev();
1548 }
1549 }
1550 }
1551 }
1552
1553 pub fn dropdown_next(&mut self) {
1555 self.mark_field_edited();
1556 if let Some(item) = self.current_item_mut() {
1557 if let SettingControl::Dropdown(state) = &mut item.control {
1558 if state.open {
1559 state.select_next();
1560 }
1561 }
1562 }
1563 }
1564
1565 pub fn dropdown_confirm(&mut self) {
1567 if let Some(item) = self.current_item_mut() {
1568 if let SettingControl::Dropdown(state) = &mut item.control {
1569 state.open = false;
1570 }
1571 }
1572 }
1573
1574 pub fn delete_list_item(&mut self) {
1576 self.mark_field_edited();
1577 if let Some(item) = self.current_item_mut() {
1578 if let SettingControl::TextList(state) = &mut item.control {
1579 if let Some(idx) = state.focused_item {
1581 state.remove_item(idx);
1582 }
1583 }
1584 }
1585 }
1586
1587 pub fn delete(&mut self) {
1589 if !self.editing_text {
1590 return;
1591 }
1592 self.mark_field_edited();
1593 if let Some(item) = self.current_item_mut() {
1594 match &mut item.control {
1595 SettingControl::Text(state) => {
1596 state.delete();
1597 }
1598 SettingControl::TextList(state) => {
1599 state.delete();
1600 }
1601 SettingControl::Json(state) => {
1602 state.delete();
1603 }
1604 _ => {}
1605 }
1606 }
1607 }
1608
1609 pub fn cursor_home(&mut self) {
1611 if !self.editing_text {
1612 return;
1613 }
1614 if let Some(item) = self.current_item_mut() {
1615 match &mut item.control {
1616 SettingControl::Text(state) => {
1617 state.move_home();
1618 }
1619 SettingControl::TextList(state) => {
1620 state.move_home();
1621 }
1622 SettingControl::Json(state) => {
1623 state.move_home();
1624 }
1625 _ => {}
1626 }
1627 }
1628 }
1629
1630 pub fn cursor_end(&mut self) {
1632 if !self.editing_text {
1633 return;
1634 }
1635 if let Some(item) = self.current_item_mut() {
1636 match &mut item.control {
1637 SettingControl::Text(state) => {
1638 state.move_end();
1639 }
1640 SettingControl::TextList(state) => {
1641 state.move_end();
1642 }
1643 SettingControl::Json(state) => {
1644 state.move_end();
1645 }
1646 _ => {}
1647 }
1648 }
1649 }
1650
1651 pub fn select_all(&mut self) {
1653 if !self.editing_text {
1654 return;
1655 }
1656 if let Some(item) = self.current_item_mut() {
1657 if let SettingControl::Json(state) = &mut item.control {
1658 state.select_all();
1659 }
1660 }
1662 }
1663
1664 pub fn selected_text(&self) -> Option<String> {
1666 if !self.editing_text {
1667 return None;
1668 }
1669 if let Some(item) = self.current_item() {
1670 if let SettingControl::Json(state) = &item.control {
1671 return state.selected_text();
1672 }
1673 }
1674 None
1675 }
1676
1677 pub fn is_editing(&self) -> bool {
1679 self.editing_text
1680 || self
1681 .current_item()
1682 .map(|item| {
1683 matches!(
1684 &item.control,
1685 SettingControl::Dropdown(s) if s.open
1686 )
1687 })
1688 .unwrap_or(false)
1689 }
1690}
1691
1692#[cfg(test)]
1693mod tests {
1694 use super::*;
1695
1696 fn create_test_schema() -> SettingSchema {
1697 SettingSchema {
1698 path: "/test".to_string(),
1699 name: "Test".to_string(),
1700 description: Some("Test schema".to_string()),
1701 setting_type: SettingType::Object {
1702 properties: vec![
1703 SettingSchema {
1704 path: "/enabled".to_string(),
1705 name: "Enabled".to_string(),
1706 description: Some("Enable this".to_string()),
1707 setting_type: SettingType::Boolean,
1708 default: Some(serde_json::json!(true)),
1709 read_only: false,
1710 section: None,
1711 order: None,
1712 nullable: false,
1713 enum_from: None,
1714 dual_list_sibling: None,
1715 dynamically_extendable_status_bar_elements: false,
1716 },
1717 SettingSchema {
1718 path: "/command".to_string(),
1719 name: "Command".to_string(),
1720 description: Some("Command to run".to_string()),
1721 setting_type: SettingType::String,
1722 default: Some(serde_json::json!("")),
1723 read_only: false,
1724 section: None,
1725 order: None,
1726 nullable: false,
1727 enum_from: None,
1728 dual_list_sibling: None,
1729 dynamically_extendable_status_bar_elements: false,
1730 },
1731 ],
1732 },
1733 default: None,
1734 read_only: false,
1735 section: None,
1736 order: None,
1737 nullable: false,
1738 enum_from: None,
1739 dual_list_sibling: None,
1740 dynamically_extendable_status_bar_elements: false,
1741 }
1742 }
1743
1744 #[test]
1745 fn from_schema_creates_key_item_first() {
1746 let schema = create_test_schema();
1747 let dialog = EntryDialogState::from_schema(
1748 "test".to_string(),
1749 &serde_json::json!({}),
1750 &schema,
1751 "/test",
1752 false,
1753 false,
1754 &HashMap::new(),
1755 );
1756
1757 assert!(!dialog.items.is_empty());
1758 assert_eq!(dialog.items[0].path, "__key__");
1759 assert_eq!(dialog.items[0].name, "Key");
1760 }
1761
1762 #[test]
1763 fn from_schema_creates_items_from_properties() {
1764 let schema = create_test_schema();
1765 let dialog = EntryDialogState::from_schema(
1766 "test".to_string(),
1767 &serde_json::json!({"enabled": true, "command": "test-cmd"}),
1768 &schema,
1769 "/test",
1770 false,
1771 false,
1772 &HashMap::new(),
1773 );
1774
1775 assert_eq!(dialog.items.len(), 3);
1777 assert_eq!(dialog.items[1].name, "Enabled");
1778 assert_eq!(dialog.items[2].name, "Command");
1779 }
1780
1781 #[test]
1782 fn get_key_returns_key_value() {
1783 let schema = create_test_schema();
1784 let dialog = EntryDialogState::from_schema(
1785 "mykey".to_string(),
1786 &serde_json::json!({}),
1787 &schema,
1788 "/test",
1789 false,
1790 false,
1791 &HashMap::new(),
1792 );
1793
1794 assert_eq!(dialog.get_key(), "mykey");
1795 }
1796
1797 #[test]
1798 fn to_value_excludes_key() {
1799 let schema = create_test_schema();
1800 let dialog = EntryDialogState::from_schema(
1801 "test".to_string(),
1802 &serde_json::json!({"enabled": true, "command": "cmd"}),
1803 &schema,
1804 "/test",
1805 false,
1806 false,
1807 &HashMap::new(),
1808 );
1809
1810 let value = dialog.to_value();
1811 assert!(value.get("__key__").is_none());
1812 assert!(value.get("enabled").is_some());
1813 }
1814
1815 #[test]
1816 fn focus_navigation_works() {
1817 let schema = create_test_schema();
1818 let mut dialog = EntryDialogState::from_schema(
1819 "test".to_string(),
1820 &serde_json::json!({}),
1821 &schema,
1822 "/test",
1823 false, false, &HashMap::new(),
1826 );
1827
1828 assert_eq!(dialog.first_editable_index, 1);
1832 assert_eq!(dialog.selected_item, 1); assert!(!dialog.focus_on_buttons);
1834
1835 dialog.focus_next();
1836 assert_eq!(dialog.selected_item, 2); dialog.focus_next();
1839 assert!(dialog.focus_on_buttons); assert_eq!(dialog.focused_button, 0);
1841
1842 dialog.focus_prev();
1844 assert!(!dialog.focus_on_buttons);
1845 assert_eq!(dialog.selected_item, 2); dialog.focus_prev();
1848 assert_eq!(dialog.selected_item, 1); dialog.focus_prev();
1851 assert!(dialog.focus_on_buttons); }
1853
1854 #[test]
1859 fn focus_cycles_through_both_field_action_buttons() {
1860 let schema = SettingSchema {
1861 path: "/test".to_string(),
1862 name: "Test".to_string(),
1863 description: None,
1864 setting_type: SettingType::Object {
1865 properties: vec![SettingSchema {
1866 path: "/wrap".to_string(),
1867 name: "Wrap".to_string(),
1868 description: None,
1869 setting_type: SettingType::Boolean,
1870 default: Some(serde_json::json!(true)),
1873 read_only: false,
1874 section: None,
1875 order: None,
1876 nullable: true,
1877 enum_from: None,
1878 dual_list_sibling: None,
1879 dynamically_extendable_status_bar_elements: false,
1880 }],
1881 },
1882 default: None,
1883 read_only: false,
1884 section: None,
1885 order: None,
1886 nullable: false,
1887 enum_from: None,
1888 dual_list_sibling: None,
1889 dynamically_extendable_status_bar_elements: false,
1890 };
1891 let mut dialog = EntryDialogState::from_schema(
1894 "k".to_string(),
1895 &serde_json::json!({ "wrap": false }),
1896 &schema,
1897 "/test",
1898 false,
1899 false,
1900 &HashMap::new(),
1901 );
1902
1903 assert_eq!(dialog.selected_item, 1);
1905 let buttons = dialog.field_action_buttons(1);
1906 assert_eq!(
1907 buttons.iter().map(|(a, _)| *a).collect::<Vec<_>>(),
1908 vec![FieldAction::Reset, FieldAction::Inherit]
1909 );
1910 assert_eq!(dialog.field_button_focus, None);
1911
1912 dialog.focus_next();
1914 assert_eq!(dialog.field_button_focus, Some(0)); dialog.focus_next();
1916 assert_eq!(dialog.field_button_focus, Some(1)); dialog.focus_next();
1918 assert!(dialog.focus_on_buttons);
1919
1920 dialog.focus_prev();
1922 assert!(!dialog.focus_on_buttons);
1923 assert_eq!(dialog.field_button_focus, Some(1)); dialog.focus_prev();
1925 assert_eq!(dialog.field_button_focus, Some(0)); dialog.focus_prev();
1927 assert_eq!(dialog.field_button_focus, None); dialog.focus_prev();
1929 assert!(dialog.focus_on_buttons); }
1931
1932 #[test]
1936 fn focus_reaches_action_buttons_on_json_field() {
1937 let schema = SettingSchema {
1938 path: "/test".to_string(),
1939 name: "Test".to_string(),
1940 description: None,
1941 setting_type: SettingType::Object {
1942 properties: vec![SettingSchema {
1943 path: "/formatter".to_string(),
1944 name: "Formatter".to_string(),
1945 description: None,
1946 setting_type: SettingType::Object { properties: vec![] },
1948 default: Some(serde_json::json!({ "command": "clang-format" })),
1949 read_only: false,
1950 section: None,
1951 order: None,
1952 nullable: true,
1953 enum_from: None,
1954 dual_list_sibling: None,
1955 dynamically_extendable_status_bar_elements: false,
1956 }],
1957 },
1958 default: None,
1959 read_only: false,
1960 section: None,
1961 order: None,
1962 nullable: false,
1963 enum_from: None,
1964 dual_list_sibling: None,
1965 dynamically_extendable_status_bar_elements: false,
1966 };
1967 let mut dialog = EntryDialogState::from_schema(
1969 "c".to_string(),
1970 &serde_json::json!({ "formatter": { "command": "my-fmt" } }),
1971 &schema,
1972 "/languages",
1973 false,
1974 false,
1975 &HashMap::new(),
1976 );
1977
1978 assert_eq!(dialog.selected_item, 1);
1980 assert!(
1981 !dialog.field_action_buttons(1).is_empty(),
1982 "overridden JSON field should offer action buttons"
1983 );
1984 assert_eq!(dialog.field_button_focus, None);
1985 dialog.focus_next();
1986 assert_eq!(
1987 dialog.field_button_focus,
1988 Some(0),
1989 "Tab should land on the JSON field's first action button"
1990 );
1991 }
1992
1993 #[test]
1994 fn entry_path_joins_map_path_and_entry_key() {
1995 let schema = create_test_schema();
1996
1997 let existing = EntryDialogState::from_schema(
1999 "rust".to_string(),
2000 &serde_json::json!({}),
2001 &schema,
2002 "/lsp",
2003 false,
2004 false,
2005 &HashMap::new(),
2006 );
2007 assert_eq!(existing.entry_path(), "/lsp/rust");
2008
2009 let new_entry = EntryDialogState::from_schema(
2012 String::new(),
2013 &serde_json::json!({}),
2014 &schema,
2015 "/lsp",
2016 true,
2017 false,
2018 &HashMap::new(),
2019 );
2020 assert_eq!(new_entry.entry_path(), "/lsp");
2021 }
2022
2023 #[test]
2024 fn entry_path_tracks_live_key_edits_for_new_entries() {
2025 let schema = create_test_schema();
2026 let mut dialog = EntryDialogState::from_schema(
2027 String::new(),
2028 &serde_json::json!({}),
2029 &schema,
2030 "/universal_lsp",
2031 true,
2032 false,
2033 &HashMap::new(),
2034 );
2035
2036 for item in dialog.items.iter_mut() {
2038 if item.path == "__key__" {
2039 if let SettingControl::Text(state) = &mut item.control {
2040 state.value = "myserver".to_string();
2041 }
2042 }
2043 }
2044
2045 assert_eq!(dialog.entry_path(), "/universal_lsp/myserver");
2046 }
2047
2048 #[test]
2049 fn button_count_differs_for_new_vs_existing() {
2050 let schema = create_test_schema();
2051
2052 let new_dialog = EntryDialogState::from_schema(
2053 "test".to_string(),
2054 &serde_json::json!({}),
2055 &schema,
2056 "/test",
2057 true,
2058 false,
2059 &HashMap::new(),
2060 );
2061 assert_eq!(new_dialog.button_count(), 2); let existing_dialog = EntryDialogState::from_schema(
2064 "test".to_string(),
2065 &serde_json::json!({}),
2066 &schema,
2067 "/test",
2068 false,
2069 false, &HashMap::new(),
2071 );
2072 assert_eq!(existing_dialog.button_count(), 3); let no_delete_dialog = EntryDialogState::from_schema(
2076 "test".to_string(),
2077 &serde_json::json!({}),
2078 &schema,
2079 "/test",
2080 false,
2081 true, &HashMap::new(),
2083 );
2084 assert_eq!(no_delete_dialog.button_count(), 2); }
2086}