pty

Cross-platform PTY (pseudo-terminal) spawning for MoonBit native targets, integrated with moonbitlang/async.

pty
pseudo-terminal
async
native
moon add moonbit-community/pty@0.4.0
Download zip
Version
0.4.0
License
Apache-2.0
Last updated
2 days ago
Downloads
9K

Dependencies

README

#moonbit-community/pty

Cross-platform PTY (pseudo-terminal) spawning for MoonBit native targets, integrated with moonbitlang/async so reads and writes go through the async event loop instead of blocking the thread.

#API

@async.with_task_group(group => {
let pty = @pty.spawn(
group,
"sh", ["-c", "echo hello"],
rows=24, cols=80,
cwd="/workspace",
)
let text = pty.read_all().text() // Pty implements @io.Reader
pty.write(b"ls\n") // ... and @io.Writer
pty.resize(rows=40, cols=120)
let pid : Int = pty.pid()
let exit_code : Int = pty.wait()
})

The pty's resources are released when the task group exits. On cancellation the child gets a 5 s grace period, then a hard kill. Pty::wait returns the child's exit code; call it explicitly when the exit code matters.

#Drain the pty concurrently

Do not wait() first and read() afterwards. A pty master is a bounded kernel queue, and on macOS the kernel discards whatever is still queued a few hundred milliseconds after the child exits. Read concurrently while waiting; reading late yields a clean but empty EOF.

Relatedly, ConPTY output is a screen rendering, not a byte stream: expect VT escape sequences (including an initial clear-screen) surrounding your child's output. Strip or escape them before asserting on pty output in tests — a failing diff that prints raw pty output replays those sequences into your terminal.

#Further reading

  • docs/internals.md — executable resolution, environment merging, and platform architecture: unix fork/execve, Windows ConPTY startup, argv encoding.
  • docs/invariants.md — hard-won debugging conclusions and decision records that must not regress.

#
Pty

type Pty

impl Reader for Pty
impl Writer for Pty

#
Pty::_direct_read

#deprecated("internal")
async fn Pty::_direct_read(self : Pty, buf : FixedArray[Byte], offset~ : Int, max_len~ : Int) -> Int

#
Pty::_get_internal_buffer

#deprecated("internal")
fn Pty::_get_internal_buffer(self : Pty) ->
ReaderBuffer

#
Pty::drop

async fn Pty::drop(self : Pty, len : Int) -> Int

#
Pty::pid

fn Pty::pid(self : Pty) -> Int

#
Pty::read

async fn Pty::read(self : Pty, dst : FixedArray[Byte], offset? : Int, max_len? : Int) -> Int

#
Pty::read_all

async fn Pty::read_all(self : Pty) -> &
Data

#
Pty::read_exactly

async fn Pty::read_exactly(self : Pty, len : Int) -> Bytes

#
Pty::read_some

async fn Pty::read_some(self : Pty, max_len? : Int) -> Bytes?

#
Pty::read_until

async fn Pty::read_until(self : Pty, sep : StringView) -> String?

#
Pty::resize

fn Pty::resize(self : Pty, rows~ : Int, cols~ : Int) -> Unit raise
OSError

#
Pty::wait

async fn Pty::wait(self : Pty) -> Int

#
Pty::write

async fn Pty::write(self : Pty, data : &
Data
) -> Unit

#
Pty::write_once

async fn Pty::write_once(self : Pty, buf : Bytes, offset~ : Int, len~ : Int) -> Int

#
Pty::write_reader

async fn Pty::write_reader(self : Pty, reader : &
Reader
) -> Unit

#
spawn

async fn[X] spawn(group :
TaskGroup
[X], rows? : Int, cols? : Int, file : StringView, args : ArrayView[StringView], extra_env? : Map[String, String], inherit_env? : Bool, cwd? : StringView) -> Pty