Skip to main content

purple_ssh/app/
form_state.rs

1use crate::app::FormBaseline;
2use crate::app::forms::HostForm;
3use crate::app::tag_state::BulkTagEditorState;
4
5/// Host-form and bulk-tag editor state grouped off the `App` god-struct.
6/// Holds the add/edit host form, its dirty-check baseline, the bulk-tag
7/// editor, the last-apply snapshot used by `u` to revert bulk-tag changes
8/// and the pending-discard confirmation flag. Pure state container.
9pub struct FormState {
10    pub(in crate::app) host: HostForm,
11    pub(in crate::app) host_baseline: Option<FormBaseline>,
12    pub(in crate::app) bulk_tag_editor: BulkTagEditorState,
13    /// Snapshot of the last bulk tag apply, used by `u` to revert the
14    /// operation even though `undo_stack` only holds deleted hosts. Holds
15    /// `(alias, previous_tags)` pairs so restore is idempotent. Cleared
16    /// after a successful undo or on the next mutation.
17    pub(in crate::app) bulk_tag_undo: Option<Vec<(String, Vec<String>)>>,
18    /// When true, the Esc key shows a "Discard changes?" dialog instead of
19    /// closing the open host form. Mutate via `request_discard_confirm`
20    /// and `dismiss_discard_confirm`; read via `is_discard_pending`.
21    discard_pending: bool,
22}
23
24impl FormState {
25    /// Arm the "Discard changes?" Esc dialog. Called when Esc fires on a
26    /// form that has unsaved edits relative to its baseline.
27    pub fn request_discard_confirm(&mut self) {
28        self.discard_pending = true;
29    }
30
31    /// Clear the pending discard flag. Called on dialog dismiss, on
32    /// successful save, and on form close.
33    pub fn dismiss_discard_confirm(&mut self) {
34        self.discard_pending = false;
35    }
36
37    /// True when Esc should show the "Discard changes?" dialog instead of
38    /// closing the form.
39    pub fn is_discard_pending(&self) -> bool {
40        self.discard_pending
41    }
42
43    pub fn host(&self) -> &HostForm {
44        &self.host
45    }
46
47    pub fn host_mut(&mut self) -> &mut HostForm {
48        &mut self.host
49    }
50
51    pub fn host_baseline(&self) -> Option<&FormBaseline> {
52        self.host_baseline.as_ref()
53    }
54
55    pub fn set_host_baseline(&mut self, baseline: Option<FormBaseline>) {
56        self.host_baseline = baseline;
57    }
58
59    pub fn take_host_baseline(&mut self) -> Option<FormBaseline> {
60        self.host_baseline.take()
61    }
62
63    pub fn bulk_tag_editor(&self) -> &BulkTagEditorState {
64        &self.bulk_tag_editor
65    }
66
67    pub fn bulk_tag_editor_mut(&mut self) -> &mut BulkTagEditorState {
68        &mut self.bulk_tag_editor
69    }
70
71    pub fn bulk_tag_undo(&self) -> Option<&Vec<(String, Vec<String>)>> {
72        self.bulk_tag_undo.as_ref()
73    }
74
75    pub fn set_bulk_tag_undo(&mut self, undo: Option<Vec<(String, Vec<String>)>>) {
76        self.bulk_tag_undo = undo;
77    }
78
79    pub fn take_bulk_tag_undo(&mut self) -> Option<Vec<(String, Vec<String>)>> {
80        self.bulk_tag_undo.take()
81    }
82}
83
84impl Default for FormState {
85    fn default() -> Self {
86        Self {
87            host: HostForm::new(),
88            host_baseline: None,
89            bulk_tag_editor: BulkTagEditorState::default(),
90            bulk_tag_undo: None,
91            discard_pending: false,
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn default_is_empty() {
102        let s = FormState::default();
103        assert!(!s.is_discard_pending());
104        assert!(s.bulk_tag_undo.is_none());
105        assert!(s.host_baseline.is_none());
106        assert!(s.bulk_tag_editor.rows.is_empty());
107    }
108
109    #[test]
110    fn discard_confirm_lifecycle() {
111        let mut s = FormState::default();
112        assert!(!s.is_discard_pending());
113        s.request_discard_confirm();
114        assert!(s.is_discard_pending());
115        s.dismiss_discard_confirm();
116        assert!(!s.is_discard_pending());
117    }
118}