1use std::{borrow::Cow, fmt, io};
4
5use styled_str::{AnsiError, StyledString};
6
7pub(crate) type BoxedError = Box<dyn std::error::Error + Send + Sync>;
8
9#[derive(Debug)]
11#[non_exhaustive]
12pub enum TermError {
13 Ansi(AnsiError),
15 Io(io::Error),
17 FontEmbedding(BoxedError),
19}
20
21impl fmt::Display for TermError {
22 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Self::Ansi(err) => write!(formatter, "ANSI escape sequence parsing error: {err}"),
25 Self::Io(err) => write!(formatter, "I/O error: {err}"),
26 Self::FontEmbedding(err) => write!(formatter, "font embedding error: {err}"),
27 }
28 }
29}
30
31impl std::error::Error for TermError {
32 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
33 match self {
34 Self::Ansi(err) => Some(err),
35 Self::Io(err) => Some(err),
36 _ => None,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Default)]
43pub struct Transcript {
44 interactions: Vec<Interaction>,
45}
46
47impl Transcript {
48 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn interactions(&self) -> &[Interaction] {
55 &self.interactions
56 }
57
58 pub fn interactions_mut(&mut self) -> &mut [Interaction] {
60 &mut self.interactions
61 }
62}
63
64impl Transcript {
65 pub fn add_existing_interaction(&mut self, interaction: Interaction) -> &mut Self {
72 self.interactions.push(interaction);
73 self
74 }
75
76 pub fn add_interaction(
80 &mut self,
81 input: impl Into<UserInput>,
82 output: StyledString,
83 ) -> &mut Self {
84 self.add_existing_interaction(Interaction::new(input, output))
85 }
86}
87
88impl FromIterator<Interaction> for Transcript {
89 fn from_iter<I: IntoIterator<Item = Interaction>>(iter: I) -> Self {
90 Self {
91 interactions: iter.into_iter().collect(),
92 }
93 }
94}
95
96impl Extend<Interaction> for Transcript {
97 fn extend<I: IntoIterator<Item = Interaction>>(&mut self, iter: I) {
98 self.interactions.extend(iter);
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
144pub struct ExitStatus(pub i32);
145
146impl ExitStatus {
147 pub fn is_success(self) -> bool {
149 self.0 == 0
150 }
151}
152
153#[derive(Debug, Clone)]
155pub struct Interaction {
156 input: UserInput,
157 output: StyledString,
158 exit_status: Option<ExitStatus>,
159}
160
161impl Interaction {
162 pub fn new(input: impl Into<UserInput>, mut output: StyledString) -> Self {
166 while output.text().ends_with('\n') {
167 output.pop();
168 }
169
170 Self {
171 input: input.into(),
172 output,
173 exit_status: None,
174 }
175 }
176
177 pub fn set_exit_status(&mut self, exit_status: Option<ExitStatus>) {
179 self.exit_status = exit_status;
180 }
181
182 #[must_use]
184 pub fn with_exit_status(mut self, exit_status: ExitStatus) -> Self {
185 self.exit_status = Some(exit_status);
186 self
187 }
188}
189
190impl Interaction {
191 pub fn input(&self) -> &UserInput {
193 &self.input
194 }
195
196 pub fn output(&self) -> &StyledString {
198 &self.output
199 }
200
201 pub fn set_output(&mut self, mut output: StyledString) {
203 while output.text().ends_with('\n') {
204 output.pop();
205 }
206 self.output = output;
207 }
208
209 pub fn exit_status(&self) -> Option<ExitStatus> {
211 self.exit_status
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
217#[cfg_attr(feature = "svg", derive(serde::Serialize))]
218pub struct UserInput {
219 text: String,
220 prompt: Option<Cow<'static, str>>,
221 hidden: bool,
222}
223
224impl UserInput {
225 #[cfg(feature = "test")]
226 pub(crate) const EMPTY: Self = Self {
227 text: String::new(),
228 prompt: None,
229 hidden: false,
230 };
231
232 #[cfg(feature = "test")]
233 pub(crate) fn new(text: String) -> Self {
234 Self {
235 prompt: None,
236 text,
237 hidden: false,
238 }
239 }
240
241 #[cfg(feature = "test")]
242 #[must_use]
243 pub(crate) fn with_prompt(mut self, prompt: Option<String>) -> Self {
244 self.prompt = prompt.map(|prompt| match prompt.as_str() {
245 "$" => Cow::Borrowed("$"),
246 ">>>" => Cow::Borrowed(">>>"),
247 "..." => Cow::Borrowed("..."),
248 _ => Cow::Owned(prompt),
249 });
250 self
251 }
252
253 pub fn command(text: impl Into<String>) -> Self {
255 Self {
256 text: text.into(),
257 prompt: Some(Cow::Borrowed("$")),
258 hidden: false,
259 }
260 }
261
262 pub fn repl(text: impl Into<String>) -> Self {
264 Self {
265 text: text.into(),
266 prompt: Some(Cow::Borrowed(">>>")),
267 hidden: false,
268 }
269 }
270
271 pub fn repl_continuation(text: impl Into<String>) -> Self {
273 Self {
274 text: text.into(),
275 prompt: Some(Cow::Borrowed("...")),
276 hidden: false,
277 }
278 }
279
280 pub fn prompt(&self) -> Option<&str> {
282 self.prompt.as_deref()
283 }
284
285 #[must_use]
287 pub fn hide(mut self) -> Self {
288 self.hidden = true;
289 self
290 }
291
292 pub fn is_hidden(&self) -> bool {
294 self.hidden
295 }
296}
297
298impl AsRef<str> for UserInput {
300 fn as_ref(&self) -> &str {
301 &self.text
302 }
303}
304
305impl From<&str> for UserInput {
307 fn from(command: &str) -> Self {
308 Self::command(command)
309 }
310}