1use serde::Deserialize;
8
9use super::schema::{invalid_value, SetConfigError};
10
11#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
13#[serde(deny_unknown_fields)]
14pub struct TerminalConfig {
15 pub default_profile: String,
18 pub height: u16,
20 pub confirm_quit: String,
25 pub confirm_close: String,
27 pub profile: Vec<TerminalProfile>,
29}
30
31impl TerminalConfig {
32 #[must_use]
34 pub fn has_profile(&self, name: &str) -> bool {
35 name.trim().is_empty()
36 || self
37 .profile
38 .iter()
39 .any(|p| p.name.eq_ignore_ascii_case(name.trim()))
40 }
41
42 pub(super) fn validate(&self) -> Result<(), SetConfigError> {
43 if !TERMINAL_HEIGHT_RANGE.contains(&self.height) {
44 return Err(invalid_value(
45 "terminal.height",
46 &self.height.to_string(),
47 TERMINAL_HEIGHT_REASON,
48 ));
49 }
50 parse_confirm("terminal.confirm_quit", &self.confirm_quit)?;
51 parse_confirm("terminal.confirm_close", &self.confirm_close)?;
52 for (index, profile) in self.profile.iter().enumerate() {
53 let key = format!("terminal.profile[{}]", index + 1);
54 if profile.name.trim().is_empty() {
55 return Err(invalid_value(&key, "", "needs a name"));
56 }
57 if profile
58 .command
59 .first()
60 .map_or(true, |p| p.trim().is_empty())
61 {
62 return Err(invalid_value(
63 &key,
64 &profile.name,
65 "needs a command, e.g. command = [\"pwsh.exe\", \"-NoLogo\"]",
66 ));
67 }
68 if self.profile[..index]
69 .iter()
70 .any(|p| p.name.eq_ignore_ascii_case(&profile.name))
71 {
72 return Err(invalid_value(&key, &profile.name, "is a duplicate name"));
73 }
74 }
75 if !self.has_profile(&self.default_profile) {
76 return Err(invalid_value(
77 "terminal.default_profile",
78 &self.default_profile,
79 "names no [[terminal.profile]]",
80 ));
81 }
82 Ok(())
83 }
84}
85
86#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
88#[serde(deny_unknown_fields)]
89pub struct TerminalProfile {
90 pub name: String,
92 pub command: Vec<String>,
95}
96
97pub(super) const TERMINAL_HEIGHT_RANGE: std::ops::RangeInclusive<u16> = 10..=90;
99pub(super) const TERMINAL_HEIGHT_REASON: &str = "must be a percentage between 10 and 90";
100
101pub(super) fn parse_confirm(key: &str, value: &str) -> Result<String, SetConfigError> {
102 crate::terminal::ConfirmPolicy::parse(value)
103 .ok_or_else(|| invalid_value(key, value, crate::terminal::CONFIRM_REASON))?;
104 Ok(value.trim().to_ascii_lowercase())
105}
106
107pub(super) fn parse_terminal_chord(key: &str, value: &str) -> Result<(), SetConfigError> {
108 crate::terminal::keys::KeyChord::parse(value)
109 .map(|_| ())
110 .ok_or_else(|| invalid_value(key, value, crate::terminal::keys::KEY_CHORD_REASON))
111}