When true, selection_find_word_boundary_right() behaves as before - it
stops as soon as it encounters a character that isn’t of the
same *type* as the “initial” character (the last character in the
selection).
Take this, for example:
The Quick Brown Fox
The selection will first stop at the end of “the”, then just *before*
“quick”, then at the end of “quick”. Then just *before* “brown”, and
then at the end of “brown”, and so on.
This suits mouse selections pretty good. But when
selection_find_word_boundary_right() is used to extend a search match,
it’s better to ignore space-to-word character transitions. That is, we
want
The Quick Brown Fox
to first extend to the end of “the”, then immediately to the end of
“quick”, then to the end of “brown”, and so on.
Setting the ‘stop_to_space_to_word_boundary’ argument to false results
in latter behavior.
This is now done by search, when executing the
“extend-to-word-boundary” and “extend-to-next-whitespace” key
bindings.
find_next() did not always terminate correctly, causing
search_matches_next() to never terminate, which finally leads to an
infinite loop when rendering the search overlay surface, while finding
all matches to highlight.
The problem is that find_next(), after having found the initial
matching characters, enters a nested while loop that tries to match
the rest of the search criteria. This inner while loop did not check
if we’ve reached the last cell, and happily continued past
it (eventually wrappping around the scrollback buffer).
Closes#1047
5c4ddebc3c refactored
search_update_selection(), specifically, the logic that moves the
viewport.
It did so by converting the absolute row number (of the match) to
scrollback relative coordinates. This way we could ensure the viewport
wasn’t moved “too much” (e.g. beyond the scrollback start).
However, grid_row_abs_to_sb() and grid_row_sb_to_abs() doesn’t take a
partially filled scrollback into account. This means the row (numbers)
it returns may refer to *uninitialized* rows.
Since:
* The match row itself is valid (we *know* it has text on it)
* We *subtract* from it, when setting the new viewport (to center the
match on the screen).
it’s only the *upper* part of the new viewport that may be
uninitialized. I.e. we may have adjusted it too much.
So, what we need to do is move the viewport forward until its *first*
row is initialized. Then we know the rest will be too.
Unmapping a sub-surface in Sway does not damage the underlying
surface.
As a result, "committing" a scrollback search will typically leave
most of the foot window dimmed.
It can be seen when "cancelling" a search as well, but there it's less
obvious - only the margins are left dimmed. This is because cancelling
a search damaged the current viewport (something that shouldn't be
needed).
Out of sway, river, weston and mutter, only Sway needs this
workaround.
This is a workaround for https://github.com/swaywm/sway/issues/6960
* When extending the selection to the next word boundary, ensure the
row numbers are valid:
- use selection_get_end() when retrieving the current end
coordinate. This alone fixes a crash where we previously would
crash in an out-of-bounds array access in grid->rows[], due to
term->selection.coords.end being unbounded.
- ensure the new end coordinate is bounded before and after calling
selection_find_word_boundary_right().
* When moving the viewport (to ensure a new match is visible), make
sure we don’t end up with the match outside the new viewport.
Under certain, unusual, circumstances, the moved viewport _still_
did not contain the match. This resulted in assertions triggering
later, that assumed the match(es) are *always* visible.
It’s fairly easy to trigger this one by running foot with e.g.
foot -o scrollback.lines=0 --window-size-chars 25x3
and then hitting enter a couple of times, to fill the scrollback
history (which should consist of a single row in the example above),
and the do a scrollback search for (part of) the prompt, and keep
searching backward until it crashes.
This would happen if calculated (new) viewport had to be adjusted
(for example, to ensure it didn’t go past the scrollback end).
This patch changes the logic used when calculating the new
viewport. Instead of calculating the wanted viewport (match is
vertically centered) and then trying to adjust it to ensure the new
viewport is valid, start with a “safe” new viewport value, and then
determine how much we can move it, if at all, to center the match.
This is done by using scrollback relative coordinates. In this
coordinate system, the new viewport must be
>= 0, and < grid->num_lines - term->rows
This makes it very easy to limit the amount by which the viewport is
adjusted.
As a side-effect, we can remove all the old re-adjustment logic.
* The match iterator no longer special cases the primary match. This
was needed before, when the search iterator did not handle
overlapping matches correctly. Now that we do, the iterator is
guaranteed to find the primary match, and thus we no longer need to
special case it.
This fixes a bug where the primary match was returned twice, due to
the logic checking if a secondary match is the same as the primary
match was flawed...
Closes#1036
As this means the last call to sarch_matches_next() found a match at
the bottom of the view, and then set the iter’s *next* start position
to a row outside the view.
This is fine, but we need to handle it, by immediately stopping the
iter.
When bumping the iter’s start.row, we’re working with view-local
coordinates. That is, 0 >= row < term->rows.
This means it is wrong to and with grid->num_rows - 1, because a),
‘row’ should **never** be that big. And b), if we do, we’ll just end
up in an infinite loop, where the next call to matches_next() just
starts over from the beginning again (and eventually hitting the exact
same place that got us started).
The match logic uses the last start coordinate to determine which end
points in the selection to update. This sometimes fails when the start
coordinate has been changed by e.g. a key binding - the new start
coordinate is incorrectly matched against the old-but-modified start
coordinate, causing foot to e.g. *not* upate the selection start
coordinate.
Example:
$ echo 'test\n\test\ntest'
Then do a scrollback search for 'test. The first match is found
correctly (the last 'test'), but searching for the previous match
(ctrl+r) does not select the middle 'test'.
Fix by passing the search direction to search_find_next(), and have
_it_ calculate the coordinate to start search. There are three possibilities:
* forward
* backward
* "backward", but at the same position
The first two are used when searching for next/prev match with ctrl+s
and ctrl+r. The last one is used when the search criteria is
updated. In this case, we don't want to move to the previous match,
*unless* the current match no longer matches.
Normally, the primary match is automatically included by the iterator,
but if the secondary and primary matches overlap, it is possible we
don’t return the _actual_ primary match. This lead to highlighting
issues, where part of the primary match wasn’t highlighted correctly.
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes#931
Before this patch, only the currently “selected” match was
highlighted (by having the “selected” attribute, and by *not* dimming
it, like the rest of the grid during a scrollback search).
With this patch, we now highlight matches within the viewport. While
searching, only the “primary” match is searched-for, and tracked.
Then, when rendering a frame, we find all “secondary” matches as
well. “holes” are added to the search-mode overlay by the means of an
search-match iterator.
The iterator’s text matching logic is *very* similar to what we do
when the search criteria has been updated, and we re-search the
scrollback. It should be possible to refactor this, and share code.
We have a number of sub-surfaces for which we are *not* interrested in
pointer (or touch) input.
Up until now, we’ve manually dealt with these, by recognizing these
surfaces in all pointer events, and ignoring them.
But, lo and behold, there are better ways of doing this. By clearing
the subsurface’s input region, the compositor will do this for us -
when a pointer is outside a surface’s input region, the event is
passed to the next surface underneath it.
This is exactly what we want! Do this for all subsurfaces, *except*
the CSDs.
When matching “untranslated” bindings (by matching the base symbol of
the key, e.g. ctrl+shift+2 in US layout), require that no
non-significant modifiers are active.
This fixes an issue where AltGr was “ignored”, and would cause certain
combinations to match a key binding.
Example: ctrl+altgr+0, on many European layouts matched against the
default ctrl+0 (reset the font size), instead of emitting ^]
To make this work, we now need to filter out “locked”
modifiers (e.g. NumLock and CapsLock). Otherwise having e.g. NumLock
active would prevent *all* untranslated matching to fail.
Closes#983
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
Regardless of how we exit search mode (commit or cancel), the search
string is remembered.
The next time we enter search mode, the last searched-for string will
be used when searching for the next/prev match (ctrl+r, ctrl+s), and
the search query is empty.
search_add_chars() converts the incoming UTF-8 string into a wide-char
string, and then calls add_wchars().
add_wchars() inserts the string into the search buffer, at the current
cursor position.
While we’re in scrollback search mode, the selection may be
cancelled (for example, if the application is scrolling out the
selected text). Trying to e.g. extend the search selection after this
has happened triggered a crash.
This fixes it by simply resetting the search match state when the
selection is cancelled.
Closes#644
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.