From 9483a3a7c031cf6518aff153f07d4996ae86d830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 00:49:52 +0200 Subject: [PATCH 01/36] changelog: pgo helper binary build fix (missing key-binding stubs) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6682adc..477202df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,10 @@ ### Deprecated ### Removed ### Fixed + +* build: missing symbols when linking the `pgo` helper binary. + + ### Security ### Contributors From ae2999740e8b3923685b01dd4878ac240aedc940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 11:10:09 +0200 Subject: [PATCH 02/36] readme: default foot.ini is now installed to /etc/xdg/foot/foot.ini --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fdf28828..f12de1c8 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ See [INSTALL.md](INSTALL.md). **foot** can be configured by creating a file `$XDG_CONFIG_HOME/foot/foot.ini` (defaulting to `~/.config/foot/foot.ini`). A template for that can usually be found -in `/usr/share/foot/foot.ini` or +in `/etc/xdg/foot/foot.ini` or [here](https://codeberg.org/dnkl/foot/src/branch/master/foot.ini). Further information can be found in foot's man page `foot.ini(5)`. From ce4fd6df3fe0970db918375145055bd317212b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 11:10:37 +0200 Subject: [PATCH 03/36] readme: add OSC 22 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f12de1c8..9623ffe4 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,7 @@ with the terminal emulator itself. Foot implements the following OSCs: * `OSC 12` - change cursor color * `OSC 17` - change highlight (selection) background color * `OSC 19` - change highlight (selection) foreground color +* `OSC 22` - set the xcursor (mouse) pointer * `OSC 52` - copy/paste clipboard data * `OSC 104` - reset color palette * `OSC 110` - reset default foreground color From 4ca04079459adcf492aebbd9289277401ed1e4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 11:11:34 +0200 Subject: [PATCH 04/36] raedme: add a reference to foot-ctlseq(7) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9623ffe4..5b819bf5 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,9 @@ with the terminal emulator itself. Foot implements the following OSCs: * `OSC 777` - desktop notification (only the `;notify` sub-command of OSC 777 is supported.) +See the **foot-ctlseq**(7) man page for a complete list of supported +control sequences. + ## Programmatically checking if running in foot From 1913fb6efdb2dff4c600daa54513366cced38b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 11:13:25 +0200 Subject: [PATCH 05/36] changelog: hyperlink lists under their corresponding sub-section --- CHANGELOG.md | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 477202df..a87a63ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,13 @@ binaries will neither be built, nor will `ninja test` attempt to execute them. Enabled by default ([#919][919]). +[325]: https://codeberg.org/dnkl/foot/issues/325 +[950]: https://codeberg.org/dnkl/foot/issues/950 +[1004]: https://codeberg.org/dnkl/foot/issues/1004 +[1019]: https://codeberg.org/dnkl/foot/issues/1019 +[964]: https://codeberg.org/dnkl/foot/issues/964 +[919]: https://codeberg.org/dnkl/foot/issues/919 + ### Changed @@ -100,6 +107,12 @@ `${sysconfdir}/xdg/foot/foot.ini`, typically resolving to `/etc/xdg/foot/foot.ini` ([#1001][1001]). +[922]: https://codeberg.org/dnkl/foot/issues/922 +[971]: https://codeberg.org/dnkl/foot/issues/971 +[980]: https://codeberg.org/dnkl/foot/issues/980 +[988]: https://codeberg.org/dnkl/foot/issues/988 +[1001]: https://codeberg.org/dnkl/foot/issues/1001 + ### Removed @@ -143,6 +156,18 @@ * Various minor fixes to scrollback search, and how it finds the next/prev match. +[918]: https://codeberg.org/dnkl/foot/issues/918 +[922]: https://codeberg.org/dnkl/foot/issues/922 +[924]: https://codeberg.org/dnkl/foot/issues/924 +[926]: https://codeberg.org/dnkl/foot/issues/926 +[943]: https://codeberg.org/dnkl/foot/issues/943 +[963]: https://codeberg.org/dnkl/foot/issues/963 +[983]: https://codeberg.org/dnkl/foot/issues/983 +[1005]: https://codeberg.org/dnkl/foot/issues/1005 +[1008]: https://codeberg.org/dnkl/foot/issues/1008 +[1009]: https://codeberg.org/dnkl/foot/issues/1009 +[931]: https://codeberg.org/dnkl/foot/issues/931 + ### Contributors @@ -158,29 +183,6 @@ * jvoisin * merkix -[325]: https://codeberg.org/dnkl/foot/issues/325 -[950]: https://codeberg.org/dnkl/foot/issues/950 -[1004]: https://codeberg.org/dnkl/foot/issues/1004 -[1019]: https://codeberg.org/dnkl/foot/issues/1019 -[964]: https://codeberg.org/dnkl/foot/issues/964 -[919]: https://codeberg.org/dnkl/foot/issues/919 -[922]: https://codeberg.org/dnkl/foot/issues/922 -[971]: https://codeberg.org/dnkl/foot/issues/971 -[980]: https://codeberg.org/dnkl/foot/issues/980 -[988]: https://codeberg.org/dnkl/foot/issues/988 -[1001]: https://codeberg.org/dnkl/foot/issues/1001 -[918]: https://codeberg.org/dnkl/foot/issues/918 -[922]: https://codeberg.org/dnkl/foot/issues/922 -[924]: https://codeberg.org/dnkl/foot/issues/924 -[926]: https://codeberg.org/dnkl/foot/issues/926 -[943]: https://codeberg.org/dnkl/foot/issues/943 -[963]: https://codeberg.org/dnkl/foot/issues/963 -[983]: https://codeberg.org/dnkl/foot/issues/983 -[1005]: https://codeberg.org/dnkl/foot/issues/1005 -[1008]: https://codeberg.org/dnkl/foot/issues/1008 -[1009]: https://codeberg.org/dnkl/foot/issues/1009 -[931]: https://codeberg.org/dnkl/foot/issues/931 - ## 1.11.0 From 155a2e47905a07810eb6ede94951d20a75ddf79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 11:24:44 +0200 Subject: [PATCH 06/36] ci: enable -Db_pgo=generate on release builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hopefully, this’ll catch missing stubs in pgo/pgo.c in the future. --- .builds/alpine-x64.yml | 2 +- .builds/alpine-x86.yml.disabled | 2 +- .builds/freebsd-x64.yml | 2 +- .gitlab-ci.yml | 4 ++-- .woodpecker.yml | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index a7782b20..8f341f3d 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -43,7 +43,7 @@ tasks: meson test -C bld/debug --print-errorlogs - release: | mkdir -p bld/release - meson --buildtype=minsize -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release + meson --buildtype=minsize -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release ninja -C bld/release -k0 meson test -C bld/release --print-errorlogs - codespell: | diff --git a/.builds/alpine-x86.yml.disabled b/.builds/alpine-x86.yml.disabled index 24909eb2..22a9e637 100644 --- a/.builds/alpine-x86.yml.disabled +++ b/.builds/alpine-x86.yml.disabled @@ -38,6 +38,6 @@ tasks: meson test -C bld/debug --print-errorlogs - release: | mkdir -p bld/release - meson --buildtype=minsize -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release + meson --buildtype=minsize -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release ninja -C bld/release -k0 meson test -C bld/release --print-errorlogs diff --git a/.builds/freebsd-x64.yml b/.builds/freebsd-x64.yml index bd4b073c..89803a6e 100644 --- a/.builds/freebsd-x64.yml +++ b/.builds/freebsd-x64.yml @@ -41,7 +41,7 @@ tasks: - release: | mkdir -p bld/release - meson --buildtype=minsize -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release + meson --buildtype=minsize -Db_pgo=generate -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release ninja -C bld/release -k0 meson test -C bld/release --print-errorlogs bld/release/foot --version diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5857b87d..b2a459dc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,7 +57,7 @@ release-x64: - cd .. - mkdir -p bld/release - cd bld/release - - meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ + - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - ninja -v -k0 - ninja -v test - ./foot --version @@ -93,7 +93,7 @@ release-x86: - cd .. - mkdir -p bld/release - cd bld/release - - meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ + - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../../ - ninja -v -k0 - ninja -v test - ./foot --version diff --git a/.woodpecker.yml b/.woodpecker.yml index b790c68f..8493aa47 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -53,7 +53,7 @@ pipeline: # Release - mkdir -p bld/release-x64 - cd bld/release-x64 - - meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. - ninja -v -k0 - ninja -v test - ./foot --version @@ -100,7 +100,7 @@ pipeline: # Release - mkdir -p bld/release-x86 - cd bld/release-x86 - - meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. - ninja -v -k0 - ninja -v test - ./foot --version From f0f0fac77fd5a60af080e961468619e93e44ed09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 20:08:09 +0200 Subject: [PATCH 07/36] doc: foot.ini: drop empty line after *show-urls-launch* --- doc/foot.ini.5.scd | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index d4226e67..8aa63d2f 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -747,7 +747,6 @@ e.g. *search-start=none*. Default: _not bound_ *show-urls-launch* - Enter URL mode, where all currently visible URLs are tagged with a jump label with a key sequence that will open the URL (and exit URL mode). Default: _Control+Shift+u_. From b68d5da71b5014d7570c6d8c097ab4a9905ed287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Apr 2022 12:03:31 +0200 Subject: [PATCH 08/36] search: fix debug log This has been broken since the forward/backward search logic was refactored. --- search.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/search.c b/search.c index a1ef8eb4..77492e3f 100644 --- a/search.c +++ b/search.c @@ -467,7 +467,8 @@ search_find_next(struct terminal *term, enum search_direction direction) LOG_DBG( "update: %s: starting at row=%d col=%d " "(offset = %d, view = %d)", - backward ? "backward" : "forward", start.row, start.col, + direction != SEARCH_FORWARD ? "backward" : "forward", + start.row, start.col, grid->offset, grid->view); struct coord end = start; From 2cbcfb315909969c126f9fd322c68236151a17e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Apr 2022 12:04:06 +0200 Subject: [PATCH 09/36] render: fix refresh logic of pending csd|search|url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our CSDs, the search-box and URL labels are all implemented using sub-surfaces, synchronized with the main grid. This means we *must* commit the main surface as well, when updating one of these sub-surfaces. The logic for doing so in the frame callback was flawed, and only triggered when the main grid was actually dirty. That is, e.g. search box updates that did not also resulted in grid updates (for example - pasting a search criteria that doesn’t match), did not result in a UI refresh. Closes #1040 --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 31d5a61f..27f21bd5 100644 --- a/render.c +++ b/render.c @@ -3572,7 +3572,7 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da if (urls) render_urls(term); - if (grid && (!term->delayed_render_timer.is_armed | csd | search | urls)) + if ((grid && !term->delayed_render_timer.is_armed) || (csd | search | urls)) grid_render(term); tll_foreach(term->wl->seats, it) { From 47d1ba58e5b2bbdb534ac4993e1ab9d30c229ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Apr 2022 12:08:23 +0200 Subject: [PATCH 10/36] changelog: UI not refreshing when pasting into the scrollback search box --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a87a63ba..c44cc100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,11 @@ ### Fixed * build: missing symbols when linking the `pgo` helper binary. +* UI not refreshing when pasting something into the scrollback search + box, that does not result in a grid update (for example, when the + search criteria did not result in any matches) ([#1040][1040]). + +[1040]: https://codeberg.org/dnkl/foot/issues/1040 ### Security From 8c0fca30db83576f7df5d48c94ecfa9896f8a075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 12:23:27 +0200 Subject: [PATCH 11/36] =?UTF-8?q?selection:=20find=5Fword=5Fboundary:=20as?= =?UTF-8?q?sert=20=E2=80=98pos=E2=80=99=20is=20valid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- selection.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/selection.c b/selection.c index f279a834..da62d354 100644 --- a/selection.c +++ b/selection.c @@ -270,6 +270,11 @@ void selection_find_word_boundary_left(struct terminal *term, struct coord *pos, bool spaces_only) { + xassert(pos->row >= 0); + xassert(pos->row < term->rows); + xassert(pos->col >= 0); + xassert(pos->col < term->cols); + const struct row *r = grid_row_in_view(term->grid, pos->row); char32_t c = r->cells[pos->col].wc; @@ -343,6 +348,11 @@ void selection_find_word_boundary_right(struct terminal *term, struct coord *pos, bool spaces_only) { + xassert(pos->row >= 0); + xassert(pos->row < term->rows); + xassert(pos->col >= 0); + xassert(pos->col < term->cols); + const struct row *r = grid_row_in_view(term->grid, pos->row); char32_t c = r->cells[pos->col].wc; From f7c29ee39497a23b95e4ef97fc3ed87360014d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 12:24:28 +0200 Subject: [PATCH 12/36] search: maches_next: assert match coordinates are valid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * They are within range (i.e. ‘row’ does not exceed term->rows-1) * ‘end’ comes after ‘start’ --- search.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/search.c b/search.c index 77492e3f..330ea791 100644 --- a/search.c +++ b/search.c @@ -561,6 +561,15 @@ search_matches_next(struct search_match_iterator *iter) match.end.row = match.end.row - grid->view + grid->num_rows; match.end.row &= grid->num_rows - 1; + xassert(match.start.row >= 0); + xassert(match.start.row < term->rows); + xassert(match.end.row >= 0); + xassert(match.end.row < term->rows); + + xassert(match.end.row > match.start.row || + (match.end.row == match.start.row && + match.end.col >= match.start.col)); + if (return_primary_match) { iter->start.row = 0; iter->start.col = 0; From d068e821d60dc9d9ae606969010b087040b98b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 12:25:21 +0200 Subject: [PATCH 13/36] =?UTF-8?q?search:=20matches=5Fnext:=20don=E2=80=99t?= =?UTF-8?q?=20wrap=20around=20grid->num=5Frows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- search.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/search.c b/search.c index 330ea791..8f99fae7 100644 --- a/search.c +++ b/search.c @@ -580,10 +580,14 @@ search_matches_next(struct search_match_iterator *iter) if (iter->start.col >= term->cols) { iter->start.col = 0; - iter->start.row++; - iter->start.row &= grid->num_rows - 1; + iter->start.row++; /* Overflow is caught in next iteration */ } + xassert(iter->start.row >= 0); + xassert(iter->start.row <= term->rows); + xassert(iter->start.col >= 0); + xassert(iter->start.col < term->cols); + if (match.start.row == term->search.match.row && match.start.col == term->search.match.col) { From 082e242ce5c954c59f52a6878a6d3794694a1182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 12:28:12 +0200 Subject: [PATCH 14/36] search: matches_next: stop searching when start.row >= term->rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- search.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/search.c b/search.c index 8f99fae7..337db697 100644 --- a/search.c +++ b/search.c @@ -539,7 +539,16 @@ search_matches_next(struct search_match_iterator *iter) /* First, return the primary match */ match = term->selection.coords; found = true; - } else { + } + + else if (iter->start.row >= term->rows) { + goto no_match; + } + + else { + xassert(iter->start.row >= 0); + xassert(iter->start.row < term->rows); + struct coord abs_start = iter->start; abs_start.row = grid_row_absolute_in_view(grid, abs_start.row); From 1d48b7b77c69ca518d0bbdc079ca3736311b9c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 12:35:07 +0200 Subject: [PATCH 15/36] =?UTF-8?q?search:=20matches=5Fnext:=20assert=20star?= =?UTF-8?q?t=E2=80=99s=20=E2=80=98col=E2=80=99=20is=20valid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- search.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/search.c b/search.c index 337db697..73f831eb 100644 --- a/search.c +++ b/search.c @@ -548,6 +548,8 @@ search_matches_next(struct search_match_iterator *iter) else { xassert(iter->start.row >= 0); xassert(iter->start.row < term->rows); + xassert(iter->start.col >= 0); + xassert(iter->start.col < term->cols); struct coord abs_start = iter->start; abs_start.row = grid_row_absolute_in_view(grid, abs_start.row); From 312f0dbcfd6e027981b99254421ed5fda6faac34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 15:19:32 +0200 Subject: [PATCH 16/36] changelog: scrollback mode freezing, with 100% CPU --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c44cc100..a7ad4371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,8 +49,10 @@ * UI not refreshing when pasting something into the scrollback search box, that does not result in a grid update (for example, when the search criteria did not result in any matches) ([#1040][1040]). +* foot freezing in scrollback search mode, using 100% CPU ([#1036][1036]). [1040]: https://codeberg.org/dnkl/foot/issues/1040 +[1036]: https://codeberg.org/dnkl/foot/issues/1036 ### Security From 9c0f1a671cb015b650f7ed80e8fcf66d07c9e87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 15:49:25 +0200 Subject: [PATCH 17/36] selection: assert serial is non-zero before copying data to the clipboard --- selection.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/selection.c b/selection.c index da62d354..8a18c6bb 100644 --- a/selection.c +++ b/selection.c @@ -1533,6 +1533,8 @@ static const struct zwp_primary_selection_source_v1_listener primary_selection_s bool text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t serial) { + xassert(serial != 0); + struct wl_clipboard *clipboard = &seat->clipboard; if (clipboard->data_source != NULL) { @@ -1568,7 +1570,6 @@ text_to_clipboard(struct seat *seat, struct terminal *term, char *text, uint32_t wl_data_device_set_selection(seat->data_device, clipboard->data_source, serial); /* Needed when sending the selection to other client */ - xassert(serial != 0); clipboard->serial = serial; return true; } @@ -1981,6 +1982,8 @@ text_to_primary(struct seat *seat, struct terminal *term, char *text, uint32_t s if (term->wl->primary_selection_device_manager == NULL) return false; + xassert(serial != 0); + struct wl_primary *primary = &seat->primary; /* TODO: somehow share code with the clipboard equivalent */ From a26eb1ea09b9a3c49cb664937c736915e2ab07ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 15:54:37 +0200 Subject: [PATCH 18/36] input: assert serial received from compositor is non-zero --- input.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/input.c b/input.c index be495066..961b73ba 100644 --- a/input.c +++ b/input.c @@ -500,6 +500,7 @@ keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { xassert(surface != NULL); + xassert(serial != 0); struct seat *seat = data; struct wl_window *win = wl_surface_get_user_data(surface); @@ -1269,6 +1270,8 @@ static void key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, uint32_t key, uint32_t state) { + xassert(serial != 0); + seat->kbd.serial = serial; if (seat->kbd.xkb == NULL || seat->kbd.xkb_keymap == NULL || @@ -1621,6 +1624,7 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, wl_fixed_t surface_x, wl_fixed_t surface_y) { xassert(surface != NULL); + xassert(serial != 0); if (surface == NULL) { /* Seen on mutter-3.38 */ @@ -1984,6 +1988,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, LOG_DBG("BUTTON: pointer=%p, serial=%u, button=%x, state=%u", (void *)wl_pointer, serial, button, state); + xassert(serial != 0); + struct seat *seat = data; struct wayland *wayl = seat->wayl; struct terminal *term = seat->mouse_focus; From b4f666118febfc95bf97e31aa1e39e3df9522dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 25 Apr 2022 19:57:18 +0200 Subject: [PATCH 19/36] grid: add abs-to-sb and sb-to-abs utility function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- grid.c | 28 ++++++++++++++++++++++++++++ grid.h | 5 +++++ 2 files changed, 33 insertions(+) diff --git a/grid.c b/grid.c index 5a0b5c7e..4a3995a0 100644 --- a/grid.c +++ b/grid.c @@ -15,6 +15,34 @@ #define TIME_REFLOW 0 +/* + * “sb” (scrollback relative) coordinates + * + * The scrollback relative row number 0 is the *first*, and *oldest* + * row in the scrollback history (and thus the *first* row to be + * scrolled out). Thus, a higher number means further *down* in the + * scrollback, with the *highest* number being at the bottom of the + * screen, where new input appears. + */ +int +grid_row_abs_to_sb(const struct grid *grid, int screen_rows, int abs_row) +{ + const int scrollback_start = grid->offset + screen_rows; + int rebased_row = abs_row - scrollback_start + grid->num_rows; + + rebased_row &= grid->num_rows - 1; + return rebased_row; +} + +int grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row) +{ + const int scrollback_start = grid->offset + screen_rows; + int abs_row = sb_rel_row + scrollback_start; + + abs_row &= grid->num_rows - 1; + return abs_row; +} + static void ensure_row_has_extra_data(struct row *row) { diff --git a/grid.h b/grid.h index 7819db4d..22bd76bb 100644 --- a/grid.h +++ b/grid.h @@ -21,6 +21,10 @@ void grid_resize_and_reflow( size_t tracking_points_count, struct coord *const _tracking_points[static tracking_points_count]); +/* Convert row numbers between scrollback-relative and absolute coordinates */ +int grid_row_abs_to_sb(const struct grid *grid, int screen_rows, int abs_row); +int grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row); + static inline int grid_row_absolute(const struct grid *grid, int row_no) { @@ -33,6 +37,7 @@ grid_row_absolute_in_view(const struct grid *grid, int row_no) return (grid->view + row_no) & (grid->num_rows - 1); } + static inline struct row * _grid_row_maybe_alloc(struct grid *grid, int row_no, bool alloc_if_null) { From 6316a5eb0cf2b1fe25ca0e9d60c4e11fce9c22d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 25 Apr 2022 19:59:23 +0200 Subject: [PATCH 20/36] selection: add start/end coordinate getters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- selection.c | 24 ++++++++++++++++++++++++ selection.h | 3 +++ 2 files changed, 27 insertions(+) diff --git a/selection.c b/selection.c index 8a18c6bb..091d3ed0 100644 --- a/selection.c +++ b/selection.c @@ -38,6 +38,29 @@ static const char *const mime_type_map[] = { [DATA_OFFER_MIME_TEXT_UTF8_STRING] = "UTF8_STRING", }; +static inline struct coord +bounded(const struct grid *grid, struct coord coord) +{ + coord.row &= grid->num_rows - 1; + return coord; +} + +struct coord +selection_get_start(const struct terminal *term) +{ + if (term->selection.coords.start.row < 0) + return term->selection.coords.start; + return bounded(term->grid, term->selection.coords.start); +} + +struct coord +selection_get_end(const struct terminal *term) +{ + if (term->selection.coords.end.row < 0) + return term->selection.coords.end; + return bounded(term->grid, term->selection.coords.end); +} + bool selection_on_rows(const struct terminal *term, int row_start, int row_end) { @@ -2461,3 +2484,4 @@ const struct zwp_primary_selection_device_v1_listener primary_selection_device_l .data_offer = &primary_data_offer, .selection = &primary_selection, }; + diff --git a/selection.h b/selection.h index 295d8d1c..0a6ece91 100644 --- a/selection.h +++ b/selection.h @@ -79,3 +79,6 @@ void selection_find_word_boundary_left( struct terminal *term, struct coord *pos, bool spaces_only); void selection_find_word_boundary_right( struct terminal *term, struct coord *pos, bool spaces_only); + +struct coord selection_get_start(const struct terminal *term); +struct coord selection_get_end(const struct terminal *term); From 1d4e1b921d351da349ccfbc5d587489d7b77e690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 25 Apr 2022 20:00:14 +0200 Subject: [PATCH 21/36] sixel/terminal: use the new grid and selection APIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use grid_row_abs_to_sb() instead of manually “rebasing” row numbers. Use selection_get_{start,end}() to retrieve the current selection coordinates. --- sixel.c | 55 ++++++++++++++++++++++++------------------------------ terminal.c | 25 +++++++++++++------------ 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/sixel.c b/sixel.c index 5316c0ad..0c94117f 100644 --- a/sixel.c +++ b/sixel.c @@ -7,8 +7,9 @@ #define LOG_ENABLE_DBG 0 #include "log.h" #include "debug.h" -#include "render.h" +#include "grid.h" #include "hsl.h" +#include "render.h" #include "util.h" #include "xmalloc.h" #include "xsnprintf.h" @@ -138,25 +139,6 @@ sixel_erase(struct terminal *term, struct sixel *sixel) sixel_destroy(sixel); } -/* - * Calculates the scrollback relative row number, given an absolute row number. - * - * The scrollback relative row number 0 is the *first*, and *oldest* - * row in the scrollback history (and thus the *first* row to be - * scrolled out). Thus, a higher number means further *down* in the - * scrollback, with the *highest* number being at the bottom of the - * screen, where new input appears. - */ -static int -rebase_row(const struct terminal *term, int abs_row) -{ - int scrollback_start = term->grid->offset + term->rows; - int rebased_row = abs_row - scrollback_start + term->grid->num_rows; - - rebased_row &= term->grid->num_rows - 1; - return rebased_row; -} - /* * Verify the sixels are sorted correctly. * @@ -175,7 +157,8 @@ verify_list_order(const struct terminal *term) size_t idx = 0; tll_foreach(term->grid->sixel_images, it) { - int row = rebase_row(term, it->item.pos.row + it->item.rows - 1); + int row = grid_row_abs_to_sb( + term->grid, term->rows, it->item.pos.row + it->item.rows - 1); int col = it->item.pos.col; int col_count = it->item.cols; @@ -232,7 +215,8 @@ verify_scrollback_consistency(const struct terminal *term) int last_row = -1; for (int i = 0; i < six->rows; i++) { - int row_no = rebase_row(term, six->pos.row + i); + int row_no = grid_row_abs_to_sb( + term->grid, term->rows, six->pos.row + i); if (last_row != -1) xassert(last_row < row_no); @@ -295,10 +279,14 @@ verify_sixels(const struct terminal *term) static void sixel_insert(struct terminal *term, struct sixel sixel) { - int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1); + int end_row = grid_row_abs_to_sb( + term->grid, term->rows, sixel.pos.row + sixel.rows - 1); tll_foreach(term->grid->sixel_images, it) { - if (rebase_row(term, it->item.pos.row + it->item.rows - 1) < end_row) { + int rebased = grid_row_abs_to_sb( + term->grid, term->rows, it->item.pos.row + it->item.rows - 1); + + if (rebased < end_row) { tll_insert_before(term->grid->sixel_images, it, sixel); goto out; } @@ -325,7 +313,7 @@ sixel_scroll_up(struct terminal *term, int rows) tll_rforeach(term->grid->sixel_images, it) { struct sixel *six = &it->item; - int six_start = rebase_row(term, six->pos.row); + int six_start = grid_row_abs_to_sb(term->grid, term->rows, six->pos.row); if (six_start < rows) { sixel_erase(term, six); @@ -358,7 +346,8 @@ sixel_scroll_down(struct terminal *term, int rows) tll_foreach(term->grid->sixel_images, it) { struct sixel *six = &it->item; - int six_end = rebase_row(term, six->pos.row + six->rows - 1); + int six_end = grid_row_abs_to_sb( + term->grid, term->rows, six->pos.row + six->rows - 1); if (six_end >= term->grid->num_rows - rows) { sixel_erase(term, six); tll_remove(term->grid->sixel_images, it); @@ -668,7 +657,8 @@ _sixel_overwrite_by_rectangle( /* We should never generate scrollback wrapping sixels */ xassert(end < term->grid->num_rows); - const int scrollback_rel_start = rebase_row(term, start); + const int scrollback_rel_start = grid_row_abs_to_sb( + term->grid, term->rows, start); bool UNUSED would_have_breaked = false; @@ -677,7 +667,8 @@ _sixel_overwrite_by_rectangle( const int six_start = six->pos.row; const int six_end = (six_start + six->rows - 1); - const int six_scrollback_rel_end = rebase_row(term, six_end); + const int six_scrollback_rel_end = + grid_row_abs_to_sb(term->grid, term->rows, six_end); /* We should never generate scrollback wrapping sixels */ xassert(six_end < term->grid->num_rows); @@ -776,7 +767,7 @@ sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width) width = term->grid->num_cols - col; const int row = (term->grid->offset + _row) & (term->grid->num_rows - 1); - const int scrollback_rel_row = rebase_row(term, row); + const int scrollback_rel_row = grid_row_abs_to_sb(term->grid, term->rows, row); tll_foreach(term->grid->sixel_images, it) { struct sixel *six = &it->item; @@ -786,7 +777,8 @@ sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width) /* We should never generate scrollback wrapping sixels */ xassert(six_end >= six_start); - const int six_scrollback_rel_end = rebase_row(term, six_end); + const int six_scrollback_rel_end = + grid_row_abs_to_sb(term->grid, term->rows, six_end); if (six_scrollback_rel_end < scrollback_rel_row) { /* All remaining sixels are *before* "our" row */ @@ -888,7 +880,8 @@ sixel_reflow(struct terminal *term) int last_row = -1; for (int j = 0; j < six->rows; j++) { - int row_no = rebase_row(term, six->pos.row + j); + int row_no = grid_row_abs_to_sb( + term->grid, term->rows, six->pos.row + j); if (last_row != -1 && last_row >= row_no) { sixel_destroy(six); sixel_destroyed = true; diff --git a/terminal.c b/terminal.c index f1b05f57..7d230cdf 100644 --- a/terminal.c +++ b/terminal.c @@ -2154,18 +2154,18 @@ term_erase(struct terminal *term, int start_row, int start_col, void term_erase_scrollback(struct terminal *term) { - const int num_rows = term->grid->num_rows; + const struct grid *grid = term->grid; + const int num_rows = grid->num_rows; const int mask = num_rows - 1; - const int start = (term->grid->offset + term->rows) & mask; - const int end = (term->grid->offset - 1) & mask; + const int start = (grid->offset + term->rows) & mask; + const int end = (grid->offset - 1) & mask; - const int scrollback_start = term->grid->offset + term->rows; - const int rel_start = (start - scrollback_start + num_rows) & mask; - const int rel_end = (end - scrollback_start + num_rows) & mask; + const int rel_start = grid_row_abs_to_sb(grid, term->rows, start); + const int rel_end = grid_row_abs_to_sb(grid, term->rows, end); - const int sel_start = term->selection.coords.start.row; - const int sel_end = term->selection.coords.end.row; + const int sel_start = selection_get_start(term).row; + const int sel_end = selection_get_end(term).row; if (sel_end >= 0) { /* @@ -2183,8 +2183,8 @@ term_erase_scrollback(struct terminal *term) * closer to the screen bottom. */ - const int rel_sel_start = (sel_start - scrollback_start + num_rows) & mask; - const int rel_sel_end = (sel_end - scrollback_start + num_rows) & mask; + const int rel_sel_start = grid_row_abs_to_sb(grid, term->rows, sel_start); + const int rel_sel_end = grid_row_abs_to_sb(grid, term->rows, sel_end); if ((rel_sel_start <= rel_start && rel_sel_end >= rel_start) || (rel_sel_start <= rel_end && rel_sel_end >= rel_end) || @@ -2196,8 +2196,9 @@ term_erase_scrollback(struct terminal *term) tll_foreach(term->grid->sixel_images, it) { struct sixel *six = &it->item; - const int six_start = (six->pos.row - scrollback_start + num_rows) & mask; - const int six_end = (six->pos.row + six->rows - 1 - scrollback_start + num_rows) & mask; + const int six_start = grid_row_abs_to_sb(grid, term->rows, six->pos.row); + const int six_end = grid_row_abs_to_sb( + grid, term->rows, six->pos.row + six->rows - 1); if ((six_start <= rel_start && six_end >= rel_start) || (six_start <= rel_end && six_end >= rel_end) || From 5c4ddebc3c57724bd89b959a33f5ad0bd97f482b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 25 Apr 2022 20:00:47 +0200 Subject: [PATCH 22/36] search: fix multiple crashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- search.c | 194 ++++++++++++++++++++++--------------------------------- 1 file changed, 76 insertions(+), 118 deletions(-) diff --git a/search.c b/search.c index 73f831eb..09b59d50 100644 --- a/search.c +++ b/search.c @@ -82,11 +82,7 @@ search_ensure_size(struct terminal *term, size_t wanted_size) static bool has_wrapped_around(const struct terminal *term, int abs_row_no) { - const struct grid *grid = term->grid; - int scrollback_start = grid->offset + term->rows; - int rebased_row = abs_row_no - scrollback_start + grid->num_rows; - rebased_row &= grid->num_rows - 1; - + int rebased_row = grid_row_abs_to_sb(term->grid, term->rows, abs_row_no); return rebased_row == 0; } @@ -182,6 +178,9 @@ search_update_selection(struct terminal *term, const struct range *match) int end_row = match->end.row; int end_col = match->end.col; + xassert(start_row >= 0); + xassert(start_row < grid->num_rows); + bool move_viewport = true; int view_end = (grid->view + term->rows - 1) & (grid->num_rows - 1); @@ -196,24 +195,14 @@ search_update_selection(struct terminal *term, const struct range *match) } if (move_viewport) { - int old_view = grid->view; - int new_view = start_row - term->rows / 2; + int rebased_new_view = grid_row_abs_to_sb(grid, term->rows, start_row); - while (new_view < 0) - new_view += grid->num_rows; + rebased_new_view -= term->rows / 2; + rebased_new_view = + min(max(rebased_new_view, 0), grid->num_rows - term->rows); - new_view = ensure_view_is_allocated(term, new_view); - - /* Don't scroll past scrollback history */ - int end = (grid->offset + term->rows - 1) & (grid->num_rows - 1); - if (end >= grid->offset) { - /* Not wrapped */ - if (new_view >= grid->offset && new_view <= end) - new_view = grid->offset; - } else { - if (new_view >= grid->offset || new_view <= end) - new_view = grid->offset; - } + const int old_view = grid->view; + int new_view = grid_row_sb_to_abs(grid, term->rows, rebased_new_view); #if defined(_DEBUG) /* Verify all to-be-visible rows have been allocated */ @@ -227,23 +216,8 @@ search_update_selection(struct terminal *term, const struct range *match) term_damage_view(term); } -#if 0 - /* Selection endpoint is inclusive */ - if (--end_col < 0) { - end_col = term->cols - 1; - end_row--; - } -#endif - - /* - * Begin a new selection if the start coords changed - * - * Note: check column against selection.coords, since our “old” - * start column isn’t reliable - we modify it when searching - * “next” or “prev”. - */ if (start_row != term->search.match.row || - start_col != term->selection.coords.start.col) + start_col != term->search.match.col) { int selection_row = start_row - grid->view + grid->num_rows; selection_row &= grid->num_rows - 1; @@ -516,7 +490,7 @@ search_matches_new_iter(struct terminal *term) { return (struct search_match_iterator){ .term = term, - .start = {-2, -2}, + .start = {0, 0}, }; } @@ -529,86 +503,63 @@ search_matches_next(struct search_match_iterator *iter) if (term->search.match_len == 0) goto no_match; - struct range match; - bool found; - - const bool return_primary_match = - iter->start.row == -2 && term->selection.coords.end.row >= 0; - - if (return_primary_match) { - /* First, return the primary match */ - match = term->selection.coords; - found = true; - } - - else if (iter->start.row >= term->rows) { + if (iter->start.row >= term->rows) goto no_match; + + xassert(iter->start.row >= 0); + xassert(iter->start.row < term->rows); + xassert(iter->start.col >= 0); + xassert(iter->start.col < term->cols); + + struct coord abs_start = iter->start; + abs_start.row = grid_row_absolute_in_view(grid, abs_start.row); + + struct coord abs_end = { + term->cols - 1, + grid_row_absolute_in_view(grid, term->rows - 1)}; + + struct range match; + bool found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match); + if (!found) + goto no_match; + + LOG_DBG("match at (absolute coordinates) %dx%d-%dx%d", + match.start.row, match.start.col, + match.end.row, match.end.col); + + /* Convert absolute row numbers back to view relative */ + match.start.row = match.start.row - grid->view + grid->num_rows; + match.start.row &= grid->num_rows - 1; + match.end.row = match.end.row - grid->view + grid->num_rows; + match.end.row &= grid->num_rows - 1; + + LOG_DBG("match at (view-local coordinates) %dx%d-%dx%d, view=%d", + match.start.row, match.start.col, + match.end.row, match.end.col, grid->view); + + xassert(match.start.row >= 0); + xassert(match.start.row < term->rows); + xassert(match.end.row >= 0); + xassert(match.end.row < term->rows); + + xassert(match.end.row > match.start.row || + (match.end.row == match.start.row && + match.end.col >= match.start.col)); + + /* Continue at next column, next time */ + iter->start.row = match.start.row; + iter->start.col = match.start.col + 1; + + if (iter->start.col >= term->cols) { + iter->start.col = 0; + iter->start.row++; /* Overflow is caught in next iteration */ } - else { - xassert(iter->start.row >= 0); - xassert(iter->start.row < term->rows); - xassert(iter->start.col >= 0); - xassert(iter->start.col < term->cols); - - struct coord abs_start = iter->start; - abs_start.row = grid_row_absolute_in_view(grid, abs_start.row); - - struct coord abs_end = { - term->cols - 1, - grid_row_absolute_in_view(grid, term->rows - 1)}; - - found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match); - } - - if (found) { - LOG_DBG("match at %dx%d-%dx%d", - match.start.row, match.start.col, - match.end.row, match.end.col); - - /* Convert absolute row numbers back to view relative */ - match.start.row = match.start.row - grid->view + grid->num_rows; - match.start.row &= grid->num_rows - 1; - match.end.row = match.end.row - grid->view + grid->num_rows; - match.end.row &= grid->num_rows - 1; - - xassert(match.start.row >= 0); - xassert(match.start.row < term->rows); - xassert(match.end.row >= 0); - xassert(match.end.row < term->rows); - - xassert(match.end.row > match.start.row || - (match.end.row == match.start.row && - match.end.col >= match.start.col)); - - if (return_primary_match) { - iter->start.row = 0; - iter->start.col = 0; - } else { - /* Continue at next column, next time */ - iter->start.row = match.start.row; - iter->start.col = match.start.col + 1; - - if (iter->start.col >= term->cols) { - iter->start.col = 0; - iter->start.row++; /* Overflow is caught in next iteration */ - } - - xassert(iter->start.row >= 0); - xassert(iter->start.row <= term->rows); - xassert(iter->start.col >= 0); - xassert(iter->start.col < term->cols); - - if (match.start.row == term->search.match.row && - match.start.col == term->search.match.col) - { - /* Primary match is handled explicitly */ - LOG_DBG("primary match: skipping"); - return search_matches_next(iter); - } - } - return match; - } + xassert(iter->start.row >= 0); + xassert(iter->start.row <= term->rows); + xassert(iter->start.col >= 0); + xassert(iter->start.col < term->cols); + return match; no_match: iter->start.row = -1; @@ -663,15 +614,18 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) if (term->search.match_len == 0) return; - xassert(term->selection.coords.end.row != -1); + xassert(term->selection.coords.end.row >= 0); struct grid *grid = term->grid; const bool move_cursor = term->search.cursor == term->search.len; - const struct coord old_end = term->selection.coords.end; + struct coord old_end = selection_get_end(term); struct coord new_end = old_end; struct row *row = NULL; + xassert(new_end.row >= 0); + xassert(new_end.row < grid->num_rows); + /* Advances a coordinate by one column, to the right. Returns * false if we’ve reached the scrollback wrap-around */ #define advance_pos(coord) __extension__ \ @@ -691,12 +645,16 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) if (!advance_pos(new_end)) return; + xassert(new_end.row >= 0); + xassert(new_end.row < grid->num_rows); xassert(grid->rows[new_end.row] != NULL); /* Find next word boundary */ - new_end.row -= grid->view; + new_end.row -= grid->view + grid->num_rows; + new_end.row &= grid->num_rows - 1; selection_find_word_boundary_right(term, &new_end, spaces_only); new_end.row += grid->view; + new_end.row &= grid->num_rows - 1; struct coord pos = old_end; row = grid->rows[pos.row]; From 1b5b1d5d925f296619c511a5b12a10f48717efce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 17:40:00 +0200 Subject: [PATCH 23/36] changelog: crash when extending selection in search mode --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ad4371..a03b4c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ box, that does not result in a grid update (for example, when the search criteria did not result in any matches) ([#1040][1040]). * foot freezing in scrollback search mode, using 100% CPU ([#1036][1036]). +* Crash when extending a selection to the next word boundary in + scrollback search mode ([#1036][1036]). [1040]: https://codeberg.org/dnkl/foot/issues/1040 [1036]: https://codeberg.org/dnkl/foot/issues/1036 From b94f540113dd10e4b34f5dda826f732b29b47217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 17:40:20 +0200 Subject: [PATCH 24/36] changelog: search mode not always highlighting all matches correctly --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a03b4c82..89c19a0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ * foot freezing in scrollback search mode, using 100% CPU ([#1036][1036]). * Crash when extending a selection to the next word boundary in scrollback search mode ([#1036][1036]). +* Scrollback search mode not always highlighting all matches + correctly. [1040]: https://codeberg.org/dnkl/foot/issues/1040 [1036]: https://codeberg.org/dnkl/foot/issues/1036 From 1e87dbc4dc87c9567a7851135d91728796b0696f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 25 Apr 2022 19:55:00 +0200 Subject: [PATCH 25/36] search: work around Sway sub-surface unmap bug 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 --- search.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/search.c b/search.c index 09b59d50..72c589f2 100644 --- a/search.c +++ b/search.c @@ -116,6 +116,11 @@ search_cancel_keep_selection(struct terminal *term) term_xcursor_update(term); render_refresh(term); + + /* Work around Sway bug - unmapping a sub-surface does not damage + * the underlying surface */ + term_damage_margins(term); + term_damage_view(term); } void @@ -795,7 +800,6 @@ execute_binding(struct seat *seat, struct terminal *term, grid->view = ensure_view_is_allocated( term, term->search.original_view); } - term_damage_view(term); search_cancel(term); return true; From 398d96fdb2a18f7652c116639dc17741f5e24404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 17:24:55 +0200 Subject: [PATCH 26/36] term: flash: work around Sway sub-surface unmap bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unmapping a sub-surface in Sway does not damage the underlying surface. As a result, the OSC-555 escape (“flash”) will leave yellow margins on ~every second frame. Out of sway, river, weston and mutter, only Sway needs this workaround. This is a workaround for https://github.com/swaywm/sway/issues/6960 Closes #1046 --- terminal.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index 7d230cdf..9199aa31 100644 --- a/terminal.c +++ b/terminal.c @@ -355,8 +355,12 @@ fdm_flash(struct fdm *fdm, int fd, int events, void *data) (unsigned long long)expiration_count); term->flash.active = false; - term_damage_view(term); render_refresh(term); + + /* Work around Sway bug - unmapping a sub-surface does not damage + * the underlying surface */ + term_damage_margins(term); + term_damage_view(term); return true; } From 3abb23c81c5da902587b5b639eff555fcab5a337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 17:28:36 +0200 Subject: [PATCH 27/36] changelog: workaround for Sway bug #6960 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c19a0e..47ca1107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,15 @@ ## Unreleased ### Added + +* Workaround for Sway bug [#6960][sway-6960]: scrollback search and + the OSC-555 (“flash”) escape sequence leaves dimmed (search )and + yellow (flash) artifacts ([#1046][1046]). + +[sway-6960]: https://github.com/swaywm/sway/issues/6960 +[1046]: https://codeberg.org/dnkl/foot/issues/1046 + + ### Changed ### Deprecated ### Removed From 93dcb7dc9cf753462bfea40b890a9d68945a5d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 17:52:00 +0200 Subject: [PATCH 28/36] changelog: typo: space on the wrong side of the parenthesis --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ca1107..bb094d93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ ### Added * Workaround for Sway bug [#6960][sway-6960]: scrollback search and - the OSC-555 (“flash”) escape sequence leaves dimmed (search )and + the OSC-555 (“flash”) escape sequence leaves dimmed (search) and yellow (flash) artifacts ([#1046][1046]). [sway-6960]: https://github.com/swaywm/sway/issues/6960 From c82c6116ede2706a9d5b28d43e8271ec13be2794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 19:32:08 +0200 Subject: [PATCH 29/36] search: regression: crash when moving viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5c4ddebc3c57724bd89b959a33f5ad0bd97f482b 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. --- search.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/search.c b/search.c index 72c589f2..8f2315a0 100644 --- a/search.c +++ b/search.c @@ -209,6 +209,13 @@ search_update_selection(struct terminal *term, const struct range *match) const int old_view = grid->view; int new_view = grid_row_sb_to_abs(grid, term->rows, rebased_new_view); + /* Scrollback may not be completely filled yet */ + { + const int mask = grid->num_rows - 1; + while (grid->rows[new_view] == NULL) + new_view = (new_view + 1) & mask; + } + #if defined(_DEBUG) /* Verify all to-be-visible rows have been allocated */ for (int r = 0; r < term->rows; r++) From 694938b85bed679c95ac506711475bf06cd80b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 19:47:02 +0200 Subject: [PATCH 30/36] search: assert that the match is *inside* the new viewport --- search.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/search.c b/search.c index 8f2315a0..d4460e5b 100644 --- a/search.c +++ b/search.c @@ -222,6 +222,15 @@ search_update_selection(struct terminal *term, const struct range *match) xassert(grid->rows[(new_view + r) & (grid->num_rows - 1)] != NULL); #endif +#if defined(_DEBUG) + { + int rel_start_row = grid_row_abs_to_sb(grid, term->rows, start_row); + int rel_view = grid_row_abs_to_sb(grid, term->rows, new_view); + xassert(rel_view <= rel_start_row); + xassert(rel_start_row < rel_view + term->rows); + } +#endif + /* Update view */ grid->view = new_view; if (new_view != old_view) From 0e9ebf433b589c15e17bd37f73991ecc71e9b3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 18:24:22 +0200 Subject: [PATCH 31/36] search: fix infinite loop when highlighting all matches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 4 +++- search.c | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb094d93..65dd2542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,8 @@ * UI not refreshing when pasting something into the scrollback search box, that does not result in a grid update (for example, when the search criteria did not result in any matches) ([#1040][1040]). -* foot freezing in scrollback search mode, using 100% CPU ([#1036][1036]). +* foot freezing in scrollback search mode, using 100% CPU + ([#1036][1036], [#1047][1047]). * Crash when extending a selection to the next word boundary in scrollback search mode ([#1036][1036]). * Scrollback search mode not always highlighting all matches @@ -66,6 +67,7 @@ [1040]: https://codeberg.org/dnkl/foot/issues/1040 [1036]: https://codeberg.org/dnkl/foot/issues/1036 +[1047]: https://codeberg.org/dnkl/foot/issues/1036 ### Security diff --git a/search.c b/search.c index d4460e5b..b4d06c6c 100644 --- a/search.c +++ b/search.c @@ -375,6 +375,13 @@ find_next(struct terminal *term, enum search_direction direction, if (match_len != term->search.len) { /* Didn't match (completely) */ + + if (match_start_row == abs_end.row && + match_start_col == abs_end.col) + { + break; + } + continue; } @@ -563,10 +570,16 @@ search_matches_next(struct search_match_iterator *iter) xassert(match.end.row >= 0); xassert(match.end.row < term->rows); + /* Assert match end comes *after* the match start */ xassert(match.end.row > match.start.row || (match.end.row == match.start.row && match.end.col >= match.start.col)); + /* Assert the match starts at, or after, the iterator position */ + xassert(match.start.row > iter->start.row || + (match.start.row == iter->start.row && + match.start.col >= iter->start.col)); + /* Continue at next column, next time */ iter->start.row = match.start.row; iter->start.col = match.start.col + 1; From aa4c7c5a30a792a9519b971ff14582dc2704b6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 18:34:18 +0200 Subject: [PATCH 32/36] config: add ctrl+shift+v and XF86 paste to SEARCH_CLIPBOARD_PASTE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now bind ctrl+v, ctrl+shift+v, ctrl+y and XF86Paste to pasting from the clipboard into the scrollback search buffer. Why all these? Because we can, and because all are common shortcuts for pasting: * ctrl+v: “normal” apps use this by default * ctrl+shift+v: used in terminals (including foot) * ctrl+y: Emacs * XF86Paste: special keyboard key, for pasting --- CHANGELOG.md | 4 ++++ README.md | 2 +- config.c | 2 ++ doc/foot.1.scd | 2 +- foot.ini | 2 +- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65dd2542..31c05401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,10 @@ * Workaround for Sway bug [#6960][sway-6960]: scrollback search and the OSC-555 (“flash”) escape sequence leaves dimmed (search) and yellow (flash) artifacts ([#1046][1046]). +* `Control+Shift+v` and `XF86Paste` have been added to the default set + of key bindings that paste from the clipboard into the scrollback + search buffer. This is in addition to the pre-existing `Control+v` + and `Control+y` bindings. [sway-6960]: https://github.com/swaywm/sway/issues/6960 [1046]: https://codeberg.org/dnkl/foot/issues/1046 diff --git a/README.md b/README.md index 5b819bf5..227c528c 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ These are the default shortcuts. See `man foot.ini` and the example : Same as ctrl+w, except that the only word separating characters are whitespace characters. -ctrl+v +ctrl+v, ctrl+shift+v, ctrl+y, XF86Paste : Paste from clipboard into the search buffer. shift+insert diff --git a/config.c b/config.c index 10b899e0..c5fad01a 100644 --- a/config.c +++ b/config.c @@ -2686,7 +2686,9 @@ add_default_search_bindings(struct config *conf) {BIND_ACTION_SEARCH_EXTEND_WORD, m_ctrl, {{XKB_KEY_w}}}, {BIND_ACTION_SEARCH_EXTEND_WORD_WS, m_ctrl_shift, {{XKB_KEY_w}}}, {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl, {{XKB_KEY_v}}}, + {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl_shift, {{XKB_KEY_v}}}, {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_ctrl, {{XKB_KEY_y}}}, + {BIND_ACTION_SEARCH_CLIPBOARD_PASTE, m_none, {{XKB_KEY_XF86Paste}}}, {BIND_ACTION_SEARCH_PRIMARY_PASTE, m_shift, {{XKB_KEY_Insert}}}, }; diff --git a/doc/foot.1.scd b/doc/foot.1.scd index dfbc6651..837da048 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -224,7 +224,7 @@ default) available; see *foot.ini*(5). Same as *ctrl*+*w*, except that the only word separating characters are whitespace characters. -*ctrl*+*v*, *ctrl*+*y* +*ctrl*+*v*, *ctrl*+*shift*+*v*, *ctrl*+*y*, *XF86Paste* Paste from clipboard into the search buffer. *shift*+*insert* diff --git a/foot.ini b/foot.ini index abd3c412..21c49174 100644 --- a/foot.ini +++ b/foot.ini @@ -162,7 +162,7 @@ # delete-next-word=Mod1+d Control+Delete # extend-to-word-boundary=Control+w # extend-to-next-whitespace=Control+Shift+w -# clipboard-paste=Control+v Control+y +# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste # primary-paste=Shift+Insert [url-bindings] From 32d9895697584988056086cbecdc96375b4b9115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 26 Apr 2022 21:05:17 +0200 Subject: [PATCH 33/36] term: reset sixel options when hard resetting the terminal state --- CHANGELOG.md | 2 ++ terminal.c | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c05401..07c3dfbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,8 @@ scrollback search mode ([#1036][1036]). * Scrollback search mode not always highlighting all matches correctly. +* Sixel options not being reset on hard resets (`\Ec`) + [1040]: https://codeberg.org/dnkl/foot/issues/1040 [1036]: https://codeberg.org/dnkl/foot/issues/1036 diff --git a/terminal.c b/terminal.c index 9199aa31..dcaca033 100644 --- a/terminal.c +++ b/terminal.c @@ -1929,6 +1929,16 @@ term_reset(struct terminal *term, bool hard) tll_free(term->alt.scroll_damage); term->render.last_cursor.row = NULL; term_damage_all(term); + + term->sixel.scrolling = true; + term->sixel.cursor_right_of_graphics = false; + term->sixel.use_private_palette = true; + term->sixel.max_width = SIXEL_MAX_WIDTH; + term->sixel.max_height = SIXEL_MAX_HEIGHT; + term->sixel.palette_size = SIXEL_MAX_COLORS; + free(term->sixel.private_palette); + free(term->sixel.shared_palette); + term->sixel.private_palette = term->sixel.shared_palette = NULL; } static bool From 8356dfac2f8afa854bcf2ceaca26e1b3090deeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 27 Apr 2022 18:44:17 +0200 Subject: [PATCH 34/36] Disable debug logging --- extract.c | 2 +- key-binding.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extract.c b/extract.c index 310d94a3..31c32248 100644 --- a/extract.c +++ b/extract.c @@ -2,7 +2,7 @@ #include #define LOG_MODULE "extract" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" #include "char32.h" diff --git a/key-binding.c b/key-binding.c index 9133f60c..2135abbc 100644 --- a/key-binding.c +++ b/key-binding.c @@ -3,7 +3,7 @@ #include #define LOG_MODULE "key-binding" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" #include "config.h" From 76305104486a90c2320dd102c32fe26831ebb658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 27 Apr 2022 18:44:57 +0200 Subject: [PATCH 35/36] =?UTF-8?q?selection:=20find=5Fword=5Fboundary=5Frig?= =?UTF-8?q?ht:=20add=20=E2=80=9Cstop-on-space-to-word-boundary=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- search.c | 2 +- selection.c | 27 +++++++++++++++++++-------- selection.h | 3 ++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/search.c b/search.c index b4d06c6c..f6d377ea 100644 --- a/search.c +++ b/search.c @@ -686,7 +686,7 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only) /* Find next word boundary */ new_end.row -= grid->view + grid->num_rows; new_end.row &= grid->num_rows - 1; - selection_find_word_boundary_right(term, &new_end, spaces_only); + selection_find_word_boundary_right(term, &new_end, spaces_only, false); new_end.row += grid->view; new_end.row &= grid->num_rows - 1; diff --git a/selection.c b/selection.c index 091d3ed0..6b57f48c 100644 --- a/selection.c +++ b/selection.c @@ -369,7 +369,8 @@ selection_find_word_boundary_left(struct terminal *term, struct coord *pos, void selection_find_word_boundary_right(struct terminal *term, struct coord *pos, - bool spaces_only) + bool spaces_only, + bool stop_on_space_to_word_boundary) { xassert(pos->row >= 0); xassert(pos->row < term->rows); @@ -395,6 +396,7 @@ selection_find_word_boundary_right(struct terminal *term, struct coord *pos, !initial_is_space && !isword(c, spaces_only, term->conf->word_delimiters); bool initial_is_word = c != 0 && isword(c, spaces_only, term->conf->word_delimiters); + bool have_seen_word = initial_is_word; while (true) { int next_col = pos->col + 1; @@ -435,13 +437,22 @@ selection_find_word_boundary_right(struct terminal *term, struct coord *pos, bool is_word = c != 0 && isword(c, spaces_only, term->conf->word_delimiters); - if (initial_is_space && !is_space) - break; - if (initial_is_delim && !is_delim) - break; + if (stop_on_space_to_word_boundary) { + if (initial_is_space && !is_space) + break; + if (initial_is_delim && !is_delim) + break; + } else { + if (initial_is_space && ((have_seen_word && is_space) || is_delim)) + break; + if (initial_is_delim && ((have_seen_word && is_delim) || is_space)) + break; + } if (initial_is_word && !is_word) break; + have_seen_word = is_word; + pos->col = next_col; pos->row = next_row; } @@ -522,7 +533,7 @@ selection_start(struct terminal *term, int col, int row, case SELECTION_WORD_WISE: { struct coord start = {col, row}, end = {col, row}; selection_find_word_boundary_left(term, &start, spaces_only); - selection_find_word_boundary_right(term, &end, spaces_only); + selection_find_word_boundary_right(term, &end, spaces_only, true); term->selection.coords.start = (struct coord){ start.col, term->grid->view + start.row}; @@ -863,7 +874,7 @@ selection_update(struct terminal *term, int col, int row) case SELECTION_RIGHT: { struct coord end = {col, row}; selection_find_word_boundary_right( - term, &end, term->selection.spaces_only); + term, &end, term->selection.spaces_only, true); new_end = (struct coord){end.col, term->grid->view + end.row}; break; } @@ -1013,7 +1024,7 @@ selection_extend_normal(struct terminal *term, int col, int row, struct coord pivot_end = pivot_start; selection_find_word_boundary_left(term, &pivot_start, spaces_only); - selection_find_word_boundary_right(term, &pivot_end, spaces_only); + selection_find_word_boundary_right(term, &pivot_end, spaces_only, true); term->selection.pivot.start = (struct coord){pivot_start.col, term->grid->view + pivot_start.row}; diff --git a/selection.h b/selection.h index 0a6ece91..3d0c224e 100644 --- a/selection.h +++ b/selection.h @@ -78,7 +78,8 @@ void selection_stop_scroll_timer(struct terminal *term); void selection_find_word_boundary_left( struct terminal *term, struct coord *pos, bool spaces_only); void selection_find_word_boundary_right( - struct terminal *term, struct coord *pos, bool spaces_only); + struct terminal *term, struct coord *pos, bool spaces_only, + bool stop_on_space_to_word_boundary); struct coord selection_get_start(const struct terminal *term); struct coord selection_get_end(const struct terminal *term); From 5308b8cdb8fe3c14c9a48954880b2f858d80df35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 27 Apr 2022 18:52:08 +0200 Subject: [PATCH 36/36] =?UTF-8?q?changelog:=20changed=20behavior=20of=20?= =?UTF-8?q?=E2=80=9Cextend-to-word-boundary=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c3dfbf..7c8c7511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,12 @@ ### Changed + +* Scrollback search’s `extend-to-word-boundary` no longer stops at + space-to-word boundaries, making selection extension feel more + natural. + + ### Deprecated ### Removed ### Fixed