Skip to main content

twine_core/
terminal.rs

1use std::path::PathBuf;
2
3use thiserror::Error;
4
5mod launcher;
6mod manager;
7mod process;
8mod pty;
9mod shell;
10mod stream;
11mod transcript;
12
13#[cfg(not(test))]
14pub(crate) use launcher::login_shell;
15pub(crate) use manager::{TerminalManager, TerminalObservation};
16pub(crate) use shell::ShellMark;
17pub(crate) use stream::ShellObservation;
18pub(crate) use stream::{ReplayPosition, TerminalStream};
19pub(crate) use transcript::Recording;
20pub(crate) use transcript::TranscriptRecorder;
21pub use transcript::TranscriptRequest;
22pub use transcript::TranscriptSize;
23pub use transcript::{MAX_TRANSCRIPT_READ_BYTES, TranscriptError, TranscriptPage, TranscriptRead};
24
25#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
26pub struct TerminalId(u64);
27
28impl TerminalId {
29    #[must_use]
30    pub const fn from_value(value: u64) -> Self {
31        Self(value)
32    }
33
34    #[must_use]
35    pub const fn value(self) -> u64 {
36        self.0
37    }
38}
39
40#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
41pub struct TerminalSize {
42    pub rows: u16,
43    pub columns: u16,
44    pub pixel_width: u16,
45    pub pixel_height: u16,
46}
47
48impl TerminalSize {
49    /// # Errors
50    ///
51    /// Returns an error when either character dimension is zero.
52    pub fn validate(self) -> Result<Self, TerminalError> {
53        if self.rows == 0 || self.columns == 0 {
54            return Err(TerminalError::InvalidSize {
55                rows: self.rows,
56                columns: self.columns,
57            });
58        }
59        Ok(self)
60    }
61}
62
63#[derive(Clone, Debug, Eq, PartialEq)]
64pub struct TerminalExit {
65    pub exit_code: u32,
66    pub signal: Option<String>,
67}
68
69#[derive(Clone, Debug, Eq, PartialEq)]
70pub enum TerminalStatus {
71    Running,
72    Exited(TerminalExit),
73    Failed { message: String },
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct TerminalState {
78    pub terminal_id: TerminalId,
79    pub status: TerminalStatus,
80}
81
82#[derive(Clone, Debug, Eq, PartialEq)]
83pub struct TerminalChunk {
84    pub terminal_id: TerminalId,
85    pub offset: u64,
86    pub bytes: Vec<u8>,
87}
88
89#[derive(Debug, Error)]
90pub enum TerminalError {
91    #[error("terminal output chunk has {chunk_bytes} bytes, exceeding capacity {capacity_bytes}")]
92    ChunkTooLarge {
93        chunk_bytes: usize,
94        capacity_bytes: usize,
95    },
96    #[error("terminal output chunks must not be empty")]
97    EmptyChunk,
98    #[error("terminal size must be nonzero, received {columns} columns by {rows} rows")]
99    InvalidSize { rows: u16, columns: u16 },
100    #[error("terminal byte offset overflowed")]
101    OffsetOverflow,
102    #[error("terminal {terminal_id:?} is not open")]
103    NotOpen { terminal_id: TerminalId },
104    #[error("terminal {terminal_id:?} is no longer running")]
105    NotRunning { terminal_id: TerminalId },
106    #[error("failed to {operation}: {message}")]
107    Pty {
108        operation: &'static str,
109        message: String,
110    },
111    #[error("terminal state lock is poisoned")]
112    Poisoned,
113    #[error("failed to {operation}: {message}")]
114    Thread {
115        operation: &'static str,
116        message: String,
117    },
118    #[error("cannot use {path} as a terminal working directory: {message}")]
119    WorkingDirectory { path: PathBuf, message: String },
120    #[error("terminal output capacity must be greater than zero")]
121    ZeroCapacity,
122    #[error(transparent)]
123    Transcript(#[from] TranscriptError),
124}