1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
12pub struct Profile {
13 #[serde(default)]
15 pub serial: SerialSection,
16 #[serde(default)]
18 pub line_endings: LineEndingsSection,
19 #[serde(default)]
21 pub modem: ModemSection,
22 #[serde(default)]
24 pub screen: ScreenSection,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(default)]
30pub struct SerialSection {
31 pub baud: u32,
33 pub data_bits: u8,
35 pub stop_bits: u8,
37 pub parity: String,
39 pub flow: String,
41}
42
43impl Default for SerialSection {
44 fn default() -> Self {
45 Self {
46 baud: 115_200,
47 data_bits: 8,
48 stop_bits: 1,
49 parity: "none".into(),
50 flow: "none".into(),
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(default)]
58pub struct LineEndingsSection {
59 pub omap: String,
61 pub imap: String,
63 pub emap: String,
65}
66
67impl Default for LineEndingsSection {
68 fn default() -> Self {
69 Self {
70 omap: "none".into(),
71 imap: "none".into(),
72 emap: "none".into(),
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79#[serde(default)]
80pub struct ModemSection {
81 pub initial_dtr: String,
83 pub initial_rts: String,
85}
86
87impl Default for ModemSection {
88 fn default() -> Self {
89 Self {
90 initial_dtr: "unchanged".into(),
91 initial_rts: "unchanged".into(),
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98#[serde(default)]
99pub struct ScreenSection {
100 pub modal_style: ModalStyle,
102 pub scrollback_rows: usize,
104 pub wheel_scroll_lines: u16,
111}
112
113impl Default for ScreenSection {
114 fn default() -> Self {
115 Self {
116 modal_style: ModalStyle::Overlay,
117 scrollback_rows: 10_000,
118 wheel_scroll_lines: 3,
119 }
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
125#[serde(rename_all = "kebab-case")]
126pub enum ModalStyle {
127 #[default]
129 Overlay,
130 DimmedOverlay,
132 Fullscreen,
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn profile_default_values() {
142 let p = Profile::default();
143 assert_eq!(p.serial.baud, 115_200);
144 assert_eq!(p.serial.data_bits, 8);
145 assert_eq!(p.screen.modal_style, ModalStyle::Overlay);
146 assert_eq!(p.screen.scrollback_rows, 10_000);
147 assert_eq!(p.screen.wheel_scroll_lines, 3);
148 }
149
150 #[test]
151 fn profile_partial_screen_section_keeps_wheel_default() {
152 let partial = r#"
157 [screen]
158 modal_style = "fullscreen"
159 "#;
160 let parsed: Profile = toml::from_str(partial).expect("parse");
161 assert_eq!(parsed.screen.modal_style, ModalStyle::Fullscreen);
162 assert_eq!(parsed.screen.wheel_scroll_lines, 3);
163 }
164
165 #[test]
166 fn profile_roundtrip_toml() {
167 let original = Profile::default();
168 let serialized = toml::to_string(&original).expect("serialize");
169 let parsed: Profile = toml::from_str(&serialized).expect("parse");
170 assert_eq!(parsed, original);
171 }
172
173 #[test]
174 fn profile_unknown_keys_are_dropped() {
175 let with_unknown = r#"
176 [serial]
177 baud = 9600
178 unknown_field = "ignored"
179 data_bits = 8
180 stop_bits = 1
181 parity = "none"
182 flow = "none"
183 "#;
184 let parsed: Profile = toml::from_str(with_unknown).expect("parse");
185 assert_eq!(parsed.serial.baud, 9600);
186 }
187
188 #[test]
189 fn profile_partial_section_uses_defaults_for_missing_leaf_fields() {
190 let partial = r"
191 [serial]
192 baud = 9600
193 ";
194 let parsed: Profile = toml::from_str(partial).expect("parse");
195 assert_eq!(parsed.serial.baud, 9600);
196 assert_eq!(parsed.serial.data_bits, 8); assert_eq!(parsed.serial.parity, "none"); }
199}