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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 477202df..7c8c7511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,12 +40,46 @@ ## 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]). +* `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 + + ### 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 * 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]). +* 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 + 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 +[1047]: https://codeberg.org/dnkl/foot/issues/1036 ### Security @@ -84,6 +118,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 +141,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 +190,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 +217,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 diff --git a/README.md b/README.md index fdf28828..227c528c 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)`. @@ -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 @@ -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 @@ -411,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 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/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_. 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/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] 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) { 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; 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" 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) { diff --git a/search.c b/search.c index a1ef8eb4..f6d377ea 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; } @@ -120,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 @@ -182,6 +183,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,23 +200,20 @@ 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); + const int old_view = grid->view; + int new_view = grid_row_sb_to_abs(grid, term->rows, rebased_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; + /* 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) @@ -221,29 +222,23 @@ 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) 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; @@ -380,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; } @@ -467,7 +469,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; @@ -515,7 +518,7 @@ search_matches_new_iter(struct terminal *term) { return (struct search_match_iterator){ .term = term, - .start = {-2, -2}, + .start = {0, 0}, }; } @@ -528,62 +531,69 @@ search_matches_next(struct search_match_iterator *iter) if (term->search.match_len == 0) goto no_match; + 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; + bool found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match); + if (!found) + goto no_match; - const bool return_primary_match = - iter->start.row == -2 && term->selection.coords.end.row >= 0; + LOG_DBG("match at (absolute coordinates) %dx%d-%dx%d", + match.start.row, match.start.col, + match.end.row, match.end.col); - if (return_primary_match) { - /* First, return the primary match */ - match = term->selection.coords; - found = true; - } else { - struct coord abs_start = iter->start; - abs_start.row = grid_row_absolute_in_view(grid, abs_start.row); + /* 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; - struct coord abs_end = { - term->cols - 1, - grid_row_absolute_in_view(grid, term->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); - found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match); + xassert(match.start.row >= 0); + xassert(match.start.row < term->rows); + 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; + + if (iter->start.col >= term->cols) { + iter->start.col = 0; + iter->start.row++; /* Overflow is caught in next iteration */ } - 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; - - 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++; - iter->start.row &= grid->num_rows - 1; - } - - 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; @@ -638,15 +648,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__ \ @@ -666,12 +679,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; - selection_find_word_boundary_right(term, &new_end, spaces_only); + 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, false); new_end.row += grid->view; + new_end.row &= grid->num_rows - 1; struct coord pos = old_end; row = grid->rows[pos.row]; @@ -812,7 +829,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; diff --git a/selection.c b/selection.c index f279a834..6b57f48c 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) { @@ -270,6 +293,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; @@ -341,8 +369,14 @@ 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); + 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; @@ -362,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; @@ -402,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; } @@ -489,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}; @@ -830,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; } @@ -980,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}; @@ -1523,6 +1567,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) { @@ -1558,7 +1604,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; } @@ -1971,6 +2016,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 */ @@ -2448,3 +2495,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..3d0c224e 100644 --- a/selection.h +++ b/selection.h @@ -78,4 +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); 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..dcaca033 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; } @@ -1925,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 @@ -2154,18 +2168,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 +2197,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 +2210,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) ||