1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
use std::fmt;
use std::io::{self, Read, Write};
#[cfg(CHANNEL_NIGHTLY)]
use std::io::{IoSlice, IoSliceMut};
use std::process::{Child as ChildProcess, Command};
use crate::TerminalSize;
use crate::pal as r#impl;
/// Extension trait that adds pseudo-terminal spawning capabilities to `std::process::Command`.
///
/// This trait is automatically implemented for `Command` and provides methods to spawn
/// a new process with a pseudo-terminal as its controlling terminal.
pub trait CommandExt {
/// Spawns a new process with a pseudo-terminal (PTY) as its controlling terminal.
///
/// This method creates a new PTY and configures the process to use it as its
/// standard input, output, and error streams. It uses a default terminal size
/// of 80 columns by 24 rows.
///
/// # Examples
///
/// ```no_run
/// use std::process::Command;
/// use pseudoterminal::CommandExt;
///
/// let mut cmd = Command::new("bash");
/// let mut terminal = cmd.spawn_terminal().expect("Failed to spawn terminal");
///
/// // Now you can interact with the terminal
/// // ...
/// ```
///
/// # Errors
///
/// This function will return an error if the PTY creation fails, if setting up the
/// process environment fails, or if spawning the process fails.
fn spawn_terminal(&mut self) -> io::Result<Terminal>;
/// Spawns a new process with a pseudo-terminal (PTY) as its controlling terminal,
/// using the specified terminal size.
///
/// This method creates a new PTY of the specified size and configures the process to use it
/// as its standard input, output, and error streams.
///
/// # Examples
///
/// ```no_run
/// use std::process::Command;
/// use pseudoterminal::{CommandExt, TerminalSize};
///
/// let mut cmd = Command::new("bash");
/// let size = TerminalSize {
/// columns: 100,
/// rows: 30,
/// };
///
/// let mut terminal = cmd.spawn_terminal_with_size(size)
/// .expect("Failed to spawn terminal");
///
/// // Now you can interact with the terminal
/// // ...
/// ```
///
/// # Errors
///
/// This function will return an error if the PTY creation fails, if setting up the
/// process environment fails, or if spawning the process fails.
fn spawn_terminal_with_size(&mut self, size: TerminalSize) -> io::Result<Terminal>;
}
impl CommandExt for Command {
fn spawn_terminal(&mut self) -> io::Result<Terminal> {
// Use a default terminal size
let default_size = TerminalSize {
rows: 24,
columns: 80,
};
self.spawn_terminal_with_size(default_size)
}
fn spawn_terminal_with_size(&mut self, size: TerminalSize) -> io::Result<Terminal> {
r#impl::Terminal::spawn_terminal(self, size).map(Terminal::from)
}
}
/// Represents a synchronous pseudo-terminal (PTY).
///
/// This struct manages the PTY lifecycle, provides access to its I/O streams,
/// and allows interaction with the spawned child process.
pub struct Terminal {
handle: r#impl::Terminal,
child_proc: ChildProcess,
/// The input stream (write) for the terminal. `None` if split.
pub terminal_in: Option<TerminalIn>,
/// The output stream (read) for the terminal. `None` if split.
pub terminal_out: Option<TerminalOut>,
}
impl From<(r#impl::Terminal, ChildProcess, r#impl::TerminalIo)> for Terminal {
fn from(
(handle, child_proc, io): (r#impl::Terminal, ChildProcess, r#impl::TerminalIo),
) -> Terminal {
Terminal {
handle,
child_proc,
terminal_in: io.input.map(TerminalIn::from),
terminal_out: io.output.map(TerminalOut::from),
}
}
}
impl Terminal {
/// Splits the terminal into its input (`TerminalIn`) and output (`TerminalOut`) streams.
///
/// This method consumes the terminal's I/O streams and returns them to the caller,
/// allowing them to be used independently from the Terminal object itself.
/// This is useful when you need to pass the streams to different parts of your
/// application or handle them in separate threads.
///
/// After calling this, `self.terminal_in` and `self.terminal_out` will be `None`.
/// Returns `None` if the terminal has already been split.
///
/// # Examples
///
/// ```no_run
/// use std::process::Command;
/// use pseudoterminal::CommandExt;
/// use std::io::{Read, Write};
///
/// let mut cmd = Command::new("bash");
/// let mut terminal = cmd.spawn_terminal().expect("Failed to spawn terminal");
///
/// // Split the terminal to handle I/O separately
/// if let Some((mut input, mut output)) = terminal.split() {
/// // Now you can use input and output independently
/// input.write_all(b"echo hello\n").expect("Write failed");
///
/// let mut buffer = String::new();
/// output.read_to_string(&mut buffer).expect("Read failed");
/// println!("Terminal output: {}", buffer);
/// }
/// ```
pub fn split(&mut self) -> Option<(TerminalIn, TerminalOut)> {
let terminal_in = self.terminal_in.take()?;
let terminal_out = self.terminal_out.take()?;
Some((terminal_in, terminal_out))
}
/// Gets the current size of the pseudo-terminal.
///
/// Returns the terminal dimensions as a `TerminalSize` struct containing
/// the number of rows and columns.
///
/// # Examples
///
/// ```no_run
/// # #[cfg(unix)]
/// # fn example() -> std::io::Result<()> {
/// use std::process::Command;
/// use pseudoterminal::CommandExt;
///
/// let mut cmd = Command::new("bash");
/// let terminal = cmd.spawn_terminal()?;
///
/// let size = terminal.get_term_size()?;
/// println!("Terminal size: {} rows by {} columns", size.rows, size.columns);
/// # Ok(())
/// # }
/// ```
#[cfg(unix)]
pub fn get_term_size(&self) -> io::Result<TerminalSize> {
self.handle.get_term_size()
}
/// Sets the size of the pseudo-terminal.
///
/// This method resizes the terminal to the specified dimensions. The size change
/// is communicated to the running program, which may redraw its output accordingly.
///
/// # Parameters
///
/// - `new_size`: The desired size for the terminal, containing rows and columns.
///
/// # Examples
///
/// ```no_run
/// use std::process::Command;
/// use pseudoterminal::{CommandExt, TerminalSize};
///
/// let mut cmd = Command::new("bash");
/// let mut terminal = cmd.spawn_terminal().expect("Failed to spawn terminal");
///
/// // Resize the terminal to 100x30
/// let new_size = TerminalSize {
/// rows: 30,
/// columns: 100,
/// };
///
/// terminal.set_term_size(new_size).expect("Failed to resize terminal");
/// ```
///
/// # Errors
///
/// This function will return an error if the terminal resizing operation fails.
/// This can happen due to operating system limitations or if the provided dimensions
/// are invalid for the underlying terminal implementation.
pub fn set_term_size(&mut self, new_size: TerminalSize) -> io::Result<()> {
self.handle.set_term_size(new_size)
}
/// Terminates the process running in the terminal and closes the terminal.
///
/// This method will kill the child process and release any resources associated
/// with the terminal. After calling this method, the terminal can no longer be used.
///
/// # Examples
///
/// ```no_run
/// use std::process::Command;
/// use pseudoterminal::CommandExt;
///
/// let mut cmd = Command::new("bash");
/// let terminal = cmd.spawn_terminal().expect("Failed to spawn terminal");
///
/// // Do some work with the terminal...
///
/// // When done, close the terminal and terminate the process
/// terminal.close().expect("Failed to close terminal");
/// ```
///
/// # Errors
///
/// This function will return an error if killing the child process fails.
pub fn close(mut self) -> io::Result<()> {
self.child_proc.kill()?;
Ok(())
}
}
/// An input stream for writing to a pseudo-terminal.
///
/// This struct represents the input side of a pseudo-terminal and implements the
/// standard `Write` trait, allowing you to send data to the process running in the
/// terminal. It wraps a platform-specific implementation to provide a consistent
/// interface across different operating systems.
///
/// # Examples
///
/// ```no_run
/// use std::process::Command;
/// use pseudoterminal::CommandExt;
/// use std::io::Write;
///
/// let mut cmd = Command::new("bash");
/// let mut terminal = cmd.spawn_terminal().expect("Failed to spawn terminal");
///
/// // Write a command to the terminal
/// if let Some(ref mut terminal_in) = terminal.terminal_in {
/// terminal_in.write_all(b"echo Hello, World!\n").expect("Write failed");
/// terminal_in.flush().expect("Flush failed");
/// }
/// ```
pub struct TerminalIn {
inner: r#impl::TerminalIn,
}
impl Write for TerminalIn {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
#[cfg(CHANNEL_NIGHTLY)]
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
self.inner.write_vectored(bufs)
}
#[cfg(CHANNEL_NIGHTLY)]
#[inline]
fn is_write_vectored(&self) -> bool {
io::Write::is_write_vectored(&self.inner)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl AsRef<r#impl::TerminalIn> for TerminalIn {
#[inline]
fn as_ref(&self) -> &r#impl::TerminalIn {
&self.inner
}
}
impl From<TerminalIn> for r#impl::TerminalIn {
fn from(val: TerminalIn) -> Self {
val.inner
}
}
impl From<r#impl::TerminalIn> for TerminalIn {
fn from(pipe: r#impl::TerminalIn) -> TerminalIn {
TerminalIn { inner: pipe }
}
}
impl fmt::Debug for TerminalIn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TerminalIn").finish_non_exhaustive()
}
}
/// An output stream for reading from a pseudo-terminal.
///
/// This struct represents the output side of a pseudo-terminal and implements the
/// standard `Read` trait, allowing you to receive data from the process running in the
/// terminal. It wraps a platform-specific implementation to provide a consistent
/// interface across different operating systems.
///
/// # Examples
///
/// ```no_run
/// use std::process::Command;
/// use pseudoterminal::CommandExt;
/// use std::io::Read;
///
/// let mut cmd = Command::new("bash");
/// let mut terminal = cmd.spawn_terminal().expect("Failed to spawn terminal");
///
/// // Send a command
/// if let Some(ref mut terminal_in) = terminal.terminal_in {
/// use std::io::Write;
/// terminal_in.write_all(b"echo Hello, World!\n").expect("Write failed");
/// }
///
/// // Read the output
/// if let Some(ref mut terminal_out) = terminal.terminal_out {
/// let mut buffer = [0; 1024];
/// let bytes_read = terminal_out.read(&mut buffer).expect("Read failed");
/// println!("Read {} bytes: {}", bytes_read,
/// String::from_utf8_lossy(&buffer[..bytes_read]));
/// }
/// ```
pub struct TerminalOut {
inner: r#impl::TerminalOut,
}
impl Read for TerminalOut {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
#[cfg(CHANNEL_NIGHTLY)]
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
self.inner.read_vectored(bufs)
}
#[cfg(CHANNEL_NIGHTLY)]
#[inline]
fn is_read_vectored(&self) -> bool {
self.inner.is_read_vectored()
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
self.inner.read_to_end(buf)
}
}
impl AsRef<r#impl::TerminalOut> for TerminalOut {
#[inline]
fn as_ref(&self) -> &r#impl::TerminalOut {
&self.inner
}
}
impl From<TerminalOut> for r#impl::TerminalOut {
fn from(val: TerminalOut) -> Self {
val.inner
}
}
impl From<r#impl::TerminalOut> for TerminalOut {
fn from(pipe: r#impl::TerminalOut) -> TerminalOut {
TerminalOut { inner: pipe }
}
}
impl fmt::Debug for TerminalOut {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TerminalOut").finish_non_exhaustive()
}
}