Skip to main content

par_term_config/
automation.rs

1//! Configuration types for triggers and coprocesses.
2
3use serde::{Deserialize, Serialize};
4
5/// Scope for a prettify trigger action.
6#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum PrettifyScope {
9    /// Apply to the matched line only.
10    Line,
11    /// Apply to a delimited block (start pattern → block_end pattern).
12    Block,
13    /// Apply to the entire command output containing the match.
14    #[default]
15    CommandOutput,
16}
17
18/// Payload packed into a Notify relay for prettify trigger actions.
19///
20/// When the core trigger system fires, the frontend intercepts Notify actions
21/// with the `__prettify__` title prefix and deserializes this from the message.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct PrettifyRelayPayload {
24    pub format: String,
25    pub scope: PrettifyScope,
26    #[serde(default)]
27    pub block_end: Option<String>,
28    #[serde(default)]
29    pub sub_format: Option<String>,
30    #[serde(default)]
31    pub command_filter: Option<String>,
32}
33
34/// Magic label prefix used to relay prettify actions through the core MarkLine system.
35/// Using MarkLine (instead of Notify) because it carries the matched `row`.
36pub const PRETTIFY_RELAY_PREFIX: &str = "__prettify__";
37
38/// A trigger definition that matches terminal output and fires actions.
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
40pub struct TriggerConfig {
41    pub name: String,
42    pub pattern: String,
43    #[serde(default = "crate::defaults::bool_true")]
44    pub enabled: bool,
45    #[serde(default)]
46    pub actions: Vec<TriggerActionConfig>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50#[serde(tag = "type", rename_all = "snake_case")]
51pub enum TriggerActionConfig {
52    Highlight {
53        #[serde(default)]
54        fg: Option<[u8; 3]>,
55        #[serde(default)]
56        bg: Option<[u8; 3]>,
57        #[serde(default = "default_highlight_duration")]
58        duration_ms: u64,
59    },
60    Notify {
61        title: String,
62        message: String,
63    },
64    MarkLine {
65        #[serde(default)]
66        label: Option<String>,
67        #[serde(default)]
68        color: Option<[u8; 3]>,
69    },
70    SetVariable {
71        name: String,
72        value: String,
73    },
74    RunCommand {
75        command: String,
76        #[serde(default)]
77        args: Vec<String>,
78    },
79    PlaySound {
80        #[serde(default)]
81        sound_id: String,
82        #[serde(default = "default_volume")]
83        volume: u8,
84    },
85    SendText {
86        text: String,
87        #[serde(default)]
88        delay_ms: u64,
89    },
90    /// Invoke a specific prettifier renderer on matched content.
91    Prettify {
92        /// Which renderer to invoke (e.g., "json", "markdown", "none").
93        format: String,
94        /// What scope to apply the renderer to.
95        #[serde(default)]
96        scope: PrettifyScope,
97        /// Optional regex for block end (for block-scoped rendering).
98        #[serde(default)]
99        block_end: Option<String>,
100        /// Optional sub-format (e.g., "plantuml" for diagrams).
101        #[serde(default)]
102        sub_format: Option<String>,
103        /// Optional regex to filter by preceding command.
104        #[serde(default)]
105        command_filter: Option<String>,
106    },
107}
108
109/// Policy for restarting a coprocess when it exits
110#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub enum RestartPolicy {
113    /// Never restart (default)
114    #[default]
115    Never,
116    /// Always restart regardless of exit code
117    Always,
118    /// Restart only on non-zero exit code
119    OnFailure,
120}
121
122impl RestartPolicy {
123    /// All available restart policies for UI dropdowns
124    pub fn all() -> &'static [RestartPolicy] {
125        &[Self::Never, Self::Always, Self::OnFailure]
126    }
127
128    /// Human-readable display name
129    pub fn display_name(self) -> &'static str {
130        match self {
131            Self::Never => "Never",
132            Self::Always => "Always",
133            Self::OnFailure => "On Failure",
134        }
135    }
136
137    /// Convert to core library RestartPolicy
138    pub fn to_core(self) -> par_term_emu_core_rust::coprocess::RestartPolicy {
139        match self {
140            Self::Never => par_term_emu_core_rust::coprocess::RestartPolicy::Never,
141            Self::Always => par_term_emu_core_rust::coprocess::RestartPolicy::Always,
142            Self::OnFailure => par_term_emu_core_rust::coprocess::RestartPolicy::OnFailure,
143        }
144    }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
148pub struct CoprocessDefConfig {
149    pub name: String,
150    pub command: String,
151    #[serde(default)]
152    pub args: Vec<String>,
153    #[serde(default)]
154    pub auto_start: bool,
155    #[serde(default = "crate::defaults::bool_true")]
156    pub copy_terminal_output: bool,
157    #[serde(default)]
158    pub restart_policy: RestartPolicy,
159    #[serde(default)]
160    pub restart_delay_ms: u64,
161}
162
163fn default_highlight_duration() -> u64 {
164    5000
165}
166
167fn default_volume() -> u8 {
168    50
169}
170
171impl TriggerActionConfig {
172    /// Convert to core library TriggerAction
173    pub fn to_core_action(&self) -> par_term_emu_core_rust::terminal::TriggerAction {
174        use par_term_emu_core_rust::terminal::TriggerAction;
175        match self.clone() {
176            Self::Highlight {
177                fg,
178                bg,
179                duration_ms,
180            } => TriggerAction::Highlight {
181                fg: fg.map(|c| (c[0], c[1], c[2])),
182                bg: bg.map(|c| (c[0], c[1], c[2])),
183                duration_ms,
184            },
185            Self::Notify { title, message } => TriggerAction::Notify { title, message },
186            Self::MarkLine { label, color } => TriggerAction::MarkLine {
187                label,
188                color: color.map(|c| (c[0], c[1], c[2])),
189            },
190            Self::SetVariable { name, value } => TriggerAction::SetVariable { name, value },
191            Self::RunCommand { command, args } => TriggerAction::RunCommand { command, args },
192            Self::PlaySound { sound_id, volume } => TriggerAction::PlaySound { sound_id, volume },
193            Self::SendText { text, delay_ms } => TriggerAction::SendText { text, delay_ms },
194            Self::Prettify {
195                format,
196                scope,
197                block_end,
198                sub_format,
199                command_filter,
200            } => {
201                // Relay through the core MarkLine mechanism. MarkLine carries the
202                // matched `row`, which we need for scope handling. The frontend
203                // intercepts ActionResult::MarkLine with the __prettify__ label
204                // prefix and dispatches to the PrettifierPipeline.
205                let payload = PrettifyRelayPayload {
206                    format,
207                    scope,
208                    block_end,
209                    sub_format,
210                    command_filter,
211                };
212                TriggerAction::MarkLine {
213                    label: Some(format!(
214                        "{}{}",
215                        PRETTIFY_RELAY_PREFIX,
216                        serde_json::to_string(&payload).unwrap_or_default()
217                    )),
218                    color: None,
219                }
220            }
221        }
222    }
223}