Skip to main content

command_stream/terminal/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::fmt;
4use std::path::PathBuf;
5use std::time::Duration;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct TerminalResize {
9    pub cols: u16,
10    pub rows: u16,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum TerminalKey {
15    Backspace,
16    CtrlC,
17    CtrlD,
18    Down,
19    Enter,
20    Escape,
21    Left,
22    Right,
23    Tab,
24    Up,
25    Raw(String),
26}
27
28impl TerminalKey {
29    pub(crate) fn sequence(&self) -> &str {
30        match self {
31            Self::Backspace => "\u{7f}",
32            Self::CtrlC => "\u{3}",
33            Self::CtrlD => "\u{4}",
34            Self::Down => "\u{1b}[B",
35            Self::Enter => "\r",
36            Self::Escape => "\u{1b}",
37            Self::Left => "\u{1b}[D",
38            Self::Right => "\u{1b}[C",
39            Self::Tab => "\t",
40            Self::Up => "\u{1b}[A",
41            Self::Raw(sequence) => sequence,
42        }
43    }
44}
45
46#[derive(Debug, Clone, Default)]
47pub struct TerminalInteraction {
48    pub after: Option<String>,
49    pub text: Option<String>,
50    pub key: Option<TerminalKey>,
51    pub resize: Option<TerminalResize>,
52}
53
54#[derive(Debug, Clone)]
55pub struct TerminalCaptureOptions {
56    pub file: String,
57    pub args: Vec<String>,
58    pub cwd: Option<PathBuf>,
59    pub env: HashMap<String, String>,
60    pub cols: u16,
61    pub rows: u16,
62    pub settle_duration: Duration,
63    pub interactions: Vec<TerminalInteraction>,
64    pub stop_marker: Option<String>,
65    pub stop_marker_grace: Duration,
66    pub timeout: Duration,
67    pub artifact_directory: Option<PathBuf>,
68}
69
70impl Default for TerminalCaptureOptions {
71    fn default() -> Self {
72        Self {
73            file: String::new(),
74            args: Vec::new(),
75            cwd: None,
76            env: HashMap::new(),
77            cols: 80,
78            rows: 24,
79            settle_duration: Duration::from_millis(35),
80            interactions: Vec::new(),
81            stop_marker: None,
82            stop_marker_grace: Duration::from_millis(250),
83            timeout: Duration::from_secs(30),
84            artifact_directory: None,
85        }
86    }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct TerminalCursor {
91    pub x: u16,
92    pub y: u16,
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96pub struct TerminalFrame {
97    pub time: f64,
98    pub cols: u16,
99    pub rows: u16,
100    pub cursor: TerminalCursor,
101    pub alternate: bool,
102    pub lines: Vec<String>,
103    pub screen: Vec<String>,
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107pub struct AsciicastHeader {
108    pub version: u8,
109    pub width: u16,
110    pub height: u16,
111    pub timestamp: i64,
112    pub env: HashMap<String, String>,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct AsciicastEvent {
117    pub time: f64,
118    pub code: String,
119    pub data: String,
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub struct Asciicast {
124    pub header: AsciicastHeader,
125    pub events: Vec<AsciicastEvent>,
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129pub struct TerminalCapture {
130    pub exit_code: i32,
131    pub signal: Option<String>,
132    pub output: String,
133    pub transcript: String,
134    pub frames: Vec<TerminalFrame>,
135    pub interaction_count: usize,
136    pub asciicast: Asciicast,
137}
138
139#[derive(Debug)]
140pub struct TerminalCaptureError {
141    message: String,
142    partial: Option<Box<TerminalCapture>>,
143}
144
145impl TerminalCaptureError {
146    pub(crate) fn new(message: impl Into<String>, partial: Option<TerminalCapture>) -> Self {
147        Self {
148            message: message.into(),
149            partial: partial.map(Box::new),
150        }
151    }
152
153    pub fn partial_capture(&self) -> Option<&TerminalCapture> {
154        self.partial.as_deref()
155    }
156}
157
158impl fmt::Display for TerminalCaptureError {
159    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
160        formatter.write_str(&self.message)
161    }
162}
163
164impl std::error::Error for TerminalCaptureError {}