Commit graph

168 commits

Author SHA1 Message Date
Daniel Eklöf
0d3f2f27e3
grid: refactor: replace {uri,curly}_range_append() with range_append()
Also add range_append_by_ref(), with replaces
uri_range_append_no_strdup().
2024-06-26 19:01:54 +02:00
Daniel Eklöf
cb4a74e10b
grid: row_range_put(): use correct type in call to range_delete() 2024-06-26 18:39:24 +02:00
Daniel Eklöf
45f4eb48fb
grid: refactor: remove union range_data_for_insertion
This union is identical to row_range_data, except the URI char pointer
is const. Let's ignore that, and re-use row_range_data, casting the
URI pointer when necessary.

Also remove uri_range_insert() and curly_range_insert(), and use the
generic version of range_insert() everywhere.
2024-06-26 18:39:24 +02:00
Daniel Eklöf
6a0110446c
grid: grid_row_{uri,curly}_range_put(): share code
grid_row_uri_range_put() and grid_row_curly_range_put() now share the
same base logic.

Range specific data is passed through a union, and range specific
checks are done through switched functions.
2024-06-26 18:39:24 +02:00
Daniel Eklöf
b20302c2a7
grid: reflow: handle styled underlines 2024-06-26 18:39:23 +02:00
Daniel Eklöf
32effc6657
csi: wip: styled underlines
This is work in progress, and fairly untested.

This adds initial tracking of styled underlines. Setting attributes
seems to work (both color and underline style). Grid reflow has *not*
been tested.

When rendering, style is currently ignored (all styles are rendered as
a plain, legacy underline).

Color however, *is* applied.
2024-06-26 18:39:23 +02:00
Daniel Eklöf
20923bb2e8
grid: refactor: first step towards a more generic range handling 2024-06-26 18:39:23 +02:00
Craig Barnes
e8b04e0e2c
xmalloc: add xmemdup() and use to replace some uses of xmalloc+memcpy 2024-03-17 12:31:02 +01:00
Daniel Eklöf
7999975016
Don't use fancy Unicode quotes, stick to ASCII 2024-02-06 12:36:45 +01:00
Daniel Eklöf
231e6eb3f1
grid: resize with reflow: reflow FTCS_COMMAND_{EXECUTED,FINISHED} 2024-02-06 12:13:09 +01:00
Daniel Eklöf
110a6dd6f0
grid: resize without reflow: truncate shell_integration.cmd_{start,end}
This ensures the cmd start/end columns are valid in the new grid.
2024-02-06 12:13:09 +01:00
Daniel Eklöf
e9607de5ae
osc: store column of FTCS_COMMAND_{EXECUTED,FINISHED} in row struct 2024-02-06 12:12:57 +01:00
Daniel Eklöf
f8e875a7cd
term: move row->prompt_marker into new struct, row->shell_integration 2024-02-06 12:12:56 +01:00
Daniel Eklöf
58d967b2f3
Codespell fixes 2023-10-03 14:11:55 +02:00
Daniel Eklöf
49fb0cf359
sixel: re-scale images when the cell dimensions change
Before this patch, when the cell dimensions changed (i.e. when the
font size changes), sixel images were either removed (the new cell
dimensions are smaller than the old), or simply kept at their original
size (new cell dimensions are larger).

With this patch, sixels are instead resized. This means a
sixel *always* occupies the same number of rows and columns,
regardless of how much the font size is changed.

This is done by maintaining two sets of image data and pixman images,
as well as their dimensions. These two sets are the new ‘original’ and
‘scaled’ members of the sixel struct.

The "top-level" pixman image pointer, and the ‘width’ and ‘height’
members either point to the "original", or the "scaled" version.

They are invalidated as soon as the cell dimensions change. They, and
the ‘scaled’ image is updated on-demand (when we need to render a
sixel).

Note that the ‘scaled’ image is always NULL when the current cell
dimensions matches the ones used when emitting the sixel (to save
run-time memory).

Closes #1383
2023-06-30 08:29:35 +02:00
Harri Nieminen
ae26915916 fix typos 2023-03-29 00:45:18 +03:00
Daniel Eklöf
c4f08a3b9a
grid_free(): allow being called with grid == NULL 2022-10-18 18:30:02 +02:00
Daniel Eklöf
18ef36523f
grid: resize: assert grid->cur_row is not NULL after a grid resize 2022-10-10 17:19:18 +02:00
Daniel Eklöf
8179d73daa
render: delay reflow for ‘resize-delay-ms’ milliseconds
Reflowing a large scrollback is *slow*. During an interactive resize,
it can easily take long enough that the compositor fills the Wayland
socket with configure events. Eventually, the socket becomes full and
the compositor terminates the connection, causing foot to exit.

This patch is work-in-progress, and the first step towards alleviating
this.

It delays the reflow by:

* Snapshotting (copying) the original grid when an interactive resize
  is started.
* While resizing, we apply a simple truncation resize of the
  grid (like we handle the alt screen).
* When the resize is done, or paused for ‘resize-delay-ms’, the grid
  is reflowed.

TODO: we *must* not allow any changes to the temporary (truncated)
grid during the resize. Any changes to the grid would be lost when the
final reflow is applied. That is, we must completely pause the ptmx
pipe while a resize is in progress.

Future improvements:

The initial copy can be slow. We should be able to avoid it by
rewriting the reflow algorithm to not free anything. This is
complicated by the fact that some resources (e.g. sixel images) are
currently *moved* to the new grid. They’d instead have to be copied.
2022-10-10 17:19:17 +02:00
Daniel Eklöf
454c82f0f5
grid: reflow: assert there aren’t any open URIs on the last row 2022-08-29 21:03:21 +02:00
Daniel Eklöf
5c86358cd1
grid: reflow: don’t line-wrap the last row
Before this patch, we would line-wrap the last row, just like any
other row, and then afterwards try to reverse this, by adjusting the
offset and free:ing and NULL:ing the "last row".

The problem with this is if the scrollback is full. In this case, the
row we’re freeing is the first row in the scrollback history. This
means we’ll crash as soon as the viewport is moved to the top of the
scrollback.

The fix is fairly, simple. Skip the post-processing logic, and instead
detect when we’re line-wrapping the last row, and skip the call to
line_wrap().

This way, the last row in the new grid corresponds to the last row in
the old grid.
2022-08-29 20:47:33 +02:00
Daniel Eklöf
13281f327b
grid: when setting the new viewport, ensure it’s correctly bounded
Do this by using scrollback relative coordinates, and ensure the new
viewport is not larger than (grid_rows - screen_rows), as that would
mean the viewport crosses the scrollback wrap-around.
2022-08-29 20:46:19 +02:00
Daniel Eklöf
aaf5894ad9
grid: get rid of empty row at the bottom after reflowing
When the window is resized and we reflow the text, we ended up
inserting an empty row at the bottom.

This happens whenever the actual last row has a hard linebreak (which
almost always is the case); we then end the reflow with a line break,
causing an extra, empty, row to be allocated and inserted.

This patch fixes this by detecting when:

1) the last row is empty
2) the next to last row has a hard line break

In this case, we roll back the last line break, by adjusting the new
offset we just calculated, and free:ing the empty row.

TODO: it would be nice if we could detect this in the reflow loop
instead, and avoid doing the last line break all together. I haven’t
yet been able to find a way to do this correctly.

Closes #1108
2022-08-05 18:26:37 +02:00
Daniel Eklöf
0c60bb3f29
grid: reflow: require col count > 0 when skipping line truncation
When reflowing the grid, we truncate lines with a hard linebreak after
the last non-empty cell. This way we don’t reflow trailing empty cells
to a new line when resizing the window to a smaller size.

However, “logical” lines (i.e. those without a hard linebreak)
are *not* truncated. This is to ensure we don’t trim empty cells in
the middle of a logical line spanning multiple physical lines.

Since newly allocated rows are initialized with linebreak=false, we
need to ensure _those_ are still truncated - otherwise all that empty
space under the current prompt will be reflowed.

Note that this is a temporary workaround. The correct solution, I
think, is to track whether a line has been printed to or not, and
simply ignore (not reflow) lines that haven’t yet been touched.
2022-06-30 19:47:18 +02:00
Daniel Eklöf
37f094280b
Revert "grid: invert the default value of ‘linebreak’, from false to true"
This reverts commit cdd46cdf85.
2022-06-30 19:37:01 +02:00
Daniel Eklöf
bdb79e8b9f
osc: add support for OSC 133;A (prompt markers)
This patch adds support for the OSC-133;A sequence, introduced by
FinalTerm and implemented by iTerm2, Kitty and more. See
https://iterm2.com/documentation-one-page.html#documentation-escape-codes.html.

The shell emits the OSC just before printing the prompt. This lets the
terminal know where, in the scrollback, there are prompts.

We implement this using a simple boolean in the row struct ("this row
has a prompt"). The prompt marker must be reflowed along with the text
on window resizes.

In an ideal world, erasing, or overwriting the cell where the OSC was
emitted, would remove the prompt mark. Since we don't store this
information in the cell struct, we can't do that. The best we can do
is reset it in erase_line(). This works well enough in the "normal"
screen, when used with a "normal" shell. It doesn't really work in
fullscreen apps, on the alt screen. But that doesn't matter since we
don't support jumping between prompts on the alt screen anyway.

To be able to jump between prompts, two new key bindings have been
added: prompt-prev and prompt-next, bound to ctrl+shift+z and
ctrl+shift+x respectively.

prompt-prev will jump to the previous, not currently visible, prompt,
by moving the viewport, ensuring the prompt is at the top of the
screen.

prompt-next jumps to the next prompt, visible or not. Again, by moving
the viewport to ensure the prompt is at the top of the screen. If
we're at the bottom of the scrollback, the viewport is instead moved
as far down as possible.

Closes #30
2022-06-16 19:02:10 +02:00
Daniel Eklöf
9567694bab
grid: reflow: don’t trim trailing empty cells from logical lines
When a line in the old grid (the one being reflowed) doesn’t have a
hard linebreak, don’t trim trailing empty cells.

Doing so means we’ll “compress” (remove) empty cells between text
if/when we revert to a larger window size.

The output from neofetch suffers from this; it prints a logo to the
left, and system information to the right. The logo and the system
info column is separated by empty cells (i.e. *not* spaces).

If the window is reduced in size such that the system info is pushed
to a new line, each logo line ends with a number of empty cells. The
next time the window is resized, these empty cells were
ignored (i.e. removed).

That meant that once the window was enlarged again, the system info
column was a) no longer aligned, and b) had been pulled closer to the
logo.

This patch doesn’t special case trailing empty cells when the line
being reflowed doesn’t have a hard linebreak. This means e.g. ‘ls’
output is unaffected.

Closes #1055
2022-06-11 12:02:20 +02:00
Daniel Eklöf
cdd46cdf85
grid: invert the default value of ‘linebreak’, from false to true 2022-06-11 12:02:20 +02:00
Daniel Eklöf
b4f666118f
grid: add abs-to-sb and sb-to-abs utility function
These functions convert row numbers between absolute coordinates and
“scrollback relative” coordinates.

Absolute row numbers can be used to index into the grid->rows[] array.

Scrollback relative numbers are ordered with the *oldest* row first,
and the *newest* row last. That is, in these coordinates, row 0 is the
*first* (oldest) row in the scrollback history, and row N is the
*last* (newest) row.

Scrollback relative numbers are used when we need to sort things after
their age, when determining if something has scrolled out, or when
limiting an operation to ensure we don’t go past the scrollback
wrap-around.
2022-04-25 19:57:18 +02:00
Daniel Eklöf
0800515c04
grid: reflow: add TODO to detect selection on re-used rows, and cancel it 2022-02-07 15:06:55 +01:00
Pranjal Kole
0da19a81bc replace gettimeofday with clock_gettime
POSIX.1-2008 has marked gettimeofday(2) as obsolete, recommending the
use of clock_gettime(2) instead.

CLOCK_MONOTONIC has been used instead of CLOCK_REALTIME because it is
unaffected by manual changes in the system clock. This makes it better
for our purposes, namely, measuring the difference between two points in
time.

tv_sec has been casted to long in most places since POSIX does not
define the actual type of time_t.
2022-01-15 21:35:45 +05:30
Daniel Eklöf
2e8c9c2fe5
grid: reload pointers into the uri range vector after inserting
Inserting elements into the URI range vector typically triggers a
vector resize. This is done using realloc(). Sometimes this causes the
vector to move, thus invalidating all existing pointers into the
vector.
2021-12-08 17:41:29 +01:00
Daniel Eklöf
2fe2dfa847
grid: uri_range_erase(): unittest: try erasing a row without any URIs 2021-11-28 11:05:00 +01:00
Daniel Eklöf
66b8c92c30
grid: uri_range_erase: walk the ranges backwards
This way, ranges are deleted last-to-first, thus avoiding memmoving
multiple ranges over and over again when erasing e.g. a full line.
2021-11-28 11:03:07 +01:00
Daniel Eklöf
747da83c76
grid: remove useless assertion
grid.c:1041:24: warning: comparison of unsigned expression in ‘>= 0’ is always true [-Wtype-limits]
2021-11-27 18:15:59 +01:00
Daniel Eklöf
5ce6c89df6
grid: resize without reflowing: use URI range utility functions 2021-11-27 16:31:33 +01:00
Daniel Eklöf
dfeca4e134
grid: uri_range_{append,insert}: pass range attributes as separate parameters 2021-11-27 16:25:29 +01:00
Daniel Eklöf
5c6c2de051
grid: snapshot: use uri range utility functions 2021-11-27 16:11:26 +01:00
Daniel Eklöf
5c7e881cd4
grid: uri_range_ensure_size(): add the requested amount of entries 2021-11-27 16:03:07 +01:00
Daniel Eklöf
7699271316
grid: reflow: no need to keep a uri-range-idx variable
The URI ranges are now an array, which means we can get the next URI
range by incrementing the pointer.

Instead of checking if range is not NULL, check that it isn’t the
range terminator (which is NULL when we don’t have any ranges, and
the first address *after* the last range otherwise).
2021-11-26 20:57:08 +01:00
Daniel Eklöf
41565a0d0e
grid: resize without reflow: only verify URI ranges in debug builds 2021-11-26 20:36:59 +01:00
Daniel Eklöf
be203aeae1
grid: bug: OSC-8 URIs were incorrectly skipped while resizing the alt screen 2021-11-26 20:29:23 +01:00
Daniel Eklöf
4eb0aa0f18
osc8: replace grid_row_uri_range_add() with internal uri_range_append()
grid_row_uri_range_add() was only used while reflowing. In this case,
we know the new URIs being added are always going at the end of the
URI list (since we’re going top-to-bottom, left-to-right).

Thus, we don’t need the insertion logic, and can simply append instead.
2021-11-26 20:25:07 +01:00
Daniel Eklöf
ccee08a393
osc8: uri ranges: use a dynamically re-sizable array instead of a tllist 2021-11-26 20:09:15 +01:00
Daniel Eklöf
8c50a7afd4
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).

But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.

Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.

However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.

This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.

To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.

Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.

Closes #816
2021-11-25 19:31:03 +01:00
Daniel Eklöf
f27ccd999e
grid: refactor grid_row_uri_range_erase()
The old algorithm always created a new URI, followed by (maybe)
removing the existing URI, when an URI needed to be modified.

That is, if e.g. the tail of an URI was being erased, the old
algorithm would create a new URI for the part of the URI that should
remain, and then removed the old URI.

This isn’t very effective. The new algorithm instead identifies all
possible overlap cases, and handles each one differently:

* URI ends *before* erase range starts - continue with the next URI
  without further checks
* URI starts *after* the erase range ends - return, we’re done
* Erase range erases the entire URI - remove the URI
* Erase range erases a part in the middle - split the URI
* Erase range erases the head of the URI - adjust the URI’s start
* Erase range erases the tail of the URI - adjust the URI’s end
2021-11-21 18:09:27 +01:00
Daniel Eklöf
503c2ebd50
grid: row_uri_range_erase(): assume caller has checked row->extra != NULL 2021-11-21 18:09:27 +01:00
Daniel Eklöf
1a0de0017f
grid: add grid_row_uri_range_erase()
This function handles erasing of an URI range. That is, a range of the
row is being either erased, or overwritten (from the URI perspective,
these two are the same thing).

We handle both partial overwrites (split up, or truncate URI), as well
as complete overwrites (remove URI).
2021-11-21 18:09:26 +01:00
Daniel Eklöf
fe8ca23cfe
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.

However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.

Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.

Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.

So what can we do?

We can’t sort the array, because

a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).

b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.

This patch uses a binary search tree to store the chains instead of a
simple array.

The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.

The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.

Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.

Lookup is of course slower than before, O(log n) instead of
O(1).

Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.

But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.

With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.

Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.

As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.

Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 17:30:49 +02:00
Daniel Eklöf
3292bb5b8e
grid: reflow: ‘amount’ has already been added to ‘from’ 2021-06-02 20:13:52 +02:00