LEVIATHAN v962456e · 962456eee1

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() is true when the child was started. A path that does not exist is not a start failure: the child fails to run it and exits with 127. A rows or cols that is zero or negative, or an empty path, is refused (ok() is false).
  • 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, and onClose(callback) runs when the output ends. The terminal translates newlines in the output, so a child's \n arrives as \r\n.
  • resize(rows, cols) changes the terminal's size and returns 0, or -1 if it could not. The system notifies the child with a window-size signal; you never signal it by hand.
  • exitCode() returns a Promise<int> that resolves with the child's exit status once it has exited, with the same conventions as Process: 128 plus the signal number if a signal ended it, 127 if it never ran.
  • kill() asks the child to terminate; a pending exitCode() then resolves 143.

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() resolves 127. Windows has no signals, so a child you killed reports exit code 254 instead of 143, 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 of Pty::Deterministic is accepted and ignored there.
  • A pseudo-terminal has one stream. Output and error cannot be told apart.
  • Output has \r\n line 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}");
}
not run — spawns a child process on a pseudo-terminal

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}");
}
not run — needs a pseudo-terminal

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 pid

The 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() -> void

Asks 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() -> bool

Tells 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) -> void

Sets 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) -> void

Sets 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) -> int

Changes 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) -> void

Sends 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.

See also

  • Process — Runs another program as a child process and gives you its standard input, standard output and standard error as streams.
  • TcpStream — A connected network socket that reads and writes text.
  • term — Control of the terminal the program runs in: raw input mode and the window size.