Skip to content

v0.11.8

Choose a tag to compare

@github-actions github-actions released this 03 Nov 14:11
· 19 commits to main since this release
61f597e

Changed

  • A Graphics::Kitty terminal answers kitty's a=q query as kitty does
    (#639): ESC _Gi=<id>;OK ESC \ for a query naming an image id, and
    nothing for one that names none (i= absent or 0, or I= alone) or asks
    for quiet (q=1 suppresses an OK, q=2 every reply). termlens used to
    answer all of these OK, with i=0 when there was no id, which put bytes
    into the input of an application that had asked for none. A query without
    an id is now named in the next timeout instead; a quiet one is not, since
    the application asked for silence. A test that read the i=0;OK reply
    should send i= with a nonzero id, as kitty requires.

  • A kitty a=q query naming both an image id and an image number is
    refused as kitty refuses it (#672): the reply echoes its i= and I=
    (and p=, if it names one) with
    EINVAL:Must not specify both image id and image number, and only q=2
    silences it. It was answered OK. A test that read OK there should
    drop one of the two keys.

  • A kitty query with its data in the escape is part of the upload in
    progress, as in kitty (#672): its data is the upload's next chunk.
    Without m=1 it is the last one, and the upload's image is counted and
    placed before the query is answered OK. With m=1 kitty removes the
    image being loaded: no image comes of the upload, the query gets no
    reply (and is named in the next timeout), and the next chunk is refused —
    a query sent then is answered EILSEQ. termlens used to leave the upload
    alone and answer the query OK on its own. A test should finish an
    upload before it probes, as the protocol requires of the client.

  • unsupported() is right about three more shapes (#625, #641). A window
    title with a ; in it (OSC 2 ; make; test) is no longer listed: the
    title was always honoured, and title() reported it whole. An ANSI mode
    list that names insert mode with another mode (CSI 4;20 h) is listed,
    as CSI 20;4 h already was, since LNM is dropped either way.
    XTSMGRAPHICS (CSI ? Pi ; Pa ; Pv S) is no longer listed; it is a
    question now, named in the next timeout as LIMITATIONS promises for a
    program blocked on one. A test that pins unsupported() exactly drops
    any ^[]0;…, ^[]2;… or ^[[?…S entry from its expected list and adds
    a mode list such as ^[[4;20h where its application sends one.

  • The urxvt mouse encoding (mode 1015) is honoured (#640). An application
    that enables it gets xterm's CSI 32;5;3M form, decimal and with no 222
    limit, where it got the legacy bytes; DECRQM answers for 1015; and the
    last of 1005, 1006 and 1015 set is the one in force, as in xterm, so
    resetting the one in force returns to the legacy form. The error for a
    click past column 222 no longer says the application selected the legacy
    encoding; it names the extended ones that would reach that far. A test
    that read legacy reports from an application enabling 1015 reads the
    urxvt form instead.

  • X10 mouse tracking (CSI ? 9 h) reports what xterm reports (#642).
    click_with sends the button alone, without the modifier bits, and
    scroll and scroll_with return Error::Input, since the mode reports
    no wheel. A test that scrolled under X10 expects that error, or has its
    application enable ?1000 instead.

  • Insert mode (CSI 4 h) reserves room a character at a time, at the
    cursor, so the screen no longer depends on where the PTY split the output
    (#624). A run that reached the right margin wrapped onto the next row over
    its text when it arrived in one read, and pushed that row right from its
    second character when it arrived in pieces; now the next row is pushed
    right from the first character, as xterm does, which removes the
    exception docs/LIMITATIONS.md documented. An emoji-presentation
    selector that arrives in a read of its own reserves the column it widens
    the emoji into, where it used to overwrite the next character. A test
    that asserted the overwritten row should assert the pushed one.

  • The plain output of termlens diff ends with one newline, not a blank
    line, and an unchanged-row count of one reads … 1 row unchanged
    (#652). ScreenDiff's Display already ended its last line, and the CLI
    printed a second newline after it; "no difference" and the coloured
    output were not affected. The library's Display says "1 row" too. A
    script that compares diff --color never output byte for byte against a
    file saved from 0.11.7 or earlier should drop that file's final empty
    line and, where the count is one, the s of rows.

  • A multi-row needle no longer matches in cells a previous match used, in
    Screen::find_all and mask_matching (#670). The scan resumed on the
    row a match ended on and took any match starting there, so on four rows
    reading a, find_all("a\na") was three matches, each sharing a cell
    with the next, and the mask covered all of them, where the rustdoc said
    matches do not overlap. A match may still start on that row to the right
    of the last cell the previous one used, counted in grid columns, so on
    xxxa, axxxa, axxx the needle is still found twice. A test that
    asserts find_all(…).len() for such a needle expects the
    non-overlapping count, [(0, 0), (2, 0)] above rather than three, and a
    snapshot of such a mask shows the rows no match now covers unmasked.

Fixed

  • The report action writes each screen into the step summary verbatim
    (#648). A screen with a row of three backticks closed the action's code
    block early, the rows after it rendered as Markdown, and every later
    entry came out inside out; the fence is now longer than any run of
    backticks in the text. File names are HTML-escaped in the summary, and a
    name holding a newline is one entry rather than two that name no file.
    Characters upload-artifact refuses in a path are replaced in the
    artifact's file names, so one odd name no longer fails the upload.

  • The CI snippet in the README, the action's README and the skill works in
    a matrix and with any directory (#654). It passes the artifact name:
    per matrix leg, since upload-artifact refuses a second artifact of one
    name in a run and only one failing leg's screens were kept, and passes
    artifact-dir:, since the test step's env: never reaches the action:
    the default worked only while the directory was $RUNNER_TEMP/termlens.

  • The skill says record().stop() refuses an application without DEC 2026
    synchronized updates; it said Terminal::record(), which cannot fail,
    and a ? on it does not compile (#659).

  • The README's decode and masking examples compile (#660): decode() and
    Regex::new fail with errors of their own that ? cannot turn into a
    termlens::Error. README's Rust blocks are now compiled in CI as the
    skill's are.

  • A GitHub Release's notes carry the link definitions their section uses
    (#658). They sit at the foot of CHANGELOG.md, and the v0.11.0 notes
    showed [`Unsupported`] in brackets, linked to nothing, as v0.11.1's
    did [Keep a Changelog].

  • A label quoted inside a code span or a fenced block no longer counts as a
    use, so release notes no longer end with definitions nothing in them links
    to (#674). Quoting the two above in this file was enough to append both
    to these notes, and the v0.11.1 notes gained today's [Unreleased]
    compare link. A link whose text is code, as in 0.11.0, still counts.

  • A kitty a=p placement takes its size from the newest transmission of
    its image id (#626). When that one declared no size, a PNG for instance,
    the size of an older transmission of the same id moved the cursor, though
    kitty replaces an image on retransmission.

  • A kitty placement with a huge c=, r=, s= or v= no longer panics
    the emulator in a debug build, or moves the cursor by a wrapped amount in
    a release one (#634). The cells are computed in 64 bits and saturate.

  • A sixel past the capture bound keeps its declared size, and the cursor
    still moves past it by its full height (#635). Both were read off the
    retained data, so capture_graphics(0) reported size() == None and left
    the cursor where the image started. They are now measured as the stream
    arrives, at no extra memory.

  • A kitty placement (a=p), animation (a=a) or compose (a=c) that
    arrives during a chunked upload is a payload of its own, and the upload
    goes on to its last chunk (#636). It used to be swallowed as the upload's
    next chunk, so the placement was lost and neither image decoded. A
    transmission from a file or shared memory (t=f, t=t, t=s) arriving
    mid-upload starts an image of its own, as kitty does.

  • Kitty data whose chunks each end in base64 padding decodes (#637). kitty
    decodes every escape's payload on its own and the protocol allows a padded
    chunk, but decode() read the joined body once and called it bad base64.

  • A sixel that paints past its declared raster size decodes to everything it
    painted (#638), as xterm draws it: the larger of the declared and the
    painted extent on each axis, still within the 4096-pixel cap. It was
    cropped to the declared size.

  • A kitty a=p of an image a delete freed no longer moves the cursor
    (#672). After d=I of its id, or d=R over a range that holds it, kitty
    has forgotten the image and refuses the placement; termlens sized it from
    the deleted image, c= and r= or not. A lower-case delete keeps the
    image, as in kitty. The deletes termlens cannot attribute are listed in
    docs/LIMITATIONS.md.

  • Screen::parse and termlens render read two text snapshots earlier
    releases wrote and 0.11.7 refused (#643): a visible cursor one past the
    last column (cursor: 1,6 on a 6-column screen), which releases up to
    0.11.1 wrote after a write filled the last cell, and a wide character a
    narrowing resize stranded in the last column with no second half, which
    releases up to 0.11.6 wrote. The cursor reads onto the last cell, as the
    JSON reader already read it, and the stranded character reads as that
    cell, so the JSON twin's own text also parses again. Both files, written
    by the published releases, are in the compatibility corpus.

  • Screen::rect_text and mask_rect clamp an open-ended range that starts
    past the edge, as they clamp an explicit one (#644). rect_text(12.., ..)
    on a 10-column screen panicked with "column range starts at 12 but ends
    at 10", a range the caller never wrote, so a pane read such as
    rect_text(split.., ..) panicked as soon as the terminal was narrowed
    below split. A range written backwards still panics.

  • Screen::to_html dims a run's text and no longer its background (#645).
    The run's span carried opacity:0.6, which faded a coloured background
    into the page, while to_svg faded only the text over a full-strength
    <rect>. The span now carries the foreground blended 60% over the
    background, the colour the SVG's text composites to, so the two
    renderings paint the same picture.

  • Recording::to_asciicast places each frame's cursor (#646). An event
    repainted the grid and wrote nothing for the cursor, so asciinema play
    and agg drew a visible cursor after the last row of every frame, even
    for a program that had hidden it. Each event now ends by moving the
    cursor to where the test saw it and showing or hiding it as it was.

  • Screen::find, find_all and mask_matching with a needle that spans
    rows no longer slow down with the needle's height (#647). Every row was
    rebuilt for each candidate start row it was compared from, so on a
    1000x1000 screen a 400-row needle took 565 ms where contains took
    5 ms. Each row is now built once per call and the whole rows in the
    middle of the needle are matched in one pass; the same search takes as
    long as contains whatever the needle's height.

  • A ScreenDiff between screens of different sizes prints each row clipped
    to the overlap it compares (#649). The wider screen's rows were printed
    whole under the note saying the rest is clipped, so the │ separators
    left the marker line's columns and the after side's ^ sat under the
    wrong cells. termlens diff --color clips the same way and no longer
    leaves painted blanks at the end of the after side. A wide character cut
    by the overlap's edge prints as one blank, and its cell is still marked.

  • Every wait takes Duration::MAX as "no deadline", per call or as the
    builder's timeout (#629). Each one added the timeout to the current
    instant and panicked with "overflow when adding duration to instant"; the
    deadline is now the furthest instant the clock can represent.

  • A send under a builder timeout of zero, or of a few microseconds, no
    longer fails while its bytes are delivered (#632). It waited for the
    writer thread's acknowledgement for exactly the deadline, which expired
    before that thread had run, and the error then said the application was
    not reading and the buffer was full. A send now waits at least a tenth of
    a second, and the error claims a full buffer only when a write into it is
    seen blocked; it reads "a write into it was still blocked after …" where
    it read "no progress in …".

  • spawn("sub/prog") of a file in the working directory that cannot be
    executed, or is not there, fails with the reason the OS gave for that
    file, "cannot execute …/sub/prog: Permission denied (os error 13)" or
    "No such file or directory (os error 2)" (#631). It reported a PATH
    search, "No viable candidates found in PATH", the one place the file was
    never meant to be; the usual case is a target/debug/app downloaded as
    an artifact without its executable bit. A program a PATH entry provides
    under the same name is still found.

  • In a test process that ignores SIGCHLD, a terminal drops at once instead
    of taking two seconds, and nothing is signalled after the child has gone
    (#630). The kernel reaps every child itself there, and teardown read the
    child it had already reaped as one still running: it sent SIGHUP and
    SIGKILL to a pid that could belong to another process by then, and waited
    out its whole reap grace. wait_exit now fails naming the cause ("…the
    kernel reaped it, because SIGCHLD is ignored…") where it gave a bare "No
    child processes", and signal refuses with Error::Input, as it does for
    a child already waited for.

  • Dropping a terminal returns when the application left more input unread
    than its terminal holds, which a raw-mode application that stops reading
    allows (#628). The PTY layer writes an end-of-file as its writer is
    dropped, and that write waited forever on the full queue, with the child
    already gone, while holding the lock every spawn in the test binary takes:
    the binary never exited. Teardown now makes the terminal's writes give up
    rather than wait and empties the queue nothing will read, so the writer
    thread stuck behind it ends too, instead of keeping the PTY open.

  • A spawn that fails closes the terminal it opened before it releases the
    PTY lifecycle lock (#633). It released the lock first, so on macOS the
    close could revoke a device another thread had just opened, the failure
    the lock exists to prevent; and a spawn that failed after opening the
    terminal but before starting the child could deadlock on the lock it
    held, instead of reporting why.

  • A cursor-position or size query is answered without building a snapshot
    (#618). Each reply copied the whole grid, history and graphics log to
    read two numbers, and 0.11.7 doubled that in debug builds, so 2000
    CSI 6 n at 200x50 took 10 s to answer under cargo test and the
    replies left in bursts several seconds apart; a reader with an
    inter-byte timeout, like termlens-demo's, saw them stop. The same 2000
    now take about 20 ms, and the reply is the one the snapshot gave.

  • CSI ? 3 J and CSI 3:1 J keep the history termlens had retained, as
    CSI 3 J does (#621). vt100 erases its saved lines for both, and the
    tracker missed them, so the emptied history read as rows pulled back
    onto the screen and the kept copy was popped.

  • RIS turns focus reporting off and ends an open synchronized update
    (#623). After ESC c, focus_in still typed ^[[I, DECRQM answered
    Set for 1004 and 2026, and an update begun before the reset never
    ended, so every later wait_stable timed out. The half-drawn frame is
    dropped.

  • A control character inside an escape sequence is carried out and the
    sequence goes on, as the parser that feeds the grid does (#627). The
    tracker dropped to plain text there, so ESC CR before line-drawing
    text blanked the rest of the write, and the query in ESC BEL [6n went
    unanswered with its bell uncounted. The same holds inside a CSI.

  • An emoji with a presentation selector (⚠️, ❤️) drawn into the
    bottom-right cell no longer panics the emulator when the row is redrawn
    (#619). vt100 0.18.1 moved the wrong cell to the new bottom row and left
    the emoji wide in the last column of the row above, where the next write
    over it panicked on the reader thread and the screen stopped advancing.
    termlens now moves the emoji itself, to the start of the new row, as
    xterm does.

  • A resize round trip over a soft-wrapped line no longer makes the attribute
    shadow diverge from the grid (#620). Copying the rows a wider, shorter
    screen pushed into history widened them and cleared their wrap flags, in
    the primary grid only unless styled history was on, so the resize back
    pulled different rows into the two and a debug build panicked on the next
    snapshot; with both moved, row_wrapped turned false and logical_text()
    split the line.

  • A history at its retention bound is copied a row at a time, as below it,
    rather than read again whole on every output. vt100's length stops
    changing at the bound, so termlens re-read the window, and that read cut
    every retained row to the screen's width at the moment, losing the text
    past it after a narrower resize, and cleared the wrap flag of a row a
    wider resize had left narrow. The rows vt100 drops off the front are
    counted instead. The count also settles a resize made while the
    alternate screen is up: a shorter screen that pushed a row, then a
    taller one that pulled it back, dropped the newest retained row from
    scrollback_text() although vt100 still held it (#671).

  • Rows that scroll off in the same read that switches to the alternate
    screen (CSI ? 1049 h, ? 47 h, ? 1047 h) are in scrollback_text()
    while the alternate screen is up, and survive a reset before it comes
    down (#622). They were copied only once the main screen was back, and a
    RIS before that lost them for good.

  • termlens render and termlens diff accept -- as the end of their
    options, as inspect does (#650), so a file whose name begins with -
    can be named: termlens render --text -- -x.snap. A - after -- is
    still standard input.

  • termlens diff missing.snap - reports the missing file at once (#651).
    Both operands were read before either result was looked at, so diff
    first waited for standard input to end, which at a terminal meant
    Ctrl-D.

  • termlens render and termlens diff refuse an input larger than 1 GiB
    with exit code 2 and a message saying so (#653). They read a whole file
    or stream into memory with no bound, so render --text /dev/zero or
    yes | termlens render --text - grew until the allocator or the OOM
    killer stopped them. The largest screen termlens writes — 1000x1000
    cells, each styled differently, as the pretty-printed JSON — is about
    600 MB, so every saved screen an earlier release wrote still reads. A
    regular file past the bound is refused before it is read.