Skip to content
Program status

Program status

Use OSC 7501 to tell the terminal what your program is doing. Reports can include progress, a message, and the reason that user input is required. The terminal chooses how to present them. Unsupported terminals ignore them.

Send a report

Program::set_program_status sends and flushes a complete report.

use uncurses::program::{Program, ProgramStatus, ProgramStatusState};

fn main() -> std::io::Result<()> {
    let mut program = Program::stdio()?;
    let result = (|| {
        program.init()?;
        let mut report = ProgramStatus::new(ProgramStatusState::WORKING);
        report.app = Some("my-build");
        report.progress = Some(25);
        report.message = Some("Compile libraries");
        program.set_program_status(&report)?;

        report.state = ProgramStatusState::DONE;
        report.progress = None;
        report.message = Some("Build complete");
        program.set_program_status(&report)
    })();
    let finish = program.finish();
    result.and(finish)
}

Each report replaces its record completely. Repeat app, title, and message on each update when they should remain present. Supply plain UTF-8 text; uncurses performs base64 encoding.

The writer rejects invalid identifiers, control characters, excessive text lengths, and percentages above 100 before it writes any output. An invalid report returns io::ErrorKind::InvalidInput. Rustdoc lists the exact limits.

Report tasks and user input

An absent id selects the root record. Set id to a path such as build/test for a child record. An absent app inherits from the nearest ancestor that supplies one.

Use ProgramStatusState::BLOCKED when work needs user input. ProgramStatusKind provides PERMISSION, QUESTION, and AUTH. Only working and blocked records use progress. An absent percentage means indeterminate progress.

Use ProgramStatusState::CLEAR to remove a record and its descendants. A clear report without an ID removes all records in the terminal.

Record lifetime

The terminal owns these records. Pause, resume, screen switches, and finish() leave them unchanged. Done and error records survive process exit and the next shell prompt so the user can find the result.

The terminal removes working and blocked records when the process exits or the next shell prompt starts.

On cancellation, report IDLE. Report DONE or ERROR before exit when you want the result to remain visible. OSC 9;4 progress is a separate API with separate teardown behavior.

Detect support explicitly

Detection is optional. program.request_program_status()? sends the query. A supporting terminal replies with Event::ProgramStatus(report). The ProgramStatusCapabilities payload owns the accepted states and kinds lists. Synchronous program reads observe the reply automatically. program.capabilities().program_status() returns Some(&report) after a reply. Read the accepted names from report.states and report.kinds.

For async streams, pass the reply through program.observe_event(&event)?. Use a bounded wait with poll_event when you need a support decision. Before a reply, the accessor returns None, which means support remains unconfirmed. Use .is_some() when you need only the support result.

A reply that omits a list uses the protocol’s fixed defaults: idle, working, done, blocked, and error for states; permission, question, and auth for kinds.

An explicit empty list stays empty. Names from future revisions remain available to the application. Unknown reply fields are ignored.

Use ProgramStatusState::new(name) and ProgramStatusKind::new(name) to send extension names that the terminal advertises. Both types borrow the name. Their as_str() methods return the wire value for comparison with the accepted lists.

Standard constants and constructed names compare by their text. For example, ProgramStatusState::WORKING equals ProgramStatusState::new("working"). State and kind remain distinct types.

The writer accepts commas under the protocol’s value grammar. Query replies interpret commas as list separators, so discovery cannot represent a comma-containing name as one entry.

The writer validates custom names before output and enforces the complete 4096-byte sequence limit. Choose a supported standard name when the terminal does not advertise an extension.

For a query batch, set ProgramOptions::query_program_status to true in init_with, then call program.query_capabilities(&[])?. The option defaults to false. It selects the query; initialization sends no queries. The final Primary DA reply marks the end of the batch.

The option controls discovery only. Reports and reply observation work regardless of its value. For custom batches, the query bytes are available as uncurses::ansi::program_status::REQUEST_PROGRAM_STATUS.

Output without a session

uncurses::ansi::program_status::write_program_status writes to any std::io::Write. It uses the same report type and leaves flushing to the caller.

Run cargo run -p examples --example program_status for working, blocked, clear, and done reports. The example also prints a description of each report, so its output remains useful outside a supporting terminal. It also prints an escaped wire preview for the illustrative extension names paused and confirm, without sending those reports to the terminal.

Add -- --query to detect support through ProgramOptions first. This mode requires a terminal and waits up to 300 milliseconds for replies. It prints whether support was confirmed and the accepted state and kind names, then sends the same reports.

See the OSC 7501 specification for protocol details.