Skip to content

v0.9.0

Latest

Choose a tag to compare

@github-actions github-actions released this 05 Sep 01:51
· 7 commits to main since this release
8ca2494

Added

  • Error::Emulator: an emulator panic is a diagnosis, not a timeout. The
    emulation runs on the reader thread, so a panic there propagated nowhere —
    the drain died, the screen froze, and every wait burned its full deadline
    reporting a predicate that could never come true. The reader now catches it,
    records it, and keeps draining (a stalled drain blocks the child writing
    into a full buffer); wait_until, wait_frame and wait_idle fail at once
    with the emulator's own message and the last screen taken before the
    failure. The emulator is never asked for a screen again — after a panic its
    state means nothing. wait_exit is deliberately unaffected: the child's
    exit status is still true. (#211)

  • snapshot_after and wait_stable: the whole-screen snapshot as one
    call, and a settle that output changing nothing cannot hold up.

    snapshot_after(pred) waits for the predicate, then for the picture to
    hold still for 100ms, and returns that screen — DESIGN §2's three rules
    for race-free waits without having to remember them. wait_stable(quiet)
    is the settle on its own, and differs from wait_idle in what resets the
    clock: changes rather than bytes, so a bell, a cell rewritten with the
    glyph already in it or an answered query — output wait_idle can never
    see silence through — is invisible to it. Both have _for twins, refuse
    to settle inside an open synchronized update, count stillness that
    predates the call, and return the screen they settled on.

  • termlens::bin!("myapp") spawns one of your package's binaries under
    the harness defaults.
    Every integration test of a binary opened with the
    same five lines — a fixed 80x24 grid, env_clear(), a five-second
    deadline, spawn(env!("CARGO_BIN_EXE_myapp")) — so the chain has a name.
    Builder calls follow the name and override any default:
    termlens::bin!("myapp", size(120, 40), env("NO_COLOR", "1"))?. A
    misspelled binary is a compile error naming the variable, not a spawn
    failure at run time.

  • Screen::mouse_modes reports every mouse tracking mode the application
    enabled, and DECRQM answers each one on its own evidence.
    The backend
    collapses ?9/?1000/?1002/?1003 into the one protocol a terminal
    reports in — right for the input path, and unchanged there — so it could
    not say which members of the group an application asked for: crossterm's
    EnableMouseCapture sends three at once and only the last survived, a
    regression from any-motion to button-motion tracking (losing hover) was
    invisible, and a DECRQM probe for any member but the last had to be
    answered "not recognized". The sequence tracker now keeps the requested
    set; mouse_mode() still reports the protocol. (#151)

  • The fresh-install check verifies a --no-default-features consumer as
    well as a decode one.
    install.yml is the only job that builds
    termlens from outside this workspace, and it did so in one shape — the
    one the fewest real consumers use: of the three in-house ones, two declare
    default-features = false. Its matrix now runs both shapes on Ubuntu and
    macOS, the registry check demands the decode feature only on the leg
    that asks for it, and the no-defaults leg fails if the consumer's tree
    still resolves insta. (#238)

  • The inspect example answers --help, and takes its deadline and
    silence window from flags.
    inspect --help used to look for a program
    called --help, and both timings were hardcoded, so an application slower
    than five seconds to paint its first screen could not be inspected at all.
    --timeout SECONDS (default 5) and --idle MILLIS (default 300) now sit
    beside --size; --help/-h print one usage text to stdout and exit 0,
    a missing program prints the same text to stderr and exits 1, and
    --version names the termlens version the example was built from. An
    unknown option is refused rather than spawned. (#229, #236)

Changed

  • The smallest terminal is 2x2, not 1x1. One column panics the emulator on
    a double-width character, and one row panics it on a line that wraps — on
    the reader thread, in both profiles, where the panic propagates nowhere: the
    grid froze, every later wait ran to its deadline against a plausible-looking
    screen, and cargo test printed test result: ok over a suite that had
    stopped testing anything. 80x1 is an ordinary shape, not an exotic one.
    spawn and resize now refuse a dimension below 2 with Error::Size, the
    way they already refused 0 — #49's "no path can reach the emulator with a
    zero" was satisfied exactly one value too low. 2x8 and 2x2 render both
    trigger shapes correctly, so the floor is the smallest guard that closes
    them. (#211)

  • A child starts in the test process's working directory, not $HOME.
    Without current_dir the PTY layer fell back to the home directory, so a
    relative spawn() path and a directory-sensitive program behaved unlike
    every other Rust process API — while current_dir's rustdoc promised the
    test runner's directory all along. The default is now what
    std::process::Command does; current_dir still overrides it. A test that
    relied on the old fallback should say .current_dir(std::env::home_dir())
    explicitly. (#215)

Fixed

  • G2/G3 designation and SS2/SS3 single shifts are modelled, so a
    one-character line-drawing shift draws rather than l.
    ESC * 0
    / ESC + 0 designate the DEC Special Graphics set into G2/G3, and
    ESC N (SS2) / ESC O (SS3) invoke that set for exactly one character
    before the locking shift resumes. The designation was already consumed;
    the shift did nothing, so a mixed line of text and box-drawing showed
    the letter. A pending single shift is consumed by the next character —
    including a multi-byte UTF-8 one — and does not survive RIS. Locking
    shifts remain G0/G1 only (SO/SI); LS2/LS3 and DECSC/DECRC of
    charset state are still unmodelled. (#235)

  • DECSC/DECRC save and restore the character-set state. Save,
    jump, draw the frame, restore is how a full-screen application draws a
    border, and the restore lost the designation, so the border after it
    rendered as lqk — the failure #204 fixed, arriving through a different
    door. ESC 7 now saves G0–G3 and the locking shift alongside the cursor
    the backend already saved, ESC 8 restores them, a restore with nothing
    saved returns to ASCII as xterm does, and RIS clears the slot so a
    restore cannot resurrect a designation from before the reset. (#232)

  • DECSTR (soft reset, CSI ! p) is modelled. The polite reset a
    well-behaved TUI sends on startup and teardown parsed cleanly and did
    nothing, so text printed after it kept rendering in the character set the
    application had told the terminal to forget, while RIS got this right.
    It now returns the character sets and the DECSC slot to power-on, the
    cursor shape to the terminal's default, and turns off cursor-key mode,
    bracketed paste, every mouse tracking mode and encoding, and focus
    reporting; the cursor becomes visible and the alternate screen is left
    alone, as specified. Attributes, margins, origin and insert modes and the
    keypad are not replayed — nothing on Screen observes them — and the
    README says so. (#233)

  • The UK character set is translated: ESC ( A then # draws £.
    The designation was parsed and then rendered as ASCII, so an application
    printing a price in the UK set showed #42 on the grid, a test asserting
    £42 failed against a correct application, and a snapshot that blessed
    #42 kept passing. The set differs from ASCII in that one position, so
    that is the one byte translated; the alternate-ROM sets and the other
    national sets still read as ASCII, and the docs now say which sets are
    translated. (#234)

  • find no longer matches the blank padding past the end of a row. Its
    single-row path searched the row padded out to the terminal width while
    contains searched the trimmed text, so find("Total: ") was Some on
    a screen whose row read Total: — against the invariant both rustdocs
    state, that a needle is found precisely when contains is true. Both now
    trim trailing whitespace per row first; the trim treats a drawn trailing
    U+00A0/U+3000 as padding too, and both rustdocs say so. (#212)

  • env_clear() no longer leaks the machine's login shell. The PTY layer
    fills SHELL from the host when the variable is absent, so a child's
    supposedly hermetic environment differed between two machines with
    different shells. SHELL=/bin/sh is now pinned under env_clear the way
    TERM is, an explicit .env("SHELL", …) still wins, and a test asserts
    the whole environment rather than probing one name. (#221)

  • A bare program name under env_clear() is refused with the remedies.
    Clearing the environment removes PATH, so spawn("sh") could not
    resolve and failed with the PTY layer's "Unable to resolve the PATH",
    which named neither the cause nor a way out. spawn now refuses it up
    front with an Error::Spawn that says env_clear removed PATH and
    offers both fixes: an absolute path, or .env("PATH", …). (#222)

  • A byte that is not UTF-8 shows as U+FFFD instead of vanishing. The
    backend drops both a byte it cannot decode and the U+FFFD its own parser
    substitutes for one, so a Latin-1 é in a file name left no trace on the
    grid and every column after it shifted left — a test asserting on the
    column of what followed passed against the wrong screen. Bytes are now
    decoded once on the reader thread: an invalid sequence becomes the
    replacement character a terminal shows (carried through the backend as a
    noncharacter it will draw, and restored in the snapshot), and a character
    split across two reads is carried rather than replaced. wait_idle treats
    a stream that stopped mid-character as not yet idle. (#217)

  • A highlight over CJK or emoji is one span again. A wide character's
    continuation column carried no style, so with_styles() rendered a bar
    over ab汉cd as two spans with a hole and cell(row, col).style() on
    the second column reported Default — DESIGN §3 rule 5 and find_by's
    rustdoc both promised the opposite. The snapshot now gives the
    continuation the leading cell's style, as a terminal paints it. (#218)