use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct Profile {
#[serde(default)]
pub serial: SerialSection,
#[serde(default)]
pub line_endings: LineEndingsSection,
#[serde(default)]
pub modem: ModemSection,
#[serde(default)]
pub screen: ScreenSection,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct SerialSection {
pub baud: u32,
pub data_bits: u8,
pub stop_bits: u8,
pub parity: String,
pub flow: String,
}
impl Default for SerialSection {
fn default() -> Self {
Self {
baud: 115_200,
data_bits: 8,
stop_bits: 1,
parity: "none".into(),
flow: "none".into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct LineEndingsSection {
pub omap: String,
pub imap: String,
pub emap: String,
}
impl Default for LineEndingsSection {
fn default() -> Self {
Self {
omap: "none".into(),
imap: "none".into(),
emap: "none".into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct ModemSection {
pub initial_dtr: String,
pub initial_rts: String,
}
impl Default for ModemSection {
fn default() -> Self {
Self {
initial_dtr: "unchanged".into(),
initial_rts: "unchanged".into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct ScreenSection {
pub modal_style: ModalStyle,
pub scrollback_rows: usize,
pub wheel_scroll_lines: u16,
}
impl Default for ScreenSection {
fn default() -> Self {
Self {
modal_style: ModalStyle::Overlay,
scrollback_rows: 10_000,
wheel_scroll_lines: 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ModalStyle {
#[default]
Overlay,
DimmedOverlay,
Fullscreen,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn profile_default_values() {
let p = Profile::default();
assert_eq!(p.serial.baud, 115_200);
assert_eq!(p.serial.data_bits, 8);
assert_eq!(p.screen.modal_style, ModalStyle::Overlay);
assert_eq!(p.screen.scrollback_rows, 10_000);
assert_eq!(p.screen.wheel_scroll_lines, 3);
}
#[test]
fn profile_partial_screen_section_keeps_wheel_default() {
let partial = r#"
[screen]
modal_style = "fullscreen"
"#;
let parsed: Profile = toml::from_str(partial).expect("parse");
assert_eq!(parsed.screen.modal_style, ModalStyle::Fullscreen);
assert_eq!(parsed.screen.wheel_scroll_lines, 3);
}
#[test]
fn profile_roundtrip_toml() {
let original = Profile::default();
let serialized = toml::to_string(&original).expect("serialize");
let parsed: Profile = toml::from_str(&serialized).expect("parse");
assert_eq!(parsed, original);
}
#[test]
fn profile_unknown_keys_are_dropped() {
let with_unknown = r#"
[serial]
baud = 9600
unknown_field = "ignored"
data_bits = 8
stop_bits = 1
parity = "none"
flow = "none"
"#;
let parsed: Profile = toml::from_str(with_unknown).expect("parse");
assert_eq!(parsed.serial.baud, 9600);
}
#[test]
fn profile_partial_section_uses_defaults_for_missing_leaf_fields() {
let partial = r"
[serial]
baud = 9600
";
let parsed: Profile = toml::from_str(partial).expect("parse");
assert_eq!(parsed.serial.baud, 9600);
assert_eq!(parsed.serial.data_bits, 8); assert_eq!(parsed.serial.parity, "none"); }
}