Standard Library
class Pty
Runs another program on a pseudo-terminal, so the child believes it is talking to a real terminal.
since 0.1.0-alpha.1linux
Overview
Unlike Process, which gives the child three pipes, a Pty gives it one two-way stream that carries both the child's standard output and its standard error; there is no separate error handler. The child has a real controlling terminal, so line editing, job control and full-screen programs behave as they would in a terminal emulator. Use it for interactive programs and anything that checks whether it is attached to a terminal.
path is used exactly as written; no search of the PATH is made. The terminal applies its usual output translation, so a newline written by the child arrives as a carriage return followed by a newline. By default the terminal also echoes what you write back into the output; Pty::Deterministic turns the echo off, which makes the output reproducible.
A terminal has no separate end-of-input marker for the write side: to send end of file, write the end-of-file character, write("\x04"), at the start of a line. Check ok() after constructing, as for Process. A start failure (an empty path, or a row or column count that is not positive) resolves exitCode() with 127.
Description
Pty(path, args, rows, cols) starts a child on a pseudo-terminal instead of pipes. As with
Process, the path is used as given with no search of PATH. The difference is the shape of the
streams: a pseudo-terminal merges the child's standard output and standard error into one
bidirectional stream, so there is one stream, not three, and there is no onStderr.
The child has a real controlling terminal. isatty is true for it, line editing and job control
work, and full-screen programs draw as they would inside a terminal emulator.
Pty::Deterministic(path, args, rows, cols)is the second constructor. It selects a fixed terminal profile with echo turned off. Use it whenever the output must be reproducible: with echo on, what you write is interleaved back into what you read.ok()istruewhen the child was started. A path that does not exist is not a start failure: the child fails to run it and exits with127. Arowsorcolsthat is zero or negative, or an empty path, is refused (ok()isfalse).write(text)sends input to the child, queueing what the terminal cannot take yet. A terminal has no separate "close input" operation: to signal end of input, write the terminal's end-of-file character,"\x04"(Ctrl-D).onData(callback)delivers the merged output in chunks, andonClose(callback)runs when the output ends. The terminal translates newlines in the output, so a child's\narrives as\r\n.resize(rows, cols)changes the terminal's size and returns0, or-1if it could not. The system notifies the child with a window-size signal; you never signal it by hand.exitCode()returns aPromise<int>that resolves with the child's exit status once it has exited, with the same conventions asProcess:128plus the signal number if a signal ended it,127if it never ran.kill()asks the child to terminate; a pendingexitCode()then resolves143.
The terminal's master side is kept open until the child has been reaped, not merely until its output ends. Closing it earlier would hang the terminal up on a child that is still running its exit path.
Rules
- The reliable platform is Linux. On Windows targets the class compiles and uses the system's
pseudoconsole where the host has one (Windows 10 version 1809 or newer); on an older host the
start fails and
exitCode()resolves127. Windows has no signals, so a child you killed reports exit code254instead of143, and the output stream carries the console's own rendering of the child's output, so test behavior after stripping escape sequences, never exact bytes. The fixed terminal profile ofPty::Deterministicis accepted and ignored there. - A pseudo-terminal has one stream. Output and error cannot be told apart.
- Output has
\r\nline endings. - End input with
write("\x04"). - A program that never calls
exitCode()may finish while the child is still running.
Examples
Running a small shell command on a terminal with the reproducible profile and waiting for it:
Pty shell = Pty::Deterministic("/bin/sh", ["-c", "read line; echo got:$line"], 24, 80);
if (!shell.ok()) {
console.writeln("could not start the child");
} else {
shell.onData((chunk) => console.writeln("output: ${chunk}"));
shell.write("hello\n");
console.writeln("resize result: ${shell.resize(40, 120)}");
int code = await shell.exitCode();
console.writeln("exit code ${code}");
}
Notes
Use Process instead when the child does not need a terminal and you want its standard output
and standard error separately.
Examples
Talking to an interactive program
Pty term = Pty("/bin/cat", [], 24, 80);
if (term.ok()) {
term.onData((chunk) => console.write(chunk));
term.write("hello\n");
term.write("\x04");
int code = await term.exitCode();
console.writeln("exit code: ${code}");
}
Constructors
Deterministic
Pty::Deterministic(string path, Array<string> args, int rows, int cols)Starts path on a pseudo-terminal whose settings are fixed so that the output is reproducible.
It differs from Pty(...) in one way: the terminal does not echo what you write back into the output. With echo on, your input and the child's output are mixed in what you read, which makes tests hard to compare. Everything else is the same.
Parameters
- path
- The full path of the executable to run. An empty path starts nothing.
- args
- The arguments passed to the program, not counting the program name itself.
- rows
- The height of the terminal in character rows; must be positive.
- cols
- The width of the terminal in character columns; must be positive.
See also: new
new
new(string path, Array<string> args, int rows, int cols)Starts path on a new pseudo-terminal of the given size.
The terminal echoes input back into the output, as an ordinary terminal does. Use Pty::Deterministic when the output must be reproducible. Call ok() to find out whether the child was created.
Parameters
- path
- The full path of the executable to run. An empty path starts nothing.
- args
- The arguments passed to the program, not counting the program name itself.
- rows
- The height of the terminal in character rows; must be positive.
- cols
- The width of the terminal in character columns; must be positive.
Fields
pid
int pidThe operating-system process id of the child, or -1 when it could not be started.
Methods
exitCode
exitCode() -> Promise<int>Returns a promise that resolves with the child's exit code once it has ended.
The code is the value the child passed to its exit call (0 to 255), 128 plus the signal number when a signal ended it, or 127 when the child could not be started or run. The terminal is kept open until the child has ended, and the output still waiting in it is delivered before the promise resolves. Calling exitCode again returns the same promise.
Returns
A promise of the exit code.
kill
kill() -> voidAsks the child to stop by sending it the termination signal (SIGTERM).
Does nothing when the child was not started or has already ended. The terminal itself is not closed at once, so that the output the child produced while stopping can still be read; it is closed when exitCode resolves, normally with 143 (128 plus the signal number).
See also: exitCode
ok
ok() -> boolTells whether the child was created on the pseudo-terminal.
As with Process, a path that does not exist still gives true; the failure appears as exit code 127. Empty paths and non-positive sizes give false.
Returns
true when the child was created, false when nothing was started.
onClose
onClose(() => void cb) -> voidSets the function called when the child's side of the terminal closes.
This happens when the child, and everything else holding the terminal, has closed it. It does not mean that the child's exit code is known; call exitCode for that.
Parameters
- cb
- The function called when the terminal closes.
onData
onData((string) => void cb) -> voidSets the function that receives everything the child prints.
cb is called with each piece of output as it arrives; a piece is not necessarily a whole line. Standard output and standard error arrive merged in one stream. Newlines arrive as a carriage return followed by a newline, as on any terminal. Calling onData again replaces the previous function.
Only call this when ok() is true. On a terminal that was not started the registration keeps the event loop alive and the program never ends on its own.
Parameters
- cb
- The function called with each chunk of output.
resize
resize(int rows, int cols) -> intChanges the size of the terminal.
The operating system tells the child about the new size, so a full-screen program redraws itself. You never need to signal the child yourself.
Parameters
- rows
- The new height in character rows; must be positive.
- cols
- The new width in character columns; must be positive.
Returns
0 on success, -1 when the terminal was not started, was already closed, or a size was not positive.
write
write(string s) -> voidSends text to the child as if it had been typed on the terminal.
The text is queued and written as the terminal has room. A terminal has no way to close only its write half, so to signal the end of input write the end-of-file character "\x04" at the start of a line. Writing to a terminal that was not started does nothing.
Parameters
- s
- The text to send.