Adopt the new blob lease layer to storing and referencing
image files.
This reduces the number of open files needed when
images are being displayed in the terminal.
refs: https://github.com/wez/wezterm/issues/3263
Input data:
\e_Ga=T,f=32,s=10,v=22,c=1,r=1,m=1\e\\e_Gm=1;/xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T//P=\e\\e_Gm=1;/xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T////E////xP///8T//P=\e\\e_Gm=0\e\
There were two issues in handling this:
* We expected there to be `;payload` in the first transmission packet,
but there wasn't one, so we ignored it as ill-formed.
* The standard base64 decoder in the rust ecosystem is super strict
and rejects the "sloppy" python base64 encoder output that isn't
strictly conformant with the RFC. We need to jump through some
hoops to get it to relax and accept the input.
refs: https://github.com/wez/wezterm/issues/2716
Continuing from the previous commit, this shifts:
* In-memory data -> temporary file
* Image decoding -> background thread
The background thread asynchronously decodes frames and
sends them to the render thread via a bounded channel.
While decoding frames, it writes them, uncompressed, to
a scratch file so that when the animation loops, it is
a very cheap operation to rewind and pull that data
from the file, without having to burn CPU to re-decode
the data from the start.
Memory usage is bounded to 4 uncompressed frames while
decoding, then 3 uncompressed frames (triple buffered)
while looping over the rest.
However, disk usage is N uncompressed frames.
refs: https://github.com/wez/wezterm/issues/3263
This makes decoding animation frames a lazy operation, but it
comes at the cost of needing to re-decode the image from scratch
when it loops, because the image crate doesn't provide a way
to rewind its frame iterator.
That initial decode can have a significant time cost; a small
webp file consistently takes 150ms to decode, which is too
much to do inline in the render thread.
Next steps will be to move that cost off the render thread.
Rust 1.67, released today, is a bit more particular about the layout
of the memory used in TeenyString and segfaults in the test suite.
On closer inspection, our Drop impl was casting to Vec<u8> instead of
TeenyStringHeap and the fault was freeing bogus memory within that
region.
Fixing the cast to the correct type resolves the issue.
When a line is rapidly updated with only some of the cells being
actually changed (eg: progress counter or other status being frequently
updated), it is desirable to avoid paying the cost of shaping the entire
line.
When bidi is not enabled we can assume that it is safe to break clusters
on whitespace boundaries. Doing so allows each of those whitespace
separated words to be shaped and potentially cached independently,
which reduces the amount of CPU time spent for the whole line.
This commit just adjusts the clustering, which reduces the CPU
utilization a bit.
refs: https://github.com/wez/wezterm/issues/2701
I spent a few hours in heap profilers. What I found was:
* Inefficient use of heap when building up runs of
`Action::Print(char)`.
-> Solve by adding `Action::PrintString(String)`
and accumulating utf8 bytes rather than u32 codepoints.
* Inefficient use of heap when building Quad buffers: the default
exponential growth of `Vec` tended to waste 40%-75% of the allocated
capacity, and since we could keep ~1024 of these in cache, there's
a lot of potential for waste.
-> Solve by bounding the growth to 64 at a time. This has similar
characteristics to exponential growth at the default 80x24 terminal
size. May need to add a config option for this step size for users
with very large terminals.
* Lazy eviction from the LFU caches. The underlying cache advisor is
somewhat probabilistic and has a minimum cache size of 256, making
it difficult to maintain low heap utilization.
-> Solve by replacing it with a very simple LFU algorithm. It doesn't
seem to hurt much at the default terminal size with the default
cache sizes. If we make the cache sizes smaller, its overhead is
reduced.
Some further experimentation is needed to adjust defaults, but this
should help reduce heap usage.
refs: https://github.com/wez/wezterm/issues/2626
I noticed from https://github.com/void-linux/void-packages/pull/36903
that 32-bit systems were failing to pass the test suite.
Running `cargo test --target i686-unknown-linux-gnu -- --nocapture
teeny_string` showed that we were faulting in the teenystring code
and digging a bit deeper showed that it was because our assumptions
about the high bits were wrong for 32-bit systems.
Fix this by making TeenyString based around a u64 rather than usize
so that we guarantee its size on all current systems.
We weren't including the invisible space cells into the model
as part of building up the logical line, resulting in the logical
line being shorter than it should have been.
That resulted in some of the components of the double wide cells
not being rendered in the correct place.
refs: https://github.com/wez/wezterm/issues/2571
refs: https://github.com/wez/wezterm/issues/2568
When in raw mode, go to level 2, but use level 1 for cooked
so that we don't obfuscate ctrl-c.
A consequnce of this is that CTRL-C is now reported to the app
as CTRL-lowercase-c where we previously reported as CTRL-uppercase-C.
Made a note of the breaking nature of this change in a new changelog
file.
Fixed recognizing SHIFT-TAB
refs: https://github.com/wez/wezterm/issues/2511
With a TERM of "screen-256color" or "screen", common attributes such
as bold are rendered with a \x0f AKA ^O AKA \017 code that
makes pagers such as less and streampager grumpy. In particular,
streampager renders as inverted "<0F>" and less as inverted "^O".
Add a new flag force_terminfo_render_to_use_ansi_sgr which makes the
TerminfoRenderer ignore the terminfo entries for common attributes,
instead using the standard ANSI/ECMA-48 sequences.
I've expanded the number of bits from 16->32 without impacting
the overall struct sizes and reserved 2 bits for super/subscript.
I refer to these as vertical alignment properties for conceptual
consistency with css.
SGR 73, 74, 75 are used to set super, sub and normal vertical alignment.
These are compatible with mintty.
However, mintty just added support for setting both attributes to render in
small caps in 06ac446049
(https://github.com/mintty/mintty/issues/1171)
According to its benchmarks, it's almost 2x faster than
unicode_segmentation. It doesn't appear to make a visible
difference to `time cat bigfile`, but I'll take anything
that gives more headroom for such little effort of switching.
Action is used to encode parsed terminal output and shuttle it between
the thread that does the parsing and the main gui thread that applies
it to the terminal model.
Take it down from 184 bytes to 40 bytes (on 64-bit systems). This seems
to boost `time cat bigfile` by reducing the runtime to ~40% of its prior
duration: down from 8s -> 4.5s on an M1 macbook air.
Size reductions achieved by Box'ing relatively less frequently
used enum variants. The kitty image data variant is particularly
large, and the Window variant is also pretty heavy.
There were two problems:
* We weren't correctly invalidating when the hover state changed
(a recent regression caused by recent caching changes)
* We'd underline every link with the same destination on hover,
not just the one under the mouse (longstanding wart)
Recent changes allow the application layer to reference the underlying
Lines directly, so we can restore the original and expected
only-highlight-under-the-mouse by switching to those newer APIs.
Adjust the cache values so that we know to also verify the current
highlight and invalidate.
I was a little surprised to see that this also works with mux client
panes: I was expecting to need to do some follow up on those because
they return copies of Line rather than references to them. That happens
to work because the mux client updates the hyperlinks at the time where
it inserts into its cache. The effect of that is that lines in mux
client panes won't update to new hyperlink rules if they were received
prior to a change in the config.
refs: https://github.com/wez/wezterm/issues/2496
Given this sequence:
ENABLE-UNDERLINE CRLF SGR-RESET
if the CRLF caused the terminal to scroll, the newly created line
at the bottom would be filled in with a "blank" cell that had
the underline attribute set.
That's because we're supposed to preserve the coloring in that
scenario, but we were also preserving other SGR attributes.
This commit explicitly clears out under, over and strikethrough
lines from these blank attributes.
refs: https://github.com/wez/wezterm/issues/2489
Move is_double_wide to a box; it is relatively rare to need
this and we're okay with it being a separate heap allocation
when it is needed if it reduces the size of Line in the common case.
I don't think these are really necessary any more; the implementation
cannot go out of bounds, so the worst that can happen is that we
don't return any changes.
refs: https://github.com/wez/wezterm/issues/2222
Adds Pane::for_each_logical_line_in_stable_range_mut and
Pane::with_lines_mut which allow iterating mutably over lines.
The idea is that this will allow the renderer to directly cache
data in the Line via its appdata without having to build cumbersome
external caching logic and managing cache keys.
This commit just swaps the implementation around for localpane
and sanity checks that the renderer functions.
Various overlays and the mux client don't properly implement these
yet and current warn at compile time and panic at runtime.
To follow is the logic to cache the data and make sure that it
works the way that I think before converting the other Pane
implementations.
It didn't really belong there; it was added as a bit of a hack
to propagate screen reverse video mode.
Move that to the RenderableDims struct and remove the related
bits from Line
Introduces a heap-based quad allocator that we cache on a per-line
basis, so if a line is unchanged we simply need to copy the previously
computed set of quads for it into the gpu quad buffer.
The results are encouraging wrt. constructing those quads; the
`quad_buffer_apply` is the cost of the copy operation, compare with
`render_screen_line_opengl` which is the cost of computing the quads;
it's 300x better at the p50 and >100x better at p95 for a full-screen
updating program:
full 2880x1800 screen top:
```
STAT p50 p75 p95
Key(quad_buffer_apply) 2.26µs 5.22µs 9.60µs
Key(render_screen_line_opengl) 610.30µs 905.22µs 1.33ms
Key(gui.paint.opengl) 35.39ms 37.75ms 45.88ms
```
However, the extra buffering does increase the latency of
`gui.paint.opengl` (the overall cost of painting a frame); contrast the
above with the latency in the same scenario with the current `main`
(rather than this branch):
```
Key(gui.paint.opengl) 19.14ms 21.10ms 28.18ms
```
Note that for an idle screen this latency is ~1.5ms but that is also true
of `main`.
While the overall latency in the histogram isn't a slam dunk,
running `time cat bigfile` is ~10% faster on my mac.
I'm sure there's something that can be shaved off to get a more
convincing win.
Allows the following assignment actions; I was just over-using z for
no real reason, I'm not suggesting that these are good assignments.
```
-- move the cursor backwards to the start of the current zone, or
-- to the prior zone if already at the start
{ key = 'z', mods = 'NONE', action = act.CopyMode 'MoveBackwardSemanticZone' },
-- move the cursor forwards to the start of the next zone
{ key = 'Z', mods = 'NONE', action = act.CopyMode 'MoveForwardSemanticZone' },
-- start selecting by zone: both the start point and the cursor
-- position will be expanded to the containing zone and the union
-- of those two will be used for the selection
{
key = 'z',
mods = 'CTRL',
action = act.CopyMode { SetSelectionMode = 'SemanticZone' },
},
-- like MoveBackwardSemanticZone by only considers zones of the
-- specified type
{ key = 'z', mods = 'ALT', action = act.CopyMode { MoveBackwardZoneOfType ='Output' }},
-- like MoveForwardSemanticZone by only considers zones of the
-- specified type
{ key = 'Z', mods = 'ALT', action = act.CopyMode { MoveForwardZoneOfType ='Output' }},
```
refs: https://github.com/wez/wezterm/issues/2346
Without the feature the build fails with:
--> src\escape\apc.rs:377:57
|
377 | let mut memory_info = MEMORY_BASIC_INFORMATION::default();
| ^^^^^^^ function or associated item not found in `MEMORY_BASIC_INFORMATION`
|
When `parse_first_as_vec` is parsing an OSC sequence (e.g.
`SetHyperlink`) that is terminated by the escaped form of ST (`ESC \`),
ensure that the ST sequence is included in the returned vector.
This is achieved by ensuring the VTParser has returned to the "ground"
state: i.e. the stored state after the `ESC` is processed is not enough
for `parse_first_as_vec` to terminate. We must also parse the `\` to
ensure that we return a complete span to the caller.
Fixes https://github.com/markbt/streampager/issues/57
`4` is actually defined as CMYK according to ITU-T Rec. T.416:
> A parameter substring for values 38 or 48 may be divided by one or more separators (03/10) into parameter elements,
> denoted as Pe. The format of such a parameter sub-string is indicated as:
>
> Pe : P ...
>
> Each parameter element consists of zero, one or more bit combinations from 03/00 to 03/09, representing the digits
> 0 to 9. An empty parameter element represents a default value for this parameter element. Empty parameter elements at
> the end of the parameter substring need not be included.
>
> The first parameter element indicates a choice between:
>
> 0 implementation defined (only applicable for the character foreground colour)
> 1 transparent;
> 2 direct colour in RGB space;
> 3 direct colour in CMY space;
> 4 direct colour in CMYK space;
> 5 indexed colour.
refs: 6e9a22e199 (commitcomment-79669016)
This commit extends the sgr color parser to support a wezterm
extension that I just made up:
```
printf "\e[48:4:255:0:0:60mhello\e0m"
```
The `4` is wezterm specific and denotes 4 channel color, in this case
RGBA. red is 255, green is 0, blue is 0 and alpha is 60; the values are
interpreted as u8 values.
CSI 38 (fg), 48 (bg) and 58 (underline) support this.
refs: https://github.com/wez/wezterm/issues/2313
This doesn't really change any behavior, but adjusts the types
such that CSIs that set colors have the potential to track the
alpha channel and that can make it through to the GUI/render layer.
This reduces the resident memory by another ~10% because it avoids
keeping as many runs of whitespace.
Runtime for `time cat enwiki8.wiki` is still ~11-12s, resident: 530K
refs: https://github.com/wez/wezterm/issues/1626
The previous commit added the option to convert the storage to
the cluster format. That saves memory as rows are moved to scrollback,
but makes scrolling back more expensive due to that conversion.
This commit adds a fast(ish) path for the common case of simply
appending text to a new line before it gets scrolled: the default
format for lines in the screen is now the cluster format and,
provided that the cursor moves from left to right as text is
emitted, can simply append to the cluster storage in-place
and avoids a conversion when the line is moved to scrollback.
This improves the throughput of `time cat enwiki8.wiki` so
that the runtime is typically around 11-12s (compared to 9-10s
before introducing cluster storage). However, this is often
a deal of variance in the measured time and I believe that
that is due to the renderer triggering conversions back to
the vec storage and introducing slowdowns from the render side.
That's what I'll investigate next.
Previously this would create a new String because it had to, but
with the clustered storage we may be able to simply reference the
existing string as a str reference, so allow for that.
Adds the option to use an alternative clusted line storage for
the cells component of the line.
This structure is not optimal for mutation, but is better structured
for:
* matching/extracting textual content
* using less memory than the prior simple vector
For some contrast: the line "hello" occupies 5 Cells in the cell based
storage; that 5 discrete Cells each with their own tiny string
and a copy of their attributes.
The clustered version of the line stores one copy of the cell
attributes, the string "hello" and some small (almost constant size)
overhead for some metadata. For simple lines of ascii text, the
clustered version is smaller as there are fewer copies of the cell
attributes. Over the span of a large scrollback and typical terminal
display composition, this saving is anticipated to be significant.
The clustered version is also cheaper to search as it doesn't require
building a copy of the search text for each line (provided the line is
already in clustered form).
This commit introduces the capability: none of the internals request the
new form yet, and there are likely a few call sites that need to be
tweaked to avoid coersion from clustered to vector form.
While profiling `time cat bigfile` I noted that a big chunk of the
time is spent computing widths, so I wanted to dig into a bit.
After playing around with a few options, I settled on the approach
in this commit.
The key observations:
* WcWidth::from_char performs a series of binary searches.
The fast path was for ASCII, but anything outside that range
suffered in terms of latency.
* Binary search does a lot more work than a simple table lookup,
so it is desirable to use a lookup, and moreso to combine the
different tables into a single table so that classification
is an O(1) super fast lookup in the most common cases.
Here's some benchmarking results comparing the prior implementation
(grapheme_column_width) against this new pre-computed table
implementation (grapheme_column_width_tbl).
The ASCII case is more than 5x faster than before at a reasonably snappy
~3.5ns, with the more complex cases being closer to a constant ~20ns
down from 120ns in some cases.
There are changes here to widechar_width.rs that should get
upstreamed.
```
column_width ASCII/grapheme_column_width
time: [23.413 ns 23.432 ns 23.451 ns]
column_width ASCII/grapheme_column_width_tbl
time: [3.4066 ns 3.4092 ns 3.4121 ns]
column_width variation selector/grapheme_column_width
time: [119.99 ns 120.13 ns 120.28 ns]
column_width variation selector/grapheme_column_width_tbl
time: [21.185 ns 21.253 ns 21.346 ns]
column_width variation selector unicode 14/grapheme_column_width
time: [119.44 ns 119.56 ns 119.69 ns]
column_width variation selector unicode 14/grapheme_column_width_tbl
time: [21.214 ns 21.236 ns 21.264 ns]
column_width WidenedIn9/grapheme_column_width
time: [99.652 ns 99.905 ns 100.18 ns]
column_width WidenedIn9/grapheme_column_width_tbl
time: [21.394 ns 21.419 ns 21.446 ns]
column_width Unassigned/grapheme_column_width
time: [82.767 ns 82.843 ns 82.926 ns]
column_width Unassigned/grapheme_column_width_tbl
time: [24.230 ns 24.319 ns 24.428 ns]
```
Here's the benchmark summary after cleaning this diff up ready
to commit; it shows ~70-80% improvement in these cases:
```
; cargo criterion -- column_width
column_width ASCII/grapheme_column_width
time: [3.4237 ns 3.4347 ns 3.4463 ns]
change: [-85.401% -85.353% -85.302%] (p = 0.00 < 0.05)
Performance has improved.
column_width variation selector/grapheme_column_width
time: [20.918 ns 20.935 ns 20.957 ns]
change: [-82.562% -82.384% -82.152%] (p = 0.00 < 0.05)
Performance has improved.
column_width variation selector unicode 14/grapheme_column_width
time: [21.190 ns 21.210 ns 21.233 ns]
change: [-82.294% -82.261% -82.224%] (p = 0.00 < 0.05)
Performance has improved.
column_width WidenedIn9/grapheme_column_width
time: [21.603 ns 21.630 ns 21.662 ns]
change: [-78.429% -78.375% -78.322%] (p = 0.00 < 0.05)
Performance has improved.
column_width Unassigned/grapheme_column_width
time: [23.283 ns 23.355 ns 23.435 ns]
change: [-71.826% -71.734% -71.641%] (p = 0.00 < 0.05)
Performance has improved.
```
This enables tentative support for https://sw.kovidgoyal.net/kitty/keyboard-protocol
It's only been lightly tested with the notcurses-input program and
eyeballed against a few random keypresses in kitty running
`printf "\x1b[=11u" ; od -c`
I tried with neovim, but it doesn't seem like the version available
in Fedora 36 supports this yet.
refs: https://github.com/wez/wezterm/issues/1141