Commit graph

225 commits

Author SHA1 Message Date
Daniel Eklöf
0d22e9fa01
selection: explicitly reject *all* dnd offers not targeting the grid 2022-06-20 20:57:23 +02:00
Daniel Eklöf
2e4da6fbf6
selection: ignore drag-and-drops with unsupported mime-types
Specifically, make sure we do *not* call wl_data_offer_receive() with
a NULL mime-type, as this causes libwayland to error out, which in
turn causes foot to exit.

Closes #1092
2022-06-20 20:56:11 +02:00
Daniel Eklöf
dbe2c0a068
selection: allow HT, VT and FF, disallow NUL in non-bracketed paste mode
This syncs foot with more recent versions of XTerm, where it’s
“disallowedPasteControls” resource has changed its default value to

  BS,DEL,ENQ,EOT,ESC,NULL

Note that we’re already stripping out ENQ,EOT,ESC in all modes.

What does it mean for foot:

* HT, VT and FF are now allowed, regardless of paste mode
* NUL is now stripped in non-bracketed paste mode

Closes #1084
2022-06-15 19:09:40 +02:00
Daniel Eklöf
7630510448
selection: find_word_boundary_right: add “stop-on-space-to-word-boundary”
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.
2022-04-27 18:44:57 +02:00
Daniel Eklöf
6316a5eb0c
selection: add start/end coordinate getters
Internally, selection coordinates are *unbounded* (that is, the row
numbers may be larger than grid->num_rows) while a selection is
ongoing. Only after it has been finalized are the coordinates bounded.

This means it isn’t safe to use term->selection.coords.* directly.
2022-04-25 19:59:23 +02:00
Daniel Eklöf
9c0f1a671c
selection: assert serial is non-zero before copying data to the clipboard 2022-04-24 20:18:51 +02:00
Daniel Eklöf
8c0fca30db
selection: find_word_boundary: assert ‘pos’ is valid 2022-04-24 20:17:29 +02:00
Daniel Eklöf
c5519e2aa6
search: fix next/prev not updating selection correctly when matches overlap
When the new match overlapped with the old match, the selection wasn’t
updated correctly.
2022-04-17 19:16:47 +02:00
Daniel Eklöf
5b1f1602bc
refactor: add a ‘range’ struct, grouping a start and end coord together 2022-04-09 15:09:02 +02:00
Daniel Eklöf
485c473e76
selection: don’t translate \r to \n when pasting
In non-bracketed paste mode, we translate \n to \r, and \r\n to
\r. The latter matches XTerm, urxvt, alacritty and kitty. The former
matches alacritty and kitty (xterm and urxvt just blindly replaces all
\n occurrences with \r, meaning \r\n is translated to \r\r.

For some reason, we then unconditionally translated all \r back to \n,
regardless of whether bracketed paste was enabled or not. Unsure
why/where this comes from, but it doesn't match any of the other
terminal emulators I tested.

One example where this caused issues is in older versions of nano (at
least up to 2.9).

Closes #980
2022-03-19 19:01:25 +01:00
Daniel Eklöf
ef522e292f
selection: foreach: sort start/end based on their scrollback-start relative values
When iterating the characters in a selection, we want to go from the
“start” to the “end”, where start is the upper left-most character,
and “end” is the lower right-most character.

There are two things to consider:

* The ‘start’ coordinate may actually sort after the ‘end’
  coordinate (user selected from bottom of the window and upward)
* The scrollback wraparound.

What we do is calculate both the star and end coordinates’
scrollback-start relative row numbers. That is, the number of rows
from the beginning of the scrollback. So if the very first
row (i.e. the oldest) in the scrollback is selected, that has the
scrollback-start relative number “0”.

Then we loop from whichever (start or end coordinate) is highest up in
the scrollback, to the “other” coordinate.
2022-02-07 15:06:55 +01:00
Daniel Eklöf
2e828248d0
selection: ensure start/end coordinates are bounded by the current grid
Closes #924
2022-02-07 15:06:39 +01:00
Daniel Eklöf
e0227266ca
fcft: adapt to API changes in fcft-3.x
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
2022-02-05 17:00:54 +01:00
Daniel Eklöf
5ee902551a
selection: don’t quote file URIs on the alt screen
Closes #379
2022-02-02 21:17:01 +01:00
Daniel Eklöf
5ab49de7f2
selection: convert \r -> \n when reading clipboard data
This fixes an issue where pasting (using e.g. OSC-52) in client
applications that doesn’t do this conversion themselves, like tmux,
doesn’t work.

Closes #752
2021-10-19 21:34:04 +02:00
Daniel Eklöf
548d7be4c6
selection: line-wise selection now handles soft line-wrapping
Previously, soft-wrapped lines were not selected correctly, as the
selection logic was hardcoded to simply select everything between the
first and last column on the current terminal row.

Now, we scan backward and forward, looking for hard-wrapped
lines. This is similar to how word-based selection works.

Closes #726
2021-09-30 13:39:23 +02:00
Daniel Eklöf
636fea55f5
selection: modify: no need to check for NULL before calling free() 2021-08-16 19:15:52 +02:00
Daniel Eklöf
02fbd0bbce
selection: modify: convert ‘keep_selection’ bitmask from uint64_t to uint8_t 2021-08-16 19:15:52 +02:00
Daniel Eklöf
f0fd3b7578
selection: modify: replace reset_context() macro with a function 2021-08-16 19:15:52 +02:00
Daniel Eklöf
ae70596a50
selection: don’t require two cell attr bits for selection updating
When updating the selection (i.e when changing it - adding or removing
cells to the selection), we need to do two things:

* Unset the ‘selected’ bit on all cells that are no longer selected.
* Set the ‘selected’ bit on all cells that *are* selected.

Since it’s quite tricky to calculate the difference between the “old”
and “new” selection, this is done by first un-selecting the old
selection, and then selecting the new, updated selection. I.e. first
we clear the ‘selected’ bit from *all* cells, and then we re-set it on
those cells that are still selected.

This process also dirties the cells, to make sure they are
re-rendered (needed to reflect their new selected/un-selected status).

To avoid dirtying *all* previously selected, and newly selected cells,
we have used an algorithm that first runs a “pre-pass”, marking all
cells that *will* be selected as such. The un-select pass would then
skip (no dirty) cells that have been marked by the pre-pass. Finally,
the select pass would only dirty cells that have *not* been marked by
the pre-pass.

In short, we only dirty cells whose selection state have *changed*.

To do this, we used a second ‘selected’ bit in the cell attribute
struct.

Those bits are *scarce*.

This patch implements an alternative algorithm, that frees up one of
the two ‘selected’ bits.

This is done by lazy allocating a bitmask for the entire grid. The
pre-pass sets bits in the bitmask. Thus, after the pre-pass, the
bitmask has set bits for all cells that *will* be selected.

The un-select pass simply skips cells with a one-bit in the
bitmask. Cells without a one-bit in the bitmask are dirtied, and their
‘selected’ bit is cleared.

The select-pass doesn’t even have to look at the bitmask - if the cell
already has its ‘selected’ bit set, it does nothing. Otherwise it sets
it and dirties the cell.

The bitmask is implemented as an array of arrays of 64-bit
integers. Each outer element represents one row. These pointers are
calloc():ed before starting the pre-pass.

The pre-pass allocates the inner arrays on demand.

The unselect pass is designed to handle both the complete absence of a
bitmask, as well as row entries being NULL (both means the cell
is *not* pre-marked, and will thus be dirtied).
2021-08-16 19:15:41 +02:00
Daniel Eklöf
251545203b
search: reset match state when selection is cancelled
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
2021-07-22 17:57:43 +02:00
Daniel Eklöf
5f0ceb72f1
csi: erase scrollback: cancel selection if it touches the scrollback
This breaks out the scrollback erasing logic for \E[3J from csi.c, and
moves it to the new function term_erase_scrollback(), and changes the
logic to calculate the start and end row (absolute) numbers of the
scrollback, and only iterate those, instead of iterating *all* rows,
filtering out those that are on-screen.

It also adds an intersection range check of the selection range, and
cancels the selection if it touches any of the deleted scrollback
rows.

This fixes a crash when trying to render the next frame, since the
selection now references rows that have been freed.

Closes #633
2021-07-18 16:14:21 +02: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
b9ef703eb1
wip: grapheme shaping 2021-06-24 17:30:45 +02:00
Daniel Eklöf
49bb00fb64
selection: add support for TEXT/STRING/UTF8_STRING mime-types in incoming offers
We were already offering these mime types for our own clipboard data,
but ignored them in incoming offers.

Fixes paste issues from Geany.

Closes #583
2021-06-09 11:23:55 +02:00
Daniel Eklöf
0de55182ac
selection: reset ‘empty_count’ after we’ve emitted the empty cells
When marking and unmarking cells, we don’t highlight trailing empty
cells. We do however highlight empty cells if they are followed by
non-empty cells.

I think this was an intentional choice. If one row ended with trailing
empty cells, but *no* hard linebreak, then we’d continue on the next
row, and emit all the empty cells once we hit a non-emtpy cell on the
second row.

But this is something that shouldn’t happen in any real-world use
cases.
2021-06-05 13:41:17 +02:00
Daniel Eklöf
cb83d60089
selection: fix bad assertion
When there are multiple multi-column characters back-to-back, the cell
before the pivot end point may in fact be a SPACER+1 cell.
2021-06-02 20:13:52 +02:00
Daniel Eklöf
f0041882f1
selection: pay attention to hard linebreaks when search for word boundaries
Double-clicking on a word in the left or right margin, would line-wrap
the selection if there was a non-empty cell in the corresponding
right/left margin on the prev/next line. Regardless of whether there
was a hard linebreak or not.

Script to reprouce:

  !/bin/bash

  cols=$(tput cols)
  printf "%*coo\nbar\n" $((${cols} - 2)) f

Run, then double click either “foo” or “bar”. Neither should select
the other part.

Closes #565
2021-05-31 17:15:20 +02:00
Daniel Eklöf
a6d9f01c0d
extract: move ‘strip_trailing_empty’ parameter from extra_finish() to extract_begin() 2021-05-17 18:14:10 +02:00
Daniel Eklöf
1bc9fd5fe1
extract: add extract_finish_wide(), and optionally skip stripping trailing empty cells
extract_finish() returns the extracted text in UTF-8, while
extract_finish_wide() returns the extracted text in Unicode.

This patch also adds a new argument to extract_finish{,_wide},
that when set to true, skips stripping trailing empty cells.
2021-05-17 18:14:09 +02:00
Daniel Eklöf
eab874eb06
selection: expose find_word_boundary_{left,right}() 2021-05-17 18:14:09 +02:00
Daniel Eklöf
d9e1aefb91
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.

So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.

A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.

In other words, the last spacer is always CELL_SPACER+1.

CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
Daniel Eklöf
0da766e505
selection: don’t strip formatting C0 control characters in bracketed paste mode
It’s ok to let the receiving end handle formatting C0 control
characters in bracketed paste mode.

In fact, we *must* let them through. Otherwise it is impossible to
paste e.g. tabs into editors and similar applications.
2021-02-12 15:59:07 +01:00
Craig Barnes
3c86af52c2 Convert all but 2 remaining uses of xassert(false) to BUG("...") 2021-02-10 09:01:51 +00:00
Daniel Eklöf
eff8481cdc
selection: remove selection_enabled()
Its name did not reflect its semantics. Since it was only used in a
single place, “inline” it there, and get rid of it.
2021-02-02 09:52:22 +01:00
Daniel Eklöf
81fb756ea7
selection: don’t replace \r\n and \n with \r in bracketed paste mode 2021-01-27 10:44:28 +01:00
Daniel Eklöf
49d6dbd761
selection: remove duplicate ‘ESC’ in switch case 2021-01-26 19:31:23 +01:00
Daniel Eklöf
e70776fc8c
selection: DEL is 0x7f, not 0x1f 2021-01-26 19:31:23 +01:00
Daniel Eklöf
9e5ef6efac
selection: codespell: stript -> strip 2021-01-26 19:31:23 +01:00
Daniel Eklöf
357af41d7e
selection: strip non-formatting C0, BS, HT and DEL from pasted text 2021-01-26 19:31:21 +01:00
Daniel Eklöf
b31d0c080b
selection: unbreak text/uri-list decoding: we’re not using \r, not \n
Before passing the pasted text to the decoder, we now replace \r\n,
and \n, with \r.

The URI decoder was looking for a \n, which meant we failed to split
up the list and instead pasted a single “multi-line” URI.
2021-01-26 19:31:12 +01:00
Daniel Eklöf
5168aa72cd
selection: replace \r\n and \n with \r, and strip \e from pasted text
Closes #305
Closes #306
2021-01-26 19:31:12 +01:00
Daniel Eklöf
4f578189cc
config: add ‘none’ as a possible value for ‘selection-target’
When ‘selection-target’ is set to ‘none’, selecting text does not copy
the text to _any_ clipboard.

This patch also refactors the value parsing to be data driven.
2021-01-23 10:43:59 +01:00
Daniel Eklöf
07f6b3b1af
selection: copy selected text to the target configured by ‘selection-target’
Closes #288
2021-01-23 10:43:59 +01:00
Craig Barnes
e56136ce11 debug: rename assert() to xassert(), to avoid clashing with <assert.h> 2021-01-16 20:16:00 +00:00
Daniel Eklöf
767bd4f1db
config: add ‘select-extend-character-wise’ bind action
This forces the (new) selection mode to be character-wise when
extending a word- or line-wise selection.

Default key binding is ctrl+RMB.
2021-01-14 16:29:29 +01:00
Daniel Eklöf
3be80622ef
selection: uri decode: move zero-length check into decode_one_uri() 2021-01-12 14:56:47 +01:00
Daniel Eklöf
c8bcce83d5
selection: add a ‘finish’ function, called at the end of receiving clipboard data
This is necessary to decode the final URI in a text/uri-list offer if
it hasn’t been newline terminated.
2021-01-12 14:45:41 +01:00
Daniel Eklöf
e3e3ffc67c
selection: URI decoder: break out decoding of a single URI 2021-01-12 14:45:04 +01:00
Daniel Eklöf
21dbb44a30
selection: extend-block: update pivot point and selection direction 2021-01-06 11:14:11 +01:00