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    /// True if the host form differs from its captured baseline.
60    pub fn host_form_is_dirty(&self) -> bool {
61        match &self.host_baseline {
62            Some(b) => {
63                self.host.alias != b.alias
64                    || self.host.hostname != b.hostname
65                    || self.host.user != b.user
66                    || self.host.port != b.port
67                    || self.host.identity_file != b.identity_file
68                    || self.host.proxy_jump != b.proxy_jump
69                    || self.host.askpass != b.askpass
70                    || self.host.vault_ssh != b.vault_ssh
71                    || self.host.vault_addr != b.vault_addr
72                    || self.host.tags != b.tags
73            }
74            None => false,
75        }
76    }
77
78    pub fn take_host_baseline(&mut self) -> Option<FormBaseline> {
79        self.host_baseline.take()
80    }
81
82    pub fn bulk_tag_editor(&self) -> &BulkTagEditorState {
83        &self.bulk_tag_editor
84    }
85
86    pub fn bulk_tag_editor_mut(&mut self) -> &mut BulkTagEditorState {
87        &mut self.bulk_tag_editor
88    }
89
90    pub fn bulk_tag_undo(&self) -> Option<&Vec<(String, Vec<String>)>> {
91        self.bulk_tag_undo.as_ref()
92    }
93
94    pub fn set_bulk_tag_undo(&mut self, undo: Option<Vec<(String, Vec<String>)>>) {
95        self.bulk_tag_undo = undo;
96    }
97
98    pub fn take_bulk_tag_undo(&mut self) -> Option<Vec<(String, Vec<String>)>> {
99        self.bulk_tag_undo.take()
100    }
101}
102
103impl Default for FormState {
104    fn default() -> Self {
105        Self {
106            host: HostForm::new(),
107            host_baseline: None,
108            bulk_tag_editor: BulkTagEditorState::default(),
109            bulk_tag_undo: None,
110            discard_pending: false,
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn default_is_empty() {
121        let s = FormState::default();
122        assert!(!s.is_discard_pending());
123        assert!(s.bulk_tag_undo.is_none());
124        assert!(s.host_baseline.is_none());
125        assert!(s.bulk_tag_editor.rows.is_empty());
126    }
127
128    #[test]
129    fn discard_confirm_lifecycle() {
130        let mut s = FormState::default();
131        assert!(!s.is_discard_pending());
132        s.request_discard_confirm();
133        assert!(s.is_discard_pending());
134        s.dismiss_discard_confirm();
135        assert!(!s.is_discard_pending());
136    }
137}