Repository navigation
v0.11.8
Changed
-
A
Graphics::Kittyterminal answers kitty'sa=qquery 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, orI=alone) or asks
for quiet (q=1suppresses anOK,q=2every reply). termlens used to
answer all of theseOK, withi=0when 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 thei=0;OKreply
should sendi=with a nonzero id, as kitty requires. -
A kitty
a=qquery naming both an image id and an image number is
refused as kitty refuses it (#672): the reply echoes itsi=andI=
(andp=, if it names one) with
EINVAL:Must not specify both image id and image number, and onlyq=2
silences it. It was answeredOK. A test that readOKthere 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.
Withoutm=1it is the last one, and the upload's image is counted and
placed before the query is answeredOK. Withm=1kitty 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 answeredEILSEQ. termlens used to leave the upload
alone and answer the queryOKon 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, andtitle()reported it whole. An ANSI mode
list that names insert mode with another mode (CSI 4;20 h) is listed,
asCSI 20;4 halready 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 pinsunsupported()exactly drops
any^[]0;…,^[]2;…or^[[?…Sentry from its expected list and adds
a mode list such as^[[4;20hwhere its application sends one. -
The urxvt mouse encoding (mode 1015) is honoured (#640). An application
that enables it gets xterm'sCSI 32;5;3Mform, 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_withsends the button alone, without the modifier bits, and
scrollandscroll_withreturnError::Input, since the mode reports
no wheel. A test that scrolled under X10 expects that error, or has its
application enable?1000instead. -
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
exceptiondocs/LIMITATIONS.mddocumented. 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 diffends with one newline, not a blank
line, and an unchanged-row count of one reads… 1 row unchanged
(#652).ScreenDiff'sDisplayalready ended its last line, and the CLI
printed a second newline after it; "no difference" and the coloured
output were not affected. The library'sDisplaysays "1 row" too. A
script that comparesdiff --color neveroutput 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, thesofrows. -
A multi-row needle no longer matches in cells a previous match used, in
Screen::find_allandmask_matching(#670). The scan resumed on the
row a match ended on and took any match starting there, so on four rows
readinga,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,axxxthe needle is still found twice. A test that
assertsfind_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 artifactname:
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'senv: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 saidTerminal::record(), which cannot fail,
and a?on it does not compile (#659). -
The README's decode and masking examples compile (#660):
decode()and
Regex::newfail 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=pplacement 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=orv=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, socapture_graphics(0)reportedsize() == Noneand 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, butdecode()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=pof an image a delete freed no longer moves the cursor
(#672). Afterd=Iof its id, ord=Rover a range that holds it, kitty
has forgotten the image and refuses the placement; termlens sized it from
the deleted image,c=andr=or not. A lower-case delete keeps the
image, as in kitty. The deletes termlens cannot attribute are listed in
docs/LIMITATIONS.md. -
Screen::parseandtermlens renderread two text snapshots earlier
releases wrote and 0.11.7 refused (#643): a visible cursor one past the
last column (cursor: 1,6on 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_textandmask_rectclamp 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
belowsplit. A range written backwards still panics. -
Screen::to_htmldims a run's text and no longer its background (#645).
The run's span carriedopacity:0.6, which faded a coloured background
into the page, whileto_svgfaded 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_asciicastplaces each frame's cursor (#646). An event
repainted the grid and wrote nothing for the cursor, soasciinema play
andaggdrew 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_allandmask_matchingwith 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 wherecontainstook
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 ascontainswhatever the needle's height. -
A
ScreenDiffbetween 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 --colorclips 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::MAXas "no deadline", per call or as the
builder'stimeout(#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
timeoutof 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 atarget/debug/appdownloaded 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_exitnow fails naming the cause ("…the
kernel reaped it, because SIGCHLD is ignored…") where it gave a bare "No
child processes", andsignalrefuses withError::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 nat 200x50 took 10 s to answer undercargo testand 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 JandCSI 3:1 Jkeep the history termlens had retained, as
CSI 3 Jdoes (#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). AfterESC c,focus_instill typed^[[I, DECRQM answered
Setfor 1004 and 2026, and an update begun before the reset never
ended, so every laterwait_stabletimed 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, soESC CRbefore line-drawing
text blanked the rest of the write, and the query inESC BEL [6nwent
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_wrappedturned false andlogical_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 inscrollback_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 renderandtermlens diffaccept--as the end of their
options, asinspectdoes (#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, sodiff
first waited for standard input to end, which at a terminal meant
Ctrl-D. -
termlens renderandtermlens diffrefuse 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, sorender --text /dev/zeroor
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.