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 after_regex: Option<String>,
50 pub idle_duration: Duration,
51 pub text: Option<String>,
52 pub key: Option<TerminalKey>,
53 pub resize: Option<TerminalResize>,
54}
55
56#[derive(Debug, Clone)]
57pub struct TerminalCaptureOptions {
58 pub file: String,
59 pub args: Vec<String>,
60 pub cwd: Option<PathBuf>,
61 pub env: HashMap<String, String>,
62 pub cols: u16,
63 pub rows: u16,
64 pub settle_duration: Duration,
65 pub frames_on_write: bool,
67 pub recording: bool,
69 pub interactions: Vec<TerminalInteraction>,
70 pub stop_marker: Option<String>,
71 pub stop_marker_grace: Duration,
72 pub timeout: Option<Duration>,
75 pub artifact_directory: Option<PathBuf>,
76}
77
78impl Default for TerminalCaptureOptions {
79 fn default() -> Self {
80 Self {
81 file: String::new(),
82 args: Vec::new(),
83 cwd: None,
84 env: HashMap::new(),
85 cols: 80,
86 rows: 24,
87 settle_duration: Duration::from_millis(35),
88 frames_on_write: false,
89 recording: true,
90 interactions: Vec::new(),
91 stop_marker: None,
92 stop_marker_grace: Duration::from_millis(250),
93 timeout: Some(Duration::from_secs(30)),
94 artifact_directory: None,
95 }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct TerminalCursor {
101 pub x: u16,
102 pub y: u16,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct TerminalFrame {
107 pub time: f64,
108 pub cols: u16,
109 pub rows: u16,
110 pub cursor: TerminalCursor,
111 pub alternate: bool,
112 pub lines: Vec<String>,
113 pub screen: Vec<String>,
114}
115
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117pub struct AsciicastHeader {
118 pub version: u8,
119 pub width: u16,
120 pub height: u16,
121 pub timestamp: i64,
122 pub env: HashMap<String, String>,
123}
124
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126pub struct AsciicastEvent {
127 pub time: f64,
128 pub code: String,
129 pub data: String,
130}
131
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133pub struct Asciicast {
134 pub header: AsciicastHeader,
135 pub events: Vec<AsciicastEvent>,
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub struct TerminalCapture {
140 pub exit_code: i32,
141 pub signal: Option<String>,
142 pub output: String,
143 pub transcript: String,
144 pub frames: Vec<TerminalFrame>,
145 pub interaction_count: usize,
146 pub asciicast: Asciicast,
147}
148
149#[derive(Debug)]
150pub struct TerminalCaptureError {
151 message: String,
152 partial: Option<Box<TerminalCapture>>,
153}
154
155impl TerminalCaptureError {
156 pub(crate) fn new(message: impl Into<String>, partial: Option<TerminalCapture>) -> Self {
157 Self {
158 message: message.into(),
159 partial: partial.map(Box::new),
160 }
161 }
162
163 pub fn partial_capture(&self) -> Option<&TerminalCapture> {
164 self.partial.as_deref()
165 }
166}
167
168impl fmt::Display for TerminalCaptureError {
169 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
170 formatter.write_str(&self.message)
171 }
172}
173
174impl std::error::Error for TerminalCaptureError {}